- Added clock timezone coalesce assignment and clone constructor test - Added CSS selector unicode escape preg_replace_callback test - Added dotenv escaped dollars protection test - Added filesystem dynamic call error handler test - Added finder gitignore preg_replace_callback test - Added finder directory normalization with array_merge unpack test - Added intl environment variable filtering with static cache test - Added LDAP collection iterator caching test - Added process environment union and command line string conversion test - Added semaphore negated coalesce assignment test - Added serializer context group merging with unpack test - Added validator constraint serialization with private key handling test - Added word count trimming and filtering test - Added weblink header parser with repeated attributes testpull/15/head
parent
75bf9f4113
commit
addbf897f9
14 changed files with 642 additions and 0 deletions
@ -0,0 +1,56 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Clock style timezone coalesce assignment and clone in constructor |
||||||
|
--XFAIL-- |
||||||
|
Known AOT bug: DateTimeZone assigned through clone during constructor can become an uninitialized internal object. |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyNativeClockCase |
||||||
|
{ |
||||||
|
private DateTimeZone $timezone; |
||||||
|
|
||||||
|
public function __construct(DateTimeZone|string|null $timezone = null) |
||||||
|
{ |
||||||
|
$this->timezone = is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone; |
||||||
|
} |
||||||
|
|
||||||
|
public function withTimeZone(DateTimeZone|string $timezone): static |
||||||
|
{ |
||||||
|
if (is_string($timezone)) { |
||||||
|
$timezone = new DateTimeZone($timezone); |
||||||
|
} |
||||||
|
|
||||||
|
$clone = clone $this; |
||||||
|
$clone->timezone = $timezone; |
||||||
|
|
||||||
|
return $clone; |
||||||
|
} |
||||||
|
|
||||||
|
public function name(): string |
||||||
|
{ |
||||||
|
return $this->timezone->getName(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$previous = date_default_timezone_get(); |
||||||
|
date_default_timezone_set('UTC'); |
||||||
|
|
||||||
|
$clock = new SymfonyNativeClockCase(); |
||||||
|
var_dump($clock->name()); |
||||||
|
|
||||||
|
$tokyo = $clock->withTimeZone('Asia/Tokyo'); |
||||||
|
var_dump($clock->name()); |
||||||
|
var_dump($tokyo->name()); |
||||||
|
|
||||||
|
$custom = new SymfonyNativeClockCase(new DateTimeZone('Europe/Paris')); |
||||||
|
var_dump($custom->name()); |
||||||
|
|
||||||
|
date_default_timezone_set($previous); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(3) "UTC" |
||||||
|
string(3) "UTC" |
||||||
|
string(10) "Asia/Tokyo" |
||||||
|
string(12) "Europe/Paris" |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony CssSelector pattern: unicode escape preg_replace_callback with bit operations |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
function cssUnicodeUnescape(string $value): string |
||||||
|
{ |
||||||
|
return preg_replace_callback('/\\\\([0-9a-fA-F]{1,6})\s?/', static function ($match) { |
||||||
|
$c = hexdec($match[1]); |
||||||
|
|
||||||
|
if (0x80 > $c %= 0x200000) { |
||||||
|
return chr($c); |
||||||
|
} |
||||||
|
if (0x800 > $c) { |
||||||
|
return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F); |
||||||
|
} |
||||||
|
if (0x10000 > $c) { |
||||||
|
return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); |
||||||
|
} |
||||||
|
|
||||||
|
return ''; |
||||||
|
}, $value); |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(cssUnicodeUnescape('\\41 \\26')); |
||||||
|
var_dump(bin2hex(cssUnicodeUnescape('\\20ac'))); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(2) "A&" |
||||||
|
string(6) "e282ac" |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Dotenv pattern: preg_replace_callback preserves escaped dollars by backslash parity |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
function protectEscapedDollars(string $value): string |
||||||
|
{ |
||||||
|
if (!str_contains($value, '$')) { |
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
return preg_replace_callback('/\\\\+\$/', static function ($m) { |
||||||
|
$bs = substr($m[0], 0, -1); |
||||||
|
if (1 === strlen($bs) % 2) { |
||||||
|
return substr($bs, 0, -1)."\x00"; |
||||||
|
} |
||||||
|
|
||||||
|
return $m[0]; |
||||||
|
}, $value); |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(bin2hex(protectEscapedDollars('A\\$B'))); |
||||||
|
var_dump(protectEscapedDollars('A\\\\$B')); |
||||||
|
var_dump(protectEscapedDollars('plain')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(6) "410042" |
||||||
|
string(5) "A\\$B" |
||||||
|
string(5) "plain" |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Filesystem style dynamic function call boxed by first-class error handler |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyFilesystemBoxCase |
||||||
|
{ |
||||||
|
private static ?array $lastError = null; |
||||||
|
|
||||||
|
private static function assertFunctionExists(string $func): void |
||||||
|
{ |
||||||
|
if (!function_exists($func)) { |
||||||
|
throw new RuntimeException(sprintf('Unable to perform filesystem operation because the "%s()" function has been disabled.', $func)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static function handleError(int $type, string $message): bool |
||||||
|
{ |
||||||
|
self::$lastError = [$type, $message]; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public static function box(string $func, mixed ...$args): mixed |
||||||
|
{ |
||||||
|
self::assertFunctionExists($func); |
||||||
|
|
||||||
|
self::$lastError = null; |
||||||
|
set_error_handler(self::handleError(...)); |
||||||
|
try { |
||||||
|
return $func(...$args); |
||||||
|
} finally { |
||||||
|
restore_error_handler(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static function getLastError(): ?array |
||||||
|
{ |
||||||
|
return self::$lastError; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(SymfonyFilesystemBoxCase::box('strtoupper', 'abc')); |
||||||
|
var_dump(SymfonyFilesystemBoxCase::getLastError()); |
||||||
|
|
||||||
|
try { |
||||||
|
SymfonyFilesystemBoxCase::box('definitely_missing_function'); |
||||||
|
} catch (RuntimeException $e) { |
||||||
|
echo $e->getMessage(), "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(3) "ABC" |
||||||
|
NULL |
||||||
|
Unable to perform filesystem operation because the "definitely_missing_function()" function has been disabled. |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Finder Gitignore pattern: preg_replace_callback with static arrow function |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
function normalizeGitignoreCharacterClass(string $regex): string |
||||||
|
{ |
||||||
|
return preg_replace_callback( |
||||||
|
'~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', |
||||||
|
static fn (array $matches): string => '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', |
||||||
|
$regex |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(normalizeGitignoreCharacterClass('\\[a\\-z\\]')); |
||||||
|
var_dump(normalizeGitignoreCharacterClass('\\[\\!0\\-9\\]')); |
||||||
|
var_dump(normalizeGitignoreCharacterClass('plain')); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(5) "[a-z]" |
||||||
|
string(6) "[^0-9]" |
||||||
|
string(5) "plain" |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Finder pattern: object method first-class callable with array_merge unpack |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyFinderDirNormalizer |
||||||
|
{ |
||||||
|
private array $dirs = []; |
||||||
|
|
||||||
|
public function in(array|string $dirs): self |
||||||
|
{ |
||||||
|
$resolvedDirs = []; |
||||||
|
foreach ((array) $dirs as $dir) { |
||||||
|
$glob = str_contains($dir, '*') ? ['src/', 'tests/'] : [$dir]; |
||||||
|
$resolvedDirs[] = array_map($this->normalizeDir(...), $glob); |
||||||
|
} |
||||||
|
|
||||||
|
$this->dirs = array_merge($this->dirs, ...$resolvedDirs); |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
private function normalizeDir(string $dir): string |
||||||
|
{ |
||||||
|
return rtrim(str_replace('\\', '/', $dir), '/').'/'; |
||||||
|
} |
||||||
|
|
||||||
|
public function getDirs(): array |
||||||
|
{ |
||||||
|
return $this->dirs; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$finder = (new SymfonyFinderDirNormalizer()) |
||||||
|
->in('var/cache') |
||||||
|
->in(['app/*', 'vendor/package']); |
||||||
|
|
||||||
|
var_dump($finder->getDirs()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(4) { |
||||||
|
[0]=> |
||||||
|
string(10) "var/cache/" |
||||||
|
[1]=> |
||||||
|
string(4) "src/" |
||||||
|
[2]=> |
||||||
|
string(6) "tests/" |
||||||
|
[3]=> |
||||||
|
string(15) "vendor/package/" |
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Intl pattern: env fallback with filter_var and static coalesce cache |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyIntlEnvFlag |
||||||
|
{ |
||||||
|
private static ?bool $withUserAssigned = null; |
||||||
|
|
||||||
|
public static function reset(): void |
||||||
|
{ |
||||||
|
self::$withUserAssigned = null; |
||||||
|
} |
||||||
|
|
||||||
|
public static function withUserAssigned(): bool |
||||||
|
{ |
||||||
|
return self::$withUserAssigned ??= filter_var( |
||||||
|
$_ENV['SYMFONY_INTL_WITH_USER_ASSIGNED'] ?? $_SERVER['SYMFONY_INTL_WITH_USER_ASSIGNED'] ?? getenv('SYMFONY_INTL_WITH_USER_ASSIGNED'), |
||||||
|
FILTER_VALIDATE_BOOLEAN |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
unset($_ENV['SYMFONY_INTL_WITH_USER_ASSIGNED'], $_SERVER['SYMFONY_INTL_WITH_USER_ASSIGNED']); |
||||||
|
putenv('SYMFONY_INTL_WITH_USER_ASSIGNED=1'); |
||||||
|
var_dump(SymfonyIntlEnvFlag::withUserAssigned()); |
||||||
|
|
||||||
|
putenv('SYMFONY_INTL_WITH_USER_ASSIGNED=0'); |
||||||
|
var_dump(SymfonyIntlEnvFlag::withUserAssigned()); |
||||||
|
|
||||||
|
SymfonyIntlEnvFlag::reset(); |
||||||
|
$_SERVER['SYMFONY_INTL_WITH_USER_ASSIGNED'] = 'false'; |
||||||
|
var_dump(SymfonyIntlEnvFlag::withUserAssigned()); |
||||||
|
|
||||||
|
SymfonyIntlEnvFlag::reset(); |
||||||
|
$_ENV['SYMFONY_INTL_WITH_USER_ASSIGNED'] = 'true'; |
||||||
|
var_dump(SymfonyIntlEnvFlag::withUserAssigned()); |
||||||
|
|
||||||
|
unset($_ENV['SYMFONY_INTL_WITH_USER_ASSIGNED'], $_SERVER['SYMFONY_INTL_WITH_USER_ASSIGNED']); |
||||||
|
putenv('SYMFONY_INTL_WITH_USER_ASSIGNED'); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
bool(true) |
||||||
|
bool(true) |
||||||
|
bool(false) |
||||||
|
bool(true) |
||||||
@ -0,0 +1,64 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Ldap pattern: iterator_to_array without keys cached by coalesce assignment |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyLdapEntryCollection implements IteratorAggregate |
||||||
|
{ |
||||||
|
private ?array $entries = null; |
||||||
|
public int $iterations = 0; |
||||||
|
|
||||||
|
public function __construct(private array $source) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function getIterator(): Traversable |
||||||
|
{ |
||||||
|
++$this->iterations; |
||||||
|
|
||||||
|
return new ArrayIterator($this->source); |
||||||
|
} |
||||||
|
|
||||||
|
public function toArray(): array |
||||||
|
{ |
||||||
|
return $this->entries ??= iterator_to_array($this->getIterator(), false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$collection = new SymfonyLdapEntryCollection([ |
||||||
|
'uid' => ['alice'], |
||||||
|
'mail' => ['alice@example.com'], |
||||||
|
]); |
||||||
|
|
||||||
|
var_dump($collection->toArray()); |
||||||
|
var_dump($collection->toArray()); |
||||||
|
var_dump($collection->iterations); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(5) "alice" |
||||||
|
} |
||||||
|
[1]=> |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(17) "alice@example.com" |
||||||
|
} |
||||||
|
} |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(5) "alice" |
||||||
|
} |
||||||
|
[1]=> |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(17) "alice@example.com" |
||||||
|
} |
||||||
|
} |
||||||
|
int(1) |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Process style env array union and commandline array_map(strval(...)) |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyProcessCommandCase |
||||||
|
{ |
||||||
|
private static array $executables = []; |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
private array|string $commandline, |
||||||
|
private array $env = [], |
||||||
|
) { |
||||||
|
} |
||||||
|
|
||||||
|
public function normalize(array $runtimeEnv, array $defaultEnv): array |
||||||
|
{ |
||||||
|
$env = $runtimeEnv; |
||||||
|
if ($this->env) { |
||||||
|
$env += '\\' === DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env; |
||||||
|
} |
||||||
|
|
||||||
|
$env += '\\' === DIRECTORY_SEPARATOR ? array_diff_ukey($defaultEnv, $env, 'strcasecmp') : $defaultEnv; |
||||||
|
|
||||||
|
if (is_array($commandline = $this->commandline)) { |
||||||
|
$commandline = array_values(array_map(strval(...), $commandline)); |
||||||
|
} |
||||||
|
|
||||||
|
if ('\\' === DIRECTORY_SEPARATOR && isset($commandline[0][0]) && strlen($commandline[0]) === strcspn($commandline[0], ':/\\')) { |
||||||
|
$commandline[0] = (self::$executables[$commandline[0]] ??= $commandline[0]) ?? $commandline[0]; |
||||||
|
} |
||||||
|
|
||||||
|
return [$env, $commandline]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$process = new SymfonyProcessCommandCase(['php', '-r', 123], ['APP_ENV' => 'local', 'NEW_VAR' => 'yes']); |
||||||
|
[$env, $commandline] = $process->normalize(['APP_ENV' => 'runtime'], ['PATH' => '/usr/bin', 'APP_ENV' => 'default']); |
||||||
|
|
||||||
|
var_dump($env); |
||||||
|
var_dump($commandline); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(3) { |
||||||
|
["APP_ENV"]=> |
||||||
|
string(7) "runtime" |
||||||
|
["NEW_VAR"]=> |
||||||
|
string(3) "yes" |
||||||
|
["PATH"]=> |
||||||
|
string(8) "/usr/bin" |
||||||
|
} |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
string(3) "php" |
||||||
|
[1]=> |
||||||
|
string(2) "-r" |
||||||
|
[2]=> |
||||||
|
string(3) "123" |
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Semaphore pattern: negated coalesce assignment in condition |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonySemaphoreLike |
||||||
|
{ |
||||||
|
public function __construct(private ?float $ttlInSecond = null) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function refresh(?float $ttlInSecond = null): string |
||||||
|
{ |
||||||
|
if (!$ttlInSecond ??= $this->ttlInSecond) { |
||||||
|
return 'missing'; |
||||||
|
} |
||||||
|
|
||||||
|
return 'ttl='.$ttlInSecond; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump((new SymfonySemaphoreLike())->refresh()); |
||||||
|
var_dump((new SymfonySemaphoreLike(2.5))->refresh()); |
||||||
|
var_dump((new SymfonySemaphoreLike(2.5))->refresh(1.25)); |
||||||
|
var_dump((new SymfonySemaphoreLike(2.5))->refresh(0.0)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(7) "missing" |
||||||
|
string(7) "ttl=2.5" |
||||||
|
string(8) "ttl=1.25" |
||||||
|
string(7) "missing" |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Serializer pattern: group contexts merged with unpack |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyAttributeContextMetadata |
||||||
|
{ |
||||||
|
private array $normalizationContexts = []; |
||||||
|
|
||||||
|
public function setNormalizationContextForGroups(array $context, array $groups = []): void |
||||||
|
{ |
||||||
|
if (!$groups) { |
||||||
|
$this->normalizationContexts['*'] = $context; |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($groups as $group) { |
||||||
|
$this->normalizationContexts[$group] = $context; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function getNormalizationContextForGroups(array $groups): array |
||||||
|
{ |
||||||
|
$contexts = []; |
||||||
|
foreach ($groups as $group) { |
||||||
|
$contexts[] = $this->normalizationContexts[$group] ?? []; |
||||||
|
} |
||||||
|
|
||||||
|
return array_merge($this->normalizationContexts['*'] ?? [], ...$contexts); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$metadata = new SymfonyAttributeContextMetadata(); |
||||||
|
$metadata->setNormalizationContextForGroups(['skip_null_values' => true, 'groups' => ['default']]); |
||||||
|
$metadata->setNormalizationContextForGroups(['groups' => ['admin'], 'max_depth' => 2], ['admin']); |
||||||
|
$metadata->setNormalizationContextForGroups(['ignored_attributes' => ['secret']], ['public']); |
||||||
|
|
||||||
|
var_dump($metadata->getNormalizationContextForGroups(['missing', 'admin', 'public'])); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(4) { |
||||||
|
["skip_null_values"]=> |
||||||
|
bool(true) |
||||||
|
["groups"]=> |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(5) "admin" |
||||||
|
} |
||||||
|
["max_depth"]=> |
||||||
|
int(2) |
||||||
|
["ignored_attributes"]=> |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
string(6) "secret" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Validator Constraint pattern: serialize object vars with match(true) private key normalization |
||||||
|
--XFAIL-- |
||||||
|
AOT does not yet match private object array keys with NUL-prefixed class names in str_starts_with(). |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyConstraintLike |
||||||
|
{ |
||||||
|
public string $publicName = 'public'; |
||||||
|
protected string $protectedName = 'protected'; |
||||||
|
private string $privateName = 'private'; |
||||||
|
|
||||||
|
public function __serialize(): array |
||||||
|
{ |
||||||
|
$data = []; |
||||||
|
$class = $this::class; |
||||||
|
foreach ((array) $this as $k => $v) { |
||||||
|
$data[match (true) { |
||||||
|
'' === $k || "\0" !== $k[0] => $k, |
||||||
|
str_starts_with($k, "\0*\0") => substr($k, 3), |
||||||
|
str_starts_with($k, "\0{$class}\0") => substr($k, 2 + strlen($class)), |
||||||
|
default => $k, |
||||||
|
}] = $v; |
||||||
|
} |
||||||
|
|
||||||
|
return $data; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump((new SymfonyConstraintLike())->__serialize()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(3) { |
||||||
|
["publicName"]=> |
||||||
|
string(6) "public" |
||||||
|
["protectedName"]=> |
||||||
|
string(9) "protected" |
||||||
|
["privateName"]=> |
||||||
|
string(7) "private" |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony Validator pattern: array_map(trim(...)) filtered by static arrow callback |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
class SymfonyWordCounter |
||||||
|
{ |
||||||
|
public static function countWords(array $words): int |
||||||
|
{ |
||||||
|
return count(array_filter(array_map(trim(...), $words), static fn ($word) => '' !== $word)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(SymfonyWordCounter::countWords([' one ', '', " \t ", 'two', ' three'])); |
||||||
|
var_dump(SymfonyWordCounter::countWords([])); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(3) |
||||||
|
int(0) |
||||||
@ -0,0 +1,63 @@ |
|||||||
|
--TEST-- |
||||||
|
Symfony WebLink style header parser with match(true), rel ??= and repeated attributes |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
function symfony_parse_link_attributes(string $attributesString): array |
||||||
|
{ |
||||||
|
$attributes = []; |
||||||
|
$rels = null; |
||||||
|
|
||||||
|
if (preg_match_all('/;\s*([a-zA-Z0-9_-]+)(?:=(?:"((?:\\\"|[^"])*)"|([^;,\s]+)))?/', $attributesString, $attributeMatches, PREG_SET_ORDER)) { |
||||||
|
foreach ($attributeMatches as $pm) { |
||||||
|
$key = $pm[1]; |
||||||
|
$value = match (true) { |
||||||
|
($pm[2] ?? '') !== '' => stripcslashes($pm[2]), |
||||||
|
($pm[3] ?? '') !== '' => $pm[3], |
||||||
|
default => true, |
||||||
|
}; |
||||||
|
|
||||||
|
if ('rel' === $key) { |
||||||
|
$rels ??= true === $value ? [] : preg_split('/\s+/', $value, 0, PREG_SPLIT_NO_EMPTY); |
||||||
|
} elseif (is_array($attributes[$key] ?? null)) { |
||||||
|
$attributes[$key][] = $value; |
||||||
|
} elseif (isset($attributes[$key])) { |
||||||
|
$attributes[$key] = [$attributes[$key], $value]; |
||||||
|
} else { |
||||||
|
$attributes[$key] = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return [$rels, $attributes]; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
[$rels, $attributes] = symfony_parse_link_attributes('; rel="preload module"; rel=ignored; as=script; title="a \"quoted\" title"; hreflang=en; hreflang=fr; disabled'); |
||||||
|
|
||||||
|
var_dump($rels); |
||||||
|
var_dump($attributes); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(7) "preload" |
||||||
|
[1]=> |
||||||
|
string(6) "module" |
||||||
|
} |
||||||
|
array(4) { |
||||||
|
["as"]=> |
||||||
|
string(6) "script" |
||||||
|
["title"]=> |
||||||
|
string(16) "a "quoted" title" |
||||||
|
["hreflang"]=> |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(2) "en" |
||||||
|
[1]=> |
||||||
|
string(2) "fr" |
||||||
|
} |
||||||
|
["disabled"]=> |
||||||
|
bool(true) |
||||||
|
} |
||||||
Loading…
Reference in new issue