Picture this: data that's as stable as bedrock – set it once, and it stays that way forever. That's exactly what PHP 8.1 delivered with readonly properties. Think of it as giving your objects a safety vault – keeping their data secure from accidental changes. Let's explore how this powerful feature can streamline your code and what gotchas you need to watch out for.
Here's a quick taste of what we're talking about:
Once that name is set, it's locked in place. No accidental changes, no sneaky updates.
When is uninitialized really uninitialized?
Here's a common misconception: many developers think readonly properties must be set in the constructor. But PHP is actually much more flexible than that – you can set them at any point in an object's lifecycle, with one crucial rule: only once! Before that first assignment, they exist in a special ‘uninitialized’ state – think of it as a blank slate waiting for its first and only value.
Here's an interesting twist – readonly properties can't have default values. Why? Think about it: if they had default values, they'd essentially be constants – set at object creation and unchangeable from that point on.
Types are mandatory
When using readonly properties, you must explicitly declare their type. This
isn't just PHP being picky – the ‘uninitialized’ state only works with
typed variables. No type declaration means no readonly variable. Don't know the
exact type? No worries – you can always fall back on mixed
.
Readonly classes: Taking immutability to the next level
PHP 8.2 kicked things up a notch. Instead of securing individual properties, you can now lock down an entire class. It's like upgrading from a safe to a vault:
But hold on – this power comes with some strict rules:
- Every property must have a type
- Static properties are off-limits
- Dynamic properties? Not happening, even with
#[AllowDynamicProperties]
- And in PHP 8.2, readonly classes could only inherit from other readonly classes (PHP 8.3 loosened this restriction)
Initialization rules: Who can set what, and when?
Here's where things get interesting. Take a look at this code:
Caught you by surprise? Even though it's the first assignment, PHP won't allow it. The same goes for child classes trying to initialize their parent's readonly properties:
Until recently, only the defining class could initialize a readonly property. But PHP 8.4 changes the game with two major updates:
- Child classes can now initialize readonly properties (finally!)
- The new public(set) modifier lets you open up initialization:
When readonly isn't quite readonly
Think of readonly like a secure container – while you can't swap out the container, you can still modify what's inside. This means readonly doesn't guarantee complete immutability. When you store an object in a readonly property, its internal state remains changeable:
See the catch? While you can't replace the $settings
object
itself, its properties are still fair game.
Arrays have their own special rules. You can't directly modify array elements because PHP sees this as changing the entire array:
But there's a clever workaround – references. If your array contains references, you can modify their values because PHP doesn't consider this a change to the array itself:
Wither methods and readonly: A practical approach
When working with immutable objects, you often need methods to create modified versions. Enter “wither” methods – unlike traditional setters, these return a new instance with your changes while leaving the original untouched. The PSR-7 HTTP request specification is a prime example of this pattern.
But here's the challenge: readonly properties can't be changed even in
wither methods, not even in object copies. While PHP 8.3 lets you modify
readonly properties in __clone()
, that alone isn't enough since you
can't access the new value during cloning. Here's a neat solution to this
puzzle:
Testing made easier with BypassFinals
Testing code with readonly properties can be tricky – like their cousin
final
, they can make mocking and testing more complex. Enter BypassFinals library, a clever
library that solves this headache.
This handy tool can temporarily remove final
and
readonly
keywords at runtime, making your previously unmockable
code testable. Using it is straightforward:
Key takeaways
Readonly properties are a powerful addition to your PHP toolbox, helping you write more reliable code. Here are the essential points to remember:
- You can initialize readonly properties any time, but you only get one shot
- Type declarations are non-negotiable
- Pre-PHP 8.4, only the defining class could initialize properties
- Readonly doesn't mean completely immutable – especially for nested objects
- PHP 8.2 lets you declare entire classes as readonly
- Testing is made easier with BypassFinals
- PHP 8.4's public(set) modifier adds welcome flexibility
Leave a comment