Last active
January 10, 2023 15:43
-
-
Save Crell/2c4c35bbfe3b431b07d09cc730ceb310 to your computer and use it in GitHub Desktop.
Immutable options, PSR-7 example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// PSR-7 today | |
class Request implements RequestInterface | |
{ | |
private UriInterface $uri; | |
private array $headers = []; | |
private string $method = 'GET'; | |
private string $version = '1.1'; | |
public function getUri(): UriInterface | |
{ | |
return $this->uri; | |
} | |
public function getMethod(): string | |
{ | |
return $this->method; | |
} | |
public function getVersion(): string | |
{ | |
return $this->version; | |
} | |
public function getHeaders(): array | |
{ | |
return $this->headers; | |
} | |
public function getHeader($name): string | |
{ | |
return $this->headers[strtolower($name)] ?? ''; | |
} | |
public function withMethod(string $method): static | |
{ | |
$new = clone($this); | |
$new->method = $method; | |
return $new; | |
} | |
public function withProtocolVersion(string $version): static | |
{ | |
if (!in_array($version, ['1.1', '1.0', '2.0'])) { | |
throw new InvalidArgumentException(); | |
} | |
$new = clone($this); | |
$new->version = $version; | |
return $new; | |
} | |
public function withUri(UriInterface $uri, bool $preserveHost = false): static | |
{ | |
$new = clone($this); | |
$new->uri = $uri; | |
if ($preserveHost && isset($new->headers['host'])) { | |
return $new; | |
} | |
$new->headers['Host'] = $new->uri->getHost(); | |
return $new; | |
} | |
public function withHeader(string $name, string $value): static | |
{ | |
$new = clone($this); | |
$new->headers[strtolower($name)] = $value; | |
return $this; | |
} | |
} | |
$r1 = new Request(); | |
$r2 = $r1->withMethod('POST'); | |
$r3 = $r2->withUri(new Uri('https://php.net/')); | |
$r4 = $r3->withProtocolVersion('2.0'); | |
$r5 = $r4->withHeader('cache', 'none'); | |
print $r5->getMethod(); | |
print $r5->getUri()->getHost(); | |
print $r5->getProtocolVersion(); | |
print_r($r5->getHeaders()); | |
print $r5->getHeader('cache'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// PSR-7 with asymmetric visibility and clone-with | |
class Request implements RequestInterface | |
{ | |
get:public set:private UriInterface $uri; | |
get:public set:private $headers = []; | |
get:public set:private $method = 'GET'; | |
get:public set:private $version = '1.1'; | |
public function __clone(...$args) | |
{ | |
foreach ($args as $k => $v) { | |
switch ($k) { | |
case 'version': | |
if (!in_array($v, ['1.1', '1.0', '2.0'])) { | |
throw new InvalidArgumentException($k); | |
} | |
$this->version = $v; | |
break; | |
case 'uri': | |
// This will type fail for us if $v isn't a Uri object. | |
$this->uri = $v; | |
$this->headers['host'] = $v->host; | |
break; | |
case 'method': | |
case 'headers': | |
$this->$k = $v; | |
break; | |
} | |
} | |
} | |
public function getHeader($name): string | |
{ | |
return $this->headers[strtolower($name)] ?? ''; | |
} | |
public function withMethod(string $method): static | |
{ | |
return clone($this, method: $method); | |
} | |
public function withProtocolVersion(string $version): static | |
{ | |
if (!in_array($version, ['1.1', '1.0', '2.0'])) { | |
throw new InvalidArgumentException(); | |
} | |
return clone($this, version: $version }; | |
} | |
public function withUri(UriInterface $uri, bool $preserveHost = false): static | |
{ | |
$new = clone($this, uri: $uri); | |
if ($preserveHost && isset($new->headers['host'])) { | |
return $new; | |
} | |
$new->headers['Host'] = $new->uri->host; | |
return $new; | |
} | |
public function withHeader(string $name, string $value): static | |
{ | |
$headers = $this->headers; | |
$headers[strtolower($name)] = $value; | |
return clone($this, headers: $headers); | |
} | |
} | |
$r1 = new Request(); | |
$r2 = $r1->withMethod('POST'); | |
$r3 = $r2->withUri(new Uri('https://php.net/')); | |
$r4 = $r3->withProtocolVersion('2.0'); | |
$r5 = $r4->withHeader('cache', 'none'); | |
print $r5->method; | |
print $r5->uri->host; | |
print $r5->version; | |
print_r($r5->headers); | |
print $r5->getHeader('cache'); | |
// This errors out correctly, because the properties | |
// are not publicly settable. | |
$r6 = clone($r5, | |
uri: new Uri('https://python.org/'), | |
headers: [host: 'http://java.com/'], | |
version: 'the old one', | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// PSR-7 with asymmetric visibility and clone-with | |
class Request implements RequestInterface | |
{ | |
get:public set:private UriInterface $uri; | |
get:public set:private $headers = []; | |
get:public set:private $method = 'GET'; | |
get:public set:private $version = '1.1'; | |
public function getHeader($name): string | |
{ | |
return $this->headers[strtolower($name)] ?? ''; | |
} | |
public function withMethod(string $method): static | |
{ | |
return clone($this) with {method: $method}; | |
} | |
public function withProtocolVersion(string $version): static | |
{ | |
if (!in_array($version, ['1.1', '1.0', '2.0'])) { | |
throw new InvalidArgumentException(); | |
} | |
return clone($this) with {version: $version }; | |
} | |
public function withUri(UriInterface $uri, bool $preserveHost = false): static | |
{ | |
$new = clone($this) with {uri: $uri}; | |
if ($preserveHost && isset($new->headers['host'])) { | |
return $new; | |
} | |
$new->headers['Host'] = $new->uri->host; | |
return $new; | |
} | |
public function withHeader(string $name, string $value): static | |
{ | |
$headers = $this->headers; | |
$headers[strtolower($name)] = $value; | |
return clone($this) with { headers: $headers }; | |
} | |
} | |
$r1 = new Request(); | |
$r2 = $r1->withMethod('POST'); | |
$r3 = $r2->withUri(new Uri('https://php.net/')); | |
$r4 = $r3->withProtocolVersion('2.0'); | |
$r5 = $r4->withHeader('cache', 'none'); | |
print $r5->method; | |
print $r5->uri->host; | |
print $r5->version; | |
print_r($r5->headers); | |
print $r5->getHeader('cache'); | |
// This errors out correctly, because the properties | |
// are not publicly settable. | |
$r6 = clone($r5) with { | |
uri: new Uri('https://python.org/'), | |
headers: [host: 'http://java.com/'], | |
version: 'the old one', | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// PSR-7 with initonly and __clone args | |
class Request implements RequestInterface | |
{ | |
public initonly UriInterface $uri; | |
public initonly array $headers = []; | |
public initonly string $method = 'GET'; | |
public initonly string $version = '1.1'; | |
public function __clone(...$args) | |
{ | |
foreach ($args as $k => $v) { | |
switch ($k) { | |
case 'version': | |
if (!in_array($v, ['1.1', '1.0', '2.0'])) { | |
throw new InvalidArgumentException($k); | |
} | |
$this->version = $v; | |
break; | |
case 'uri': | |
// This will type fail for us if $v isn't a Uri object. | |
$this->uri = $v; | |
$this->headers['host'] = $v->host; | |
break; | |
case 'method': | |
case 'headers': | |
$this->$k = $v; | |
break; | |
} | |
} | |
} | |
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 | |
{ | |
$args['uri'] = $uri; | |
// If headers were itself a pseudo-immutable object, this would be even uglier. | |
if ($preserveHost && isset($this->headers['host'])) { | |
$headers = $this->headers; | |
$headers['host'] = $uri->host; | |
$args['headers'] = $headers; | |
} | |
return clone($this, ...$args); | |
} | |
public function withHeader(string $name, string $value): static | |
{ | |
$headers = $this->headers; | |
$headers[strtolower($name)] = $value; | |
return clone($this, headers: $headers }; | |
} | |
} | |
$r1 = new Request(); | |
$r2 = clone $r1 with { | |
method: 'POST' }; | |
$r3 = clone($r2, uri: new Uri('https://php.net/')); | |
$r4 = clone($r3, version: '2.0'); | |
$r5 = $r4->withHeader('cache', 'none'); | |
print $r5->method; | |
print $r5->uri->host; | |
print $r5->version; | |
print_r($r5->headers); | |
print $r5->getHeader('cache'); | |
// This will now error out. | |
$r6 = clone($r5, | |
uri: new Uri('https://python.org/'), | |
headers: [host: 'http://java.com/'], | |
version: 'the old one', | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// PSR-7 with initonly and clone-with | |
class Request implements RequestInterface | |
{ | |
public initonly UriInterface $uri; | |
public initonly array $headers = []; | |
public initonly string $method = 'GET'; | |
public initonly string $version = '1.1'; | |
public function getHeader($name): string | |
{ | |
return $this->headers[strtolower($name)] ?? ''; | |
} | |
public function withProtocolVersion(string $version): static | |
{ | |
if (!in_array($version, ['1.1', '1.0', '2.0'])) { | |
throw new InvalidArgumentException(); | |
} | |
return clone($this) with { version: $version }; | |
} | |
public function withUri(UriInterface $uri, bool $preserveHost = false): static | |
{ | |
$args['uri'] = $uri; | |
// If headers were itself a pseudo-immutable object, this would be even uglier. | |
if ($preserveHost && isset($this->headers['host'])) { | |
$headers = $this->headers; | |
$headers['host'] = $uri->host; | |
$args['headers'] = $headers; | |
} | |
return clone($this) with { ...$args }; | |
} | |
public function withHeader(string $name, string $value): static | |
{ | |
$headers = $this->headers; | |
$headers[strtolower($name)] = $value; | |
return clone($this) with { headers: $headers }; | |
} | |
} | |
$r1 = new Request(); | |
$r2 = clone $r1 with { method: 'POST' }; | |
$r3 = $r2->withUri(new Uri('https://php.net/')); | |
$r4 = $r3->withProtocolVersion('2.0'); | |
$r5 = $r4->withHeader('cache', 'none'); | |
print $r5->method; | |
print $r5->uri->host; | |
print $r5->version; | |
print_r($r5->headers); | |
print $r5->getHeader('cache'); | |
// This becomes allowed, but shouldn't be. | |
$r6 = clone($r5) with { | |
uri: new Uri('https://python.org/'), | |
headers: [host: 'http://java.com/'], | |
version: 'the old one', | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment