update lock clucknut
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / build-and-push (push) Successful in 3m14s
Build, Push and Deploy / deploy-staging (push) Successful in 25s
Build, Push and Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-04-18 20:32:18 +07:00
parent 4554035227
commit dcaf267458
3359 changed files with 153185 additions and 205489 deletions

View File

@@ -761,6 +761,21 @@ public function find(string $name)
}));
}
// check whether all commands left are aliases to the same one
if (\count($commands) > 1) {
$uniqueCommands = array_unique(array_map(function ($nameOrAlias) use (&$commandList) {
if (!$commandList[$nameOrAlias] instanceof Command) {
$commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
}
return $commandList[$nameOrAlias]->getName();
}, $commands));
if (1 === \count($uniqueCommands)) {
$commands = [reset($uniqueCommands)];
}
}
if (\count($commands) > 1) {
$usableWidth = $this->terminal->getWidth() - 10;
$abbrevs = array_values($commands);

View File

@@ -115,24 +115,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'<info>Messages:</>',
]);
$command = $this->findCommand($completionInput, $output);
if ($command = $this->findCommand($completionInput, $output)) {
$command->mergeApplicationDefinition();
$completionInput->bind($command->getDefinition());
}
if (null === $command) {
$this->log(' No command found, completing using the Application class.');
$this->getApplication()->complete($completionInput, $suggestions);
} elseif (
$completionInput->mustSuggestArgumentValuesFor('command')
&& $command->getName() !== $completionInput->getCompletionValue()
&& !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true)
) {
$this->log(' No command found, completing using the Application class.');
$this->log(' Command found, completing command name.');
// expand shortcut names ("cache:cl<TAB>") into their full name ("cache:clear")
$suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases())));
$commandNames = array_filter(array_merge([$command->getName()], $command->getAliases()));
foreach ($commandNames as $name) {
if (str_starts_with($name, $completionInput->getCompletionValue())) {
$commandNames = [$name];
break;
}
}
$suggestions->suggestValues($commandNames);
} else {
$command->mergeApplicationDefinition();
$completionInput->bind($command->getDefinition());
if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
$this->log(' Completing option names for the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> command.');

View File

@@ -22,7 +22,7 @@ public function __construct(\Throwable|string $exception, public readonly RunCom
{
parent::__construct(
$exception instanceof \Throwable ? $exception->getMessage() : $exception,
$exception instanceof \Throwable ? $exception->getCode() : 0,
$exception instanceof \Throwable && \is_int($exception->getCode()) ? $exception->getCode() : 0,
$exception instanceof \Throwable ? $exception : null,
);
}

View File

@@ -133,6 +133,10 @@ public function formatAndWrap(?string $message, int $width)
return '';
}
// For ASCII-only strings, byte positions equal character positions,
// so we can use native strlen/substr which is much faster than Helper::length/substr.
$isAscii = !preg_match('/[\x80-\xFF]/', $message);
$offset = 0;
$output = '';
$openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
@@ -147,11 +151,17 @@ public function formatAndWrap(?string $message, int $width)
continue;
}
// convert byte position to character position.
$pos = Helper::length(substr($message, 0, $pos));
// add the text up to the next tag
$output .= $this->applyCurrentStyle(Helper::substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
$offset = $pos + Helper::length($text);
if ($isAscii) {
// For ASCII, byte position = character position, no conversion needed
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
$offset = $pos + \strlen($text);
} else {
// convert byte position to character position.
$pos = Helper::length(substr($message, 0, $pos));
// add the text up to the next tag
$output .= $this->applyCurrentStyle(Helper::substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
$offset = $pos + Helper::length($text);
}
// opening tag?
if ($open = '/' !== $text[1]) {
@@ -172,7 +182,7 @@ public function formatAndWrap(?string $message, int $width)
}
}
$output .= $this->applyCurrentStyle(Helper::substr($message, $offset), $output, $width, $currentLineLength);
$output .= $this->applyCurrentStyle($isAscii ? substr($message, $offset) : Helper::substr($message, $offset), $output, $width, $currentLineLength);
return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
}

View File

@@ -13,6 +13,7 @@
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
@@ -140,7 +141,9 @@ public function finish(string $message)
$this->message = $message;
$this->display();
$this->output->writeln('');
if (!$this->output instanceof ConsoleSectionOutput) {
$this->output->writeln('');
}
$this->started = false;
}
@@ -207,7 +210,9 @@ private function determineBestFormat(): string
*/
private function overwrite(string $message): void
{
if ($this->output->isDecorated()) {
if ($this->output instanceof ConsoleSectionOutput) {
$this->output->overwrite($message);
} elseif ($this->output->isDecorated()) {
$this->output->write("\x0D\x1B[2K");
$this->output->write($message);
} else {

View File

@@ -474,6 +474,8 @@ private function validateAttempts(callable $interviewer, OutputInterface $output
try {
return $question->getValidator()($interviewer());
} catch (MissingInputException $e) {
throw $error ?? $e;
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $error) {

View File

@@ -142,12 +142,27 @@ private function isRunningOS400(): bool
*/
private function openOutputStream()
{
static $stdout;
if ($stdout) {
return $stdout;
}
if (!$this->hasStdoutSupport()) {
return fopen('php://output', 'w');
return $stdout = fopen('php://output', 'w');
}
// Use STDOUT when possible to prevent from opening too many file descriptors
return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w'));
if (!\defined('STDOUT')) {
return $stdout = @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
}
// On Windows, STDOUT is opened in text mode; reopen in binary mode to prevent \n to \r\n conversion
if ('\\' === \DIRECTORY_SEPARATOR) {
return $stdout = @fopen('php://stdout', 'w') ?: \STDOUT;
}
return $stdout = \STDOUT;
}
/**
@@ -155,11 +170,26 @@ private function openOutputStream()
*/
private function openErrorStream()
{
static $stderr;
if ($stderr) {
return $stderr;
}
if (!$this->hasStderrSupport()) {
return fopen('php://output', 'w');
return $stderr = fopen('php://output', 'w');
}
// Use STDERR when possible to prevent from opening too many file descriptors
return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w'));
if (!\defined('STDERR')) {
return $stderr = @fopen('php://stderr', 'w') ?: fopen('php://output', 'w');
}
// On Windows, STDERR is opened in text mode; reopen in binary mode to prevent \n → \r\n conversion
if ('\\' === \DIRECTORY_SEPARATOR) {
return $stderr = @fopen('php://stderr', 'w') ?: \STDERR;
}
return $stderr ??= \STDERR;
}
}

View File

@@ -104,7 +104,7 @@ public function section(string $message)
public function listing(array $elements)
{
$this->autoPrependText();
$elements = array_map(fn ($element) => \sprintf(' * %s', $element), $elements);
$elements = array_map(static fn ($element) => \sprintf(' * %s', $element), $elements);
$this->writeln($elements);
$this->newLine();
@@ -475,12 +475,14 @@ private function createBlock(iterable $messages, ?string $type = null, ?string $
$message = OutputFormatter::escape($message);
}
$message = str_replace("\r\n", "\n", $message);
$lines = array_merge(
$lines,
explode(\PHP_EOL, $outputWrapper->wrap(
explode("\n", $outputWrapper->wrap(
$message,
$this->lineLength - $prefixLength - $indentLength,
\PHP_EOL
"\n"
))
);

View File

@@ -128,7 +128,7 @@ public static function hasSttyAvailable(): bool
return false;
}
return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
return self::$stty = (bool) @shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
}
private static function initDimensions(): void

View File

@@ -49,7 +49,7 @@ public function __construct(Application $application)
*/
public function run(array $input, array $options = []): int
{
$prevShellVerbosity = getenv('SHELL_VERBOSITY');
$prevShellVerbosity = [getenv('SHELL_VERBOSITY'), $_ENV['SHELL_VERBOSITY'] ?? false, $_SERVER['SHELL_VERBOSITY'] ?? false];
try {
$this->input = new ArrayInput($input);
@@ -63,22 +63,35 @@ public function run(array $input, array $options = []): int
$this->initOutput($options);
// Temporarily clear SHELL_VERBOSITY to prevent Application::configureIO
// from overriding the interactive and verbosity settings set above
if (\function_exists('putenv')) {
@putenv('SHELL_VERBOSITY');
}
unset($_ENV['SHELL_VERBOSITY'], $_SERVER['SHELL_VERBOSITY']);
return $this->statusCode = $this->application->run($this->input, $this->output);
} finally {
// SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it
// to its previous value to avoid one test's verbosity to spread to the following tests
if (false === $prevShellVerbosity) {
if (false === $prevShellVerbosity[0]) {
if (\function_exists('putenv')) {
@putenv('SHELL_VERBOSITY');
}
unset($_ENV['SHELL_VERBOSITY']);
unset($_SERVER['SHELL_VERBOSITY']);
} else {
if (\function_exists('putenv')) {
@putenv('SHELL_VERBOSITY='.$prevShellVerbosity);
@putenv('SHELL_VERBOSITY='.$prevShellVerbosity[0]);
}
$_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity;
$_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity;
}
if (false === $prevShellVerbosity[1]) {
unset($_ENV['SHELL_VERBOSITY']);
} else {
$_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity[1];
}
if (false === $prevShellVerbosity[2]) {
unset($_SERVER['SHELL_VERBOSITY']);
} else {
$_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity[2];
}
}
}

View File

@@ -26,6 +26,8 @@
*/
class CssSelectorConverter
{
public static int $maxCachedItems = 1024;
private Translator $translator;
private array $cache;
@@ -62,6 +64,21 @@ public function __construct(bool $html = true)
*/
public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string
{
return $this->cache[$prefix][$cssExpr] ??= $this->translator->cssToXPath($cssExpr, $prefix);
$cacheKey = $prefix."\0".$cssExpr;
if (isset($this->cache[$cacheKey])) {
// Move the item last in cache (LRU)
$value = $this->cache[$cacheKey];
unset($this->cache[$cacheKey]);
return $this->cache[$cacheKey] = $value;
}
if (\count($this->cache) >= self::$maxCachedItems) {
// Evict the oldest entry
unset($this->cache[array_key_first($this->cache)]);
}
return $this->cache[$cacheKey] = $this->translator->cssToXPath($cssExpr, $prefix);
}
}

View File

@@ -127,6 +127,21 @@ class DebugClassLoader
private static array $internalMethods = [];
private static array $annotatedParameters = [];
private static array $darwinCache = ['/' => ['/', []]];
/**
* @var array<string, list<array{0: string, 1: bool, 2: string, 3: string, 4: string|null}>>
*
* Maps an interface FQCN (or an abstract class accumulating entries from its interfaces) to the list of
* "@method" annotations declared on it. For interfaces, the entry is populated directly by parsing the
* annotations from the interface's docblock. For abstract classes, the information from all implemented
* interfaces is merged together, so that the check can later be applied to the first concrete subclass.
*
* Each entry is a tuple of:
* [0] string $interface - FQCN of the interface that carries the "@method" annotation
* [1] bool $static - whether the method is declared static
* [2] string $returnType - return type from the annotation, or '' if absent
* [3] string $name - method name plus its parameter signature, e.g. "foo($arg, int $n)"
* [4] string|null $description - description text (period-normalised), or null if absent
*/
private static array $method = [];
private static array $returnTypes = [];
private static array $methodTraits = [];
@@ -398,6 +413,14 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
if ($refl->isInterface() && isset($doc['method'])) {
foreach ($doc['method'] as $name => [$static, $returnType, $signature, $description]) {
if ($refl->hasMethod($static ? '__callStatic' : '__call')) {
// When the interface has "virtual" @method declarations but at the same time contains a __call/__callStatic magic method,
// do not trigger a deprecation notice. This is to address special use cases like in Predis' ClientInterface where the
// "@method" annotations never intend to actually add the method to the interface, but are used to document the "virtual"
// API provided by the interface through the technical implementation of magic calls. This might cause false negatives
// (missing notices) in the case that such interfaces are later amended with actual (real) methods.
continue;
}
self::$method[$class][] = [$class, $static, $returnType, $name.$signature, $description];
if ('' !== $returnType) {
@@ -420,6 +443,15 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
}
}
// When the parent is a concrete class, we will trigger deprecation notices to make it aware that it needs
// to add the new methods announced with @method. The parent will have to provide all those methods.
// For child classes this means they will not need to deal with @method coming from any of the interfaces
// the parent implements.
// Put those interfaces that we can ignore into $parentInterfaces.
// The ternary makes use of the fact that abstract parent classes will accumulate the methods in self::$method,
// so !isset(self::$method[$parent]) indicates a concrete parent class.
$parentInterfaces = ($parent && !isset(self::$method[$parent])) ? class_implements($parent, false) : [];
// Detect if the parent is annotated
foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) {
if (!isset(self::$checkedClasses[$use])) {
@@ -435,13 +467,15 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
$deprecations[] = \sprintf('The "%s" %s is considered internal%s It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $className);
}
if (isset(self::$method[$use])) {
if ($refl->isAbstract()) {
if ($refl->isAbstract() || $refl->isInterface()) {
// Abstract classes and interfaces inherit @method from interfaces they
// implement directly or through inheritance.
if (isset(self::$method[$class])) {
self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]);
} else {
self::$method[$class] = self::$method[$use];
}
} elseif (!$refl->isInterface()) {
} else {
if (!strncmp($vendor, str_replace('_', '\\', $use), $vendorLen)
&& str_starts_with($className, 'Symfony\\')
&& (!class_exists(InstalledVersions::class)
@@ -450,14 +484,14 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
// skip "same vendor" @method deprecations for Symfony\* classes unless symfony/symfony is being tested
continue;
}
$hasCall = $refl->hasMethod('__call');
$hasStaticCall = $refl->hasMethod('__callStatic');
foreach (self::$method[$use] as [$interface, $static, $returnType, $name, $description]) {
if ($static ? $hasStaticCall : $hasCall) {
if (isset($parentInterfaces[$interface])) {
// The @method annotation comes from an interface that has already been implemented by a concrete parent class,
// so we can ignore it here.
continue;
}
$realName = substr($name, 0, strpos($name, '('));
if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) {
if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static xor $methodRefl->isStatic())) {
$deprecations[] = \sprintf('Class "%s" should implement method "%s::%s%s"%s', $className, ($static ? 'static ' : '').$interface, $name, $returnType ? ': '.$returnType : '', null === $description ? '.' : ': '.$description);
}
}
@@ -1216,7 +1250,7 @@ private function parsePhpDoc(\Reflector $reflector): array
$static = 'static' === $parts[0];
for ($i = $static ? 2 : 0; null !== $p = $parts[$i] ?? null; $i += 2) {
if (\in_array($p, ['', '|', '&', 'callable'], true) || \in_array(substr($returnType, -1), ['|', '&'], true)) {
if (\in_array($p, ['', 'callable'], true) || \in_array(substr($returnType, -1), ['|', '&'], true) || \in_array($p[0], ['|', '&'], true)) {
$returnType .= trim($parts[$i - 1] ?? '').$p;
continue;
}

View File

0
vendor/symfony/error-handler/Resources/bin/patch-type-declarations vendored Normal file → Executable file
View File

View File

@@ -36,6 +36,9 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
private ?\SplObjectStorage $callStack = null;
private array $wrappedListeners = [];
private array $orphanedEvents = [];
private array $dispatchDepth = [];
private array $calledListenerInfos = [];
private array $calledOriginalListeners = [];
private string $currentRequestHash = '';
public function __construct(
@@ -142,20 +145,20 @@ public function dispatch(object $event, ?string $eventName = null): object
public function getCalledListeners(?Request $request = null): array
{
if (null === $this->callStack) {
if (!$this->calledListenerInfos) {
return [];
}
$hash = $request ? spl_object_hash($request) : null;
$called = [];
foreach ($this->callStack as $listener) {
[$eventName, $requestHash] = $this->callStack->getInfo();
foreach ($this->calledListenerInfos as $requestHash => $infos) {
if (null === $hash || $hash === $requestHash) {
$called[] = $listener->getInfo($eventName);
$called[] = $infos;
}
}
return $called;
return $called ? array_merge(...$called) : [];
}
public function getNotCalledListeners(?Request $request = null): array
@@ -172,16 +175,14 @@ public function getNotCalledListeners(?Request $request = null): array
$hash = $request ? spl_object_hash($request) : null;
$calledListeners = [];
if (null !== $this->callStack) {
foreach ($this->callStack as $calledListener) {
[, $requestHash] = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$calledListeners[] = $calledListener->getWrappedListener();
}
foreach ($this->calledOriginalListeners as $requestHash => $eventListeners) {
if (null === $hash || $hash === $requestHash) {
$calledListeners[] = array_merge(...array_values($eventListeners));
}
}
$calledListeners = $calledListeners ? array_merge(...$calledListeners) : [];
$notCalled = [];
foreach ($allListeners as $eventName => $listeners) {
@@ -218,6 +219,9 @@ public function reset(): void
$this->callStack = null;
$this->orphanedEvents = [];
$this->currentRequestHash = '';
$this->dispatchDepth = [];
$this->calledListenerInfos = [];
$this->calledOriginalListeners = [];
}
/**
@@ -247,6 +251,8 @@ protected function afterDispatch(string $eventName, object $event): void
private function preProcess(string $eventName): void
{
$this->dispatchDepth[$eventName] = ($this->dispatchDepth[$eventName] ?? 0) + 1;
if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[$this->currentRequestHash][] = $eventName;
@@ -265,6 +271,8 @@ private function preProcess(string $eventName): void
private function postProcess(string $eventName): void
{
--$this->dispatchDepth[$eventName];
unset($this->wrappedListeners[$eventName]);
$skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
@@ -282,10 +290,16 @@ private function postProcess(string $eventName): void
if ($listener->wasCalled()) {
$this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context);
} else {
unset($this->callStack[$listener]);
$original = $listener->getWrappedListener();
if (!\in_array($original, $this->calledOriginalListeners[$this->currentRequestHash][$eventName] ?? [], true)) {
$this->calledOriginalListeners[$this->currentRequestHash][$eventName][] = $original;
$this->calledListenerInfos[$this->currentRequestHash][] = $listener->getInfo($eventName);
}
}
unset($this->callStack[$listener]);
if (null !== $this->logger && $skipped) {
$this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
}
@@ -296,6 +310,28 @@ private function postProcess(string $eventName): void
$skipped = true;
}
}
if (0 < $this->dispatchDepth[$eventName]) {
return;
}
// Clean up stale callStack entries left by nested same-event dispatches
$stale = [];
foreach ($this->callStack as $listener) {
if ($this->callStack->getInfo()[0] === $eventName) {
$stale[] = $listener;
}
}
foreach ($stale as $listener) {
if ($listener->wasCalled()) {
$original = $listener->getWrappedListener();
if (!\in_array($original, $this->calledOriginalListeners[$this->currentRequestHash][$eventName] ?? [], true)) {
$this->calledOriginalListeners[$this->currentRequestHash][$eventName][] = $original;
$this->calledListenerInfos[$this->currentRequestHash][] = $listener->getInfo($eventName);
}
}
unset($this->callStack[$listener]);
}
}
private function sortNotCalledListeners(array $a, array $b): int

View File

@@ -443,6 +443,8 @@ public function makePathRelative(string $endPath, string $startPath): string
throw new InvalidArgumentException(\sprintf('The end path "%s" is not absolute.', $endPath));
}
$originalEndPath = $endPath;
// Normalize separators on Windows
if ('\\' === \DIRECTORY_SEPARATOR) {
$endPath = str_replace('\\', '/', $endPath);
@@ -499,6 +501,11 @@ public function makePathRelative(string $endPath, string $startPath): string
// Construct $endPath from traversing to the common path, then to the remaining $endPath
$relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
// Remove ending "/" if $endPath points to an existing file
if (str_ends_with($relativePath, '/') && is_file($originalEndPath)) {
$relativePath = substr($relativePath, 0, -1);
}
return '' === $relativePath ? './' : $relativePath;
}

View File

@@ -57,8 +57,10 @@ class Finder implements \IteratorAggregate, \Countable
private bool $reverseSorting = false;
private \Closure|int|false $sort = false;
private int $ignore = 0;
/** @var list<string> */
private array $dirs = [];
private array $dates = [];
/** @var list<iterable<SplFileInfo|\SplFileInfo|string>> */
private array $iterators = [];
private array $contains = [];
private array $notContains = [];
@@ -666,27 +668,33 @@ public function in(string|array $dirs): static
*/
public function getIterator(): \Iterator
{
if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
if (!$this->dirs && !$this->iterators) {
throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
}
if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
if (1 === \count($this->dirs) && !$this->iterators) {
$iterator = $this->searchInDirectory($this->dirs[0]);
if ($this->sort || $this->reverseSorting) {
$iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
} else {
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir))));
}
return $iterator;
}
foreach ($this->iterators as $it) {
$iterator->append(new \IteratorIterator(new LazyIterator(static function () use ($it) {
foreach ($it as $file) {
if (!$file instanceof \SplFileInfo) {
$file = new \SplFileInfo($file);
}
$key = $file->getPathname();
if (!$file instanceof SplFileInfo) {
$file = new SplFileInfo($key, $file->getPath(), $key);
}
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir))));
}
foreach ($this->iterators as $it) {
$iterator->append($it);
yield $key => $file;
}
})));
}
}
if ($this->sort || $this->reverseSorting) {
@@ -701,26 +709,13 @@ public function getIterator(): \Iterator
*
* The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
*
* @return $this
* @param iterable<SplFileInfo|\SplFileInfo|string> $iterator
*
* @throws \InvalidArgumentException when the given argument is not iterable
* @return $this
*/
public function append(iterable $iterator): static
{
if ($iterator instanceof \IteratorAggregate) {
$this->iterators[] = $iterator->getIterator();
} elseif ($iterator instanceof \Iterator) {
$this->iterators[] = $iterator;
} elseif (is_iterable($iterator)) {
$it = new \ArrayIterator();
foreach ($iterator as $file) {
$file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file);
$it[$file->getPathname()] = $file;
}
$this->iterators[] = $it;
} else {
throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
}
$this->iterators[] = $iterator;
return $this;
}

View File

@@ -87,17 +87,29 @@ public function __construct(\Traversable $iterator, int|callable $sort, bool $re
public function getIterator(): \Traversable
{
if (1 === $this->sort) {
return $this->iterator;
yield from $this->iterator;
return;
}
$array = iterator_to_array($this->iterator, true);
$keys = $values = [];
foreach ($this->iterator as $key => $value) {
$keys[] = $key;
$values[] = $value;
}
if (-1 === $this->sort) {
$array = array_reverse($array);
} else {
uasort($array, $this->sort);
for ($i = \count($values) - 1; $i >= 0; --$i) {
yield $keys[$i] => $values[$i];
}
return;
}
return new \ArrayIterator($array);
uasort($values, $this->sort);
foreach ($values as $i => $v) {
yield $keys[$i] => $v;
}
}
}

View File

@@ -260,7 +260,7 @@ public function prepare(Request $request): static
if ($start < 0 || $start > $end) {
$this->setStatusCode(416);
$this->headers->set('Content-Range', \sprintf('bytes */%s', $fileSize));
} elseif ($end - $start < $fileSize - 1) {
} else {
$this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
$this->offset = $start;

View File

@@ -184,22 +184,30 @@ public function move(string $directory, ?string $name = null): File
switch ($this->error) {
case \UPLOAD_ERR_INI_SIZE:
throw new IniSizeFileException($this->getErrorMessage());
throw new IniSizeFileException($this->getExceptionMessage());
case \UPLOAD_ERR_FORM_SIZE:
throw new FormSizeFileException($this->getErrorMessage());
throw new FormSizeFileException($this->getExceptionMessage());
case \UPLOAD_ERR_PARTIAL:
throw new PartialFileException($this->getErrorMessage());
throw new PartialFileException($this->getExceptionMessage());
case \UPLOAD_ERR_NO_FILE:
throw new NoFileException($this->getErrorMessage());
throw new NoFileException($this->getExceptionMessage());
case \UPLOAD_ERR_CANT_WRITE:
throw new CannotWriteFileException($this->getErrorMessage());
throw new CannotWriteFileException($this->getExceptionMessage());
case \UPLOAD_ERR_NO_TMP_DIR:
throw new NoTmpDirFileException($this->getErrorMessage());
throw new NoTmpDirFileException($this->getExceptionMessage());
case \UPLOAD_ERR_EXTENSION:
throw new ExtensionFileException($this->getErrorMessage());
throw new ExtensionFileException($this->getExceptionMessage());
}
throw new FileException($this->getErrorMessage());
throw new FileException($this->getExceptionMessage());
}
/**
* Retrieves a user-friendly error message for file upload issues, if any.
*/
public function getErrorMessage(): string
{
return \UPLOAD_ERR_OK !== $this->error ? $this->getExceptionMessage() : '';
}
/**
@@ -248,7 +256,7 @@ private static function parseFilesize(string $size): int|float
/**
* Returns an informative upload error message.
*/
public function getErrorMessage(): string
private function getExceptionMessage(): string
{
static $errors = [
\UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',

View File

@@ -404,8 +404,16 @@ public static function create(string $uri, string $method = 'GET', array $parame
$server['PHP_AUTH_PW'] = $components['pass'];
}
if (!isset($components['path'])) {
if ('' === $path = $components['path'] ?? '') {
$components['path'] = '/';
} elseif (!isset($components['scheme']) && !isset($components['host']) && '/' !== $path[0]) {
if (false !== $pos = strpos($path, '/')) {
$path = substr($path, 0, $pos);
}
if (str_contains($path, ':')) {
throw new BadRequestException('Invalid URI: Path is malformed.');
}
}
switch (strtoupper($method)) {

View File

@@ -72,6 +72,16 @@ public function read(#[\SensitiveParameter] string $sessionId): string
return $data;
}
public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
{
$this->igbinaryEmptyData ??= \function_exists('igbinary_serialize') ? igbinary_serialize([]) : '';
if ('' === $data || $this->igbinaryEmptyData === $data) {
return $this->destroy($sessionId);
}
return true;
}
public function write(#[\SensitiveParameter] string $sessionId, string $data): bool
{
// see https://github.com/igbinary/igbinary/issues/146

View File

@@ -194,7 +194,6 @@ public function configureSchema(Schema $schema, ?\Closure $isSameDatabase = null
$table->addColumn($this->dataCol, Types::BLOB)->setNotnull(true);
$table->addColumn($this->lifetimeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
$table->addColumn($this->timeCol, Types::INTEGER)->setUnsigned(true)->setNotnull(true);
$table->addOption('collate', 'utf8mb4_bin');
$table->addOption('engine', 'InnoDB');
break;
case 'sqlite':
@@ -252,7 +251,7 @@ public function createTable()
// - trailing space removal
// - case-insensitivity
// - language processing like é == e
'mysql' => "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB",
'mysql' => "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) ENGINE = InnoDB",
'sqlite' => "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",

View File

@@ -62,6 +62,7 @@ public static function createHandler(object|string $connection, array $options =
throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".');
}
$handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
$connection = preg_replace('/([?&])prefix=[^&]*+&?/', '\1', $connection);
$connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
return new $handlerClass($connection, array_intersect_key($options, ['prefix' => 1, 'ttl' => 1]));

View File

@@ -41,14 +41,18 @@ class MetadataBag implements SessionBagInterface
private int $updateThreshold;
private ?int $cookieLifetime;
/**
* @param string $storageKey The key used to store bag in the session
* @param int $updateThreshold The time to wait between two UPDATED updates
* @param string $storageKey The key used to store bag in the session
* @param int $updateThreshold The time to wait between two UPDATED updates
* @param int|null $cookieLifetime The configured cookie lifetime; null to read from php.ini
*/
public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0)
public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0, ?int $cookieLifetime = null)
{
$this->storageKey = $storageKey;
$this->updateThreshold = $updateThreshold;
$this->cookieLifetime = $cookieLifetime;
}
/**
@@ -143,6 +147,6 @@ private function stampCreated(?int $lifetime = null): void
{
$timeStamp = time();
$this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
$this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime');
$this->meta[self::LIFETIME] = $lifetime ?? $this->cookieLifetime ?? (int) \ini_get('session.cookie_lifetime');
}
}

View File

@@ -21,6 +21,7 @@
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Serializer\Exception\InvalidArgumentException as SerializerInvalidArgumentException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
use Symfony\Component\Serializer\Exception\UnsupportedFormatException;
@@ -104,7 +105,7 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
try {
$payload = $this->$payloadMapper($request, $type, $argument);
} catch (PartialDenormalizationException $e) {
$trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p);
$trans = $this->translator ? $this->translator->trans(...) : static fn ($m, $p) => strtr($m, $p);
foreach ($e->getErrors() as $error) {
$parameters = [];
$template = 'This value was of an unexpected type.';
@@ -119,6 +120,9 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
$violations->add(new ConstraintViolation($message, $template, $parameters, null, $error->getPath(), null));
}
$payload = $e->getData();
} catch (SerializerInvalidArgumentException $e) {
$violations->add(new ConstraintViolation($e->getMessage(), $e->getMessage(), [], null, '', null));
$payload = null;
}
if (null !== $payload && !\count($violations)) {
@@ -133,6 +137,8 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
$payload = $this->$payloadMapper($request, $type, $argument);
} catch (PartialDenormalizationException $e) {
throw new HttpException($validationFailedCode, implode("\n", array_map(static fn ($e) => $e->getMessage(), $e->getErrors())), $e);
} catch (SerializerInvalidArgumentException $e) {
throw new HttpException($validationFailedCode, $e->getMessage(), $e);
}
}
@@ -181,7 +187,7 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
}
if (\is_array($data)) {
return $this->serializer->denormalize($data, $type, 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
return $this->serializer->denormalize($data, $type, self::hasNonStringScalar($data) ? $format : 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
}
if ('form' === $format) {
@@ -196,4 +202,21 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
throw new HttpException(Response::HTTP_BAD_REQUEST, \sprintf('Request payload contains invalid "%s" data.', $format), $e);
}
}
private static function hasNonStringScalar(array $data): bool
{
$stack = [$data];
while ($stack) {
foreach (array_pop($stack) as $v) {
if (\is_array($v)) {
$stack[] = $v;
} elseif (!\is_string($v)) {
return true;
}
}
}
return false;
}
}

View File

@@ -46,7 +46,7 @@ public function getMessage(): string
return $this->data['exception']->getMessage();
}
public function getCode(): int
public function getCode(): int|string
{
return $this->data['exception']->getCode();
}

View File

@@ -171,6 +171,7 @@ public function process(ContainerBuilder $container)
}
if ($autowireAttributes) {
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
$attribute = $autowireAttributes[0]->newInstance();
$value = $parameterBag->resolveValue($attribute->value);
@@ -203,8 +204,10 @@ public function process(ContainerBuilder $container)
$args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE);
} else {
$target = preg_replace('/(^|[(|&])\\\\/', '\1', $target);
$args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior);
$targetAttribute = null;
$name = Target::parseName($p, $targetAttribute);
$target = preg_replace('/(^|[(|&])\\\\/', '\\1', $target);
$args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, $name, $targetAttribute ? [$targetAttribute] : []) : new Reference($target, $invalidBehavior);
}
}
// register the maps as a per-method service-locators

View File

@@ -41,12 +41,13 @@ public function __construct(RequestStack $requestStack, string $defaultLocale =
$this->requestStack = $requestStack;
$this->router = $router;
$this->useAcceptLanguageHeader = $useAcceptLanguageHeader;
$this->enabledLocales = $enabledLocales;
$this->enabledLocales = $enabledLocales ? array_values(array_unique(array_merge([$defaultLocale], $enabledLocales))) : [];
}
public function setDefaultLocale(KernelEvent $event): void
{
$event->getRequest()->setDefaultLocale($this->defaultLocale);
$this->setRouterLocale($this->defaultLocale);
}
public function onKernelRequest(RequestEvent $event): void
@@ -54,14 +55,12 @@ public function onKernelRequest(RequestEvent $event): void
$request = $event->getRequest();
$this->setLocale($request);
$this->setRouterContext($request);
$this->setRouterLocale($request->getLocale());
}
public function onKernelFinishRequest(FinishRequestEvent $event): void
{
if (null !== $parentRequest = $this->requestStack->getParentRequest()) {
$this->setRouterContext($parentRequest);
}
$this->setRouterLocale($this->requestStack->getParentRequest()?->getLocale() ?? $this->defaultLocale);
}
private function setLocale(Request $request): void
@@ -76,9 +75,9 @@ private function setLocale(Request $request): void
}
}
private function setRouterContext(Request $request): void
private function setRouterLocale(string $locale): void
{
$this->router?->getContext()->setParameter('_locale', $request->getLocale());
$this->router?->getContext()->setParameter('_locale', $locale);
}
public static function getSubscribedEvents(): array

View File

@@ -70,17 +70,18 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
private ?string $warmupDir = null;
private int $requestStackSize = 0;
private bool $resetServices = false;
private bool $handlingHttpCache = false;
/**
* @var array<string, bool>
*/
private static array $freshCache = [];
public const VERSION = '6.4.31';
public const VERSION_ID = 60431;
public const VERSION = '6.4.36';
public const VERSION_ID = 60436;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 4;
public const RELEASE_VERSION = 31;
public const RELEASE_VERSION = 36;
public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2026';
@@ -101,6 +102,7 @@ public function __clone()
$this->container = null;
$this->requestStackSize = 0;
$this->resetServices = false;
$this->handlingHttpCache = false;
}
/**
@@ -108,7 +110,7 @@ public function __clone()
*/
public function boot()
{
if (true === $this->booted) {
if ($this->booted) {
if (!$this->requestStackSize && $this->resetServices) {
if ($this->container->has('services_resetter')) {
$this->container->get('services_resetter')->reset();
@@ -122,7 +124,7 @@ public function boot()
return;
}
if (null === $this->container) {
if (!$this->container) {
$this->preBoot();
}
@@ -149,7 +151,7 @@ public function reboot(?string $warmupDir)
*/
public function terminate(Request $request, Response $response)
{
if (false === $this->booted) {
if (!$this->booted) {
return;
}
@@ -163,7 +165,7 @@ public function terminate(Request $request, Response $response)
*/
public function shutdown()
{
if (false === $this->booted) {
if (!$this->booted) {
return;
}
@@ -181,17 +183,26 @@ public function shutdown()
public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response
{
if (!$this->booted) {
$container = $this->container ?? $this->preBoot();
if (!$this->container) {
$this->preBoot();
}
if ($container->has('http_cache')) {
return $container->get('http_cache')->handle($request, $type, $catch);
if (HttpKernelInterface::MAIN_REQUEST === $type && !$this->handlingHttpCache && $this->container->has('http_cache')) {
$this->handlingHttpCache = true;
try {
return $this->container->get('http_cache')->handle($request, $type, $catch);
} finally {
$this->handlingHttpCache = false;
$this->resetServices = true;
}
}
$this->boot();
++$this->requestStackSize;
$this->resetServices = true;
if (!$this->handlingHttpCache) {
$this->resetServices = true;
}
try {
return $this->getHttpKernel()->handle($request, $type, $catch);
@@ -603,7 +614,7 @@ protected function buildContainer(): ContainerBuilder
{
foreach (['cache' => $this->getCacheDir(), 'build' => $this->warmupDir ?: $this->getBuildDir(), 'logs' => $this->getLogDir()] as $name => $dir) {
if (!is_dir($dir)) {
if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
if (!@mkdir($dir, 0o777, true) && !is_dir($dir)) {
throw new \RuntimeException(\sprintf('Unable to create the "%s" directory (%s).', $name, $dir));
}
} elseif (!is_writable($dir)) {

View File

@@ -30,7 +30,7 @@
"symfony/config": "^6.1|^7.0",
"symfony/console": "^5.4|^6.0|^7.0",
"symfony/css-selector": "^5.4|^6.0|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4.1|^7.0.1",
"symfony/dom-crawler": "^5.4|^6.0|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
"symfony/finder": "^5.4|^6.0|^7.0",

View File

@@ -25,9 +25,6 @@ class SentMessage
private string $messageId;
private string $debug = '';
/**
* @internal
*/
public function __construct(RawMessage $message, Envelope $envelope)
{
$message->ensureValidity();
@@ -63,11 +60,19 @@ public function getEnvelope(): Envelope
return $this->envelope;
}
/**
* Sets the transport-level message ID.
*/
public function setMessageId(string $id): void
{
$this->messageId = $id;
}
/**
* Gets the transport-level message ID.
*
* Not to be confused with the Message-ID header, which is available via getOriginalMessage()
*/
public function getMessageId(): string
{
return $this->messageId;

View File

@@ -44,15 +44,11 @@ public function isGuesserSupported(): bool
return $supported;
}
if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) {
if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('shell_exec') || !\function_exists('escapeshellarg')) {
return $supported = false;
}
ob_start();
passthru('command -v file', $exitStatus);
$binPath = trim(ob_get_clean());
return $supported = 0 === $exitStatus && '' !== $binPath;
return $supported = '' !== trim(shell_exec('command -v file') ?: '');
}
public function guessMimeType(string $path): ?string
@@ -65,17 +61,8 @@ public function guessMimeType(string $path): ?string
throw new LogicException(\sprintf('The "%s" guesser is not supported.', __CLASS__));
}
ob_start();
// need to use --mime instead of -i. see #6641
passthru(\sprintf($this->cmd, escapeshellarg((str_starts_with($path, '-') ? './' : '').$path)), $return);
if ($return > 0) {
ob_end_clean();
return null;
}
$type = trim(ob_get_clean());
$type = trim(shell_exec(\sprintf($this->cmd, escapeshellarg((str_starts_with($path, '-') ? './' : '').$path))) ?: '');
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) {
// it's not a type, but an error message

View File

@@ -135,7 +135,7 @@ public function guessMimeType(string $path): ?string
/**
* A map of MIME types and their default extensions.
*
* Updated from upstream on 2025-11-15.
* Updated from upstream on 2026-02-02.
*
* @see Resources/bin/update_mime_types.php
*/
@@ -636,6 +636,7 @@ public function guessMimeType(string $path): ?string
'application/vnd.mynfc' => ['taglet'],
'application/vnd.nato.bindingdataobject+xml' => ['bdo'],
'application/vnd.neurolanguage.nlu' => ['nlu'],
'application/vnd.nintendo.nitro.rom' => ['nds'],
'application/vnd.nintendo.snes.rom' => ['sfc', 'smc'],
'application/vnd.nitf' => ['ntf', 'nitf'],
'application/vnd.noblenet-directory' => ['nnd'],
@@ -1159,7 +1160,7 @@ public function guessMimeType(string $path): ?string
'application/x-sh' => ['sh'],
'application/x-shar' => ['shar'],
'application/x-shared-library-la' => ['la'],
'application/x-sharedlib' => ['so'],
'application/x-sharedlib' => ['so', 'so.[0-9]*'],
'application/x-shellscript' => ['sh'],
'application/x-shockwave-flash' => ['swf', 'spl'],
'application/x-shorten' => ['shn'],
@@ -1216,7 +1217,7 @@ public function guessMimeType(string $path): ?string
'application/x-trash' => ['bak', 'old', 'sik'],
'application/x-trig' => ['trig'],
'application/x-troff' => ['tr', 'roff', 't'],
'application/x-troff-man' => ['man'],
'application/x-troff-man' => ['man', '[1-9]'],
'application/x-tzo' => ['tar.lzo', 'tzo'],
'application/x-ufraw' => ['ufraw'],
'application/x-ustar' => ['ustar'],
@@ -1293,7 +1294,7 @@ public function guessMimeType(string $path): ?string
'application/xslt+xml' => ['xsl', 'xslt'],
'application/xspf+xml' => ['xspf'],
'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'],
'application/yaml' => ['yaml', 'yml'],
'application/yaml' => ['yml', 'yaml'],
'application/yang' => ['yang'],
'application/yin+xml' => ['yin'],
'application/zip' => ['zip', 'zipx'],
@@ -1302,7 +1303,7 @@ public function guessMimeType(string $path): ?string
'audio/3gpp' => ['3gpp', '3gp', '3ga'],
'audio/3gpp-encrypted' => ['3gp', '3gpp', '3ga'],
'audio/3gpp2' => ['3g2', '3gp2', '3gpp2'],
'audio/aac' => ['aac', 'adts', 'ass'],
'audio/aac' => ['adts', 'aac', 'ass'],
'audio/ac3' => ['ac3'],
'audio/adpcm' => ['adp'],
'audio/amr' => ['amr'],
@@ -1318,6 +1319,7 @@ public function guessMimeType(string $path): ?string
'audio/imelody' => ['imy', 'ime'],
'audio/m3u' => ['m3u', 'm3u8', 'vlc'],
'audio/m4a' => ['m4a', 'f4a'],
'audio/matroska' => ['mka'],
'audio/midi' => ['mid', 'midi', 'kar', 'rmi'],
'audio/mobile-xmf' => ['mxmf'],
'audio/mp2' => ['mp2'],
@@ -1355,6 +1357,8 @@ public function guessMimeType(string $path): ?string
'audio/wave' => ['wav'],
'audio/webm' => ['weba'],
'audio/wma' => ['wma'],
'audio/x-669' => ['669', 'uni'],
'audio/x-669-mod' => ['669', 'uni'],
'audio/x-aac' => ['aac', 'adts', 'ass'],
'audio/x-aifc' => ['aifc', 'aiffc'],
'audio/x-aiff' => ['aif', 'aiff', 'aifc'],
@@ -1381,10 +1385,11 @@ public function guessMimeType(string $path): ?string
'audio/x-m4b' => ['m4b', 'f4b'],
'audio/x-m4r' => ['m4r'],
'audio/x-matroska' => ['mka'],
'audio/x-med' => ['med'],
'audio/x-midi' => ['mid', 'midi', 'kar'],
'audio/x-minipsf' => ['minipsf'],
'audio/x-mo3' => ['mo3'],
'audio/x-mod' => ['mod', 'ult', 'uni', 'm15', 'mtm', '669', 'med'],
'audio/x-mod' => ['mod', 'm15', 'stk', 'nst', 'fst', '669', 'med'],
'audio/x-mp2' => ['mp2'],
'audio/x-mp3' => ['mp3', 'mpga'],
'audio/x-mp3-playlist' => ['m3u', 'm3u8', 'vlc'],
@@ -1395,6 +1400,9 @@ public function guessMimeType(string $path): ?string
'audio/x-ms-wax' => ['wax'],
'audio/x-ms-wma' => ['wma'],
'audio/x-ms-wmv' => ['wmv'],
'audio/x-mtm' => ['mtm'],
'audio/x-multimate-mod' => ['ult'],
'audio/x-multitrack' => ['mtm'],
'audio/x-musepack' => ['mpc', 'mpp', 'mp+'],
'audio/x-ogg' => ['oga', 'ogg', 'opus'],
'audio/x-oggflac' => ['oga', 'ogg'],
@@ -1417,6 +1425,7 @@ public function guessMimeType(string $path): ?string
'audio/x-stm' => ['stm'],
'audio/x-tak' => ['tak'],
'audio/x-tta' => ['tta'],
'audio/x-ult' => ['ult'],
'audio/x-voc' => ['voc'],
'audio/x-vorbis' => ['oga', 'ogg'],
'audio/x-vorbis+ogg' => ['oga', 'ogg'],
@@ -1904,6 +1913,7 @@ public function guessMimeType(string $path): ?string
'video/iso.segment' => ['m4s'],
'video/jpeg' => ['jpgv'],
'video/jpm' => ['jpm', 'jpgm'],
'video/matroska' => ['mkv', 'mks', 'mk3d'],
'video/mj2' => ['mj2', 'mjp2'],
'video/mp2t' => ['ts', 'm2t', 'm2ts', 'mts', 'cpi', 'clpi', 'mpl', 'mpls', 'bdm', 'bdmv'],
'video/mp4' => ['mp4', 'mp4v', 'mpg4', 'm4v', 'f4v', 'lrv', 'lrf'],
@@ -1933,7 +1943,7 @@ public function guessMimeType(string $path): ?string
'video/vnd.vivo' => ['viv', 'vivo'],
'video/vnd.youtube.yt' => ['yt'],
'video/webm' => ['webm'],
'video/x-anim' => ['anim1', 'anim2', 'anim3', 'anim4', 'anim5', 'anim6', 'anim7', 'anim8', 'anim9', 'animj'],
'video/x-anim' => ['anim[1-9j]', 'anim2', 'anim3', 'anim4', 'anim5', 'anim6', 'anim7', 'anim8', 'anim9', 'animj'],
'video/x-annodex' => ['axv'],
'video/x-avi' => ['avi', 'avf', 'divx'],
'video/x-f4v' => ['f4v'],
@@ -1995,7 +2005,7 @@ public function guessMimeType(string $path): ?string
'3gpp2' => ['audio/3gpp2', 'video/3gpp2'],
'3mf' => ['application/vnd.ms-3mfdocument', 'model/3mf'],
'602' => ['application/x-t602'],
'669' => ['audio/x-mod'],
'669' => ['audio/x-669', 'audio/x-669-mod', 'audio/x-mod'],
'7z' => ['application/x-7z-compressed'],
'7z.001' => ['application/x-7z-compressed'],
'C' => ['text/x-c++src'],
@@ -2003,6 +2013,7 @@ public function guessMimeType(string $path): ?string
'PAR2' => ['application/x-par2'],
'PL' => ['application/x-perl', 'text/x-perl'],
'Z' => ['application/x-compress'],
'[1-9]' => ['application/x-troff-man'],
'a' => ['application/x-archive'],
'a26' => ['application/x-atari-2600-rom'],
'a78' => ['application/x-atari-7800-rom'],
@@ -2050,7 +2061,6 @@ public function guessMimeType(string $path): ?string
'amr' => ['audio/amr', 'audio/amr-encrypted'],
'amz' => ['audio/x-amzxml'],
'ani' => ['application/x-navi-animation'],
'anim1' => ['video/x-anim'],
'anim2' => ['video/x-anim'],
'anim3' => ['video/x-anim'],
'anim4' => ['video/x-anim'],
@@ -2059,6 +2069,7 @@ public function guessMimeType(string $path): ?string
'anim7' => ['video/x-anim'],
'anim8' => ['video/x-anim'],
'anim9' => ['video/x-anim'],
'anim[1-9j]' => ['video/x-anim'],
'animj' => ['video/x-anim'],
'anx' => ['application/annodex', 'application/x-annodex'],
'ape' => ['audio/x-ape'],
@@ -2457,7 +2468,7 @@ public function guessMimeType(string $path): ?string
'fpx' => ['image/vnd.fpx', 'image/x-fpx'],
'frame' => ['application/vnd.framemaker'],
'fsc' => ['application/vnd.fsc.weblaunch'],
'fst' => ['image/vnd.fst'],
'fst' => ['audio/x-mod', 'image/vnd.fst'],
'ftc' => ['application/vnd.fluxtime.clip'],
'fti' => ['application/vnd.anser-web-funds-transfer-initiation'],
'fts' => ['application/fits', 'image/fits', 'image/x-fits'],
@@ -2828,7 +2839,7 @@ public function guessMimeType(string $path): ?string
'mdi' => ['image/vnd.ms-modi'],
'mdx' => ['application/x-genesis-32x-rom', 'text/mdx'],
'me' => ['text/troff', 'text/x-troff-me'],
'med' => ['audio/x-mod'],
'med' => ['audio/x-med', 'audio/x-mod'],
'mesh' => ['model/mesh'],
'meta4' => ['application/metalink4+xml'],
'metainfo.xml' => ['application/x-freedesktop-appstream-component'],
@@ -2852,11 +2863,11 @@ public function guessMimeType(string $path): ?string
'mjpg' => ['video/x-mjpeg'],
'mjs' => ['application/javascript', 'application/x-javascript', 'text/javascript', 'text/jscript'],
'mk' => ['text/x-makefile'],
'mk3d' => ['video/x-matroska', 'video/x-matroska-3d'],
'mka' => ['audio/x-matroska'],
'mk3d' => ['video/matroska', 'video/x-matroska', 'video/x-matroska-3d'],
'mka' => ['audio/matroska', 'audio/x-matroska'],
'mkd' => ['text/markdown', 'text/x-markdown'],
'mks' => ['video/x-matroska'],
'mkv' => ['video/x-matroska'],
'mks' => ['video/matroska', 'video/x-matroska'],
'mkv' => ['video/matroska', 'video/x-matroska'],
'ml' => ['text/x-ocaml'],
'mli' => ['text/x-ocaml'],
'mlp' => ['application/vnd.dolby.mlp'],
@@ -2884,7 +2895,7 @@ public function guessMimeType(string $path): ?string
'mp21' => ['application/mp21'],
'mp2a' => ['audio/mpeg'],
'mp3' => ['audio/mpeg', 'audio/mp3', 'audio/x-mp3', 'audio/x-mpeg', 'audio/x-mpg'],
'mp4' => ['video/mp4', 'application/mp4', 'video/mp4v-es', 'video/x-m4v'],
'mp4' => ['application/mp4', 'video/mp4', 'video/mp4v-es', 'video/x-m4v'],
'mp4a' => ['audio/mp4'],
'mp4s' => ['application/mp4'],
'mp4v' => ['video/mp4'],
@@ -2894,7 +2905,7 @@ public function guessMimeType(string $path): ?string
'mpeg' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'],
'mpf' => ['application/media-policy-dataset+xml'],
'mpg' => ['video/mpeg', 'video/mpeg-system', 'video/x-mpeg', 'video/x-mpeg-system', 'video/x-mpeg2'],
'mpg4' => ['video/mpg4', 'application/mp4', 'video/mp4'],
'mpg4' => ['application/mp4', 'video/mp4', 'video/mpg4'],
'mpga' => ['audio/mp3', 'audio/mpeg', 'audio/x-mp3', 'audio/x-mpeg', 'audio/x-mpg'],
'mpkg' => ['application/vnd.apple.installer+xml'],
'mpl' => ['text/x-mpl2', 'video/mp2t'],
@@ -2928,7 +2939,7 @@ public function guessMimeType(string $path): ?string
'msu' => ['application/microsoftupdate'],
'msx' => ['application/x-msx-rom'],
'mtl' => ['model/mtl'],
'mtm' => ['audio/x-mod'],
'mtm' => ['audio/x-mtm', 'audio/x-multitrack'],
'mts' => ['application/typescript', 'model/vnd.mts', 'video/mp2t'],
'mup' => ['text/x-mup'],
'mus' => ['application/vnd.musician'],
@@ -2950,7 +2961,7 @@ public function guessMimeType(string $path): ?string
'nbp' => ['application/vnd.wolfram.player'],
'nc' => ['application/x-netcdf'],
'ncx' => ['application/x-dtbncx+xml'],
'nds' => ['application/x-nintendo-ds-rom'],
'nds' => ['application/vnd.nintendo.nitro.rom', 'application/x-nintendo-ds-rom'],
'nef' => ['image/x-nikon-nef'],
'nes' => ['application/x-nes-rom'],
'nez' => ['application/x-nes-rom'],
@@ -2976,6 +2987,7 @@ public function guessMimeType(string $path): ?string
'nsf' => ['application/vnd.lotus-notes'],
'nsh' => ['text/x-nsis'],
'nsi' => ['text/x-nsis'],
'nst' => ['audio/x-mod'],
'nsv' => ['video/x-nsv'],
'nt' => ['application/n-triples'],
'ntar' => ['application/x-pcapng'],
@@ -3389,6 +3401,7 @@ public function guessMimeType(string $path): ?string
'snd' => ['audio/basic'],
'snf' => ['application/x-font-snf'],
'so' => ['application/x-sharedlib'],
'so.[0-9]*' => ['application/x-sharedlib'],
'socket' => ['text/x-systemd-unit'],
'spc' => ['application/x-pkcs7-certificates'],
'spd' => ['application/x-font-speedo'],
@@ -3427,7 +3440,7 @@ public function guessMimeType(string $path): ?string
'step' => ['model/step'],
'stf' => ['application/vnd.wt.stf'],
'sti' => ['application/vnd.sun.xml.impress.template'],
'stk' => ['application/hyperstudio'],
'stk' => ['application/hyperstudio', 'audio/x-mod'],
'stl' => ['application/vnd.ms-pki.stl', 'model/stl', 'model/x.stl-ascii', 'model/x.stl-binary'],
'stm' => ['audio/x-stm'],
'stp' => ['model/step'],
@@ -3570,11 +3583,11 @@ public function guessMimeType(string $path): ?string
'ufraw' => ['application/x-ufraw'],
'ui' => ['application/x-designer', 'application/x-gtk-builder'],
'uil' => ['text/x-uil'],
'ult' => ['audio/x-mod'],
'ult' => ['audio/x-multimate-mod', 'audio/x-ult'],
'ulx' => ['application/x-glulx'],
'umj' => ['application/vnd.umajin'],
'unf' => ['application/x-nes-rom'],
'uni' => ['audio/x-mod'],
'uni' => ['audio/x-669', 'audio/x-669-mod'],
'unif' => ['application/x-nes-rom'],
'unityweb' => ['application/vnd.unity'],
'uo' => ['application/vnd.uoml+xml'],

View File

@@ -141,7 +141,7 @@ public function __sleep(): array
}
$this->_headers = $this->getHeaders();
return ['_headers', '_parent', 'filename', 'mediaType'];
return ['_headers', '_parent', 'filename', 'mediaType', 'cid'];
}
/**

View File

@@ -88,6 +88,10 @@
'md' => ['text/markdown', 'text/x-markdown'],
'mdb' => ['application/x-msaccess'],
'mid' => ['audio/midi'],
'mk3d' => ['video/matroska'],
'mka' => ['audio/matroska'],
'mks' => ['video/matroska'],
'mkv' => ['video/matroska'],
'mov' => ['video/quicktime'],
'mp3' => ['audio/mpeg'],
'ogg' => ['audio/ogg'],

View File

@@ -211,6 +211,10 @@ public static function ctype_xdigit($text)
*/
private static function convert_int_to_char_for_ctype($int, $function)
{
if (\PHP_VERSION_ID >= 80100 && !\is_string($int)) {
@trigger_error($function.'(): Argument of type '.get_debug_type($int).' will be interpreted as string in the future', \E_USER_DEPRECATED);
}
if (!\is_int($int)) {
return $int;
}
@@ -219,10 +223,6 @@ private static function convert_int_to_char_for_ctype($int, $function)
return (string) $int;
}
if (\PHP_VERSION_ID >= 80100) {
@trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED);
}
if ($int < 0) {
$int += 256;
}

View File

@@ -27,6 +27,7 @@
* - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack
* - grapheme_substr - Return part of a string
* - grapheme_str_split - Splits a string into an array of individual or chunks of graphemes
* - grapheme_levenshtein - Calculate the grapheme-unit Levenshtein distance between two strings
*
* @author Nicolas Grekas <p@tchwork.com>
*
@@ -51,7 +52,7 @@ public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT,
if (!\is_scalar($s)) {
$hasError = false;
set_error_handler(function () use (&$hasError) { $hasError = true; });
set_error_handler(static function () use (&$hasError) { $hasError = true; });
$next = substr($s, $start);
restore_error_handler();
if ($hasError) {
@@ -223,6 +224,54 @@ public static function grapheme_str_split($s, $len = 1)
return $chunks;
}
public static function grapheme_levenshtein($s1, $s2, $insertion_cost = 1, $replacement_cost = 1, $deletion_cost = 1)
{
if (!preg_match('//u', $s1) || !preg_match('//u', $s2)) {
return false;
}
if (0 > $insertion_cost || 0 > $replacement_cost || 0 > $deletion_cost) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('grapheme_levenshtein(): Argument #3 ($insertion_cost), #4 ($replacement_cost), and #5 ($deletion_cost) must be greater than or equal to 0');
}
preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s1, $s1);
preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s2, $s2);
$s1 = $s1[0];
$s2 = $s2[0];
$l1 = \count($s1);
$l2 = \count($s2);
if (0 === $l1) {
return $l2 * $insertion_cost;
}
if (0 === $l2) {
return $l1 * $deletion_cost;
}
$dp = array_fill(0, $l1 + 1, array_fill(0, $l2 + 1, 0));
for ($i = 1; $i <= $l1; ++$i) {
$dp[$i][0] = $dp[$i - 1][0] + $deletion_cost;
}
for ($j = 1; $j <= $l2; ++$j) {
$dp[0][$j] = $dp[0][$j - 1] + $insertion_cost;
}
for ($i = 1; $i <= $l1; ++$i) {
for ($j = 1; $j <= $l2; ++$j) {
$cost = ($s1[$i - 1] === $s2[$j - 1]) ? 0 : $replacement_cost;
$dp[$i][$j] = min($dp[$i - 1][$j] + $deletion_cost, $dp[$i][$j - 1] + $insertion_cost, $dp[$i - 1][$j - 1] + $cost);
}
}
return $dp[$l1][$l2];
}
private static function grapheme_position($s, $needle, $offset, $mode)
{
$needle = (string) $needle;

View File

@@ -53,5 +53,8 @@ function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\G
function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); }
}
if (!function_exists('grapheme_str_split')) {
function grapheme_str_split($string, $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); }
function grapheme_str_split(string $string, int $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); }
}
if (!function_exists('grapheme_levenshtein')) {
function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = '') { return p\Php85::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); }
}

View File

@@ -14,6 +14,9 @@
if (!function_exists('grapheme_str_split')) {
function grapheme_str_split(string $string, int $length = 1): array|false { return p\Grapheme::grapheme_str_split($string, $length); }
}
if (!function_exists('grapheme_levenshtein')) {
function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = ''): int|false { return p\Grapheme::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); }
}
if (extension_loaded('intl')) {
return;

View File

@@ -133,7 +133,7 @@ public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null
public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
{
$ok = true;
array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
array_walk_recursive($vars, static function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
$ok = false;
}
@@ -194,7 +194,7 @@ public static function mb_decode_numericentity($s, $convmap, $encoding = null)
$convmap[$i + 1] += $convmap[$i + 2];
}
$s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
$s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))'.(\PHP_VERSION_ID >= 80200 ? '' : '(?!&)').';?/', static function (array $m) use ($cnt, $convmap) {
$c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
for ($i = 0; $i < $cnt; $i += 4) {
if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
@@ -268,7 +268,7 @@ public static function mb_encode_numericentity($s, $convmap, $encoding = null, $
for ($j = 0; $j < $cnt; $j += 4) {
if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
$cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
$result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
$result .= $is_hex ? \sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
continue 2;
}
}
@@ -382,7 +382,7 @@ public static function mb_internal_encoding($encoding = null)
return false;
}
throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
throw new \ValueError(\sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
}
public static function mb_language($lang = null)
@@ -403,7 +403,7 @@ public static function mb_language($lang = null)
return false;
}
throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
throw new \ValueError(\sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
}
public static function mb_list_encodings()
@@ -834,19 +834,32 @@ public static function mb_ord($s, $encoding = null)
return $code;
}
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
/** @return string|false */
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null)
{
if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
if (\PHP_VERSION_ID < 80000) {
trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING);
return false;
}
throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
}
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given');
} elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) {
return false;
}
if (self::mb_strlen($pad_string, $encoding) <= 0) {
if (\PHP_VERSION_ID < 80000) {
trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING);
return false;
}
throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
}
@@ -869,12 +882,13 @@ public static function mb_str_pad(string $string, int $length, string $pad_strin
}
}
public static function mb_ucfirst(string $string, ?string $encoding = null): string
/** @return string|false */
public static function mb_ucfirst(string $string, ?string $encoding = null)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
} elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
return false;
}
$firstChar = mb_substr($string, 0, 1, $encoding);
@@ -883,12 +897,13 @@ public static function mb_ucfirst(string $string, ?string $encoding = null): str
return $firstChar.mb_substr($string, 1, null, $encoding);
}
public static function mb_lcfirst(string $string, ?string $encoding = null): string
/** @return string|false */
public static function mb_lcfirst(string $string, ?string $encoding = null)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
} elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
return false;
}
$firstChar = mb_substr($string, 0, 1, $encoding);
@@ -968,30 +983,42 @@ private static function getEncoding($encoding)
return 'UTF-8';
}
if ('UTF-32' === $encoding) {
return 'UTF-32BE';
}
if ('UTF-16' === $encoding) {
return 'UTF-16BE';
}
return $encoding;
}
public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
}
public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
}
public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
/** @return string|false */
public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
}
private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string
/** @return string|false */
private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given');
} elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) {
return false;
}
if ('' === $characters) {
@@ -1020,7 +1047,7 @@ private static function mb_internal_trim(string $regex, string $string, ?string
$characters = preg_quote($characters);
}
$string = preg_replace(sprintf($regex, $characters), '', $string);
$string = preg_replace(\sprintf($regex, $characters), '', $string);
if (null === $encoding) {
return $string;
@@ -1029,17 +1056,22 @@ private static function mb_internal_trim(string $regex, string $string, ?string
return iconv('UTF-8', $encoding.'//IGNORE', $string);
}
private static function assertEncoding(string $encoding, string $errorFormat): void
private static function assertEncoding(string $encoding, string $errorFormat): bool
{
try {
$validEncoding = @self::mb_check_encoding('', $encoding);
} catch (\ValueError $e) {
throw new \ValueError(sprintf($errorFormat, $encoding));
throw new \ValueError(\sprintf($errorFormat, $encoding));
}
// BC for PHP 7.3 and lower
if (!$validEncoding) {
throw new \ValueError(sprintf($errorFormat, $encoding));
if (80000 > \PHP_VERSION_ID) {
trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING);
} else {
throw new \ValueError(\sprintf($errorFormat, $encoding));
}
}
return $validEncoding;
}
}

View File

@@ -133,30 +133,29 @@ function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstrin
}
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
if (!function_exists('mb_ucfirst')) {
function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
function mb_ucfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_ucfirst($string, $encoding); }
}
if (!function_exists('mb_lcfirst')) {
function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
function mb_lcfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_lcfirst($string, $encoding); }
}
if (!function_exists('mb_trim')) {
function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_trim($string, $characters, $encoding); }
}
if (!function_exists('mb_ltrim')) {
function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
}
if (!function_exists('mb_rtrim')) {
function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
}
if (extension_loaded('mbstring')) {
return;
}

View File

@@ -60,7 +60,7 @@ public static function get_debug_type($value): string
public static function get_resource_id($res): int
{
if (!\is_resource($res) && null === @get_resource_type($res)) {
throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
throw new \TypeError(\sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
}
return (int) $res;

View File

@@ -32,7 +32,7 @@ public static function json_validate(string $json, int $depth = 512, int $flags
}
if ($depth > self::JSON_MAX_DEPTH) {
throw new \ValueError(sprintf('json_validate(): Argument #2 ($depth) must be less than %d', self::JSON_MAX_DEPTH));
throw new \ValueError(\sprintf('json_validate(): Argument #2 ($depth) must be less than %d', self::JSON_MAX_DEPTH));
}
json_decode($json, true, $depth, $flags);
@@ -40,7 +40,8 @@ public static function json_validate(string $json, int $depth = 512, int $flags
return \JSON_ERROR_NONE === json_last_error();
}
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
/** @return string|false */
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null)
{
if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
@@ -50,19 +51,27 @@ public static function mb_str_pad(string $string, int $length, string $pad_strin
$encoding = mb_internal_encoding();
}
$errorToTrigger = null;
try {
$validEncoding = @mb_check_encoding('', $encoding);
if (!@mb_check_encoding('', $encoding)) {
$errorToTrigger = \sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding);
}
} catch (\ValueError $e) {
throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
}
// BC for PHP 7.3 and lower
if (!$validEncoding) {
throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
$errorToTrigger = \sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding);
}
if (mb_strlen($pad_string, $encoding) <= 0) {
throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
$errorToTrigger = 'mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string';
}
if (null !== $errorToTrigger) {
if (80000 > \PHP_VERSION_ID) {
trigger_error($errorToTrigger, \E_USER_WARNING);
return false;
}
throw new \ValueError($errorToTrigger);
}
$paddingRequired = $length - mb_strlen($string, $encoding);
@@ -135,7 +144,7 @@ public static function str_decrement(string $string): string
}
if (preg_match('/\A(?:0[aA0]?|[aA])\z/', $string)) {
throw new \ValueError(sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string));
throw new \ValueError(\sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string));
}
if (!\in_array(substr($string, -1), ['A', 'a', '0'], true)) {

View File

@@ -19,12 +19,6 @@
function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); }
}
if (extension_loaded('mbstring')) {
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
}
if (!function_exists('stream_context_set_options')) {
function stream_context_set_options($context, array $options): bool { return stream_context_set_option($context, $options); }
}
@@ -37,8 +31,14 @@ function str_increment(string $string): string { return p\Php83::str_increment($
function str_decrement(string $string): string { return p\Php83::str_decrement($string); }
}
if (\PHP_VERSION_ID >= 80100) {
return require __DIR__.'/bootstrap81.php';
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
if (extension_loaded('mbstring')) {
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
}
if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php83 as p;
if (extension_loaded('mbstring')) {
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
}
if (\PHP_VERSION_ID >= 80100) {
return require __DIR__.'/bootstrap81.php';
}
if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
}
if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
function ldap_connect_wallet(?string $uri, string $wallet, string $password, int $auth_mode = \GSLC_SSL_NO_AUTH) { return ldap_connect($uri, $wallet, $password, $auth_mode); }
}

View File

@@ -42,7 +42,7 @@ final class Uuid
public static function uuid_create($uuid_type = \UUID_TYPE_DEFAULT)
{
if (!is_numeric($uuid_type) && null !== $uuid_type) {
trigger_error(sprintf('uuid_create() expects parameter 1 to be int, %s given', \gettype($uuid_type)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_create() expects parameter 1 to be int, %s given', \gettype($uuid_type)), \E_USER_WARNING);
return null;
}
@@ -56,7 +56,7 @@ public static function uuid_create($uuid_type = \UUID_TYPE_DEFAULT)
case self::UUID_TYPE_DEFAULT:
return self::uuid_generate_random();
default:
trigger_error(sprintf("Unknown/invalid UUID type '%d' requested, using default type instead", $uuid_type), \E_USER_WARNING);
trigger_error(\sprintf("Unknown/invalid UUID type '%d' requested, using default type instead", $uuid_type), \E_USER_WARNING);
return self::uuid_generate_random();
}
@@ -65,13 +65,13 @@ public static function uuid_create($uuid_type = \UUID_TYPE_DEFAULT)
public static function uuid_generate_md5($uuid_ns, $name)
{
if (!\is_string($uuid_ns = self::toString($uuid_ns))) {
trigger_error(sprintf('uuid_generate_md5() expects parameter 1 to be string, %s given', \gettype($uuid_ns)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_generate_md5() expects parameter 1 to be string, %s given', \gettype($uuid_ns)), \E_USER_WARNING);
return null;
}
if (!\is_string($name = self::toString($name))) {
trigger_error(sprintf('uuid_generate_md5() expects parameter 2 to be string, %s given', \gettype($name)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_generate_md5() expects parameter 2 to be string, %s given', \gettype($name)), \E_USER_WARNING);
return null;
}
@@ -86,7 +86,7 @@ public static function uuid_generate_md5($uuid_ns, $name)
$hash = md5(hex2bin(str_replace('-', '', $uuid_ns)).$name);
return sprintf('%08s-%04s-3%03s-%04x-%012s',
return \sprintf('%08s-%04s-3%03s-%04x-%012s',
// 32 bits for "time_low"
substr($hash, 0, 8),
// 16 bits for "time_mid"
@@ -106,13 +106,13 @@ public static function uuid_generate_md5($uuid_ns, $name)
public static function uuid_generate_sha1($uuid_ns, $name)
{
if (!\is_string($uuid_ns = self::toString($uuid_ns))) {
trigger_error(sprintf('uuid_generate_sha1() expects parameter 1 to be string, %s given', \gettype($uuid_ns)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_generate_sha1() expects parameter 1 to be string, %s given', \gettype($uuid_ns)), \E_USER_WARNING);
return null;
}
if (!\is_string($name = self::toString($name))) {
trigger_error(sprintf('uuid_generate_sha1() expects parameter 2 to be string, %s given', \gettype($name)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_generate_sha1() expects parameter 2 to be string, %s given', \gettype($name)), \E_USER_WARNING);
return null;
}
@@ -127,7 +127,7 @@ public static function uuid_generate_sha1($uuid_ns, $name)
$hash = sha1(hex2bin(str_replace('-', '', $uuid_ns)).$name);
return sprintf('%08s-%04s-5%03s-%04x-%012s',
return \sprintf('%08s-%04s-5%03s-%04x-%012s',
// 32 bits for "time_low"
substr($hash, 0, 8),
// 16 bits for "time_mid"
@@ -149,7 +149,7 @@ public static function uuid_generate_sha1($uuid_ns, $name)
public static function uuid_is_valid($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_is_valid() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_is_valid() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
@@ -160,13 +160,13 @@ public static function uuid_is_valid($uuid)
public static function uuid_compare($uuid1, $uuid2)
{
if (!\is_string($uuid1 = self::toString($uuid1))) {
trigger_error(sprintf('uuid_compare() expects parameter 1 to be string, %s given', \gettype($uuid1)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_compare() expects parameter 1 to be string, %s given', \gettype($uuid1)), \E_USER_WARNING);
return null;
}
if (!\is_string($uuid2 = self::toString($uuid2))) {
trigger_error(sprintf('uuid_compare() expects parameter 2 to be string, %s given', \gettype($uuid2)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_compare() expects parameter 2 to be string, %s given', \gettype($uuid2)), \E_USER_WARNING);
return null;
}
@@ -193,7 +193,7 @@ public static function uuid_compare($uuid1, $uuid2)
public static function uuid_is_null($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_is_null() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_is_null() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
@@ -207,7 +207,7 @@ public static function uuid_is_null($uuid)
public static function uuid_type($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_type() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_type() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
@@ -230,7 +230,7 @@ public static function uuid_type($uuid)
public static function uuid_variant($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_variant() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_variant() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
@@ -263,7 +263,7 @@ public static function uuid_variant($uuid)
public static function uuid_time($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_time() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_time() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
@@ -292,7 +292,7 @@ public static function uuid_time($uuid)
public static function uuid_mac($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_mac() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_mac() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
@@ -313,7 +313,7 @@ public static function uuid_mac($uuid)
public static function uuid_parse($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_parse() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_parse() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
@@ -332,7 +332,7 @@ public static function uuid_parse($uuid)
public static function uuid_unparse($bytes)
{
if (!\is_string($bytes = self::toString($bytes))) {
trigger_error(sprintf('uuid_unparse() expects parameter 1 to be string, %s given', \gettype($bytes)), \E_USER_WARNING);
trigger_error(\sprintf('uuid_unparse() expects parameter 1 to be string, %s given', \gettype($bytes)), \E_USER_WARNING);
return null;
}
@@ -357,7 +357,7 @@ private static function uuid_generate_random()
{
$uuid = bin2hex(random_bytes(16));
return sprintf('%08s-%04s-4%03s-%04x-%012s',
return \sprintf('%08s-%04s-4%03s-%04x-%012s',
// 32 bits for "time_low"
substr($uuid, 0, 8),
// 16 bits for "time_mid"
@@ -401,21 +401,21 @@ private static function uuid_generate_time()
if (\function_exists('apcu_fetch')) {
$node = apcu_fetch('__symfony_uuid_node');
if (false === $node) {
$node = sprintf('%06x%06x',
$node = \sprintf('%06x%06x',
random_int(0, 0xFFFFFF) | 0x010000,
random_int(0, 0xFFFFFF)
);
apcu_store('__symfony_uuid_node', $node);
}
} else {
$node = sprintf('%06x%06x',
$node = \sprintf('%06x%06x',
random_int(0, 0xFFFFFF) | 0x010000,
random_int(0, 0xFFFFFF)
);
}
}
return sprintf('%08s-%04s-1%03s-%04x-%012s',
return \sprintf('%08s-%04s-1%03s-%04x-%012s',
// 32 bits for "time_low"
substr($time, -8),

View File

@@ -256,7 +256,12 @@ public function run(?callable $callback = null, array $env = []): int
*
* @return $this
*
* @throws ProcessFailedException if the process didn't terminate successfully
* @throws ProcessFailedException When process didn't terminate successfully
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
*
* @final
*/
@@ -331,7 +336,7 @@ public function start(?callable $callback = null, array $env = [])
$envPairs = [];
foreach ($env as $k => $v) {
if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) {
if (false !== $v && !\in_array($k = (string) $k, ['', 'argc', 'argv', 'ARGC', 'ARGV'], true) && !str_contains($k, '=') && !str_contains($k, "\0")) {
$envPairs[] = $k.'='.$v;
}
}
@@ -1574,7 +1579,7 @@ private function escapeArgument(?string $argument): string
if (str_contains($argument, "\0")) {
$argument = str_replace("\0", '?', $argument);
}
if (!preg_match('/[()%!^"<>&|\s]/', $argument)) {
if (!preg_match('/[()%!^"<>&|\s[\]=;*?\'$]/', $argument)) {
return $argument;
}
$argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);

View File

@@ -38,12 +38,13 @@ public function __construct(
*/
public function load(mixed $resource, ?string $type = null): ?RouteCollection
{
$excluded = $resource['_excluded'] ?? [];
$path = $this->locator->locate($resource['path'], $this->currentDirectory);
if (!is_dir($path)) {
return new RouteCollection();
}
return $this->loadFromDirectory($path, trim($resource['namespace'], '\\'));
return $this->loadFromDirectory($path, trim($resource['namespace'], '\\'), $excluded);
}
public function supports(mixed $resource, ?string $type = null): bool
@@ -59,7 +60,7 @@ public function forDirectory(string $currentDirectory): static
return $loader;
}
private function loadFromDirectory(string $directory, string $psr4Prefix): RouteCollection
private function loadFromDirectory(string $directory, string $psr4Prefix, array $excluded = []): RouteCollection
{
$collection = new RouteCollection();
$collection->addResource(new DirectoryResource($directory, '/\.php$/'));
@@ -74,8 +75,13 @@ private function loadFromDirectory(string $directory, string $psr4Prefix): Route
/** @var \SplFileInfo $file */
foreach ($files as $file) {
$normalizedPath = rtrim(str_replace('\\', '/', $file->getPathname()), '/');
if (isset($excluded[$normalizedPath])) {
continue;
}
if ($file->isDir()) {
$collection->addCollection($this->loadFromDirectory($file->getPathname(), $psr4Prefix.'\\'.$file->getFilename()));
$collection->addCollection($this->loadFromDirectory($file->getPathname(), $psr4Prefix.'\\'.$file->getFilename(), $excluded));
continue;
}

View File

@@ -575,6 +575,8 @@ private function pad(int $len, self $pad, int $type): static
private function wcswidth(string $string): int
{
$width = 0;
$lastChar = null;
$lastWidth = null;
foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
$codePoint = mb_ord($c, 'UTF-8');
@@ -597,6 +599,20 @@ private function wcswidth(string $string): int
return -1;
}
if (0xFE0F === $codePoint) {
if (\PCRE_VERSION_MAJOR < 10 || \PCRE_VERSION_MAJOR === 10 && \PCRE_VERSION_MINOR < 40) {
$regex = '/\p{So}/u';
} else {
$regex = '/\p{Emoji}/u';
}
if (null !== $lastChar && 1 === $lastWidth && preg_match($regex, $lastChar)) {
++$width;
$lastWidth = 2;
}
continue;
}
self::$tableZero ??= require __DIR__.'/Resources/data/wcswidth_table_zero.php';
if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) {
@@ -627,6 +643,8 @@ private function wcswidth(string $string): int
$ubound = $mid - 1;
} else {
$width += 2;
$lastChar = $c;
$lastWidth = 2;
continue 2;
}
@@ -634,6 +652,8 @@ private function wcswidth(string $string): int
}
++$width;
$lastChar = $c;
$lastWidth = 1;
}
return $width;

View File

@@ -106,7 +106,9 @@ public function endsWith(string|iterable|AbstractString $suffix): bool
return false;
}
$grapheme = grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)) ?: '';
if (false === $grapheme = grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix))) {
$grapheme = '';
}
if ($this->ignoreCase) {
return 0 === mb_stripos($grapheme, $suffix, 0, 'UTF-8');
@@ -357,7 +359,9 @@ public function startsWith(string|iterable|AbstractString $prefix): bool
return false;
}
$grapheme = grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES) ?: '';
if (false === $grapheme = grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES)) {
$grapheme = '';
}
if ($this->ignoreCase) {
return 0 === mb_stripos($grapheme, $prefix, 0, 'UTF-8');

View File

@@ -34,7 +34,7 @@ protected function loadResource(string $resource): array
throw new NotFoundResourceException(\sprintf('Error opening file "%s".', $resource), 0, $e);
}
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY | \SplFileObject::DROP_NEW_LINE);
$file->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
foreach ($file as $data) {

View File

@@ -36,7 +36,7 @@ public function __construct(?string $ulid = null)
$this->uid = $ulid;
} else {
if (!self::isValid($ulid)) {
throw new \InvalidArgumentException(\sprintf('Invalid ULID: "%s".', $ulid));
throw new \InvalidArgumentException('Invalid ULID.');
}
$this->uid = strtoupper($ulid);

View File

@@ -32,13 +32,13 @@ public function __construct(string $uuid, bool $checkVariant = false)
$type = preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $uuid) ? (int) $uuid[14] : false;
if (false === $type || (static::TYPE ?: $type) !== $type) {
throw new \InvalidArgumentException(\sprintf('Invalid UUID%s: "%s".', static::TYPE ? 'v'.static::TYPE : '', $uuid));
throw new \InvalidArgumentException(\sprintf('Invalid UUID%s.', static::TYPE ? 'v'.static::TYPE : ''));
}
$this->uid = strtolower($uuid);
if ($checkVariant && !\in_array($this->uid[19], ['8', '9', 'a', 'b'], true)) {
throw new \InvalidArgumentException(\sprintf('Invalid UUID%s: "%s".', static::TYPE ? 'v'.static::TYPE : '', $uuid));
throw new \InvalidArgumentException(\sprintf('Invalid UUID%s.', static::TYPE ? 'v'.static::TYPE : ''));
}
}

0
vendor/symfony/var-dumper/Resources/bin/var-dump-server vendored Normal file → Executable file
View File

View File

@@ -1,13 +1,6 @@
CHANGELOG
=========
8.0
---
* Restrict `ProxyHelper::generateLazyProxy()` to generating abstraction-based lazy decorators; use native lazy proxies otherwise
* Remove `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead
* Remove `ProxyHelper::generateLazyGhost()`, use native lazy objects instead
7.4
---

View File

@@ -145,7 +145,7 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
$p = $parent->hasProperty($n) ? $parent->getProperty($n) : null;
} while (!$p && $parent = $parent->getParentClass());
$c = $p && (!$p->isPublic() || $p->isProtectedSet() || $p->isPrivateSet()) ? $p->class : 'stdClass';
$c = $p && (!$p->isPublic() || (\PHP_VERSION_ID >= 80400 ? $p->isProtectedSet() || $p->isPrivateSet() : $p->isReadOnly())) ? $p->class : 'stdClass';
} elseif ('*' === $n[1]) {
$n = substr($n, 3);
$c = $reflector->getProperty($n)->class;

View File

@@ -222,7 +222,7 @@ public static function getSimpleHydrator($class)
if ($propertyReflector->isStatic()) {
continue;
}
if (!$propertyReflector->isAbstract() && $propertyReflector->getHooks()) {
if (\PHP_VERSION_ID >= 80400 && !$propertyReflector->isAbstract() && $propertyReflector->getHooks()) {
$notByRef->{$propertyReflector->name} = $propertyReflector->setRawValue(...);
} elseif ($propertyReflector->isReadOnly()) {
$notByRef->{$propertyReflector->name} = static function ($object, $value) use ($propertyReflector) {
@@ -277,7 +277,7 @@ public static function getPropertyScopes($class): array
$name = $property->name;
$access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0);
if (!$property->isAbstract() && $h = $property->getHooks()) {
if (\PHP_VERSION_ID >= 80400 && !$property->isAbstract() && $h = $property->getHooks()) {
$access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0);
}
@@ -289,7 +289,7 @@ public static function getPropertyScopes($class): array
$propertyScopes[$name] = [$class, $name, null, $access, $property];
if ($flags & \ReflectionProperty::IS_PRIVATE_SET) {
if ($flags & (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY)) {
$propertyScopes[$name][2] = $property->class;
}
@@ -310,7 +310,7 @@ public static function getPropertyScopes($class): array
$name = $property->name;
$access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0);
if ($h = $property->getHooks()) {
if (\PHP_VERSION_ID >= 80400 && $h = $property->getHooks()) {
$access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0);
}

View File

@@ -95,7 +95,7 @@ public function &__get($name): mixed
$notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF || ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET;
}
if ($notByRef || 2 !== ((Registry::$parentGet[$class] ??= Registry::getParentGet($class)) ?: 2)) {
if ($notByRef || 2 !== ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['get'] ?: 2)) {
$value = $instance->$name;
return $value;

View File

@@ -41,9 +41,11 @@ class LazyObjectRegistry
public static array $classAccessors = [];
/**
* @var array<class-string, int>
* @var array<class-string, array{set: bool, isset: bool, unset: bool, clone: bool, serialize: bool, unserialize: bool, sleep: bool, wakeup: bool, destruct: bool, get: int}>
*/
public static array $parentGet = [];
public static array $parentMethods = [];
public static ?\Closure $noInitializerState = null;
public static function getClassResetters($class)
{
@@ -84,16 +86,80 @@ public static function getClassResetters($class)
return $resetters;
}
public static function getParentGet($class): int
public static function getClassAccessors($class)
{
return \Closure::bind(static fn () => [
'get' => static function &($instance, $name, $notByRef) {
if (!$notByRef) {
return $instance->$name;
}
$value = $instance->$name;
return $value;
},
'set' => static function ($instance, $name, $value) {
$instance->$name = $value;
},
'isset' => static fn ($instance, $name) => isset($instance->$name),
'unset' => static function ($instance, $name) {
unset($instance->$name);
},
], null, \Closure::class === $class ? null : $class)();
}
public static function getParentMethods($class)
{
$parent = get_parent_class($class);
$methods = [];
if (!$parent || !method_exists($parent, '__get')) {
return 0;
foreach (['set', 'isset', 'unset', 'clone', 'serialize', 'unserialize', 'sleep', 'wakeup', 'destruct', 'get'] as $method) {
if (!$parent || !method_exists($parent, '__'.$method)) {
$methods[$method] = false;
} else {
$m = new \ReflectionMethod($parent, '__'.$method);
$methods[$method] = !$m->isAbstract() && !$m->isPrivate();
}
}
$m = new \ReflectionMethod($parent, '__get');
$methods['get'] = $methods['get'] ? ($m->returnsReference() ? 2 : 1) : 0;
return !$m->isAbstract() && !$m->isPrivate() ? ($m->returnsReference() ? 2 : 1) : 0;
return $methods;
}
public static function getScopeForRead($propertyScopes, $class, $property)
{
if (!isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) {
return null;
}
$frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) {
$scope = $frame['object']->class;
}
if ('*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) {
return null;
}
return $scope;
}
public static function getScopeForWrite($propertyScopes, $class, $property, $flags)
{
if (!($flags & (\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_READONLY | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET | \ReflectionProperty::IS_PROTECTED_SET : 0)))) {
return null;
}
$frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) {
$scope = $frame['object']->class;
}
if ($flags & (\ReflectionProperty::IS_PRIVATE | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY))) {
return $scope;
}
if ($flags & (\ReflectionProperty::IS_PROTECTED | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PROTECTED_SET : 0)) && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) {
return null;
}
return $scope;
}
}

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\VarExporter\Internal;
use Symfony\Component\VarExporter\Hydrator as PublicHydrator;
/**
* Keeps the state of lazy objects.
*
@@ -20,10 +22,80 @@
*/
class LazyObjectState
{
public ?\Closure $initializer = null;
public const STATUS_UNINITIALIZED_FULL = 1;
public const STATUS_UNINITIALIZED_PARTIAL = 2;
public const STATUS_INITIALIZED_FULL = 3;
public const STATUS_INITIALIZED_PARTIAL = 4;
/**
* @var self::STATUS_*
*/
public int $status = self::STATUS_UNINITIALIZED_FULL;
public object $realInstance;
public object $cloneInstance;
/**
* @param array<string, true> $skippedProperties
*/
public function __construct(
public ?\Closure $initializer = null,
public array $skippedProperties = [],
) {
}
public function initialize($instance, $propertyName, $writeScope)
{
if (self::STATUS_UNINITIALIZED_FULL !== $this->status) {
return $this->status;
}
$this->status = self::STATUS_INITIALIZED_PARTIAL;
try {
if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) {
PublicHydrator::hydrate($instance, $defaultProperties);
}
($this->initializer)($instance);
} catch (\Throwable $e) {
$this->status = self::STATUS_UNINITIALIZED_FULL;
$this->reset($instance);
throw $e;
}
return $this->status = self::STATUS_INITIALIZED_FULL;
}
public function reset($instance): void
{
$class = $instance::class;
$propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
$skippedProperties = $this->skippedProperties;
$properties = (array) $instance;
foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
if ($k === $key && ($access & Hydrator::PROPERTY_HAS_HOOKS || ($access >> 2) & \ReflectionProperty::IS_READONLY || !\array_key_exists($k, $properties))) {
$skippedProperties[$k] = true;
}
}
foreach (LazyObjectRegistry::$classResetters[$class] as $reset) {
$reset($instance, $skippedProperties);
}
foreach ((array) $instance as $name => $value) {
if ("\0" !== ($name[0] ?? '') && !\array_key_exists($name, $skippedProperties)) {
unset($instance->$name);
}
}
$this->status = self::STATUS_UNINITIALIZED_FULL;
}
public function __clone()
{
if (isset($this->cloneInstance)) {

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter\Internal;
use Symfony\Component\Serializer\Attribute\Ignore;
if (\PHP_VERSION_ID >= 80300) {
/**
* @internal
*/
trait LazyObjectTrait
{
#[Ignore]
private readonly LazyObjectState $lazyObjectState;
}
} else {
/**
* @internal
*/
trait LazyObjectTrait
{
#[Ignore]
private LazyObjectState $lazyObjectState;
}
}

View File

@@ -0,0 +1,376 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
use Symfony\Component\Serializer\Attribute\Ignore;
use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
use Symfony\Component\VarExporter\Internal\LazyObjectState;
use Symfony\Component\VarExporter\Internal\LazyObjectTrait;
if (\PHP_VERSION_ID >= 80400) {
trigger_deprecation('symfony/var-exporter', '7.3', 'The "%s" trait is deprecated, use native lazy objects instead.', LazyGhostTrait::class);
}
/**
* @deprecated since Symfony 7.3, use native lazy objects instead
*/
trait LazyGhostTrait
{
use LazyObjectTrait;
/**
* Creates a lazy-loading ghost instance.
*
* Skipped properties should be indexed by their array-cast identifier, see
* https://php.net/manual/language.types.array#language.types.array.casting
*
* @param \Closure(static):void $initializer The closure should initialize the object it receives as argument
* @param array<string, true>|null $skippedProperties An array indexed by the properties to skip, a.k.a. the ones
* that the initializer doesn't initialize, if any
* @param static|null $instance
*/
public static function createLazyGhost(\Closure $initializer, ?array $skippedProperties = null, ?object $instance = null): static
{
if (self::class !== $class = $instance ? $instance::class : static::class) {
$skippedProperties["\0".self::class."\0lazyObjectState"] = true;
}
if (!isset(Registry::$defaultProperties[$class])) {
Registry::$classReflectors[$class] ??= new \ReflectionClass($class);
$instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor();
Registry::$defaultProperties[$class] ??= (array) $instance;
Registry::$classResetters[$class] ??= Registry::getClassResetters($class);
if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
}
} else {
$instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor();
}
if (isset($instance->lazyObjectState)) {
$instance->lazyObjectState->initializer = $initializer;
$instance->lazyObjectState->skippedProperties = $skippedProperties ??= [];
if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $instance->lazyObjectState->status) {
$instance->lazyObjectState->reset($instance);
}
return $instance;
}
$instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []);
foreach (Registry::$classResetters[$class] as $reset) {
$reset($instance, $skippedProperties);
}
return $instance;
}
/**
* Returns whether the object is initialized.
*
* @param bool $partial Whether partially initialized objects should be considered as initialized
*/
#[Ignore]
public function isLazyObjectInitialized(bool $partial = false): bool
{
if (!$state = $this->lazyObjectState ?? null) {
return true;
}
return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status;
}
/**
* Forces initialization of a lazy object and returns it.
*/
public function initializeLazyObject(): static
{
if (!$state = $this->lazyObjectState ?? null) {
return $this;
}
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
$state->initialize($this, '', null);
}
return $this;
}
/**
* @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
*/
public function resetLazyObject(): bool
{
if (!$state = $this->lazyObjectState ?? null) {
return false;
}
if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $state->status) {
$state->reset($this);
}
return true;
}
public function &__get($name): mixed
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$notByRef = 0;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
$state = $this->lazyObjectState ?? null;
if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) {
$notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF;
if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) {
// Work around php/php-src#12695
$property = null === $scope ? $name : "\0$scope\0$name";
$property = $propertyScopes[$property][4]
?? Hydrator::$propertyScopes[$this::class][$property][4] = new \ReflectionProperty($scope ?? $class, $name);
} else {
$property = null;
}
if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) {
$scope ??= $writeScope;
}
if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope)) {
goto get_in_scope;
}
}
}
if ($parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']) {
if (2 === $parent) {
return parent::__get($name);
}
$value = parent::__get($name);
return $value;
}
if (null === $class) {
$frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $this::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE);
}
get_in_scope:
try {
if (null === $scope) {
if (!$notByRef) {
return $this->$name;
}
$value = $this->$name;
return $value;
}
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
return $accessor['get']($this, $name, $notByRef);
} catch (\Error $e) {
if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
throw $e;
}
try {
if (null === $scope) {
$this->$name = [];
return $this->$name;
}
$accessor['set']($this, $name, []);
return $accessor['get']($this, $name, $notByRef);
} catch (\Error) {
if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) {
throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious());
}
throw $e;
}
}
}
public function __set($name, $value): void
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
$state = $this->lazyObjectState ?? null;
if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"]))
&& LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status
) {
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
$state->initialize($this, $name, $writeScope ?? $scope);
}
goto set_in_scope;
}
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) {
parent::__set($name, $value);
return;
}
set_in_scope:
if (null === $scope) {
$this->$name = $value;
} else {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['set']($this, $name, $value);
}
}
public function __isset($name): bool
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
if ([$class, , $writeScope] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
$state = $this->lazyObjectState ?? null;
if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))
&& LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status
&& LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope)
) {
goto isset_in_scope;
}
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) {
return parent::__isset($name);
}
isset_in_scope:
if (null === $scope) {
return isset($this->$name);
}
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
return $accessor['isset']($this, $name);
}
public function __unset($name): void
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
$state = $this->lazyObjectState ?? null;
if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"]))
&& LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status
) {
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
$state->initialize($this, $name, $writeScope ?? $scope);
}
goto unset_in_scope;
}
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) {
parent::__unset($name);
return;
}
unset_in_scope:
if (null === $scope) {
unset($this->$name);
} else {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['unset']($this, $name);
}
}
public function __clone(): void
{
if ($state = $this->lazyObjectState ?? null) {
$this->lazyObjectState = clone $state;
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
parent::__clone();
}
}
public function __serialize(): array
{
$class = self::class;
if ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) {
$properties = parent::__serialize();
} else {
$this->initializeLazyObject();
$properties = (array) $this;
}
unset($properties["\0$class\0lazyObjectState"]);
if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
return $properties;
}
$scope = get_parent_class($class);
$data = [];
foreach (parent::__sleep() as $name) {
$value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null;
if (null === $k) {
trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE);
} else {
$data[$k] = $value;
}
}
return $data;
}
public function __destruct()
{
$state = $this->lazyObjectState ?? null;
if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state?->status) {
return;
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
parent::__destruct();
}
}
#[Ignore]
private function setLazyObjectAsInitialized(bool $initialized): void
{
if ($state = $this->lazyObjectState ?? null) {
$state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL;
}
}
}

View File

@@ -0,0 +1,376 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarExporter;
use Symfony\Component\Serializer\Attribute\Ignore;
use Symfony\Component\VarExporter\Hydrator as PublicHydrator;
use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
use Symfony\Component\VarExporter\Internal\LazyObjectState;
use Symfony\Component\VarExporter\Internal\LazyObjectTrait;
if (\PHP_VERSION_ID >= 80400) {
trigger_deprecation('symfony/var-exporter', '7.3', 'The "%s" trait is deprecated, use native lazy objects instead.', LazyProxyTrait::class);
}
/**
* @deprecated since Symfony 7.3, use native lazy objects instead
*/
trait LazyProxyTrait
{
use LazyObjectTrait;
/**
* Creates a lazy-loading virtual proxy.
*
* @param \Closure():object $initializer Returns the proxied object
* @param static|null $instance
*/
public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static
{
if (self::class !== $class = $instance ? $instance::class : static::class) {
$skippedProperties = ["\0".self::class."\0lazyObjectState" => true];
}
if (!isset(Registry::$defaultProperties[$class])) {
Registry::$classReflectors[$class] ??= new \ReflectionClass($class);
$instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor();
Registry::$defaultProperties[$class] ??= (array) $instance;
if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
}
Registry::$classResetters[$class] ??= Registry::getClassResetters($class);
} else {
$instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor();
}
if (isset($instance->lazyObjectState)) {
$instance->lazyObjectState->initializer = $initializer;
unset($instance->lazyObjectState->realInstance);
return $instance;
}
$instance->lazyObjectState = new LazyObjectState($initializer);
foreach (Registry::$classResetters[$class] as $reset) {
$reset($instance, $skippedProperties ??= []);
}
return $instance;
}
/**
* Returns whether the object is initialized.
*
* @param bool $partial Whether partially initialized objects should be considered as initialized
*/
#[Ignore]
public function isLazyObjectInitialized(bool $partial = false): bool
{
return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer;
}
/**
* Forces initialization of a lazy object and returns it.
*/
public function initializeLazyObject(): parent
{
if ($state = $this->lazyObjectState ?? null) {
return $state->realInstance ??= ($state->initializer)();
}
return $this;
}
/**
* @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
*/
public function resetLazyObject(): bool
{
if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) {
return false;
}
unset($this->lazyObjectState->realInstance);
return true;
}
public function &__get($name): mixed
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$instance = $this;
$notByRef = 0;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF;
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
}
if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) {
$scope ??= $writeScope;
}
$parent = 2;
goto get_in_scope;
}
}
$parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get'];
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
} else {
if (2 === $parent) {
return parent::__get($name);
}
$value = parent::__get($name);
return $value;
}
if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) {
$frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE);
}
get_in_scope:
$notByRef = $notByRef || 1 === $parent;
try {
if (null === $scope) {
if (!$notByRef) {
return $instance->$name;
}
$value = $instance->$name;
return $value;
}
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
return $accessor['get']($instance, $name, $notByRef);
} catch (\Error $e) {
if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
throw $e;
}
try {
if (null === $scope) {
$instance->$name = [];
return $instance->$name;
}
$accessor['set']($instance, $name, []);
return $accessor['get']($instance, $name, $notByRef);
} catch (\Error) {
throw $e;
}
}
}
public function __set($name, $value): void
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$instance = $this;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
}
goto set_in_scope;
}
}
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
} elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) {
parent::__set($name, $value);
return;
}
set_in_scope:
if (null === $scope) {
$instance->$name = $value;
} else {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['set']($instance, $name, $value);
}
}
public function __isset($name): bool
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$instance = $this;
if ([$class] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForRead($propertyScopes, $class, $name);
if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
}
goto isset_in_scope;
}
}
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
} elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) {
return parent::__isset($name);
}
isset_in_scope:
if (null === $scope) {
return isset($instance->$name);
}
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
return $accessor['isset']($instance, $name);
}
public function __unset($name): void
{
$propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
$scope = null;
$instance = $this;
if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
$scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
}
goto unset_in_scope;
}
}
if ($state = $this->lazyObjectState ?? null) {
$instance = $state->realInstance ??= ($state->initializer)();
} elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) {
parent::__unset($name);
return;
}
unset_in_scope:
if (null === $scope) {
unset($instance->$name);
} else {
$accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
$accessor['unset']($instance, $name);
}
}
public function __clone(): void
{
if (!isset($this->lazyObjectState)) {
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
parent::__clone();
}
return;
}
$this->lazyObjectState = clone $this->lazyObjectState;
}
public function __serialize(): array
{
$class = self::class;
$state = $this->lazyObjectState ?? null;
if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) {
$properties = parent::__serialize();
} else {
$properties = (array) $this;
if ($state) {
unset($properties["\0$class\0lazyObjectState"]);
$properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)();
}
}
if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
return $properties;
}
$scope = get_parent_class($class);
$data = [];
foreach (parent::__sleep() as $name) {
$value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null;
if (null === $k) {
trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE);
} else {
$data[$k] = $value;
}
}
return $data;
}
public function __unserialize(array $data): void
{
$class = self::class;
if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) {
unset($data["\0$class\0lazyObjectReal"]);
foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
$reset($this, $data);
}
if ($data) {
PublicHydrator::hydrate($this, $data);
}
$this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.'));
$this->lazyObjectState->realInstance = $instance;
} elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) {
parent::__unserialize($data);
} else {
PublicHydrator::hydrate($this, $data);
if (Registry::$parentMethods[$class]['wakeup']) {
parent::__wakeup();
}
}
}
public function __destruct()
{
if (isset($this->lazyObjectState)) {
return;
}
if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
parent::__destruct();
}
}
}

View File

@@ -21,6 +21,108 @@
*/
final class ProxyHelper
{
/**
* Helps generate lazy-loading ghost objects.
*
* @deprecated since Symfony 7.3, use native lazy objects instead
*
* @throws LogicException When the class is incompatible with ghost objects
*/
public static function generateLazyGhost(\ReflectionClass $class): string
{
if (\PHP_VERSION_ID >= 80400) {
trigger_deprecation('symfony/var-exporter', '7.3', 'Using ProxyHelper::generateLazyGhost() is deprecated, use native lazy objects instead.');
}
if (\PHP_VERSION_ID < 80300 && $class->isReadOnly()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost with PHP < 8.3: class "%s" is readonly.', $class->name));
}
if ($class->isFinal()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name));
}
if ($class->isInterface() || $class->isAbstract() || $class->isTrait()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name));
}
if (\stdClass::class !== $class->name && $class->isInternal()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name));
}
if ($class->hasMethod('__get') && 'mixed' !== (self::exportType($class->getMethod('__get')) ?? 'mixed')) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name));
}
static $traitMethods;
$traitMethods ??= (new \ReflectionClass(LazyGhostTrait::class))->getMethods();
foreach ($traitMethods as $method) {
if ($class->hasMethod($method->name) && $class->getMethod($method->name)->isFinal()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: method "%s::%s()" is final.', $class->name, $method->name));
}
}
$parent = $class;
while ($parent = $parent->getParentClass()) {
if (\stdClass::class !== $parent->name && $parent->isInternal()) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name));
}
}
$hooks = '';
$propertyScopes = Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name);
foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
$flags = $access >> 2;
if ($k !== $key || !($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) {
continue;
}
if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) {
throw new LogicException(\sprintf('Cannot generate lazy ghost: property "%s::$%s" is final or private(set).', $class->name, $name));
}
$p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name);
$type = self::exportType($p);
$hooks .= "\n "
.($p->isProtected() ? 'protected' : 'public')
.($p->isProtectedSet() ? ' protected(set)' : '')
." {$type} \${$name}"
.($p->hasDefaultValue() ? ' = '.VarExporter::export($p->getDefaultValue()) : '')
." {\n";
foreach ($p->getHooks() as $hook => $method) {
if ('get' === $hook) {
$ref = ($method->returnsReference() ? '&' : '');
$hooks .= " {$ref}get { \$this->initializeLazyObject(); return parent::\${$name}::get(); }\n";
} elseif ('set' === $hook) {
$parameters = self::exportParameters($method, true);
$arg = '$'.$method->getParameters()[0]->name;
$hooks .= " set({$parameters}) { \$this->initializeLazyObject(); parent::\${$name}::set({$arg}); }\n";
} else {
throw new LogicException(\sprintf('Cannot generate lazy ghost: hook "%s::%s()" is not supported.', $class->name, $method->name));
}
}
$hooks .= " }\n";
}
$propertyScopes = self::exportPropertyScopes($class->name, $propertyScopes);
return <<<EOPHP
extends \\{$class->name} implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyGhostTrait;
private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
{$hooks}}
// Help opcache.preload discover always-needed symbols
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
EOPHP;
}
/**
* Helps generate lazy-loading decorators.
*
@@ -36,6 +138,9 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
if ($class?->isFinal()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name));
}
if (\PHP_VERSION_ID < 80400) {
return self::generateLegacyLazyProxy($class, $interfaces);
}
if ($class && !$class->isAbstract()) {
$parent = $class;
@@ -44,7 +149,8 @@ public static function generateLazyProxy(?\ReflectionClass $class, array $interf
} while (!$extendsInternalClass && $parent = $parent->getParentClass());
if (!$extendsInternalClass) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: leverage native lazy objects instead for class "%s".', $class->name));
trigger_deprecation('symfony/var-exporter', '7.3', 'Generating lazy proxy for class "%s" is deprecated; leverage native lazy objects instead.', $class->name);
// throw new LogicException(\sprintf('Cannot generate lazy proxy: leverage native lazy objects instead for class "%s".', $class->name));
}
}
@@ -270,6 +376,156 @@ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
EOPHP;
}
private static function generateLegacyLazyProxy(?\ReflectionClass $class, array $interfaces): string
{
if (\PHP_VERSION_ID < 80300 && $class?->isReadOnly()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name));
}
$propertyScopes = $class ? Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name) : [];
$methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []];
foreach ($interfaces as $interface) {
if (!$interface->isInterface()) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name));
}
$methodReflectors[] = $interface->getMethods();
}
$extendsInternalClass = false;
if ($parent = $class) {
do {
$extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal();
} while (!$extendsInternalClass && $parent = $parent->getParentClass());
}
$methodsHaveToBeProxied = $extendsInternalClass;
$methods = [];
$methodReflectors = array_merge(...$methodReflectors);
foreach ($methodReflectors as $method) {
if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) {
continue;
}
$methodsHaveToBeProxied = true;
$trait = new \ReflectionMethod(LazyProxyTrait::class, '__get');
$body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine());
$body[0] = str_replace('): mixed', '): '.$type, $body[0]);
$methods['__get'] = strtr(implode('', $body).' }', [
'Hydrator' => '\\'.Hydrator::class,
'Registry' => '\\'.LazyObjectRegistry::class,
]);
break;
}
foreach ($methodReflectors as $method) {
if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) {
continue;
}
if ($method->isFinal()) {
if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) {
throw new LogicException(\sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name));
}
continue;
}
if (method_exists(LazyProxyTrait::class, $method->name) || ($method->isProtected() && !$method->isAbstract())) {
continue;
}
$signature = self::exportSignature($method, true, $args);
$parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})";
if ($method->isStatic()) {
$body = " $parentCall;";
} elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) {
$body = <<<EOPHP
if (isset(\$this->lazyObjectState)) {
(\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args});
} else {
{$parentCall};
}
EOPHP;
} else {
if (!$methodsHaveToBeProxied && !$method->isAbstract()) {
// Skip proxying methods that might return $this
foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) {
if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) {
continue 2;
}
foreach ([$class, ...$interfaces] as $r) {
if ($r && is_a($r->name, $type, true)) {
continue 3;
}
}
}
}
$body = <<<EOPHP
if (isset(\$this->lazyObjectState)) {
return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args});
}
return {$parentCall};
EOPHP;
}
$methods[$lcName] = " {$signature}\n {\n{$body}\n }";
}
$types = $interfaces = array_unique(array_column($interfaces, 'name'));
$interfaces[] = LazyObjectInterface::class;
$interfaces = implode(', \\', $interfaces);
$parent = $class ? ' extends \\'.$class->name : '';
array_unshift($types, $class ? 'parent' : '');
$type = ltrim(implode('&\\', $types), '&');
if (!$class) {
$trait = new \ReflectionMethod(LazyProxyTrait::class, 'initializeLazyObject');
$body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine());
$body[0] = str_replace('): parent', '): '.$type, $body[0]);
$methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods;
}
$body = $methods ? "\n".implode("\n\n", $methods)."\n" : '';
$propertyScopes = $class ? self::exportPropertyScopes($class->name, $propertyScopes) : '[]';
if (
$class?->hasMethod('__unserialize')
&& !$class->getMethod('__unserialize')->getParameters()[0]->getType()
) {
// fix contravariance type problem when $class declares a `__unserialize()` method without typehint.
$lazyProxyTraitStatement = <<<EOPHP
use \Symfony\Component\VarExporter\LazyProxyTrait {
__unserialize as private __doUnserialize;
}
EOPHP;
$body .= <<<EOPHP
public function __unserialize(\$data): void
{
\$this->__doUnserialize(\$data);
}
EOPHP;
} else {
$lazyProxyTraitStatement = <<<EOPHP
use \Symfony\Component\VarExporter\LazyProxyTrait;
EOPHP;
}
return <<<EOPHP
{$parent} implements \\{$interfaces}
{
{$lazyProxyTraitStatement}
private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
{$body}}
// Help opcache.preload discover always-needed symbols
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
EOPHP;
}
public static function exportParameters(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string
{
$byRefIndex = 0;

View File

@@ -84,6 +84,10 @@
// be called only when and if a *method* is called.
```
In addition, this component provides traits and methods to aid in implementing
the ghost and proxy strategies in previous versions of PHP. Those are deprecated
when using PHP 8.4.
Resources
---------

View File

@@ -16,12 +16,13 @@
}
],
"require": {
"php": ">=8.4"
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3"
},
"require-dev": {
"symfony/property-access": "^7.4|^8.0",
"symfony/serializer": "^7.4|^8.0",
"symfony/var-dumper": "^7.4|^8.0"
"symfony/property-access": "^6.4|^7.0|^8.0",
"symfony/serializer": "^6.4|^7.0|^8.0",
"symfony/var-dumper": "^6.4|^7.0|^8.0"
},
"autoload": {
"psr-4": { "Symfony\\Component\\VarExporter\\": "" },

View File

@@ -392,11 +392,33 @@ private static function parseSequence(string $sequence, int $flags, int &$i = 0,
// the value can be an array if a reference has been resolved to an array var
if (\is_string($value) && !$isQuoted && str_contains($value, ': ')) {
// embedded mapping?
try {
$pos = 0;
$value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
} catch (\InvalidArgumentException) {
// no, it's not
$j = $i;
$mappingValue = $value;
$mappingException = null;
do {
try {
$pos = 0;
$value = self::parseMapping('{'.$mappingValue.'}', $flags, $pos, $references);
$i = $j;
$mappingException = null;
break;
} catch (ParseException $exception) {
$mappingException = $exception;
if ($j >= $len) {
break;
}
$mappingValue .= $sequence[$j++];
if ($j >= $len) {
break;
}
$mappingValue .= self::parseScalar($sequence, $flags, [',', ']'], $j, null === $tag, $references);
}
} while ($j < $len);
if ($mappingException) {
throw $mappingException;
}
}

0
vendor/symfony/yaml/Resources/bin/yaml-lint vendored Normal file → Executable file
View File