parent
67d795a506
commit
1d2dab4875
14 changed files with 424 additions and 1 deletions
@ -0,0 +1,46 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: cache Closure::bind private property accessor |
||||||
|
--XFAIL-- |
||||||
|
Known AOT bug: Closure::bind() on a static closure can be treated as unbinding $this from a method closure. |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeCacheItem |
||||||
|
{ |
||||||
|
private mixed $value = null; |
||||||
|
|
||||||
|
public function expose(): mixed |
||||||
|
{ |
||||||
|
return $this->value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class SymfonyLikeCacheAdapter |
||||||
|
{ |
||||||
|
private static ?Closure $setValue = null; |
||||||
|
|
||||||
|
public function set(SymfonyLikeCacheItem $item, mixed $value): void |
||||||
|
{ |
||||||
|
$setValue = self::$setValue ??= Closure::bind( |
||||||
|
static function (SymfonyLikeCacheItem $item, mixed $value): void { |
||||||
|
$item->value = $value; |
||||||
|
}, |
||||||
|
null, |
||||||
|
SymfonyLikeCacheItem::class |
||||||
|
); |
||||||
|
|
||||||
|
$setValue($item, $value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$item = new SymfonyLikeCacheItem(); |
||||||
|
$adapter = new SymfonyLikeCacheAdapter(); |
||||||
|
|
||||||
|
$adapter->set($item, 'cached'); |
||||||
|
var_dump($item->expose()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(6) "cached" |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: comparison with coalesce assignment in condition |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function splitLimit(?int $limit): string |
||||||
|
{ |
||||||
|
if (1 > $limit ??= PHP_INT_MAX) { |
||||||
|
return 'small:'.$limit; |
||||||
|
} |
||||||
|
|
||||||
|
return 'ok:'.$limit; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(splitLimit(null)); |
||||||
|
var_dump(splitLimit(0)); |
||||||
|
var_dump(splitLimit(2)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(22) "ok:9223372036854775807" |
||||||
|
string(7) "small:0" |
||||||
|
string(4) "ok:2" |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: dynamic constant resolves enum case |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
enum SymfonyLikeFooEnum |
||||||
|
{ |
||||||
|
case Bar; |
||||||
|
} |
||||||
|
|
||||||
|
function resolveEnumConstant(string $name): UnitEnum |
||||||
|
{ |
||||||
|
return constant($name) instanceof UnitEnum |
||||||
|
? constant($name) |
||||||
|
: throw new TypeError('Not an enum case.'); |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$case = resolveEnumConstant(SymfonyLikeFooEnum::class.'::Bar'); |
||||||
|
var_dump($case::class); |
||||||
|
var_dump($case->name); |
||||||
|
|
||||||
|
try { |
||||||
|
resolveEnumConstant('PHP_VERSION'); |
||||||
|
} catch (Throwable $e) { |
||||||
|
var_dump($e::class); |
||||||
|
var_dump($e->getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(18) "SymfonyLikeFooEnum" |
||||||
|
string(3) "Bar" |
||||||
|
string(9) "TypeError" |
||||||
|
string(17) "Not an enum case." |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: enum case runtime class cached with ??= |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
enum SymfonyLikeRequirement |
||||||
|
{ |
||||||
|
case Slug; |
||||||
|
case Uuid; |
||||||
|
} |
||||||
|
|
||||||
|
function enumCaseClasses(UnitEnum ...$cases): array |
||||||
|
{ |
||||||
|
$class = null; |
||||||
|
$result = []; |
||||||
|
|
||||||
|
foreach ($cases as $case) { |
||||||
|
$class ??= $case::class; |
||||||
|
$result[] = [$class, $case::class, $case->name]; |
||||||
|
} |
||||||
|
|
||||||
|
return $result; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(enumCaseClasses(SymfonyLikeRequirement::Slug, SymfonyLikeRequirement::Uuid)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
string(22) "SymfonyLikeRequirement" |
||||||
|
[1]=> |
||||||
|
string(22) "SymfonyLikeRequirement" |
||||||
|
[2]=> |
||||||
|
string(4) "Slug" |
||||||
|
} |
||||||
|
[1]=> |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
string(22) "SymfonyLikeRequirement" |
||||||
|
[1]=> |
||||||
|
string(22) "SymfonyLikeRequirement" |
||||||
|
[2]=> |
||||||
|
string(4) "Uuid" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: set_error_handler with static arrow function throwing |
||||||
|
--ENV-- |
||||||
|
USE_ZEND_ALLOC=0 |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
set_error_handler(static fn ($type, $message, $file, $line) => throw new ErrorException($message, 0, $type, $file, $line)); |
||||||
|
|
||||||
|
try { |
||||||
|
trigger_error('symfony warning', E_USER_WARNING); |
||||||
|
} catch (Throwable $e) { |
||||||
|
var_dump($e::class); |
||||||
|
var_dump($e->getMessage()); |
||||||
|
} finally { |
||||||
|
restore_error_handler(); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(14) "ErrorException" |
||||||
|
string(15) "symfony warning" |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: event name defaults to runtime object class with ??= |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeEvent |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
class SymfonyLikeChildEvent extends SymfonyLikeEvent |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
function dispatch(object $event, ?string $eventName = null): string |
||||||
|
{ |
||||||
|
$eventName ??= $event::class; |
||||||
|
|
||||||
|
return $eventName; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(dispatch(new SymfonyLikeEvent())); |
||||||
|
var_dump(dispatch(new SymfonyLikeChildEvent())); |
||||||
|
var_dump(dispatch(new SymfonyLikeChildEvent(), 'custom.event')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(16) "SymfonyLikeEvent" |
||||||
|
string(21) "SymfonyLikeChildEvent" |
||||||
|
string(12) "custom.event" |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: normalize array listener method with ??= |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeListener |
||||||
|
{ |
||||||
|
public function __invoke(): string |
||||||
|
{ |
||||||
|
return 'invoked'; |
||||||
|
} |
||||||
|
|
||||||
|
public function onEvent(): string |
||||||
|
{ |
||||||
|
return 'event'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function normalizeListener(array $listener): string |
||||||
|
{ |
||||||
|
$listener[1] ??= '__invoke'; |
||||||
|
|
||||||
|
return $listener[0]::class.'::'.$listener[1]; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(normalizeListener([new SymfonyLikeListener()])); |
||||||
|
var_dump(normalizeListener([new SymfonyLikeListener(), 'onEvent'])); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(29) "SymfonyLikeListener::__invoke" |
||||||
|
string(28) "SymfonyLikeListener::onEvent" |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: foreach array destructuring with keyed rows |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$openHandles = [ |
||||||
|
'a' => [1, 'handle-a', 'buffer-a', static fn () => 'progress-a'], |
||||||
|
'b' => [2, 'handle-b', 'buffer-b', static fn () => 'progress-b'], |
||||||
|
]; |
||||||
|
|
||||||
|
foreach ($openHandles as $id => [$pauseExpiry, $handle, $buffer, $onProgress]) { |
||||||
|
var_dump($id.':'.$pauseExpiry.':'.$handle.':'.$buffer.':'.$onProgress()); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(32) "a:1:handle-a:buffer-a:progress-a" |
||||||
|
string(32) "b:2:handle-b:buffer-b:progress-b" |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: match expression used as array key |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function exportProperty(array &$properties, string $scope, string $name, mixed $value): void |
||||||
|
{ |
||||||
|
$prefix = $scope[0] ?? ''; |
||||||
|
$properties[match ($prefix) { |
||||||
|
"\0" => $scope.$name, |
||||||
|
'*' => "\0*\0".$name, |
||||||
|
default => $name, |
||||||
|
}] = $value; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$properties = []; |
||||||
|
exportProperty($properties, 'public', 'name', 'pub'); |
||||||
|
exportProperty($properties, '*', 'name', 'prot'); |
||||||
|
exportProperty($properties, "\0Private\0", 'name', 'priv'); |
||||||
|
|
||||||
|
foreach ($properties as $key => $value) { |
||||||
|
var_dump(str_replace("\0", '\\0', $key).':'.$value); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(8) "name:pub" |
||||||
|
string(14) "\0*\0name:prot" |
||||||
|
string(20) "\0Private\0name:priv" |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: nullsafe method result array offset with coalesce |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeStamp |
||||||
|
{ |
||||||
|
public function __construct(private array $attributes) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function getAttributes(): array |
||||||
|
{ |
||||||
|
return $this->attributes; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function contentType(?SymfonyLikeStamp $stamp): ?string |
||||||
|
{ |
||||||
|
return $stamp?->getAttributes()['content_type'] ?? null; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(contentType(null)); |
||||||
|
var_dump(contentType(new SymfonyLikeStamp([]))); |
||||||
|
var_dump(contentType(new SymfonyLikeStamp(['content_type' => 'application/json']))); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
NULL |
||||||
|
NULL |
||||||
|
string(16) "application/json" |
||||||
@ -0,0 +1,38 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: nullsafe call with coalesce throw expression |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeReceivedStamp |
||||||
|
{ |
||||||
|
public function __construct(private string $id) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function getId(): string |
||||||
|
{ |
||||||
|
return $this->id; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function stampId(?SymfonyLikeReceivedStamp $stamp): string |
||||||
|
{ |
||||||
|
return $stamp?->getId() ?? throw new LogicException('No stamp found.'); |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(stampId(new SymfonyLikeReceivedStamp('abc'))); |
||||||
|
|
||||||
|
try { |
||||||
|
stampId(null); |
||||||
|
} catch (Throwable $e) { |
||||||
|
var_dump($e::class); |
||||||
|
var_dump($e->getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(3) "abc" |
||||||
|
string(14) "LogicException" |
||||||
|
string(15) "No stamp found." |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: nullsafe __invoke callback check |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeTraceableDispatcher |
||||||
|
{ |
||||||
|
public function __construct(private ?Closure $disabled = null) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function dispatch(string $name): string |
||||||
|
{ |
||||||
|
if ($this->disabled?->__invoke()) { |
||||||
|
return 'disabled'; |
||||||
|
} |
||||||
|
|
||||||
|
return 'dispatch:'.$name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump((new SymfonyLikeTraceableDispatcher())->dispatch('kernel.request')); |
||||||
|
var_dump((new SymfonyLikeTraceableDispatcher(static fn () => false))->dispatch('kernel.response')); |
||||||
|
var_dump((new SymfonyLikeTraceableDispatcher(static fn () => true))->dispatch('kernel.exception')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(23) "dispatch:kernel.request" |
||||||
|
string(24) "dispatch:kernel.response" |
||||||
|
string(8) "disabled" |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: cache ReflectionMethod::invoke first-class callable |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$invoke = null; |
||||||
|
$invoke ??= (new ReflectionMethod(ReflectionMethod::class, 'getName'))->invoke(...); |
||||||
|
|
||||||
|
$method = new ReflectionMethod(DateTimeZone::class, 'getName'); |
||||||
|
var_dump($invoke($method)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(7) "getName" |
||||||
Loading…
Reference in new issue