Re: [RFC] [Discussion] Clone with
| From: | Nicolas Grekas | Date: | Sat, 10 Jun 2023 14:07:25 +0000 |
| Subject: | Re: [RFC] [Discussion] Clone with | ||
| References: | 1 2 3 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-120555@lists.php.net to get a copy of this message | ||
Le ven. 9 juin 2023 à 02:11, Larry Garfield <larry@garfieldtech.com> a
écrit :
> On Thu, Jun 8, 2023, at 5:39 PM, Stephen Reay wrote:
>
> > Is there a specific reason
clone with (foo: $bar); can’t
> > simply pass
> > the arguments given to with(), to the __clone() magic method?
> >
> > It leaves the developer free to use the passed argument(s) or deep
> > clone existing properties or a mix depending on what’s passed, and
> > seems like it has the least “magic” or unknown behaviour in terms of
> > when things happen.
> >
> > Just a thought.
>
> I experimented with that a few years back. The results showed it is
> actually pretty awful in practice.
>
>
> https://peakd.com/hive-168588/@crell/object-properties-part-2-examples
>
Thanks for the link.
Instead of passing arguments to __clone(), I wondered about a new
__clone_with(array
$properties) that could be implemented next to __clone(), with the
following behavior:
- if only __clone is implemented, same behavior as always
- if __clone_with is implemented, __clone is never called. Instead, the
engine calls __clone_with with the map of properties in the "with" (or
an empty array if no "with"). In addition and *before* the call, the engine
sets the properties according to the "with" (potentially failing
visibility/type checks as expected)
Rewriting the example in your blog post Larry, the Request class would then
look like below.
And I think I like it.
WDYT? Mate?
class Request implements RequestInterface
{
public readonly array $headers = [];
public readonly UriInterface $uri;
public readonly string $version = '1.1';
public readonly string $method = 'GET';
public function __clone()
{
$this->uri = clone $this->uri;
}
public function __clone_with(array $properties)
{
if (isset($properties['version']) && !in_array($this->version,
['1.1', '1.0',
'2.0'])) {
throw new InvalidArgumentException($k);
}
$headers = $this->headers;
if (isset($properties['uri'])) {
$headers['host'] = $this->uri->host;
} else {
$this->uri = clone $this->uri;
}
$this->headers = $headers;
}
public function getHeader($name): string
{
return $this->headers[strtolower($name)] ?? '';
}
// Still needed because of the $preserveHost = true option.
public function withUri(UriInterface $uri, bool $preserveHost = false):
static
{
$headers = $this->headers;
// If headers were itself a pseudo-immutable object, this would be even
uglier.
if ($preserveHost && isset($headers['host'])) {
$headers['host'] = $uri->host;
}
return clone $this with (uri: $uri, headers: $headers);
}
public function withHeader(string $name, string $value): static
{
$headers = $this->headers;
$headers[strtolower($name)] = $value;
return clone $this with (headers: $headers);
}
}