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