parent
4fc0acc47f
commit
7d20c4e994
13 changed files with 368 additions and 36 deletions
@ -0,0 +1,14 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS)] |
||||||
|
final class PreprocessorAttributeArrayArgument |
||||||
|
{ |
||||||
|
public function __construct(public array $methods = []) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[PreprocessorAttributeArrayArgument(methods: ['GET', 'POST'])] |
||||||
|
class PreprocessorAttributeArrayArgumentController |
||||||
|
{ |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD)] |
||||||
|
class PreprocessorAttributeSubscribedService |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
public string $key, |
||||||
|
public ?PreprocessorAttributeRequired $attribute = null, |
||||||
|
) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class PreprocessorAttributeRequired |
||||||
|
{ |
||||||
|
public function __construct(public bool $enabled = true) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class PreprocessorAttributeSubscriber |
||||||
|
{ |
||||||
|
#[PreprocessorAttributeSubscribedService(key: 'logger', attribute: new PreprocessorAttributeRequired(false))] |
||||||
|
public function logger(): string |
||||||
|
{ |
||||||
|
return 'logger'; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: anonymous exception class implements marker interface |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
interface SymfonyLikeNotFoundException |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
function createNotFoundException(string $id): Throwable |
||||||
|
{ |
||||||
|
return new class(sprintf('Service "%s" not found.', $id)) extends InvalidArgumentException implements SymfonyLikeNotFoundException { |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$exception = createNotFoundException('mailer'); |
||||||
|
|
||||||
|
var_dump($exception instanceof InvalidArgumentException); |
||||||
|
var_dump($exception instanceof SymfonyLikeNotFoundException); |
||||||
|
var_dump($exception->getMessage()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
bool(true) |
||||||
|
bool(true) |
||||||
|
string(27) "Service "mailer" not found." |
||||||
@ -0,0 +1,56 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: array_map with first-class object method callable |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeParameter |
||||||
|
{ |
||||||
|
public function __construct(private string $name, private int $position) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function toArray(): array |
||||||
|
{ |
||||||
|
return [$this->name, $this->position]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class SymfonyLikeParameterNormalizer |
||||||
|
{ |
||||||
|
public function normalize(SymfonyLikeParameter $parameter): string |
||||||
|
{ |
||||||
|
[$name, $position] = $parameter->toArray(); |
||||||
|
|
||||||
|
return $position.':'.$name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class SymfonyLikeDescriptor |
||||||
|
{ |
||||||
|
public function __construct(private SymfonyLikeParameterNormalizer $normalizer) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function describe(array $parameters): array |
||||||
|
{ |
||||||
|
return array_map($this->normalizer->normalize(...), $parameters); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$descriptor = new SymfonyLikeDescriptor(new SymfonyLikeParameterNormalizer()); |
||||||
|
|
||||||
|
var_dump($descriptor->describe([ |
||||||
|
new SymfonyLikeParameter('request', 0), |
||||||
|
new SymfonyLikeParameter('format', 1), |
||||||
|
])); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(9) "0:request" |
||||||
|
[1]=> |
||||||
|
string(8) "1:format" |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: array_map with first-class static method callable |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeScheduleRenderer |
||||||
|
{ |
||||||
|
public static function render(string $message, DateTimeImmutable $date, bool $all): ?array |
||||||
|
{ |
||||||
|
if (!$all && str_starts_with($message, 'skip')) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return [$message, $date->format('Y-m-d'), $all]; |
||||||
|
} |
||||||
|
|
||||||
|
public static function renderAll(array $messages, DateTimeImmutable $date, bool $all): array |
||||||
|
{ |
||||||
|
return array_filter(array_map( |
||||||
|
self::render(...), |
||||||
|
$messages, |
||||||
|
array_fill(0, count($messages), $date), |
||||||
|
array_fill(0, count($messages), $all) |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(SymfonyLikeScheduleRenderer::renderAll(['first', 'skip-second'], new DateTimeImmutable('2026-07-07'), false)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
string(5) "first" |
||||||
|
[1]=> |
||||||
|
string(10) "2026-07-07" |
||||||
|
[2]=> |
||||||
|
bool(false) |
||||||
|
} |
||||||
|
} |
||||||
@ -1,35 +0,0 @@ |
|||||||
--TEST-- |
|
||||||
Symfony pattern: attribute array arguments |
|
||||||
--SKIPIF-- |
|
||||||
<?php |
|
||||||
exit('skip Array arguments to attributes are not supported by the AOT compiler'); |
|
||||||
?> |
|
||||||
--FILE-- |
|
||||||
<?php |
|
||||||
|
|
||||||
#[Attribute(Attribute::TARGET_CLASS)] |
|
||||||
final class SymfonyLikeRoute |
|
||||||
{ |
|
||||||
public function __construct(public array $methods = []) |
|
||||||
{ |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[SymfonyLikeRoute(methods: ['GET', 'POST'])] |
|
||||||
class SymfonyLikeController |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
$route = (new ReflectionClass(SymfonyLikeController::class))->getAttributes(SymfonyLikeRoute::class)[0]->newInstance(); |
|
||||||
var_dump($route->methods); |
|
||||||
} |
|
||||||
?> |
|
||||||
--EXPECT-- |
|
||||||
array(2) { |
|
||||||
[0]=> |
|
||||||
string(3) "GET" |
|
||||||
[1]=> |
|
||||||
string(4) "POST" |
|
||||||
} |
|
||||||
@ -0,0 +1,35 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: attribute named arguments with class constant values |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS)] |
||||||
|
class SymfonyLikeAlias |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
public string $id, |
||||||
|
public ?string $when = null, |
||||||
|
) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface SymfonyLikeContract |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
#[SymfonyLikeAlias(id: SymfonyLikeContract::class, when: 'dev')] |
||||||
|
class SymfonyLikeImplementation implements SymfonyLikeContract |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$attribute = (new ReflectionClass(SymfonyLikeImplementation::class))->getAttributes(SymfonyLikeAlias::class)[0]->newInstance(); |
||||||
|
|
||||||
|
var_dump($attribute->id); |
||||||
|
var_dump($attribute->when); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(19) "SymfonyLikeContract" |
||||||
|
string(3) "dev" |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: filter result with coalesce throw expression |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeParameterBag |
||||||
|
{ |
||||||
|
public function __construct(private array $parameters) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function filter(string $key, mixed $default, int $filter, array $options): mixed |
||||||
|
{ |
||||||
|
return filter_var($this->parameters[$key] ?? $default, $filter, $options); |
||||||
|
} |
||||||
|
|
||||||
|
public function getInt(string $key, int $default = 0): int |
||||||
|
{ |
||||||
|
return $this->filter($key, $default, FILTER_VALIDATE_INT, ['flags' => FILTER_REQUIRE_SCALAR | FILTER_NULL_ON_FAILURE]) |
||||||
|
?? throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "int".', $key)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$bag = new SymfonyLikeParameterBag(['limit' => '42', 'bad' => 'nope']); |
||||||
|
var_dump($bag->getInt('limit')); |
||||||
|
|
||||||
|
try { |
||||||
|
$bag->getInt('bad'); |
||||||
|
} catch (Throwable $e) { |
||||||
|
var_dump($e::class); |
||||||
|
var_dump($e->getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(42) |
||||||
|
string(24) "UnexpectedValueException" |
||||||
|
string(51) "Parameter value "bad" cannot be converted to "int"." |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: match array filtered then unioned with base arguments |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function schedulerArgs(array $tagAttributes, string $serviceId): array |
||||||
|
{ |
||||||
|
return [ |
||||||
|
'$message' => $serviceId, |
||||||
|
] + array_filter(match ($tagAttributes['trigger'] ?? throw new InvalidArgumentException(sprintf('missing trigger for "%s"', $serviceId))) { |
||||||
|
'every' => [ |
||||||
|
'$frequency' => $tagAttributes['frequency'] ?? throw new InvalidArgumentException(sprintf('missing frequency for "%s"', $serviceId)), |
||||||
|
'$from' => $tagAttributes['from'] ?? null, |
||||||
|
'$until' => $tagAttributes['until'] ?? null, |
||||||
|
], |
||||||
|
'cron' => [ |
||||||
|
'$expression' => $tagAttributes['expression'] ?? throw new InvalidArgumentException(sprintf('missing expression for "%s"', $serviceId)), |
||||||
|
'$timezone' => $tagAttributes['timezone'] ?? null, |
||||||
|
], |
||||||
|
}, static fn ($value) => null !== $value); |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(schedulerArgs(['trigger' => 'every', 'frequency' => '1 hour', 'from' => null], 'task.one')); |
||||||
|
var_dump(schedulerArgs(['trigger' => 'cron', 'expression' => '* * * * *', 'timezone' => 'UTC'], 'task.two')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(2) { |
||||||
|
["$message"]=> |
||||||
|
string(8) "task.one" |
||||||
|
["$frequency"]=> |
||||||
|
string(6) "1 hour" |
||||||
|
} |
||||||
|
array(3) { |
||||||
|
["$message"]=> |
||||||
|
string(8) "task.two" |
||||||
|
["$expression"]=> |
||||||
|
string(9) "* * * * *" |
||||||
|
["$timezone"]=> |
||||||
|
string(3) "UTC" |
||||||
|
} |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony pattern: uasort with spaceship and elvis fallback comparison |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class SymfonyLikeAcceptItem |
||||||
|
{ |
||||||
|
public function __construct(private float $quality, private int $index, public string $name) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function getQuality(): float |
||||||
|
{ |
||||||
|
return $this->quality; |
||||||
|
} |
||||||
|
|
||||||
|
public function getIndex(): int |
||||||
|
{ |
||||||
|
return $this->index; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$items = [ |
||||||
|
'json' => new SymfonyLikeAcceptItem(0.9, 2, 'json'), |
||||||
|
'html' => new SymfonyLikeAcceptItem(1.0, 1, 'html'), |
||||||
|
'xml' => new SymfonyLikeAcceptItem(0.9, 0, 'xml'), |
||||||
|
]; |
||||||
|
|
||||||
|
uasort($items, static fn ($a, $b) => $b->getQuality() <=> $a->getQuality() ?: $a->getIndex() <=> $b->getIndex()); |
||||||
|
|
||||||
|
foreach ($items as $item) { |
||||||
|
var_dump($item->name); |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(4) "html" |
||||||
|
string(3) "xml" |
||||||
|
string(4) "json" |
||||||
Loading…
Reference in new issue