This commit is contained in:
2025-05-12 14:25:25 +02:00
parent ab2db755ef
commit 9e378ca2b7
2719 changed files with 46505 additions and 60181 deletions

View File

@@ -143,7 +143,7 @@ class Application implements ResetInterface
*
* @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
*/
public function run(InputInterface $input = null, OutputInterface $output = null): int
public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
{
if (\function_exists('putenv')) {
@putenv('LINES='.$this->terminal->getHeight());
@@ -169,9 +169,9 @@ class Application implements ResetInterface
}
}
$this->configureIO($input, $output);
try {
$this->configureIO($input, $output);
$exitCode = $this->doRun($input, $output);
} catch (\Throwable $e) {
if ($e instanceof \Exception && !$this->catchExceptions) {
@@ -795,7 +795,7 @@ class Application implements ResetInterface
*
* @return Command[]
*/
public function all(string $namespace = null)
public function all(?string $namespace = null)
{
$this->init();
@@ -875,7 +875,7 @@ class Application implements ResetInterface
}
if (str_contains($message, "@anonymous\0")) {
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)?[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
@@ -1177,7 +1177,7 @@ class Application implements ResetInterface
*
* This method is not part of public API and should not be used directly.
*/
public function extractNamespace(string $name, int $limit = null): string
public function extractNamespace(string $name, ?int $limit = null): string
{
$parts = explode(':', $name, -1);

View File

@@ -57,7 +57,7 @@ class GithubActionReporter
*
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
*/
public function error(string $message, string $file = null, int $line = null, int $col = null): void
public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
{
$this->log('error', $message, $file, $line, $col);
}
@@ -67,7 +67,7 @@ class GithubActionReporter
*
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
*/
public function warning(string $message, string $file = null, int $line = null, int $col = null): void
public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
{
$this->log('warning', $message, $file, $line, $col);
}
@@ -77,12 +77,12 @@ class GithubActionReporter
*
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message
*/
public function debug(string $message, string $file = null, int $line = null, int $col = null): void
public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
{
$this->log('debug', $message, $file, $line, $col);
}
private function log(string $type, string $message, string $file = null, int $line = null, int $col = null): void
private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
{
// Some values must be encoded.
$message = strtr($message, self::ESCAPED_DATA);

View File

@@ -111,7 +111,7 @@ class Command
*
* @throws LogicException When the command name is empty
*/
public function __construct(string $name = null)
public function __construct(?string $name = null)
{
$this->definition = new InputDefinition();
@@ -152,7 +152,7 @@ class Command
/**
* @return void
*/
public function setApplication(Application $application = null)
public function setApplication(?Application $application = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
@@ -460,7 +460,7 @@ class Command
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static
public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static
{
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
@@ -484,7 +484,7 @@ class Command
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {

View File

@@ -45,7 +45,7 @@ final class LazyCommand extends Command
$this->getCommand()->ignoreValidationErrors();
}
public function setApplication(Application $application = null): void
public function setApplication(?Application $application = null): void
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
@@ -116,7 +116,7 @@ final class LazyCommand extends Command
/**
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
*/
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
$this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues);
@@ -127,7 +127,7 @@ final class LazyCommand extends Command
/**
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
*/
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
$this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);

View File

@@ -29,7 +29,7 @@ trait LockableTrait
/**
* Locks a command.
*/
private function lock(string $name = null, bool $blocking = false): bool
private function lock(?string $name = null, bool $blocking = false): bool
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".');

View File

@@ -134,7 +134,7 @@ final class TraceableCommand extends Command implements SignalableCommandInterfa
parent::ignoreValidationErrors();
}
public function setApplication(Application $application = null): void
public function setApplication(?Application $application = null): void
{
$this->command->setApplication($application);
}
@@ -209,14 +209,14 @@ final class TraceableCommand extends Command implements SignalableCommandInterfa
return $this->command->getNativeDefinition();
}
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
{
$this->command->addArgument($name, $mode, $description, $default, $suggestedValues);
return $this;
}
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
{
$this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);

View File

@@ -53,7 +53,7 @@ final class CompletionInput extends ArgvInput
* Create an input based on an COMP_WORDS token list.
*
* @param string[] $tokens the set of split tokens (e.g. COMP_WORDS or argv)
* @param $currentIndex the index of the cursor (e.g. COMP_CWORD)
* @param int $currentIndex the index of the cursor (e.g. COMP_CWORD)
*/
public static function fromTokens(array $tokens, int $currentIndex): self
{

View File

@@ -27,7 +27,7 @@ use Symfony\Component\VarDumper\Cloner\Data;
*/
final class CommandDataCollector extends DataCollector
{
public function collect(Request $request, Response $response, \Throwable $exception = null): void
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
{
if (!$request instanceof CliRequest) {
return;

View File

@@ -39,7 +39,7 @@ class ApplicationDescription
*/
private array $aliases = [];
public function __construct(Application $application, string $namespace = null, bool $showHidden = false)
public function __construct(Application $application, ?string $namespace = null, bool $showHidden = false)
{
$this->application = $application;
$this->namespace = $namespace;

View File

@@ -79,7 +79,7 @@ class XmlDescriptor extends Descriptor
return $dom;
}
public function getApplicationDocument(Application $application, string $namespace = null, bool $short = false): \DOMDocument
public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = false): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));

View File

@@ -25,7 +25,7 @@ final class ConsoleErrorEvent extends ConsoleEvent
private \Throwable $error;
private int $exitCode;
public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null)
public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, ?Command $command = null)
{
parent::__construct($command, $input, $output);

View File

@@ -26,7 +26,7 @@ class ErrorListener implements EventSubscriberInterface
{
private ?LoggerInterface $logger;
public function __construct(LoggerInterface $logger = null)
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger;
}

View File

@@ -26,7 +26,7 @@ class CommandNotFoundException extends \InvalidArgumentException implements Exce
* @param int $code Exception code
* @param \Throwable|null $previous Previous exception used for the exception chaining
*/
public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null)
public function __construct(string $message, array $alternatives = [], int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);

View File

@@ -21,7 +21,7 @@ final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
return $text;
}
public function setBackground(string $color = null): void
public function setBackground(?string $color = null): void
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
@@ -29,7 +29,7 @@ final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
// do nothing
}
public function setForeground(string $color = null): void
public function setForeground(?string $color = null): void
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);

View File

@@ -33,7 +33,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
* @param string|null $foreground The style foreground color name
* @param string|null $background The style background color name
*/
public function __construct(string $foreground = null, string $background = null, array $options = [])
public function __construct(?string $foreground = null, ?string $background = null, array $options = [])
{
$this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
}
@@ -41,7 +41,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
/**
* @return void
*/
public function setForeground(string $color = null)
public function setForeground(?string $color = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
@@ -52,7 +52,7 @@ class OutputFormatterStyle implements OutputFormatterStyleInterface
/**
* @return void
*/
public function setBackground(string $color = null)
public function setBackground(?string $color = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);

View File

@@ -26,7 +26,7 @@ class OutputFormatterStyleStack implements ResetInterface
private OutputFormatterStyleInterface $emptyStyle;
public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
public function __construct(?OutputFormatterStyleInterface $emptyStyle = null)
{
$this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle();
$this->reset();
@@ -57,7 +57,7 @@ class OutputFormatterStyleStack implements ResetInterface
*
* @throws InvalidArgumentException When style tags incorrectly nested
*/
public function pop(OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface
public function pop(?OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface
{
if (!$this->styles) {
return $this->emptyStyle;

View File

@@ -26,7 +26,7 @@ final class Dumper
private ?ClonerInterface $cloner;
private \Closure $handler;
public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
public function __construct(OutputInterface $output, ?CliDumper $dumper = null, ?ClonerInterface $cloner = null)
{
$this->output = $output;
$this->dumper = $dumper;

View File

@@ -26,7 +26,7 @@ abstract class Helper implements HelperInterface
/**
* @return void
*/
public function setHelperSet(HelperSet $helperSet = null)
public function setHelperSet(?HelperSet $helperSet = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
@@ -80,7 +80,7 @@ abstract class Helper implements HelperInterface
/**
* Returns the subset of a string, using mb_substr if it is available.
*/
public static function substr(?string $string, int $from, int $length = null): string
public static function substr(?string $string, int $from, ?int $length = null): string
{
$string ??= '';

View File

@@ -38,7 +38,7 @@ class HelperSet implements \IteratorAggregate
/**
* @return void
*/
public function set(HelperInterface $helper, string $alias = null)
public function set(HelperInterface $helper, ?string $alias = null)
{
$this->helpers[$helper->getName()] = $helper;
if (null !== $alias) {

View File

@@ -32,7 +32,7 @@ class ProcessHelper extends Helper
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*/
public function run(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
{
if (!class_exists(Process::class)) {
throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
@@ -94,7 +94,7 @@ class ProcessHelper extends Helper
*
* @see run()
*/
public function mustRun(OutputInterface $output, array|Process $cmd, string $error = null, callable $callback = null): Process
public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process
{
$process = $this->run($output, $cmd, $error, $callback);
@@ -108,7 +108,7 @@ class ProcessHelper extends Helper
/**
* Wraps a Process callback to add debugging output.
*/
public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable
public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();

View File

@@ -183,9 +183,9 @@ final class ProgressBar
$this->messages[$name] = $message;
}
public function getMessage(string $name = 'message'): string
public function getMessage(string $name = 'message'): ?string
{
return $this->messages[$name];
return $this->messages[$name] ?? null;
}
public function getStartTime(): int
@@ -229,7 +229,7 @@ final class ProgressBar
public function getRemaining(): float
{
if (!$this->step) {
if (0 === $this->step || $this->step === $this->startingStep) {
return 0;
}
@@ -313,7 +313,7 @@ final class ProgressBar
*
* @return iterable<TKey, TValue>
*/
public function iterate(iterable $iterable, int $max = null): iterable
public function iterate(iterable $iterable, ?int $max = null): iterable
{
$this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));
@@ -332,7 +332,7 @@ final class ProgressBar
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
* @param int $startAt The starting point of the bar (useful e.g. when resuming a previously started bar)
*/
public function start(int $max = null, int $startAt = 0): void
public function start(?int $max = null, int $startAt = 0): void
{
$this->startTime = time();
$this->step = $startAt;
@@ -486,12 +486,21 @@ final class ProgressBar
if ($this->output instanceof ConsoleSectionOutput) {
$messageLines = explode("\n", $this->previousMessage);
$lineCount = \count($messageLines);
$lastLineWithoutDecoration = Helper::removeDecoration($this->output->getFormatter(), end($messageLines) ?? '');
// When the last previous line is empty (without formatting) it is already cleared by the section output, so we don't need to clear it again
if ('' === $lastLineWithoutDecoration) {
--$lineCount;
}
foreach ($messageLines as $messageLine) {
$messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine));
if ($messageLineLength > $this->terminal->getWidth()) {
$lineCount += floor($messageLineLength / $this->terminal->getWidth());
}
}
$this->output->clear($lineCount);
} else {
$lineCount = substr_count($this->previousMessage, "\n");

View File

@@ -50,7 +50,7 @@ class ProgressIndicator
* @param int $indicatorChangeInterval Change interval in milliseconds
* @param array|null $indicatorValues Animated indicator characters
*/
public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null)
{
$this->output = $output;

View File

@@ -501,19 +501,7 @@ class QuestionHelper extends Helper
return self::$stdinIsInteractive;
}
if (\function_exists('stream_isatty')) {
return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
}
if (\function_exists('posix_isatty')) {
return self::$stdinIsInteractive = @posix_isatty(fopen('php://stdin', 'r'));
}
if (!\function_exists('shell_exec')) {
return self::$stdinIsInteractive = true;
}
return self::$stdinIsInteractive = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
}
/**

View File

@@ -365,13 +365,15 @@ class Table
for ($i = 0; $i < $maxRows; ++$i) {
$cell = (string) ($row[$i] ?? '');
$parts = explode("\n", $cell);
$eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
$parts = explode($eol, $cell);
foreach ($parts as $idx => $part) {
if ($headers && !$containsColspan) {
if (0 === $idx) {
$rows[] = [sprintf(
'<comment>%s</>: %s',
str_pad($headers[$i] ?? '', $maxHeaderLength, ' ', \STR_PAD_LEFT),
'<comment>%s%s</>: %s',
str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
$headers[$i] ?? '',
$part
)];
} else {
@@ -466,7 +468,7 @@ class Table
*
* +-----+-----------+-------+
*/
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null): void
private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void
{
if (!$count = $this->numberOfColumns) {
return;
@@ -531,7 +533,7 @@ class Table
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null): void
private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void
{
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
$columns = $this->getRowColumns($row);
@@ -636,9 +638,10 @@ class Table
if (!str_contains($cell ?? '', "\n")) {
continue;
}
$escaped = implode("\n", array_map(OutputFormatter::escapeTrailingBackslash(...), explode("\n", $cell)));
$eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n";
$escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell)));
$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default></>\n", $cell));
$lines = explode($eol, str_replace($eol, '<fg=default;bg=default></>'.$eol, $cell));
foreach ($lines as $lineKey => $line) {
if ($colspan > 1) {
$line = new TableCell($line, ['colspan' => $colspan]);
@@ -700,8 +703,9 @@ class Table
$nbLines = $cell->getRowspan() - 1;
$lines = [$cell];
if (str_contains($cell, "\n")) {
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
$eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
$lines = explode($eol, str_replace($eol, '<fg=default;bg=default>'.$eol.'</>', $cell));
$nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines;
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
unset($lines[0]);

View File

@@ -88,7 +88,7 @@ class TableStyle
*
* @return $this
*/
public function setHorizontalBorderChars(string $outside, string $inside = null): static
public function setHorizontalBorderChars(string $outside, ?string $inside = null): static
{
$this->horizontalOutsideBorderChar = $outside;
$this->horizontalInsideBorderChar = $inside ?? $outside;
@@ -113,7 +113,7 @@ class TableStyle
*
* @return $this
*/
public function setVerticalBorderChars(string $outside, string $inside = null): static
public function setVerticalBorderChars(string $outside, ?string $inside = null): static
{
$this->verticalOutsideBorderChar = $outside;
$this->verticalInsideBorderChar = $inside ?? $outside;
@@ -167,7 +167,7 @@ class TableStyle
*
* @return $this
*/
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): static
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static
{
$this->crossingChar = $cross;
$this->crossingTopLeftChar = $topLeft;

View File

@@ -43,7 +43,7 @@ class ArgvInput extends Input
private array $tokens;
private array $parsed;
public function __construct(array $argv = null, InputDefinition $definition = null)
public function __construct(?array $argv = null, ?InputDefinition $definition = null)
{
$argv ??= $_SERVER['argv'] ?? [];

View File

@@ -27,7 +27,7 @@ class ArrayInput extends Input
{
private array $parameters;
public function __construct(array $parameters, InputDefinition $definition = null)
public function __construct(array $parameters, ?InputDefinition $definition = null)
{
$this->parameters = $parameters;

View File

@@ -34,7 +34,7 @@ abstract class Input implements InputInterface, StreamableInputInterface
protected $arguments = [];
protected $interactive = true;
public function __construct(InputDefinition $definition = null)
public function __construct(?InputDefinition $definition = null)
{
if (null === $definition) {
$this->definition = new InputDefinition();

View File

@@ -44,7 +44,7 @@ class InputArgument
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null, \Closure|array $suggestedValues = [])
public function __construct(string $name, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, \Closure|array $suggestedValues = [])
{
if (null === $mode) {
$mode = self::OPTIONAL;
@@ -95,7 +95,7 @@ class InputArgument
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault(string|bool|int|float|array $default = null)
public function setDefault(string|bool|int|float|array|null $default = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);

View File

@@ -65,7 +65,7 @@ class InputOption
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null, array|\Closure $suggestedValues = [])
public function __construct(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, array|\Closure $suggestedValues = [])
{
if (str_starts_with($name, '--')) {
$name = substr($name, 2);
@@ -75,7 +75,7 @@ class InputOption
throw new InvalidArgumentException('An option name cannot be empty.');
}
if (empty($shortcut)) {
if ('' === $shortcut || [] === $shortcut || false === $shortcut) {
$shortcut = null;
}
@@ -84,10 +84,10 @@ class InputOption
$shortcut = implode('|', $shortcut);
}
$shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
$shortcuts = array_filter($shortcuts);
$shortcuts = array_filter($shortcuts, 'strlen');
$shortcut = implode('|', $shortcuts);
if (empty($shortcut)) {
if ('' === $shortcut) {
throw new InvalidArgumentException('An option shortcut cannot be empty.');
}
}
@@ -181,7 +181,7 @@ class InputOption
/**
* @return void
*/
public function setDefault(string|bool|int|float|array $default = null)
public function setDefault(string|bool|int|float|array|null $default = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);

View File

@@ -37,7 +37,7 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
public function __construct(int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null)
{
parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);

View File

@@ -63,7 +63,7 @@ class ConsoleSectionOutput extends StreamOutput
*
* @return void
*/
public function clear(int $lines = null)
public function clear(?int $lines = null)
{
if (empty($this->content) || !$this->isDecorated()) {
return;

View File

@@ -37,7 +37,7 @@ abstract class Output implements OutputInterface
* @param bool $decorated Whether to decorate messages
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null)
{
$this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL;
$this->formatter = $formatter ?? new OutputFormatter();

View File

@@ -40,7 +40,7 @@ class StreamOutput extends Output
*
* @throws InvalidArgumentException When first argument is not a real stream
*/
public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null)
{
if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
@@ -93,21 +93,33 @@ class StreamOutput extends Output
protected function hasColorSupport(): bool
{
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
if ('' !== (($_SERVER['NO_COLOR'] ?? getenv('NO_COLOR'))[0] ?? '')) {
return false;
}
if (\DIRECTORY_SEPARATOR === '\\'
&& \function_exists('sapi_windows_vt100_support')
&& @sapi_windows_vt100_support($this->stream)
// Detect msysgit/mingw and assume this is a tty because detection
// does not work correctly, see https://github.com/composer/composer/issues/9690
if (!@stream_isatty($this->stream) && !\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) {
return false;
}
if ('\\' === \DIRECTORY_SEPARATOR && @sapi_windows_vt100_support($this->stream)) {
return true;
}
if ('Hyper' === getenv('TERM_PROGRAM')
|| false !== getenv('COLORTERM')
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
) {
return true;
}
return 'Hyper' === getenv('TERM_PROGRAM')
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| str_starts_with((string) getenv('TERM'), 'xterm')
|| stream_isatty($this->stream);
if ('dumb' === $term = (string) getenv('TERM')) {
return false;
}
// See https://github.com/chalk/supports-color/blob/d4f413efaf8da045c5ab440ed418ef02dbb28bf1/index.js#L157
return preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term);
}
}

View File

@@ -24,7 +24,7 @@ class TrimmedBufferOutput extends Output
private int $maxLength;
private string $buffer = '';
public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null)
{
if ($maxLength <= 0) {
throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength));

View File

@@ -26,11 +26,11 @@ class ChoiceQuestion extends Question
private string $errorMessage = 'Value "%s" is invalid';
/**
* @param string $question The question to ask to the user
* @param array $choices The list of available choices
* @param mixed $default The default answer to return
* @param string $question The question to ask to the user
* @param array $choices The list of available choices
* @param string|bool|int|float|null $default The default answer to return
*/
public function __construct(string $question, array $choices, mixed $default = null)
public function __construct(string $question, array $choices, string|bool|int|float|null $default = null)
{
if (!$choices) {
throw new \LogicException('Choice question must have at least 1 choice available.');

View File

@@ -36,7 +36,7 @@ class Question
* @param string $question The question to ask to the user
* @param string|bool|int|float|null $default The default answer to return if the user enters nothing
*/
public function __construct(string $question, string|bool|int|float $default = null)
public function __construct(string $question, string|bool|int|float|null $default = null)
{
$this->question = $question;
$this->default = $default;
@@ -175,7 +175,7 @@ class Question
*
* @return $this
*/
public function setAutocompleterCallback(callable $callback = null): static
public function setAutocompleterCallback(?callable $callback = null): static
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
@@ -194,7 +194,7 @@ class Question
*
* @return $this
*/
public function setValidator(callable $validator = null): static
public function setValidator(?callable $validator = null): static
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);

View File

@@ -17,7 +17,7 @@ _sf_{{ COMMAND_NAME }}() {
done
# Use newline as only separator to allow space in completion values
IFS=$'\n'
local IFS=$'\n'
local sf_cmd="${COMP_WORDS[0]}"
# for an alias, get the real script behind it

View File

@@ -27,7 +27,7 @@ class SignalMap
if (!isset(self::$map)) {
$r = new \ReflectionExtension('pcntl');
$c = $r->getConstants();
$map = array_filter($c, fn ($k) => str_starts_with($k, 'SIG') && !str_starts_with($k, 'SIG_'), \ARRAY_FILTER_USE_KEY);
$map = array_filter($c, fn ($k) => str_starts_with($k, 'SIG') && !str_starts_with($k, 'SIG_') && 'SIGBABY' !== $k, \ARRAY_FILTER_USE_KEY);
self::$map = array_flip($map);
}

View File

@@ -46,7 +46,7 @@ class SingleCommandApplication extends Command
return $this;
}
public function run(InputInterface $input = null, OutputInterface $output = null): int
public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
{
if ($this->running) {
return parent::run($input, $output);

View File

@@ -91,12 +91,12 @@ interface StyleInterface
/**
* Asks a question.
*/
public function ask(string $question, string $default = null, callable $validator = null): mixed;
public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed;
/**
* Asks a question with the user input hidden.
*/
public function askHidden(string $question, callable $validator = null): mixed;
public function askHidden(string $question, ?callable $validator = null): mixed;
/**
* Asks for confirmation.

View File

@@ -63,7 +63,7 @@ class SymfonyStyle extends OutputStyle
*
* @return void
*/
public function block(string|array $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
public function block(string|array $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
{
$messages = \is_array($messages) ? array_values($messages) : [$messages];
@@ -249,7 +249,7 @@ class SymfonyStyle extends OutputStyle
$this->horizontalTable($headers, [$row]);
}
public function ask(string $question, string $default = null, callable $validator = null): mixed
public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed
{
$question = new Question($question, $default);
$question->setValidator($validator);
@@ -257,7 +257,7 @@ class SymfonyStyle extends OutputStyle
return $this->askQuestion($question);
}
public function askHidden(string $question, callable $validator = null): mixed
public function askHidden(string $question, ?callable $validator = null): mixed
{
$question = new Question($question);
@@ -336,7 +336,7 @@ class SymfonyStyle extends OutputStyle
*
* @return iterable<TKey, TValue>
*/
public function progressIterate(iterable $iterable, int $max = null): iterable
public function progressIterate(iterable $iterable, ?int $max = null): iterable
{
yield from $this->createProgressBar()->iterate($iterable, $max);
@@ -456,7 +456,7 @@ class SymfonyStyle extends OutputStyle
$this->bufferedOutput->write($message, $newLine, $type);
}
private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
private function createBlock(iterable $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
{
$indentLength = 0;
$prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix));

View File

@@ -217,8 +217,7 @@ class Terminal
$cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0;
$process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
if (!\is_resource($process)) {
if (!$process = @proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true])) {
return null;
}

View File

@@ -1,8 +1,14 @@
CHANGELOG
=========
7.1
---
* Add support for `:is()`
* Add support for `:where()`
6.3
-----
---
* Add support for `:scope`

View File

@@ -25,17 +25,17 @@ class SyntaxErrorException extends ParseException
{
public static function unexpectedToken(string $expectedValue, Token $foundToken): self
{
return new self(sprintf('Expected %s, but %s found.', $expectedValue, $foundToken));
return new self(\sprintf('Expected %s, but %s found.', $expectedValue, $foundToken));
}
public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation): self
{
return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation));
return new self(\sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation));
}
public static function unclosedString(int $position): self
{
return new self(sprintf('Unclosed/invalid string at %s.', $position));
return new self(\sprintf('Unclosed/invalid string at %s.', $position));
}
public static function nestedNot(): self
@@ -45,7 +45,7 @@ class SyntaxErrorException extends ParseException
public static function notAtTheStartOfASelector(string $pseudoElement): self
{
return new self(sprintf('Got immediate child pseudo-element ":%s" not at the start of a selector', $pseudoElement));
return new self(\sprintf('Got immediate child pseudo-element ":%s" not at the start of a selector', $pseudoElement));
}
public static function stringAsFunctionArgument(): self

View File

@@ -23,19 +23,13 @@ namespace Symfony\Component\CssSelector\Node;
*/
class AttributeNode extends AbstractNode
{
private NodeInterface $selector;
private ?string $namespace;
private string $attribute;
private string $operator;
private ?string $value;
public function __construct(NodeInterface $selector, ?string $namespace, string $attribute, string $operator, ?string $value)
{
$this->selector = $selector;
$this->namespace = $namespace;
$this->attribute = $attribute;
$this->operator = $operator;
$this->value = $value;
public function __construct(
private NodeInterface $selector,
private ?string $namespace,
private string $attribute,
private string $operator,
private ?string $value,
) {
}
public function getSelector(): NodeInterface
@@ -73,7 +67,7 @@ class AttributeNode extends AbstractNode
$attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute;
return 'exists' === $this->operator
? sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute)
: sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value);
? \sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute)
: \sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value);
}
}

View File

@@ -23,13 +23,10 @@ namespace Symfony\Component\CssSelector\Node;
*/
class ClassNode extends AbstractNode
{
private NodeInterface $selector;
private string $name;
public function __construct(NodeInterface $selector, string $name)
{
$this->selector = $selector;
$this->name = $name;
public function __construct(
private NodeInterface $selector,
private string $name,
) {
}
public function getSelector(): NodeInterface
@@ -49,6 +46,6 @@ class ClassNode extends AbstractNode
public function __toString(): string
{
return sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name);
return \sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name);
}
}

View File

@@ -23,15 +23,11 @@ namespace Symfony\Component\CssSelector\Node;
*/
class CombinedSelectorNode extends AbstractNode
{
private NodeInterface $selector;
private string $combinator;
private NodeInterface $subSelector;
public function __construct(NodeInterface $selector, string $combinator, NodeInterface $subSelector)
{
$this->selector = $selector;
$this->combinator = $combinator;
$this->subSelector = $subSelector;
public function __construct(
private NodeInterface $selector,
private string $combinator,
private NodeInterface $subSelector,
) {
}
public function getSelector(): NodeInterface
@@ -58,6 +54,6 @@ class CombinedSelectorNode extends AbstractNode
{
$combinator = ' ' === $this->combinator ? '<followed>' : $this->combinator;
return sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector);
return \sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector);
}
}

View File

@@ -23,13 +23,10 @@ namespace Symfony\Component\CssSelector\Node;
*/
class ElementNode extends AbstractNode
{
private ?string $namespace;
private ?string $element;
public function __construct(string $namespace = null, string $element = null)
{
$this->namespace = $namespace;
$this->element = $element;
public function __construct(
private ?string $namespace = null,
private ?string $element = null,
) {
}
public function getNamespace(): ?string
@@ -51,6 +48,6 @@ class ElementNode extends AbstractNode
{
$element = $this->element ?: '*';
return sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element);
return \sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element);
}
}

View File

@@ -25,18 +25,17 @@ use Symfony\Component\CssSelector\Parser\Token;
*/
class FunctionNode extends AbstractNode
{
private NodeInterface $selector;
private string $name;
private array $arguments;
/**
* @param Token[] $arguments
*/
public function __construct(NodeInterface $selector, string $name, array $arguments = [])
{
$this->selector = $selector;
public function __construct(
private NodeInterface $selector,
string $name,
private array $arguments = [],
) {
$this->name = strtolower($name);
$this->arguments = $arguments;
}
public function getSelector(): NodeInterface
@@ -66,6 +65,6 @@ class FunctionNode extends AbstractNode
{
$arguments = implode(', ', array_map(fn (Token $token) => "'".$token->getValue()."'", $this->arguments));
return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : '');
return \sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : '');
}
}

View File

@@ -23,13 +23,10 @@ namespace Symfony\Component\CssSelector\Node;
*/
class HashNode extends AbstractNode
{
private NodeInterface $selector;
private string $id;
public function __construct(NodeInterface $selector, string $id)
{
$this->selector = $selector;
$this->id = $id;
public function __construct(
private NodeInterface $selector,
private string $id,
) {
}
public function getSelector(): NodeInterface
@@ -49,6 +46,6 @@ class HashNode extends AbstractNode
public function __toString(): string
{
return sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id);
return \sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id);
}
}

View File

@@ -0,0 +1,55 @@
<?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\CssSelector\Node;
/**
* Represents a "<selector>:is(<subSelectorList>)" node.
*
* This component is a port of the Python cssselect library,
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Hubert Lenoir <lenoir.hubert@gmail.com>
*
* @internal
*/
class MatchingNode extends AbstractNode
{
/**
* @param array<NodeInterface> $arguments
*/
public function __construct(
public readonly NodeInterface $selector,
public readonly array $arguments = [],
) {
}
public function getSpecificity(): Specificity
{
$argumentsSpecificity = array_reduce(
$this->arguments,
fn ($c, $n) => 1 === $n->getSpecificity()->compareTo($c) ? $n->getSpecificity() : $c,
new Specificity(0, 0, 0),
);
return $this->selector->getSpecificity()->plus($argumentsSpecificity);
}
public function __toString(): string
{
$selectorArguments = array_map(
fn ($n): string => ltrim((string) $n, '*'),
$this->arguments,
);
return \sprintf('%s[%s:is(%s)]', $this->getNodeName(), $this->selector, implode(', ', $selectorArguments));
}
}

View File

@@ -23,13 +23,10 @@ namespace Symfony\Component\CssSelector\Node;
*/
class NegationNode extends AbstractNode
{
private NodeInterface $selector;
private NodeInterface $subSelector;
public function __construct(NodeInterface $selector, NodeInterface $subSelector)
{
$this->selector = $selector;
$this->subSelector = $subSelector;
public function __construct(
private NodeInterface $selector,
private NodeInterface $subSelector,
) {
}
public function getSelector(): NodeInterface
@@ -49,6 +46,6 @@ class NegationNode extends AbstractNode
public function __toString(): string
{
return sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector);
return \sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector);
}
}

View File

@@ -23,12 +23,12 @@ namespace Symfony\Component\CssSelector\Node;
*/
class PseudoNode extends AbstractNode
{
private NodeInterface $selector;
private string $identifier;
public function __construct(NodeInterface $selector, string $identifier)
{
$this->selector = $selector;
public function __construct(
private NodeInterface $selector,
string $identifier,
) {
$this->identifier = strtolower($identifier);
}
@@ -49,6 +49,6 @@ class PseudoNode extends AbstractNode
public function __toString(): string
{
return sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier);
return \sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier);
}
}

View File

@@ -23,12 +23,12 @@ namespace Symfony\Component\CssSelector\Node;
*/
class SelectorNode extends AbstractNode
{
private NodeInterface $tree;
private ?string $pseudoElement;
public function __construct(NodeInterface $tree, string $pseudoElement = null)
{
$this->tree = $tree;
public function __construct(
private NodeInterface $tree,
?string $pseudoElement = null,
) {
$this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null;
}
@@ -49,6 +49,6 @@ class SelectorNode extends AbstractNode
public function __toString(): string
{
return sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : '');
return \sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : '');
}
}

View File

@@ -29,15 +29,11 @@ class Specificity
public const B_FACTOR = 10;
public const C_FACTOR = 1;
private int $a;
private int $b;
private int $c;
public function __construct(int $a, int $b, int $c)
{
$this->a = $a;
$this->b = $b;
$this->c = $c;
public function __construct(
private int $a,
private int $b,
private int $c,
) {
}
public function plus(self $specificity): self

View File

@@ -0,0 +1,49 @@
<?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\CssSelector\Node;
/**
* Represents a "<selector>:where(<subSelectorList>)" node.
*
* This component is a port of the Python cssselect library,
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Hubert Lenoir <lenoir.hubert@gmail.com>
*
* @internal
*/
class SpecificityAdjustmentNode extends AbstractNode
{
/**
* @param array<NodeInterface> $arguments
*/
public function __construct(
public readonly NodeInterface $selector,
public readonly array $arguments = [],
) {
}
public function getSpecificity(): Specificity
{
return $this->selector->getSpecificity();
}
public function __toString(): string
{
$selectorArguments = array_map(
fn ($n) => ltrim((string) $n, '*'),
$this->arguments,
);
return \sprintf('%s[%s:where(%s)]', $this->getNodeName(), $this->selector, implode(', ', $selectorArguments));
}
}

View File

@@ -29,13 +29,10 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class HashHandler implements HandlerInterface
{
private TokenizerPatterns $patterns;
private TokenizerEscaping $escaping;
public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping)
{
$this->patterns = $patterns;
$this->escaping = $escaping;
public function __construct(
private TokenizerPatterns $patterns,
private TokenizerEscaping $escaping,
) {
}
public function handle(Reader $reader, TokenStream $stream): bool

View File

@@ -29,13 +29,10 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class IdentifierHandler implements HandlerInterface
{
private TokenizerPatterns $patterns;
private TokenizerEscaping $escaping;
public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping)
{
$this->patterns = $patterns;
$this->escaping = $escaping;
public function __construct(
private TokenizerPatterns $patterns,
private TokenizerEscaping $escaping,
) {
}
public function handle(Reader $reader, TokenStream $stream): bool

View File

@@ -28,11 +28,9 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class NumberHandler implements HandlerInterface
{
private TokenizerPatterns $patterns;
public function __construct(TokenizerPatterns $patterns)
{
$this->patterns = $patterns;
public function __construct(
private TokenizerPatterns $patterns,
) {
}
public function handle(Reader $reader, TokenStream $stream): bool

View File

@@ -31,13 +31,10 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class StringHandler implements HandlerInterface
{
private TokenizerPatterns $patterns;
private TokenizerEscaping $escaping;
public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping)
{
$this->patterns = $patterns;
$this->escaping = $escaping;
public function __construct(
private TokenizerPatterns $patterns,
private TokenizerEscaping $escaping,
) {
}
public function handle(Reader $reader, TokenStream $stream): bool
@@ -52,7 +49,7 @@ class StringHandler implements HandlerInterface
$match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote));
if (!$match) {
throw new InternalErrorException(sprintf('Should have found at least an empty match at %d.', $reader->getPosition()));
throw new InternalErrorException(\sprintf('Should have found at least an empty match at %d.', $reader->getPosition()));
}
// check unclosed strings

View File

@@ -29,7 +29,7 @@ class Parser implements ParserInterface
{
private Tokenizer $tokenizer;
public function __construct(Tokenizer $tokenizer = null)
public function __construct(?Tokenizer $tokenizer = null)
{
$this->tokenizer = $tokenizer ?? new Tokenizer();
}
@@ -87,13 +87,17 @@ class Parser implements ParserInterface
];
}
private function parseSelectorList(TokenStream $stream): array
private function parseSelectorList(TokenStream $stream, bool $isArgument = false): array
{
$stream->skipWhitespace();
$selectors = [];
while (true) {
$selectors[] = $this->parserSelectorNode($stream);
if ($isArgument && $stream->getPeek()->isDelimiter([')'])) {
break;
}
$selectors[] = $this->parserSelectorNode($stream, $isArgument);
if ($stream->getPeek()->isDelimiter([','])) {
$stream->getNext();
@@ -106,15 +110,19 @@ class Parser implements ParserInterface
return $selectors;
}
private function parserSelectorNode(TokenStream $stream): Node\SelectorNode
private function parserSelectorNode(TokenStream $stream, bool $isArgument = false): Node\SelectorNode
{
[$result, $pseudoElement] = $this->parseSimpleSelector($stream);
[$result, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument);
while (true) {
$stream->skipWhitespace();
$peek = $stream->getPeek();
if ($peek->isFileEnd() || $peek->isDelimiter([','])) {
if (
$peek->isFileEnd()
|| $peek->isDelimiter([','])
|| ($isArgument && $peek->isDelimiter([')']))
) {
break;
}
@@ -129,7 +137,7 @@ class Parser implements ParserInterface
$combinator = ' ';
}
[$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream);
[$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument);
$result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
}
@@ -141,7 +149,7 @@ class Parser implements ParserInterface
*
* @throws SyntaxErrorException
*/
private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false): array
private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false, bool $isArgument = false): array
{
$stream->skipWhitespace();
@@ -154,7 +162,7 @@ class Parser implements ParserInterface
if ($peek->isWhitespace()
|| $peek->isFileEnd()
|| $peek->isDelimiter([',', '+', '>', '~'])
|| ($insideNegation && $peek->isDelimiter([')']))
|| ($isArgument && $peek->isDelimiter([')']))
) {
break;
}
@@ -215,7 +223,7 @@ class Parser implements ParserInterface
throw SyntaxErrorException::nestedNot();
}
[$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true);
[$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true, true);
$next = $stream->getNext();
if (null !== $argumentPseudoElement) {
@@ -227,6 +235,24 @@ class Parser implements ParserInterface
}
$result = new Node\NegationNode($result, $argument);
} elseif ('is' === strtolower($identifier)) {
$selectors = $this->parseSelectorList($stream, true);
$next = $stream->getNext();
if (!$next->isDelimiter([')'])) {
throw SyntaxErrorException::unexpectedToken('")"', $next);
}
$result = new Node\MatchingNode($result, $selectors);
} elseif ('where' === strtolower($identifier)) {
$selectors = $this->parseSelectorList($stream, true);
$next = $stream->getNext();
if (!$next->isDelimiter([')'])) {
throw SyntaxErrorException::unexpectedToken('")"', $next);
}
$result = new Node\SpecificityAdjustmentNode($result, $selectors);
} else {
$arguments = [];
$next = null;

View File

@@ -23,13 +23,12 @@ namespace Symfony\Component\CssSelector\Parser;
*/
class Reader
{
private string $source;
private int $length;
private int $position = 0;
public function __construct(string $source)
{
$this->source = $source;
public function __construct(
private string $source,
) {
$this->length = \strlen($source);
}

View File

@@ -31,15 +31,11 @@ class Token
public const TYPE_NUMBER = 'number';
public const TYPE_STRING = 'string';
private ?string $type;
private ?string $value;
private ?int $position;
public function __construct(?string $type, ?string $value, ?int $position)
{
$this->type = $type;
$this->value = $value;
$this->position = $position;
public function __construct(
private ?string $type,
private ?string $value,
private ?int $position,
) {
}
public function getType(): ?int
@@ -72,7 +68,7 @@ class Token
return true;
}
return \in_array($this->value, $values);
return \in_array($this->value, $values, true);
}
public function isWhitespace(): bool
@@ -103,9 +99,9 @@ class Token
public function __toString(): string
{
if ($this->value) {
return sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position);
return \sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position);
}
return sprintf('<%s at %s>', $this->type, $this->position);
return \sprintf('<%s at %s>', $this->type, $this->position);
}
}

View File

@@ -23,11 +23,9 @@ namespace Symfony\Component\CssSelector\Parser\Tokenizer;
*/
class TokenizerEscaping
{
private TokenizerPatterns $patterns;
public function __construct(TokenizerPatterns $patterns)
{
$this->patterns = $patterns;
public function __construct(
private TokenizerPatterns $patterns,
) {
}
public function escapeUnicode(string $value): string

View File

@@ -84,6 +84,6 @@ class TokenizerPatterns
public function getQuotedStringPattern(string $quote): string
{
return '~^'.sprintf($this->quotedStringPattern, $quote).'~i';
return '~^'.\sprintf($this->quotedStringPattern, $quote).'~i';
}
}

View File

@@ -47,12 +47,12 @@ class AttributeMatchingExtension extends AbstractExtension
public function translateEquals(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
{
return $xpath->addCondition(sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value)));
return $xpath->addCondition(\sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value)));
}
public function translateIncludes(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
{
return $xpath->addCondition($value ? sprintf(
return $xpath->addCondition($value ? \sprintf(
'%1$s and contains(concat(\' \', normalize-space(%1$s), \' \'), %2$s)',
$attribute,
Translator::getXpathLiteral(' '.$value.' ')
@@ -61,7 +61,7 @@ class AttributeMatchingExtension extends AbstractExtension
public function translateDashMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
{
return $xpath->addCondition(sprintf(
return $xpath->addCondition(\sprintf(
'%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))',
$attribute,
Translator::getXpathLiteral($value),
@@ -71,7 +71,7 @@ class AttributeMatchingExtension extends AbstractExtension
public function translatePrefixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
{
return $xpath->addCondition($value ? sprintf(
return $xpath->addCondition($value ? \sprintf(
'%1$s and starts-with(%1$s, %2$s)',
$attribute,
Translator::getXpathLiteral($value)
@@ -80,7 +80,7 @@ class AttributeMatchingExtension extends AbstractExtension
public function translateSuffixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
{
return $xpath->addCondition($value ? sprintf(
return $xpath->addCondition($value ? \sprintf(
'%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s',
$attribute,
\strlen($value) - 1,
@@ -90,7 +90,7 @@ class AttributeMatchingExtension extends AbstractExtension
public function translateSubstringMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
{
return $xpath->addCondition($value ? sprintf(
return $xpath->addCondition($value ? \sprintf(
'%1$s and contains(%1$s, %2$s)',
$attribute,
Translator::getXpathLiteral($value)
@@ -99,7 +99,7 @@ class AttributeMatchingExtension extends AbstractExtension
public function translateDifferent(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr
{
return $xpath->addCondition(sprintf(
return $xpath->addCondition(\sprintf(
$value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s',
$attribute,
Translator::getXpathLiteral($value)

View File

@@ -50,7 +50,7 @@ class FunctionExtension extends AbstractExtension
try {
[$a, $b] = Parser::parseSeries($function->getArguments());
} catch (SyntaxErrorException $e) {
throw new ExpressionErrorException(sprintf('Invalid series: "%s".', implode('", "', $function->getArguments())), 0, $e);
throw new ExpressionErrorException(\sprintf('Invalid series: "%s".', implode('", "', $function->getArguments())), 0, $e);
}
$xpath->addStarPrefix();
@@ -83,10 +83,10 @@ class FunctionExtension extends AbstractExtension
$expr .= ' - '.$b;
}
$conditions = [sprintf('%s %s 0', $expr, $sign)];
$conditions = [\sprintf('%s %s 0', $expr, $sign)];
if (1 !== $a && -1 !== $a) {
$conditions[] = sprintf('(%s) mod %d = 0', $expr, $a);
$conditions[] = \sprintf('(%s) mod %d = 0', $expr, $a);
}
return $xpath->addCondition(implode(' and ', $conditions));
@@ -134,7 +134,7 @@ class FunctionExtension extends AbstractExtension
}
}
return $xpath->addCondition(sprintf(
return $xpath->addCondition(\sprintf(
'contains(string(.), %s)',
Translator::getXpathLiteral($arguments[0]->getValue())
));
@@ -152,7 +152,7 @@ class FunctionExtension extends AbstractExtension
}
}
return $xpath->addCondition(sprintf(
return $xpath->addCondition(\sprintf(
'lang(%s)',
Translator::getXpathLiteral($arguments[0]->getValue())
));

View File

@@ -142,7 +142,7 @@ class HtmlExtension extends AbstractExtension
}
}
return $xpath->addCondition(sprintf(
return $xpath->addCondition(\sprintf(
'ancestor-or-self::*[@lang][1][starts-with(concat('
."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')"
.', %s)]',

View File

@@ -31,11 +31,9 @@ class NodeExtension extends AbstractExtension
public const ATTRIBUTE_NAME_IN_LOWER_CASE = 2;
public const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4;
private int $flags;
public function __construct(int $flags = 0)
{
$this->flags = $flags;
public function __construct(
private int $flags = 0,
) {
}
/**
@@ -65,6 +63,8 @@ class NodeExtension extends AbstractExtension
'Selector' => $this->translateSelector(...),
'CombinedSelector' => $this->translateCombinedSelector(...),
'Negation' => $this->translateNegation(...),
'Matching' => $this->translateMatching(...),
'SpecificityAdjustment' => $this->translateSpecificityAdjustment(...),
'Function' => $this->translateFunction(...),
'Pseudo' => $this->translatePseudo(...),
'Attribute' => $this->translateAttribute(...),
@@ -91,12 +91,42 @@ class NodeExtension extends AbstractExtension
$subXpath->addNameTest();
if ($subXpath->getCondition()) {
return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition()));
return $xpath->addCondition(\sprintf('not(%s)', $subXpath->getCondition()));
}
return $xpath->addCondition('0');
}
public function translateMatching(Node\MatchingNode $node, Translator $translator): XPathExpr
{
$xpath = $translator->nodeToXPath($node->selector);
foreach ($node->arguments as $argument) {
$expr = $translator->nodeToXPath($argument);
$expr->addNameTest();
if ($condition = $expr->getCondition()) {
$xpath->addCondition($condition, 'or');
}
}
return $xpath;
}
public function translateSpecificityAdjustment(Node\SpecificityAdjustmentNode $node, Translator $translator): XPathExpr
{
$xpath = $translator->nodeToXPath($node->selector);
foreach ($node->arguments as $argument) {
$expr = $translator->nodeToXPath($argument);
$expr->addNameTest();
if ($condition = $expr->getCondition()) {
$xpath->addCondition($condition, 'or');
}
}
return $xpath;
}
public function translateFunction(Node\FunctionNode $node, Translator $translator): XPathExpr
{
$xpath = $translator->nodeToXPath($node->getSelector());
@@ -121,11 +151,11 @@ class NodeExtension extends AbstractExtension
}
if ($node->getNamespace()) {
$name = sprintf('%s:%s', $node->getNamespace(), $name);
$name = \sprintf('%s:%s', $node->getNamespace(), $name);
$safe = $safe && $this->isSafeName($node->getNamespace());
}
$attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name));
$attribute = $safe ? '@'.$name : \sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name));
$value = $node->getValue();
$xpath = $translator->nodeToXPath($node->getSelector());
@@ -166,7 +196,7 @@ class NodeExtension extends AbstractExtension
}
if ($node->getNamespace()) {
$element = sprintf('%s:%s', $node->getNamespace(), $element);
$element = \sprintf('%s:%s', $node->getNamespace(), $element);
$safe = $safe && $this->isSafeName($node->getNamespace());
}

View File

@@ -107,7 +107,7 @@ class PseudoClassExtension extends AbstractExtension
{
$element = $xpath->getElement();
return $xpath->addCondition(sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element));
return $xpath->addCondition(\sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element));
}
public function translateEmpty(XPathExpr $xpath): XPathExpr

View File

@@ -48,7 +48,7 @@ class Translator implements TranslatorInterface
private array $pseudoClassTranslators = [];
private array $attributeMatchingTranslators = [];
public function __construct(ParserInterface $parser = null)
public function __construct(?ParserInterface $parser = null)
{
$this->mainParser = $parser ?? new Parser();
@@ -75,7 +75,7 @@ class Translator implements TranslatorInterface
$parts = [];
while (true) {
if (false !== $pos = strpos($string, "'")) {
$parts[] = sprintf("'%s'", substr($string, 0, $pos));
$parts[] = \sprintf("'%s'", substr($string, 0, $pos));
$parts[] = "\"'\"";
$string = substr($string, $pos + 1);
} else {
@@ -84,7 +84,7 @@ class Translator implements TranslatorInterface
}
}
return sprintf('concat(%s)', implode(', ', $parts));
return \sprintf('concat(%s)', implode(', ', $parts));
}
public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string
@@ -130,7 +130,7 @@ class Translator implements TranslatorInterface
public function getExtension(string $name): Extension\ExtensionInterface
{
if (!isset($this->extensions[$name])) {
throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name));
throw new ExpressionErrorException(\sprintf('Extension "%s" not registered.', $name));
}
return $this->extensions[$name];
@@ -152,7 +152,7 @@ class Translator implements TranslatorInterface
public function nodeToXPath(NodeInterface $node): XPathExpr
{
if (!isset($this->nodeTranslators[$node->getNodeName()])) {
throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName()));
throw new ExpressionErrorException(\sprintf('Node "%s" not supported.', $node->getNodeName()));
}
return $this->nodeTranslators[$node->getNodeName()]($node, $this);
@@ -164,7 +164,7 @@ class Translator implements TranslatorInterface
public function addCombination(string $combiner, NodeInterface $xpath, NodeInterface $combinedXpath): XPathExpr
{
if (!isset($this->combinationTranslators[$combiner])) {
throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner));
throw new ExpressionErrorException(\sprintf('Combiner "%s" not supported.', $combiner));
}
return $this->combinationTranslators[$combiner]($this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath));
@@ -176,7 +176,7 @@ class Translator implements TranslatorInterface
public function addFunction(XPathExpr $xpath, FunctionNode $function): XPathExpr
{
if (!isset($this->functionTranslators[$function->getName()])) {
throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName()));
throw new ExpressionErrorException(\sprintf('Function "%s" not supported.', $function->getName()));
}
return $this->functionTranslators[$function->getName()]($xpath, $function);
@@ -188,7 +188,7 @@ class Translator implements TranslatorInterface
public function addPseudoClass(XPathExpr $xpath, string $pseudoClass): XPathExpr
{
if (!isset($this->pseudoClassTranslators[$pseudoClass])) {
throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass));
throw new ExpressionErrorException(\sprintf('Pseudo-class "%s" not supported.', $pseudoClass));
}
return $this->pseudoClassTranslators[$pseudoClass]($xpath);
@@ -200,7 +200,7 @@ class Translator implements TranslatorInterface
public function addAttributeMatching(XPathExpr $xpath, string $operator, string $attribute, ?string $value): XPathExpr
{
if (!isset($this->attributeMatchingTranslators[$operator])) {
throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator));
throw new ExpressionErrorException(\sprintf('Attribute matcher operator "%s" not supported.', $operator));
}
return $this->attributeMatchingTranslators[$operator]($xpath, $attribute, $value);

View File

@@ -23,16 +23,12 @@ namespace Symfony\Component\CssSelector\XPath;
*/
class XPathExpr
{
private string $path;
private string $element;
private string $condition;
public function __construct(string $path = '', string $element = '*', string $condition = '', bool $starPrefix = false)
{
$this->path = $path;
$this->element = $element;
$this->condition = $condition;
public function __construct(
private string $path = '',
private string $element = '*',
private string $condition = '',
bool $starPrefix = false,
) {
if ($starPrefix) {
$this->addStarPrefix();
}
@@ -46,9 +42,9 @@ class XPathExpr
/**
* @return $this
*/
public function addCondition(string $condition): static
public function addCondition(string $condition, string $operator = 'and'): static
{
$this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition;
$this->condition = $this->condition ? \sprintf('(%s) %s (%s)', $this->condition, $operator, $condition) : $condition;
return $this;
}
@@ -104,7 +100,7 @@ class XPathExpr
public function __toString(): string
{
$path = $this->path.$this->element;
$condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']';
$condition = '' === $this->condition ? '' : '['.$this->condition.']';
return $path.$condition;
}

View File

@@ -25,7 +25,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",

View File

@@ -18,8 +18,10 @@ use Mockery\MockInterface;
use Phake\IMock;
use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\MockObject\Stub;
use Prophecy\Prophecy\ProphecySubjectInterface;
use ProxyManager\Proxy\ProxyInterface;
use Symfony\Component\DependencyInjection\Argument\LazyClosure;
use Symfony\Component\ErrorHandler\Internal\TentativeTypes;
use Symfony\Component\VarExporter\LazyObjectInterface;
@@ -252,6 +254,7 @@ class DebugClassLoader
for (; $i < \count($symbols); ++$i) {
if (!is_subclass_of($symbols[$i], MockObject::class)
&& !is_subclass_of($symbols[$i], Stub::class)
&& !is_subclass_of($symbols[$i], ProphecySubjectInterface::class)
&& !is_subclass_of($symbols[$i], Proxy::class)
&& !is_subclass_of($symbols[$i], ProxyInterface::class)
@@ -259,6 +262,7 @@ class DebugClassLoader
&& !is_subclass_of($symbols[$i], LegacyProxy::class)
&& !is_subclass_of($symbols[$i], MockInterface::class)
&& !is_subclass_of($symbols[$i], IMock::class)
&& !(is_subclass_of($symbols[$i], LazyClosure::class) && str_contains($symbols[$i], "@anonymous\0"))
) {
$loader->checkClass($symbols[$i]);
}
@@ -307,7 +311,7 @@ class DebugClassLoader
$this->checkClass($class, $file);
}
private function checkClass(string $class, string $file = null): void
private function checkClass(string $class, ?string $file = null): void
{
$exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
@@ -794,7 +798,7 @@ class DebugClassLoader
return $ownInterfaces;
}
private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, \ReflectionType $returnType = null): void
private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, ?\ReflectionType $returnType = null): void
{
if ('__construct' === $method) {
return;
@@ -1133,7 +1137,7 @@ EOTXT;
$braces = 0;
for (; $i < $end; ++$i) {
if (!$inClosure) {
$inClosure = str_contains($code[$i], 'function (');
$inClosure = false !== strpos($code[$i], 'function (');
}
if ($inClosure) {

View File

@@ -18,7 +18,7 @@ class FatalError extends \Error
/**
* @param array $error An array as returned by error_get_last()
*/
public function __construct(string $message, int $code, array $error, int $traceOffset = null, bool $traceArgs = true, array $trace = null)
public function __construct(string $message, int $code, array $error, ?int $traceOffset = null, bool $traceArgs = true, ?array $trace = null)
{
parent::__construct($message, $code);
@@ -31,7 +31,7 @@ class FatalError extends \Error
}
}
} elseif (null !== $traceOffset) {
if (\function_exists('xdebug_get_function_stack') && $trace = @xdebug_get_function_stack()) {
if (\function_exists('xdebug_get_function_stack') && \in_array(\ini_get('xdebug.mode'), ['develop', false], true) && $trace = @xdebug_get_function_stack()) {
if (0 < $traceOffset) {
array_splice($trace, -$traceOffset);
}

View File

@@ -107,7 +107,8 @@ class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface
private function findClassInPath(string $path, string $class, string $prefix): array
{
if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path);
if (!$path || !is_dir($path)) {
return [];
}

View File

@@ -55,7 +55,6 @@ class ErrorHandler
\E_USER_DEPRECATED => 'User Deprecated',
\E_NOTICE => 'Notice',
\E_USER_NOTICE => 'User Notice',
\E_STRICT => 'Runtime Notice',
\E_WARNING => 'Warning',
\E_USER_WARNING => 'User Warning',
\E_COMPILE_WARNING => 'Compile Warning',
@@ -73,7 +72,6 @@ class ErrorHandler
\E_USER_DEPRECATED => [null, LogLevel::INFO],
\E_NOTICE => [null, LogLevel::WARNING],
\E_USER_NOTICE => [null, LogLevel::WARNING],
\E_STRICT => [null, LogLevel::WARNING],
\E_WARNING => [null, LogLevel::WARNING],
\E_USER_WARNING => [null, LogLevel::WARNING],
\E_COMPILE_WARNING => [null, LogLevel::WARNING],
@@ -108,7 +106,7 @@ class ErrorHandler
/**
* Registers the error handler.
*/
public static function register(self $handler = null, bool $replace = true): self
public static function register(?self $handler = null, bool $replace = true): self
{
if (null === self::$reservedMemory) {
self::$reservedMemory = str_repeat('x', 32768);
@@ -179,8 +177,13 @@ class ErrorHandler
}
}
public function __construct(BufferingLogger $bootstrappingLogger = null, bool $debug = false)
public function __construct(?BufferingLogger $bootstrappingLogger = null, bool $debug = false)
{
if (\PHP_VERSION_ID < 80400) {
$this->levels[\E_STRICT] = 'Runtime Notice';
$this->loggers[\E_STRICT] = [null, LogLevel::WARNING];
}
if ($bootstrappingLogger) {
$this->bootstrappingLogger = $bootstrappingLogger;
$this->setDefaultLogger($bootstrappingLogger);
@@ -432,7 +435,7 @@ class ErrorHandler
return true;
}
} else {
if (str_contains($message, '@anonymous')) {
if (PHP_VERSION_ID < 80303 && str_contains($message, '@anonymous')) {
$backtrace = debug_backtrace(false, 5);
for ($i = 1; isset($backtrace[$i]); ++$i) {
@@ -440,8 +443,7 @@ class ErrorHandler
&& ('trigger_error' === $backtrace[$i]['function'] || 'user_error' === $backtrace[$i]['function'])
) {
if ($backtrace[$i]['args'][0] !== $message) {
$message = $this->parseAnonymousClass($backtrace[$i]['args'][0]);
$logMessage = $this->levels[$type].': '.$message;
$message = $backtrace[$i]['args'][0];
}
break;
@@ -449,6 +451,11 @@ class ErrorHandler
}
}
if (false !== strpos($message, "@anonymous\0")) {
$message = $this->parseAnonymousClass($message);
$logMessage = $this->levels[$type].': '.$message;
}
$errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
if ($throw || $this->tracedErrors & $type) {
@@ -559,7 +566,7 @@ class ErrorHandler
*
* @internal
*/
public static function handleFatalError(array $error = null): void
public static function handleFatalError(?array $error = null): void
{
if (null === self::$reservedMemory) {
return;
@@ -591,6 +598,10 @@ class ErrorHandler
set_exception_handler($h);
}
if (!$handler) {
if (null === $error && $exitCode = self::$exitCode) {
register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
}
return;
}
if ($handler !== $h) {
@@ -626,8 +637,7 @@ class ErrorHandler
// Ignore this re-throw
}
if ($exit && self::$exitCode) {
$exitCode = self::$exitCode;
if ($exit && $exitCode = self::$exitCode) {
register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
}
}
@@ -732,6 +742,6 @@ class ErrorHandler
*/
private function parseAnonymousClass(string $message): string
{
return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)?[0-9a-fA-F]++/', static fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
}
}

View File

@@ -30,9 +30,9 @@ class FileLinkFormatter
private \Closure|string|null $urlFormat;
/**
* @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand
* @param string|\Closure $urlFormat The URL format, or a closure that returns it on-demand
*/
public function __construct(string|array $fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, string|\Closure $urlFormat = null)
public function __construct(string|array|null $fileLinkFormat = null, ?RequestStack $requestStack = null, ?string $baseDir = null, string|\Closure|null $urlFormat = null)
{
$fileLinkFormat ??= $_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? '';

View File

@@ -47,7 +47,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
* @param string|callable $outputBuffer The output buffer as a string or a callable that should return it
*/
public function __construct(bool|callable $debug = false, string $charset = null, string|FileLinkFormatter $fileLinkFormat = null, string $projectDir = null, string|callable $outputBuffer = '', LoggerInterface $logger = null)
public function __construct(bool|callable $debug = false, ?string $charset = null, string|FileLinkFormatter|null $fileLinkFormat = null, ?string $projectDir = null, string|callable $outputBuffer = '', ?LoggerInterface $logger = null)
{
$this->debug = \is_bool($debug) ? $debug : $debug(...);
$this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8');
@@ -61,7 +61,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
{
$headers = ['Content-Type' => 'text/html; charset='.$this->charset];
if (\is_bool($this->debug) ? $this->debug : ($this->debug)($exception)) {
$headers['X-Debug-Exception'] = rawurlencode($exception->getMessage());
$headers['X-Debug-Exception'] = rawurlencode(substr($exception->getMessage(), 0, 2000));
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}
@@ -140,7 +140,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
'exceptionMessage' => $exceptionMessage,
'statusText' => $statusText,
'statusCode' => $statusCode,
'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger),
'logger' => null !== $this->logger && class_exists(DebugLoggerConfigurator::class) ? DebugLoggerConfigurator::getDebugLogger($this->logger) : null,
'currentContent' => \is_string($this->outputBuffer) ? $this->outputBuffer : ($this->outputBuffer)(),
]);
}
@@ -167,6 +167,8 @@ class HtmlErrorRenderer implements ErrorRendererInterface
$formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
} elseif ('resource' === $item[0]) {
$formattedValue = '<em>resource</em>';
} elseif (preg_match('/[^\x07-\x0D\x1B\x20-\xFF]/', $item[1])) {
$formattedValue = '<em>binary string</em>';
} else {
$formattedValue = str_replace("\n", '', $this->escape(var_export($item[1], true)));
}
@@ -213,7 +215,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
* @param int $line The line number
* @param string $text Use this text for the link rather than the file path
*/
private function formatFile(string $file, int $line, string $text = null): string
private function formatFile(string $file, int $line, ?string $text = null): string
{
$file = trim($file);
@@ -250,10 +252,10 @@ class HtmlErrorRenderer implements ErrorRendererInterface
if (\PHP_VERSION_ID >= 80300) {
// remove main pre/code tags
$code = preg_replace('#^<pre.*?>\s*<code.*?>(.*)</code>\s*</pre>#s', '\\1', $code);
// split multiline code tags
$code = preg_replace_callback('#<code ([^>]++)>((?:[^<]*+\\n)++[^<]*+)</code>#', fn ($m) => "<code $m[1]>".str_replace("\n", "</code>\n<code $m[1]>", $m[2]).'</code>', $code);
// Convert spaces to html entities to preserve indentation when rendered
$code = str_replace(' ', '&nbsp;', $code);
// split multiline span tags
$code = preg_replace_callback('#<span ([^>]++)>((?:[^<\\n]*+\\n)++[^<]*+)</span>#', function ($m) {
return "<span $m[1]>".str_replace("\n", "</span>\n<span $m[1]>", $m[2]).'</span>';
}, $code);
$content = explode("\n", $code);
} else {
// remove main code/span tags
@@ -299,7 +301,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
private function formatFileFromText(string $text): string
{
return preg_replace_callback('/in ("|&quot;)?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text);
return preg_replace_callback('/in ("|&quot;)?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text) ?? $text;
}
private function formatLogMessage(string $message, array $context): string

View File

@@ -34,7 +34,7 @@ class SerializerErrorRenderer implements ErrorRendererInterface
* formats not supported by Request::getMimeTypes() should be given as mime types
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
*/
public function __construct(SerializerInterface $serializer, string|callable $format, ErrorRendererInterface $fallbackErrorRenderer = null, bool|callable $debug = false)
public function __construct(SerializerInterface $serializer, string|callable $format, ?ErrorRendererInterface $fallbackErrorRenderer = null, bool|callable $debug = false)
{
$this->serializer = $serializer;
$this->format = \is_string($format) ? $format : $format(...);
@@ -47,7 +47,7 @@ class SerializerErrorRenderer implements ErrorRendererInterface
$headers = ['Vary' => 'Accept'];
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception);
if ($debug) {
$headers['X-Debug-Exception'] = rawurlencode($exception->getMessage());
$headers['X-Debug-Exception'] = rawurlencode(substr($exception->getMessage(), 0, 2000));
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}

View File

@@ -42,12 +42,12 @@ class FlattenException
private ?string $asString = null;
private Data $dataRepresentation;
public static function create(\Exception $exception, int $statusCode = null, array $headers = []): static
public static function create(\Exception $exception, ?int $statusCode = null, array $headers = []): static
{
return static::createFromThrowable($exception, $statusCode, $headers);
}
public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): static
public static function createFromThrowable(\Throwable $exception, ?int $statusCode = null, array $headers = []): static
{
$e = new static();
$e->setMessage($exception->getMessage());
@@ -85,7 +85,7 @@ class FlattenException
return $e;
}
public static function createWithDataRepresentation(\Throwable $throwable, int $statusCode = null, array $headers = [], VarCloner $cloner = null): static
public static function createWithDataRepresentation(\Throwable $throwable, ?int $statusCode = null, array $headers = [], ?VarCloner $cloner = null): static
{
$e = static::createFromThrowable($throwable, $statusCode, $headers);
@@ -228,7 +228,7 @@ class FlattenException
public function setMessage(string $message): static
{
if (str_contains($message, "@anonymous\0")) {
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)?[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
}
$this->message = $message;

View File

@@ -57,7 +57,7 @@
--page-background: #36393e;
--color-text: #e0e0e0;
--color-muted: #777;
--color-error: #d43934;
--color-error: #f76864;
--tab-background: #404040;
--tab-border-color: #737373;
--tab-active-border-color: #171717;
@@ -80,7 +80,7 @@
--metric-unit-color: #999;
--metric-label-background: #777;
--metric-label-color: #e0e0e0;
--trace-selected-background: #71663acc;
--trace-selected-background: #5d5227cc;
--table-border: #444;
--table-background: #333;
--table-header: #555;
@@ -92,7 +92,7 @@
--background-error: #b0413e;
--highlight-comment: #dedede;
--highlight-default: var(--base-6);
--highlight-keyword: #ff413c;
--highlight-keyword: #de8986;
--highlight-string: #70a6fd;
--base-0: #2e3136;
--base-1: #444;
@@ -349,7 +349,7 @@ header .container { display: flex; justify-content: space-between; }
.trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; }
.trace-code li + li { margin-top: 5px; }
.trace-code li.selected { background: var(--trace-selected-background); margin-top: 2px; }
.trace-code li code { color: var(--base-6); white-space: nowrap; }
.trace-code li code { color: var(--base-6); white-space: pre; }
.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; }

View File

@@ -145,6 +145,12 @@
}
addEventListener(toggles[i], 'click', function(e) {
var toggle = e.currentTarget;
if (e.target.closest('a, span[data-clipboard-text], .sf-toggle') !== toggle) {
return;
}
e.preventDefault();
if ('' !== window.getSelection().toString()) {
@@ -152,14 +158,6 @@
return;
}
var toggle = e.target || e.srcElement;
/* needed because when the toggle contains HTML contents, user can click */
/* on any of those elements instead of their parent '.sf-toggle' element */
while (!hasClass(toggle, 'sf-toggle')) {
toggle = toggle.parentNode;
}
var element = document.querySelector(toggle.getAttribute('data-toggle-selector'));
toggleClass(toggle, 'sf-toggle-on');
@@ -182,22 +180,6 @@
toggle.innerHTML = currentContent !== altContent ? altContent : originalContent;
});
/* Prevents from disallowing clicks on links inside toggles */
var toggleLinks = toggles[i].querySelectorAll('a');
for (var j = 0; j < toggleLinks.length; j++) {
addEventListener(toggleLinks[j], 'click', function(e) {
e.stopPropagation();
});
}
/* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */
var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]');
for (var k = 0; k < copyToClipboardElements.length; k++) {
addEventListener(copyToClipboardElements[k], 'click', function(e) {
e.stopPropagation();
});
}
toggles[i].setAttribute('data-processed', 'true');
}
})();

View File

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

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="<?= $this->charset; ?>">
<meta name="robots" content="noindex,nofollow,noarchive">
<meta charset="<?= $this->charset; ?>" />
<meta name="robots" content="noindex,nofollow,noarchive" />
<title>An Error Occurred: <?= $statusText; ?></title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>❌</text></svg>">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>❌</text></svg>" />
<style><?= $this->include('assets/css/error.css'); ?></style>
</head>
<body>

View File

@@ -2,11 +2,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="<?= $this->charset; ?>">
<meta name="robots" content="noindex,nofollow">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta charset="<?= $this->charset; ?>" />
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title><?= $_message; ?></title>
<link rel="icon" type="image/png" href="<?= $this->include('assets/images/favicon.png.base64'); ?>">
<link rel="icon" type="image/png" href="<?= $this->include('assets/images/favicon.png.base64'); ?>" />
<style><?= $this->include('assets/css/exception.css'); ?></style>
<style><?= $this->include('assets/css/exception_full.css'); ?></style>
</head>

View File

@@ -29,5 +29,5 @@ interface EventDispatcherInterface extends PsrEventDispatcherInterface
*
* @return T The passed $event MUST be returned
*/
public function dispatch(object $event, string $eventName = null): object;
public function dispatch(object $event, ?string $eventName = null): object;
}

View File

@@ -25,7 +25,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",

View File

@@ -19,6 +19,12 @@ namespace Symfony\Component\EventDispatcher\Attribute;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AsEventListener
{
/**
* @param string|null $event The event name to listen to
* @param string|null $method The method to run when the listened event is triggered
* @param int $priority The priority of this listener if several are declared for the same event
* @param string|null $dispatcher The service id of the event dispatcher to listen to
*/
public function __construct(
public ?string $event = null,
public ?string $method = null,

View File

@@ -30,25 +30,20 @@ use Symfony\Contracts\Service\ResetInterface;
*/
class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
{
protected ?LoggerInterface $logger;
protected Stopwatch $stopwatch;
/**
* @var \SplObjectStorage<WrappedListener, array{string, string}>|null
*/
private ?\SplObjectStorage $callStack = null;
private EventDispatcherInterface $dispatcher;
private array $wrappedListeners = [];
private array $orphanedEvents = [];
private ?RequestStack $requestStack;
private string $currentRequestHash = '';
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null)
{
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->requestStack = $requestStack;
public function __construct(
private EventDispatcherInterface $dispatcher,
protected Stopwatch $stopwatch,
protected ?LoggerInterface $logger = null,
private ?RequestStack $requestStack = null,
) {
}
public function addListener(string $eventName, callable|array $listener, int $priority = 0): void
@@ -81,7 +76,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
$this->dispatcher->removeSubscriber($subscriber);
}
public function getListeners(string $eventName = null): array
public function getListeners(?string $eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
@@ -101,12 +96,12 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
public function hasListeners(string $eventName = null): bool
public function hasListeners(?string $eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}
public function dispatch(object $event, string $eventName = null): object
public function dispatch(object $event, ?string $eventName = null): object
{
$eventName ??= $event::class;
@@ -115,7 +110,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
$currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
$this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
$this->logger->debug(\sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
}
$this->preProcess($eventName);
@@ -141,7 +136,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
return $event;
}
public function getCalledListeners(Request $request = null): array
public function getCalledListeners(?Request $request = null): array
{
if (null === $this->callStack) {
return [];
@@ -159,7 +154,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
return $called;
}
public function getNotCalledListeners(Request $request = null): array
public function getNotCalledListeners(?Request $request = null): array
{
try {
$allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority();
@@ -201,7 +196,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
return $notCalled;
}
public function getOrphanedEvents(Request $request = null): array
public function getOrphanedEvents(?Request $request = null): array
{
if ($request) {
return $this->orphanedEvents[spl_object_hash($request)] ?? [];

View File

@@ -26,21 +26,20 @@ final class WrappedListener
private string $name;
private bool $called = false;
private bool $stoppedPropagation = false;
private Stopwatch $stopwatch;
private ?EventDispatcherInterface $dispatcher;
private string $pretty;
private string $callableRef;
private ClassStub|string $stub;
private ?int $priority = null;
private static bool $hasClassStub;
public function __construct(callable|array $listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null, int $priority = null)
{
public function __construct(
callable|array $listener,
?string $name,
private Stopwatch $stopwatch,
private ?EventDispatcherInterface $dispatcher = null,
private ?int $priority = null,
) {
$this->listener = $listener;
$this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? $listener(...) : null);
$this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher;
$this->priority = $priority;
if (\is_array($listener)) {
[$this->name, $this->callableRef] = $this->parseListener($listener);
@@ -48,7 +47,7 @@ final class WrappedListener
$this->callableRef .= '::'.$listener[1];
} elseif ($listener instanceof \Closure) {
$r = new \ReflectionFunction($listener);
if (str_contains($r->name, '{closure}')) {
if ($r->isAnonymous()) {
$this->pretty = $this->name = 'closure';
} elseif ($class = $r->getClosureCalledClass()) {
$this->name = $class->name;

View File

@@ -21,11 +21,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
*/
class AddEventAliasesPass implements CompilerPassInterface
{
private array $eventAliases;
public function __construct(array $eventAliases)
{
$this->eventAliases = $eventAliases;
public function __construct(
private array $eventAliases,
) {
}
public function process(ContainerBuilder $container): void

View File

@@ -88,7 +88,7 @@ class RegisterListenersPass implements CompilerPassInterface
if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method'])) {
if (!$r->hasMethod('__invoke')) {
throw new InvalidArgumentException(sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "kernel.event_listener" tags.', $event['method'], $id));
throw new InvalidArgumentException(\sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "kernel.event_listener" tags.', $event['method'], $id));
}
$event['method'] = '__invoke';
@@ -123,10 +123,10 @@ class RegisterListenersPass implements CompilerPassInterface
$class = $def->getClass();
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
}
$class = $r->name;
@@ -178,7 +178,7 @@ class RegisterListenersPass implements CompilerPassInterface
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
throw new InvalidArgumentException(\sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id));
}
return $name;

View File

@@ -42,7 +42,7 @@ class EventDispatcher implements EventDispatcherInterface
}
}
public function dispatch(object $event, string $eventName = null): object
public function dispatch(object $event, ?string $eventName = null): object
{
$eventName ??= $event::class;
@@ -59,7 +59,7 @@ class EventDispatcher implements EventDispatcherInterface
return $event;
}
public function getListeners(string $eventName = null): array
public function getListeners(?string $eventName = null): array
{
if (null !== $eventName) {
if (empty($this->listeners[$eventName])) {
@@ -108,7 +108,7 @@ class EventDispatcher implements EventDispatcherInterface
return null;
}
public function hasListeners(string $eventName = null): bool
public function hasListeners(?string $eventName = null): bool
{
if (null !== $eventName) {
return !empty($this->listeners[$eventName]);

View File

@@ -50,7 +50,7 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface
*
* @return array<callable[]|callable>
*/
public function getListeners(string $eventName = null): array;
public function getListeners(?string $eventName = null): array;
/**
* Gets the listener priority for a specific event.
@@ -62,5 +62,5 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface
/**
* Checks whether an event has any registered listeners.
*/
public function hasListeners(string $eventName = null): bool;
public function hasListeners(?string $eventName = null): bool;
}

View File

@@ -25,19 +25,16 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
protected mixed $subject;
protected array $arguments;
/**
* Encapsulate an event with $subject and $arguments.
*
* @param mixed $subject The subject of the event, usually an object or a callable
* @param array $arguments Arguments to store in the event
*/
public function __construct(mixed $subject = null, array $arguments = [])
{
$this->subject = $subject;
$this->arguments = $arguments;
public function __construct(
protected mixed $subject = null,
protected array $arguments = [],
) {
}
/**
@@ -59,7 +56,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
return $this->arguments[$key];
}
throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
throw new \InvalidArgumentException(\sprintf('Argument "%s" not found.', $key));
}
/**

View File

@@ -18,14 +18,12 @@ namespace Symfony\Component\EventDispatcher;
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
private EventDispatcherInterface $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
public function __construct(
private EventDispatcherInterface $dispatcher,
) {
}
public function dispatch(object $event, string $eventName = null): object
public function dispatch(object $event, ?string $eventName = null): object
{
return $this->dispatcher->dispatch($event, $eventName);
}
@@ -50,7 +48,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
public function getListeners(string $eventName = null): array
public function getListeners(?string $eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
@@ -60,7 +58,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
public function hasListeners(string $eventName = null): bool
public function hasListeners(?string $eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}

View File

@@ -36,7 +36,7 @@ class DateComparator extends Comparator
throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
}
$operator = $matches[1] ?? '==';
$operator = $matches[1] ?: '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}

Some files were not shown because too many files have changed in this diff Show More