Hi again,

Quite some time after mentioning the "clone with" construct the first time
> (at the end of the
> https://wiki.php.net/rfc/write_once_properties#run-time_behaviour
> section),
> finally I managed to create a working implementation for this feature which
> would make it possible to properly modify readonly properties
> while simplifying how we write "wither" methods:
> https://wiki.php.net/rfc/clone_with
>

As mentioned in another thread, I'd like to make an alternative proposal
for the syntax.
Alex talked about it already but I think it deserves more attention.

What about using a real closure to define the scope we need for cloning?
That closure would take the cloned instance as argument to allow
manipulating it at will.

Internally, the engine would "just" call that closure just after calling
__clone() if it's defined.

This could look like this:
$c = clone $a with $closure;

or maybe we could skip introducing a new keyword and go for something that
looks like a function call:
$c = clone($a, $closure);

I've adapted your examples from the RFC using the latter and here is how
they could look like:

The “wither” method copy-pasted from Diactoros:
class Response implements ResponseInterface {
public readonly int $statusCode;
public readonly string $reasonPhrase;
// ...
public function withStatus($code, $reasonPhrase = ''): Response
{
return clone($this, fn ($clone) => [
$clone->statusCode = $code,
$clone->reasonPhrase = $reasonPhrase,
]);
}
// ...
}

The property name expressions:
class Foo {
private $a;
private $b;
private $c;
/**
* @param array<string, mixed> $properties
*/
public function withProperties(array $properties) {
return clone($this, function ($clone) use ($properties) {
foreach ($properties as $name => $value) {
$clone->$name = $value;
}
});
}
}

Linking the cloned instance to the original one:
class LinkedObject {
public function __construct(
private readonly LinkedObject $next,
private readonly int $number
) {
$this->number = 1;
}
public function next(): LinkedObject {
return clone($this, fn ($clone) => [
$clone->number = $clone->number + 1,
$this->next = $clone,
]);
}
}

All these look pretty neat to me, and they come with no new syntax but a
simple call-like clone() statement.

Scope semantics remain the same as usual, so we already know how to
interpret that aspect.

Does that make sense to you?

Nicolas

Reply via email to