I don't have any arguments to add other than: It adds a lot of complexity and potential confusion for (IMO) negligible gain.
I would really like to see actual use cases (and less foobar) because I truely can't think of valid ones.. For mutable constructs I would just use methods to change the state and for immutable constructs readonly
does the trick for me.
clone with
would be nice, but this works reasonably fine for me:
final readonly class SomeClass { public function __construct( public string $prop1, public bool $prop2, ) {} public function with( string $prop1 = null, bool $prop2 = null, ): self { return new self( $prop1 ?? $this->prop1, $prop2 ?? $this->prop2, ); } }
to be used with named arguments like
$someClass->with( prop1: 'changed', );
This really makes the language complex.
The part with the interactions with the readonly keyword is really hard to make sense of.
I understand the subtle differences, don't get me wrong, it's just that I cannot understand them by just looking at the property definitions quickly. Imagine having to quickly make sense of a class with a dozen of properties like that. It's just nightmarish. I wouldn't accept a merge request that messy in my codebase.
I feel like if you need that much fine-grain control this is when having explicit getters/setters make sense.
Add property hooks on top of that and you have an explosion of complexity where you don't want: your domain logic is complex enough on it's own, you don't want to have properties become a maze.
I'm conflicted about this RFC. I definitely have use cases for "publicly readable and privately writable" properties. Especially since we still don't have clone with
. I'm a afraid of the overlap between asymmetric visibility with readonly properties. By introducing asymmetric visibility, we make readonly properties essentially useless (asymmetric visibility can do the same, and more). I wonder how the community will deal with such a change: readonly properties were introduced in PHP 8.1, what happens if they become obsolete only a couple of versions later?
Despite my worry, I like this proposal on its own, and it would actually be useful to me in many cases. In the end, I'm inclined more towards voting yes than no.
There are some arguments against the implementation and I agree to those. But in addition I don't agree to the concept itself, personally, because I am convinced that immutability is key when enforcing domain logic. E.g. the example above could be replaced by:
readonly class User { public function __construct(public string $name) { if (strlen($name) === 0) { throw new ValueError('Name must be non-empty'); } } public function withName(string $newName): self { return new self($newName); } }
Or, even better IMO, with value objects that validate themselves:
readonly class User { public function __construct(public Name $name) {} public function withName(Name $newName): self { return new self($newName); } } readonly class Name { public function __construct(public string $value) { if (strlen($value) === 0) { throw new ValueError('Name must be non-empty'); } } }
Granted, this is only one example and immutabilty is not a silver-bullet. But I haven't come across a usecase for property hooks that was really convincing yet
I think complaints about the syntax being messy are really about the array manipulation functions being inherently hard to format nicely.
To be clear I agree that the proposed example isn't great, nested closures are never going to win any readabillity prizes. If however we look at any non-array based manipulation I think the readabillity is objectively better:
$name = 'my_user_name' |> fn (string $string): string => str_replace('_', ' ', $string) |> strtolower(...) |> ucwords(...) |> trim(...);
Or without first class callables:
$name = 'my_user_name' |> fn (string $string): string => str_replace('_', ' ', $string) |> fn (string $string): string => strtolower($string) |> fn (string $string): string => ucwords($string) |> fn (string $string): string => trim($string);
Bonus: this also adds runtime type checks to each step. Eg strreplace
returns string|array
I can't imagine anyone would think this is better:
$name = trim( ucwords( strtolower( str_replace('_', ' ', 'my_user_name') ) ) );
..a controversial one indeed. Personally I would try to avoid this feature in my own code (just like I avoid traits and abstract classes because I feel that they are a sign of bad abstractions).
To me the fact that convinced me is the last section of the RFC:
This feature may be used to enhance existing interfaces in PHP. Countable could add function isEmpty(): bool { return $this->count() == 0; }. Iterator could add methods like map, filter, and reduce which behave similarly to array_map, array_filter, and array_reduce.
At least once a week, I throw away an array_map because it ended up looking too bloated and go with a classic foreach instead. Short Closures 2.0 without the use(...) block would've solved this problem, just 2 votes...