PHP's typed properties feature, introduced in PHP 7.4, enables declaring property types directly in class definitions. Rather than documenting property types in comments, developers declare them explicitly: private string $username instead of private $username. This simple syntax change provides significant benefits: type checking catches errors at property assignment rather than months later in production, static analysis tools gain confidence to catch additional bugs, and PHP engines optimize code paths assuming type correctness. This comprehensive guide explores how WordPress plugins leverage typed properties for safer, faster code.
Table of Contents
- Understanding PHP Typed Properties
- Type Declaration Syntax
- Nullable and Union Types
- Readonly Properties
- Performance Implications
- Integration with WordPress
- Static Analysis and Type Checking
- Migration Strategy
Understanding PHP Typed Properties
Property types serve as both documentation and enforcement mechanism. When a developer encounters private string $email, they immediately understand the property contains a string, not an array, object, or null value. This explicit declaration eliminates ambiguity that often leads to bugs.
Type enforcement happens at property assignment. Setting a property to an invalid type raises a TypeError immediately, providing rapid feedback during development or testing. Without types, invalid assignments silently succeed, potentially causing bugs that surface months later when that property is used unexpectedly.
// Before typed properties (PHP < 7.4)
class User {
private $username; // What type should this be?
private $email; // String or array?
private $roles; // Array of role objects?
public function setUsername($username) {
// No type checking - accepts anything
$this->username = $username;
}
}
// Later, someone passes wrong type:
$user->setUsername(['name' => 'admin']); // Silent bug!
// After typed properties (PHP 7.4+)
class User {
private string $username;
private string $email;
private array $roles;
public function setUsername(string $username): void {
$this->username = $username;
}
}
// Same code now throws TypeError
$user->setUsername(['name' => 'admin']); // Error: Expected string
The value extends beyond catch bugs. Typed properties enable static analysis tools to reason about code more reliably. When a tool knows $email is always a string, it can detect uses that assume string methods like strlen() or substr(), but warn about attempts to call array methods that would fail at runtime.
Type Declaration Syntax
PHP supports declaring property types using various syntax patterns. Basic scalar types include string, int, float, bool. Object types reference specific classes. Array types specify whether a property is an array. Mixed type indicates the property can hold any type.
// Scalar type declarations
class BlogPost {
private string $title;
private int $post_id;
private float $rating;
private bool $is_published;
private mixed $meta; // Can hold any type
}
// Object type declarations
class WordPressPlugin {
private PluginMetadata $metadata;
private WP_Plugin_Manager $manager;
private \stdClass $config;
}
// Array type declarations
class DataProcessor {
private array $records;
private array $settings = []; // With default value
}
Type declarations support default values, simplifying initialization. Properties with default values don't require explicit initialization in the constructor.
class Configuration {
private string $api_key = '';
private int $retry_count = 3;
private bool $cache_enabled = true;
private array $allowed_domains = [];
}
Constructor property promotion, introduced in PHP 8.0, combines parameter declaration with property initialization, reducing boilerplate:
// PHP 8.0+ constructor property promotion
class User {
public function __construct(
private string $username,
private string $email,
private int $user_id = 0
) {}
}
// Equivalent to:
// private string $username;
// private string $email;
// private int $user_id;
//
// public function __construct($username, $email, $user_id = 0) {
// $this->username = $username;
// $this->email = $email;
// $this->user_id = $user_id;
// }
Nullable and Union Types
Reality is more complex than types being always available. Sometimes a property might be null. The ? operator indicates nullable types:
class BlogPost {
private string $title;
private ?string $subtitle = null; // String or null
private ?int $featured_image_id = null;
private ?\DateTime $scheduled_date = null;
}
PHP 8.0 introduced union types, allowing properties to have multiple possible types. This is useful when a property might be either a string or an integer, for example:
class ApiResponse {
// Can be either string or integer
private string|int $status_code;
// Can be string, array, or null
private string|array|null $response_body = null;
}
// PHP 8.1+ allows 'never' return type
class Logger {
public function fatal(string $message): never {
echo $message;
exit(1);
}
}
The mixed type explicitly indicates a property can hold any type, useful when genuinely any type is acceptable. However, using mixed should be avoided when possible, as it reduces type safety benefits.
class FlexibleContainer {
// Explicit: this property can be anything
private mixed $data;
// Better: be specific about possibilities
private string|int|array $data;
}
Readonly Properties
PHP 8.1 introduced readonly properties that can only be initialized once. After the initial assignment, attempting to modify them raises an error. This pattern is valuable for immutable objects and configuration that shouldn't change after creation.
class ImmutableConfig {
public function __construct(
public readonly string $api_key,
public readonly string $api_secret,
public readonly int $timeout_seconds = 30
) {}
}
$config = new ImmutableConfig('key123', 'secret456');
echo $config->api_key; // Works
$config->api_key = 'newkey'; // Error: Can't modify readonly property
Readonly properties are particularly valuable for WordPress settings and configurations that are loaded once and shouldn't be accidentally modified elsewhere in the codebase:
class PluginConfig {
public readonly string $plugin_slug;
public readonly string $plugin_path;
public readonly string $plugin_url;
public readonly array $required_capabilities;
public function __construct(string $plugin_file) {
$this->plugin_slug = plugin_basename($plugin_file);
$this->plugin_path = dirname($plugin_file);
$this->plugin_url = plugin_dir_url($plugin_file);
$this->required_capabilities = ['manage_options'];
}
}
Performance Implications
Type declarations provide several performance benefits, though they're often not the primary motivation—type safety and maintainability typically outweigh performance gains.
Type checking overhead: When a property is assigned, PHP validates the value matches the declared type. For scalar types, this check is very fast. For object types, PHP only verifies the value is an instance of the correct class, which is cached by the Zend engine. The overhead is typically negligible.
Optimization opportunities: Knowing property types enables PHP's Just-In-Time (JIT) compiler to optimize code. If the JIT knows a property is always an integer, it can generate specialized machine code assuming integer operations, avoiding type-checking overhead. This results in faster code execution.
Reduced uncertainty: Without types, static analysis must be conservative, assuming values might be any type. Typed properties reduce this uncertainty, enabling more aggressive optimizations.
Actual performance improvements vary by workload. CPU-intensive operations benefit more than I/O-bound code. Benchmarks show improvements of 5-15% in scenarios with heavy property access patterns.
// Benchmark: Typed vs Untyped Properties
class BenchmarkUntyped {
private $value; // Untyped
public function sum(): int {
$total = 0;
for ($i = 0; $i < 1000000; $i++) {
$this->value = $i;
$total += $this->value;
}
return $total;
}
}
class BenchmarkTyped {
private int $value; // Typed
public function sum(): int {
$total = 0;
for ($i = 0; $i < 1000000; $i++) {
$this->value = $i;
$total += $this->value;
}
return $total;
}
}
// Results (approximate):
// Untyped: 85ms
// Typed: 72ms
// Improvement: 15% faster
Integration with WordPress
Integrating typed properties in WordPress plugins requires understanding WordPress' type hints and developing a migration strategy for existing codebases.
Modern WordPress (6.0+) is increasingly typed. Core functions use type hints, though not all. Third-party plugins vary widely in type support. WP HealthKit provides utilities for plugin analysis and type verification, enabling developers to incrementally adopt typed properties.
// WordPress plugin with typed properties
class HealthKitSecurityAnalyzer {
private string $site_url;
private array $audit_results = [];
private ?\DateTime $last_analysis = null;
private int $scan_interval_hours = 24;
public function __construct(string $site_url) {
$this->site_url = $site_url;
}
public function analyze(): array {
$start = microtime(true);
$this->audit_results = [
'vulnerabilities' => $this->scanVulnerabilities(),
'configuration' => $this->auditConfiguration(),
'permissions' => $this->checkPermissions()
];
$this->last_analysis = new \DateTime();
return $this->audit_results;
}
private function scanVulnerabilities(): array {
// Analysis code here
return [];
}
private function auditConfiguration(): array {
// Configuration checks
return [];
}
private function checkPermissions(): array {
// Permission verification
return [];
}
}
Integrating with WordPress hooks requires careful consideration of variable types. Many WordPress actions pass variables of specific types, which you can declare in callbacks:
// Typed WordPress hook callback
class AdminNotices {
public function displaySecurityNotices(): void {
add_action('admin_notices', [$this, 'showNotices']);
}
public function showNotices(): void {
if (!current_user_can('manage_options')) {
return;
}
$alerts = $this->getSecurityAlerts();
foreach ($alerts as $alert) {
echo $this->renderAlert($alert);
}
}
private function getSecurityAlerts(): array {
return get_transient('healthkit_security_alerts') ?: [];
}
private function renderAlert(array $alert): string {
// Render alert HTML
return '';
}
}
Static Analysis and Type Checking
Static analysis tools examine code without executing it, catching type errors before runtime. Typed properties provide the information static analysis needs to be effective.
PHPStan is the most popular WordPress-compatible static analysis tool. It uses type information from typed properties to detect misuses:
// Code that PHPStan can now catch
class Example {
private string $email;
private int $user_count;
public function processData(string $raw_email): void {
$this->email = $raw_email;
// PHPStan catches: calling array method on string property
$this->email->map(fn($e) => strtolower($e)); // Error!
// PHPStan catches: assigning wrong type
$this->user_count = "not a number"; // Error!
// PHPStan verifies: operations match type
echo strlen($this->email); // OK: string method on string property
}
}
To use PHPStan with WordPress plugins:
# Install PHPStan
composer require --dev phpstan/phpstan phpstan/phpstan-wordpress
# Run analysis
./vendor/bin/phpstan analyse src/ --level=8
Configuration file (phpstan.neon):
includes:
- vendor/phpstan/phpstan-wordpress/extension.neon
parameters:
level: 8
paths:
- src/
bootstrapFiles:
- tests/bootstrap.php
Migration Strategy
Migrating existing WordPress plugins to typed properties requires careful planning to avoid breaking changes.
Phase 1 - Preparation: Analyze existing code to understand property usage patterns. Update to PHP 7.4+ minimum version to support typed properties.
Phase 2 - Declare types: Add type declarations to properties gradually. Start with public APIs (classes exposed to external code), then internal classes. This ensures external consumers see consistent typing.
Phase 3 - Update static analysis: Configure PHPStan or Psalm with increasing strictness levels as types improve. Start at level 1, gradually increase to level 8 as code quality improves.
Phase 4 - Test thoroughly: Test typed code thoroughly, particularly at boundaries where typed code interfaces with untyped code or WordPress internals.
// Migration example: Before
class PluginSettings {
private $options = [];
public function set($key, $value) {
$this->options[$key] = $value;
}
public function get($key) {
return $this->options[$key] ?? null;
}
}
// Migration example: After (Phase 1-2)
class PluginSettings {
private array $options = [];
public function set(string $key, mixed $value): void {
$this->options[$key] = $value;
}
public function get(string $key): mixed {
return $this->options[$key] ?? null;
}
}
// Migration example: After (Phase 3-4, more specific)
class PluginSettings {
private array $options = [];
public function setString(string $key, string $value): void {
$this->options[$key] = $value;
}
public function getString(string $key): string {
if (!isset($this->options[$key]) || !is_string($this->options[$key])) {
throw new InvalidArgumentException("Setting {$key} is not a string");
}
return $this->options[$key];
}
}
Advanced Type Patterns
Modern PHP typing goes beyond basic property declarations. Intersection types (PHP 8.1+) require values implement multiple interfaces. Mixed types indicate values can be any type, useful when genuinely any value is acceptable.
// Intersection types - value must implement both interfaces
class PluginValidator {
private Logger&Auditable $audit_log; // Must implement both
public function validate(mixed $input): bool {
// mixed accepts any type - use sparingly
$result = match(get_type($input)) {
'string' => strlen($input) > 0,
'array' => count($input) > 0,
'object' => method_exists($input, 'validate'),
default => false
};
return $result;
}
}
First-class callables (PHP 8.1+) provide type-safe ways to reference functions and methods without strings:
// Traditional string-based callback
add_filter('the_content', 'my_filter_function'); // String, not type-safe
// First-class callable
add_filter('the_content', my_filter_function(...)); // Type-safe reference
Constructor promotion combined with typed properties reduces boilerplate significantly:
// PHP 8.0+ constructor property promotion
class SecurityConfig {
public function __construct(
public readonly string $api_key,
public readonly string $api_secret,
private int $cache_ttl = 3600,
private bool $debug_mode = false
) {
// Constructor body is optional
// Properties are automatically assigned
}
}
// Usage
$config = new SecurityConfig('key', 'secret');
echo $config->api_key; // Works
$config->api_key = 'new_key'; // Error: readonly
Typed properties significantly reduce the cognitive load when reading code. Rather than guessing what types a property might hold, developers see explicit declarations. This improves code comprehension, reduces debugging time, and enables more confident refactoring.
Additional Resources
Broader Context and Best Practices
Code quality in WordPress plugins extends far beyond aesthetic preferences or stylistic choices. Quality code is fundamentally about maintainability, which directly impacts security, performance, and reliability over time. When code is well-structured with clear separation of concerns, consistent naming conventions, and comprehensive error handling, bugs are easier to spot, fixes are faster to implement, and new features can be added without introducing regressions.
The WordPress plugin ecosystem benefits enormously from shared coding standards and conventions. When developers follow established patterns for hook usage, option storage, database operations, and API interactions, their code becomes instantly readable to other WordPress developers. This readability matters not just for open-source contributions but also for commercial plugins where team members change over time.
Technical debt in WordPress plugins accumulates silently until it becomes a crisis. Each shortcut taken during development, each deprecated function left in place, each test not written adds to the debt balance. Unlike financial debt, technical debt compounds unpredictably. Proactive quality management through automated code analysis identifies these time bombs before they detonate.
Modern WordPress development demands a level of engineering discipline that matches the platform's maturity. Plugins that started as simple utility scripts a decade ago now handle payment processing, personal data management, and business-critical workflows. Applying professional software engineering practices like automated testing, continuous integration, dependency management, and architectural patterns isn't over-engineering for WordPress.
Broader Industry Context and Best Practices
Code quality in WordPress plugin development encompasses more than functional correctness. Well-structured plugins follow established design patterns, maintain clear separation of concerns, and provide comprehensive error handling that degrades gracefully under unexpected conditions. Static analysis tools catch common issues before they reach production, while automated testing validates behavior across different WordPress versions and PHP configurations. WP HealthKit evaluates plugin code quality automatically, identifying patterns that may indicate maintainability issues or potential bugs. Investing in code quality upfront reduces the total cost of ownership by minimizing debugging time, simplifying feature additions, and reducing the risk of production incidents that damage user trust.
Documentation quality directly impacts plugin adoption and long-term success. Internal documentation helps development teams maintain consistency as team members change, while external documentation determines how easily users can implement and troubleshoot the plugin. Effective documentation includes architecture decision records that explain why certain approaches were chosen, API reference guides with practical examples, and troubleshooting guides that address common issues. WP HealthKit checks documentation completeness as part of its quality assessment, ensuring plugins meet the standards expected by professional WordPress developers. Well-documented plugins also reduce support burden, freeing development resources for feature work rather than answering repetitive questions.
Performance optimization represents a critical quality dimension that affects user experience and search engine rankings. WordPress plugins that introduce unnecessary database queries, load excessive JavaScript, or fail to implement proper caching can significantly degrade site performance. Profiling tools help identify performance bottlenecks, while load testing validates behavior under realistic traffic conditions. WP HealthKit identifies performance anti-patterns during its quality scans, flagging issues like unoptimized database queries, missing indexes, and excessive HTTP requests. Performance budgets establish measurable targets that prevent gradual degradation, ensuring plugins maintain acceptable response times as features are added and content grows.
Strategic Considerations and Implementation Patterns
Automated code review tools complement manual review by catching common issues consistently and efficiently. Static analysis identifies potential bugs, security vulnerabilities, and style violations without executing code. Complexity metrics highlight functions that may be difficult to maintain or test. WP HealthKit performs automated quality analysis that identifies patterns associated with common WordPress plugin issues, providing developers with actionable feedback before code reaches production. Integrating automated review into pull request workflows ensures that every code change receives consistent quality evaluation, catching issues that human reviewers might overlook due to familiarity or time pressure.
Frequently Asked Questions
Do typed properties work with WordPress' older PHP version requirements?
Typed properties require PHP 7.4+. If your plugin supports PHP 7.0+, you can't use typed properties directly. However, you can use them in newer code paths or require PHP 7.4+ minimum.
How do I handle WordPress' mixed-type function returns?
Many WordPress functions return either the data or false on error. Use union types to represent this: string|false or array|null. This more accurately represents WordPress' actual behavior.
Should I use typed properties for backward compatibility?
If you're supporting older PHP versions, maintain backward compatibility by using comments for type hints: /** @var string $email */. Migrate to typed properties when dropping support for PHP < 7.4.
What if a property legitimately needs to be multiple types?
Use union types: string|int|array. If many types are valid, consider whether the design could be improved by using more specific types or interfaces.
Do typed properties affect WordPress serialization?
No, typed properties serialize identically to untyped properties. Serialization is unaffected by property types.
How do typed properties interact with magic methods?
Typed properties don't affect __get or __set behavior. Magic methods still operate on undefined properties. For defined typed properties, direct access bypasses magic methods.
Conclusion
PHP typed properties represent a fundamental shift toward safer, more maintainable WordPress code. By declaring property types explicitly, developers gain immediate feedback about type mismatches, enable static analysis tools to catch subtle bugs, and allow PHP engines to optimize code. Combined with modern IDE support that uses type information for autocomplete and inline documentation, typed properties dramatically improve developer experience.
WP HealthKit analyzes WordPress plugins to identify type safety opportunities and recommends migrations to typed properties. By gradually adopting typed properties, WordPress plugin developers can modernize their codebases while maintaining backward compatibility.
Ready to improve WordPress plugin code quality with typed properties? Upload your WordPress installation to WP HealthKit to receive analysis and recommendations for type safety improvements. Explore WP HealthKit's ecosystem integrations for static analysis tools, and learn more about WordPress plugin quality in our guide to PHPStan static analysis for WordPress. Reference the PHP.net typed properties documentation for additional syntax details.