This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

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

@@ -91,7 +91,7 @@ class StreamOutput extends Output
protected function hasColorSupport()
{
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
if ('' !== ($_SERVER['NO_COLOR'] ?? getenv('NO_COLOR') ?: '')) {
return false;
}

View File

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

View File

@@ -26,7 +26,7 @@ use Symfony\Component\CssSelector\XPath\Translator;
*/
class CssSelectorConverter
{
private $translator;
private Translator $translator;
private array $cache;
private static array $xmlCache = [];
@@ -62,6 +62,6 @@ class CssSelectorConverter
*/
public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string
{
return $this->cache[$prefix][$cssExpr] ?? $this->cache[$prefix][$cssExpr] = $this->translator->cssToXPath($cssExpr, $prefix);
return $this->cache[$prefix][$cssExpr] ??= $this->translator->cssToXPath($cssExpr, $prefix);
}
}

View File

@@ -43,6 +43,11 @@ class SyntaxErrorException extends ParseException
return new self('Got nested ::not().');
}
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));
}
public static function stringAsFunctionArgument(): self
{
return new self('String not allowed as function argument.');

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2023 Fabien Potencier
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -23,19 +23,13 @@ namespace Symfony\Component\CssSelector\Node;
*/
class AttributeNode extends AbstractNode
{
private $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
@@ -63,9 +57,6 @@ class AttributeNode extends AbstractNode
return $this->value;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0));

View File

@@ -23,13 +23,10 @@ namespace Symfony\Component\CssSelector\Node;
*/
class ClassNode extends AbstractNode
{
private $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
@@ -42,9 +39,6 @@ class ClassNode extends AbstractNode
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0));

View File

@@ -23,15 +23,11 @@ namespace Symfony\Component\CssSelector\Node;
*/
class CombinedSelectorNode extends AbstractNode
{
private $selector;
private string $combinator;
private $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
@@ -49,9 +45,6 @@ class CombinedSelectorNode extends AbstractNode
return $this->subSelector;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity());

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
@@ -42,9 +39,6 @@ class ElementNode extends AbstractNode
return $this->element;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return new Specificity(0, 0, $this->element ? 1 : 0);

View File

@@ -25,18 +25,17 @@ use Symfony\Component\CssSelector\Parser\Token;
*/
class FunctionNode extends AbstractNode
{
private $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
@@ -57,9 +56,6 @@ class FunctionNode extends AbstractNode
return $this->arguments;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0));
@@ -67,9 +63,7 @@ class FunctionNode extends AbstractNode
public function __toString(): string
{
$arguments = implode(', ', array_map(function (Token $token) {
return "'".$token->getValue()."'";
}, $this->arguments));
$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.']' : '');
}

View File

@@ -23,13 +23,10 @@ namespace Symfony\Component\CssSelector\Node;
*/
class HashNode extends AbstractNode
{
private $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
@@ -42,9 +39,6 @@ class HashNode extends AbstractNode
return $this->id;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0));

View File

@@ -23,13 +23,10 @@ namespace Symfony\Component\CssSelector\Node;
*/
class NegationNode extends AbstractNode
{
private $selector;
private $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
@@ -42,9 +39,6 @@ class NegationNode extends AbstractNode
return $this->subSelector;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity());

View File

@@ -21,11 +21,9 @@ namespace Symfony\Component\CssSelector\Node;
*
* @internal
*/
interface NodeInterface
interface NodeInterface extends \Stringable
{
public function getNodeName(): string;
public function getSpecificity(): Specificity;
public function __toString(): string;
}

View File

@@ -23,12 +23,12 @@ namespace Symfony\Component\CssSelector\Node;
*/
class PseudoNode extends AbstractNode
{
private $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);
}
@@ -42,9 +42,6 @@ class PseudoNode extends AbstractNode
return $this->identifier;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0));

View File

@@ -23,12 +23,12 @@ namespace Symfony\Component\CssSelector\Node;
*/
class SelectorNode extends AbstractNode
{
private $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;
}
@@ -42,9 +42,6 @@ class SelectorNode extends AbstractNode
return $this->pseudoElement;
}
/**
* {@inheritdoc}
*/
public function getSpecificity(): Specificity
{
return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0));

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

@@ -26,9 +26,6 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class CommentHandler implements HandlerInterface
{
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
if ('/*' !== $reader->getSubstring(2)) {

View File

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

View File

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

View File

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

View File

@@ -31,18 +31,12 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class StringHandler implements HandlerInterface
{
private $patterns;
private $escaping;
public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping)
{
$this->patterns = $patterns;
$this->escaping = $escaping;
public function __construct(
private TokenizerPatterns $patterns,
private TokenizerEscaping $escaping,
) {
}
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
$quote = $reader->getSubstring(1);

View File

@@ -27,9 +27,6 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class WhitespaceHandler implements HandlerInterface
{
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
$match = $reader->findPattern('~^[ \t\r\n\f]+~');

View File

@@ -19,7 +19,7 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer;
* CSS selector parser.
*
* This component is a port of the Python cssselect library,
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
* which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
@@ -27,16 +27,13 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer;
*/
class Parser implements ParserInterface
{
private $tokenizer;
private Tokenizer $tokenizer;
public function __construct(Tokenizer $tokenizer = null)
public function __construct(?Tokenizer $tokenizer = null)
{
$this->tokenizer = $tokenizer ?? new Tokenizer();
}
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
$reader = new Reader($source);
@@ -60,9 +57,7 @@ class Parser implements ParserInterface
}
}
$joined = trim(implode('', array_map(function (Token $token) {
return $token->getValue();
}, $tokens)));
$joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens)));
$int = function ($string) {
if (!is_numeric($string)) {
@@ -92,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();
@@ -111,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;
}
@@ -134,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);
}
@@ -146,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();
@@ -159,7 +162,7 @@ class Parser implements ParserInterface
if ($peek->isWhitespace()
|| $peek->isFileEnd()
|| $peek->isDelimiter([',', '+', '>', '~'])
|| ($insideNegation && $peek->isDelimiter([')']))
|| ($isArgument && $peek->isDelimiter([')']))
) {
break;
}
@@ -197,7 +200,18 @@ class Parser implements ParserInterface
if (!$stream->getPeek()->isDelimiter(['('])) {
$result = new Node\PseudoNode($result, $identifier);
if ('Pseudo[Element[*]:scope]' === $result->__toString()) {
$used = \count($stream->getUsed());
if (!(2 === $used
|| 3 === $used && $stream->getUsed()[0]->isWhiteSpace()
|| $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([','])
|| $used >= 4
&& $stream->getUsed()[$used - 3]->isWhiteSpace()
&& $stream->getUsed()[$used - 4]->isDelimiter([','])
)) {
throw SyntaxErrorException::notAtTheStartOfASelector('scope');
}
}
continue;
}
@@ -209,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) {
@@ -221,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;
@@ -242,7 +274,7 @@ class Parser implements ParserInterface
}
}
if (empty($arguments)) {
if (!$arguments) {
throw SyntaxErrorException::unexpectedToken('at least one argument', $next);
}

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);
}
@@ -53,7 +52,7 @@ class Reader
return substr($this->source, $this->position + $offset, $length);
}
public function getOffset(string $string)
public function getOffset(string $string): int|false
{
$position = strpos($this->source, $string, $this->position);
@@ -71,12 +70,12 @@ class Reader
return false;
}
public function moveForward(int $length)
public function moveForward(int $length): void
{
$this->position += $length;
}
public function moveToEnd()
public function moveToEnd(): void
{
$this->position = $this->length;
}

View File

@@ -28,9 +28,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class ClassParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
// Matches an optional namespace, optional element, and required class

View File

@@ -27,9 +27,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class ElementParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
// Matches an optional namespace, required element or `*`

View File

@@ -31,9 +31,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class EmptyStringParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
// Matches an empty string

View File

@@ -28,9 +28,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class HashParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
// Matches an optional namespace, optional element, and required id

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
@@ -68,11 +64,11 @@ class Token
return false;
}
if (empty($values)) {
if (!$values) {
return true;
}
return \in_array($this->value, $values);
return \in_array($this->value, $values, true);
}
public function isWhitespace(): bool

View File

@@ -37,7 +37,7 @@ class TokenStream
private array $used = [];
private int $cursor = 0;
private $peeked;
private ?Token $peeked;
private bool $peeking = false;
/**
@@ -145,7 +145,7 @@ class TokenStream
/**
* Skips next whitespace if any.
*/
public function skipWhitespace()
public function skipWhitespace(): void
{
$peek = $this->getPeek();

View File

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

View File

@@ -23,41 +23,26 @@ namespace Symfony\Component\CssSelector\XPath\Extension;
*/
abstract class AbstractExtension implements ExtensionInterface
{
/**
* {@inheritdoc}
*/
public function getNodeTranslators(): array
{
return [];
}
/**
* {@inheritdoc}
*/
public function getCombinationTranslators(): array
{
return [];
}
/**
* {@inheritdoc}
*/
public function getFunctionTranslators(): array
{
return [];
}
/**
* {@inheritdoc}
*/
public function getPseudoClassTranslators(): array
{
return [];
}
/**
* {@inheritdoc}
*/
public function getAttributeMatchingTranslators(): array
{
return [];

View File

@@ -26,20 +26,17 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
*/
class AttributeMatchingExtension extends AbstractExtension
{
/**
* {@inheritdoc}
*/
public function getAttributeMatchingTranslators(): array
{
return [
'exists' => [$this, 'translateExists'],
'=' => [$this, 'translateEquals'],
'~=' => [$this, 'translateIncludes'],
'|=' => [$this, 'translateDashMatch'],
'^=' => [$this, 'translatePrefixMatch'],
'$=' => [$this, 'translateSuffixMatch'],
'*=' => [$this, 'translateSubstringMatch'],
'!=' => [$this, 'translateDifferent'],
'exists' => $this->translateExists(...),
'=' => $this->translateEquals(...),
'~=' => $this->translateIncludes(...),
'|=' => $this->translateDashMatch(...),
'^=' => $this->translatePrefixMatch(...),
'$=' => $this->translateSuffixMatch(...),
'*=' => $this->translateSubstringMatch(...),
'!=' => $this->translateDifferent(...),
];
}
@@ -109,9 +106,6 @@ class AttributeMatchingExtension extends AbstractExtension
));
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'attribute-matching';

View File

@@ -25,16 +25,13 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
*/
class CombinationExtension extends AbstractExtension
{
/**
* {@inheritdoc}
*/
public function getCombinationTranslators(): array
{
return [
' ' => [$this, 'translateDescendant'],
'>' => [$this, 'translateChild'],
'+' => [$this, 'translateDirectAdjacent'],
'~' => [$this, 'translateIndirectAdjacent'],
' ' => $this->translateDescendant(...),
'>' => $this->translateChild(...),
'+' => $this->translateDirectAdjacent(...),
'~' => $this->translateIndirectAdjacent(...),
];
}
@@ -61,9 +58,6 @@ class CombinationExtension extends AbstractExtension
return $xpath->join('/following-sibling::', $combinedXpath);
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'combination';

View File

@@ -30,18 +30,15 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
*/
class FunctionExtension extends AbstractExtension
{
/**
* {@inheritdoc}
*/
public function getFunctionTranslators(): array
{
return [
'nth-child' => [$this, 'translateNthChild'],
'nth-last-child' => [$this, 'translateNthLastChild'],
'nth-of-type' => [$this, 'translateNthOfType'],
'nth-last-of-type' => [$this, 'translateNthLastOfType'],
'contains' => [$this, 'translateContains'],
'lang' => [$this, 'translateLang'],
'nth-child' => $this->translateNthChild(...),
'nth-last-child' => $this->translateNthLastChild(...),
'nth-of-type' => $this->translateNthOfType(...),
'nth-last-of-type' => $this->translateNthLastOfType(...),
'contains' => $this->translateContains(...),
'lang' => $this->translateLang(...),
];
}
@@ -161,9 +158,6 @@ class FunctionExtension extends AbstractExtension
));
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'function';

View File

@@ -36,30 +36,24 @@ class HtmlExtension extends AbstractExtension
->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true);
}
/**
* {@inheritdoc}
*/
public function getPseudoClassTranslators(): array
{
return [
'checked' => [$this, 'translateChecked'],
'link' => [$this, 'translateLink'],
'disabled' => [$this, 'translateDisabled'],
'enabled' => [$this, 'translateEnabled'],
'selected' => [$this, 'translateSelected'],
'invalid' => [$this, 'translateInvalid'],
'hover' => [$this, 'translateHover'],
'visited' => [$this, 'translateVisited'],
'checked' => $this->translateChecked(...),
'link' => $this->translateLink(...),
'disabled' => $this->translateDisabled(...),
'enabled' => $this->translateEnabled(...),
'selected' => $this->translateSelected(...),
'invalid' => $this->translateInvalid(...),
'hover' => $this->translateHover(...),
'visited' => $this->translateVisited(...),
];
}
/**
* {@inheritdoc}
*/
public function getFunctionTranslators(): array
{
return [
'lang' => [$this, 'translateLang'],
'lang' => $this->translateLang(...),
];
}
@@ -177,9 +171,6 @@ class HtmlExtension extends AbstractExtension
return $xpath->addCondition('0');
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'html';

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,
) {
}
/**
@@ -59,21 +57,20 @@ class NodeExtension extends AbstractExtension
return (bool) ($this->flags & $flag);
}
/**
* {@inheritdoc}
*/
public function getNodeTranslators(): array
{
return [
'Selector' => [$this, 'translateSelector'],
'CombinedSelector' => [$this, 'translateCombinedSelector'],
'Negation' => [$this, 'translateNegation'],
'Function' => [$this, 'translateFunction'],
'Pseudo' => [$this, 'translatePseudo'],
'Attribute' => [$this, 'translateAttribute'],
'Class' => [$this, 'translateClass'],
'Hash' => [$this, 'translateHash'],
'Element' => [$this, 'translateElement'],
'Selector' => $this->translateSelector(...),
'CombinedSelector' => $this->translateCombinedSelector(...),
'Negation' => $this->translateNegation(...),
'Matching' => $this->translateMatching(...),
'SpecificityAdjustment' => $this->translateSpecificityAdjustment(...),
'Function' => $this->translateFunction(...),
'Pseudo' => $this->translatePseudo(...),
'Attribute' => $this->translateAttribute(...),
'Class' => $this->translateClass(...),
'Hash' => $this->translateHash(...),
'Element' => $this->translateElement(...),
];
}
@@ -100,6 +97,36 @@ class NodeExtension extends AbstractExtension
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());
@@ -182,9 +209,6 @@ class NodeExtension extends AbstractExtension
return $xpath;
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'node';

View File

@@ -26,20 +26,18 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
*/
class PseudoClassExtension extends AbstractExtension
{
/**
* {@inheritdoc}
*/
public function getPseudoClassTranslators(): array
{
return [
'root' => [$this, 'translateRoot'],
'first-child' => [$this, 'translateFirstChild'],
'last-child' => [$this, 'translateLastChild'],
'first-of-type' => [$this, 'translateFirstOfType'],
'last-of-type' => [$this, 'translateLastOfType'],
'only-child' => [$this, 'translateOnlyChild'],
'only-of-type' => [$this, 'translateOnlyOfType'],
'empty' => [$this, 'translateEmpty'],
'root' => $this->translateRoot(...),
'scope' => $this->translateScopePseudo(...),
'first-child' => $this->translateFirstChild(...),
'last-child' => $this->translateLastChild(...),
'first-of-type' => $this->translateFirstOfType(...),
'last-of-type' => $this->translateLastOfType(...),
'only-child' => $this->translateOnlyChild(...),
'only-of-type' => $this->translateOnlyOfType(...),
'empty' => $this->translateEmpty(...),
];
}
@@ -48,6 +46,11 @@ class PseudoClassExtension extends AbstractExtension
return $xpath->addCondition('not(parent::*)');
}
public function translateScopePseudo(XPathExpr $xpath): XPathExpr
{
return $xpath->addCondition('1');
}
public function translateFirstChild(XPathExpr $xpath): XPathExpr
{
return $xpath
@@ -112,9 +115,6 @@ class PseudoClassExtension extends AbstractExtension
return $xpath->addCondition('not(*) and not(string-length())');
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'pseudo-class';

View File

@@ -30,7 +30,7 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class Translator implements TranslatorInterface
{
private $mainParser;
private ParserInterface $mainParser;
/**
* @var ParserInterface[]
@@ -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();
@@ -87,9 +87,6 @@ class Translator implements TranslatorInterface
return sprintf('concat(%s)', implode(', ', $parts));
}
/**
* {@inheritdoc}
*/
public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string
{
$selectors = $this->parseSelectors($cssExpr);
@@ -106,9 +103,6 @@ class Translator implements TranslatorInterface
return implode(' | ', $selectors);
}
/**
* {@inheritdoc}
*/
public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string
{
return ($prefix ?: '').$this->nodeToXPath($selector);
@@ -220,7 +214,7 @@ class Translator implements TranslatorInterface
foreach ($this->shortcutParsers as $shortcut) {
$tokens = $shortcut->parse($css);
if (!empty($tokens)) {
if ($tokens) {
return $tokens;
}
}

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

@@ -20,7 +20,7 @@
}
],
"require": {
"php": ">=8.0.2"
"php": ">=8.2"
},
"autoload": {
"psr-4": { "Symfony\\Component\\CssSelector\\": "" },

View File

@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -1,4 +1,4 @@
Copyright (c) 2020-2022 Fabien Potencier
Copyright (c) 2020-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -22,5 +22,5 @@ trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use
This will generate the following message:
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
While not recommended, the deprecation notices can be completely ignored by declaring an empty
`function trigger_deprecation() {}` in your application.

View File

@@ -15,7 +15,7 @@
}
],
"require": {
"php": ">=8.0.2"
"php": ">=8.1"
},
"autoload": {
"files": [
@@ -25,7 +25,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",

View File

@@ -33,7 +33,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

@@ -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],
@@ -183,6 +181,11 @@ class ErrorHandler
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);

View File

@@ -72,7 +72,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();
}
@@ -274,12 +274,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>#', function ($m) {
return "<code $m[1]>".str_replace("\n", "</code>\n<code $m[1]>", $m[2]).'</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);
// Convert spaces to html entities to preserve indentation when rendered
$code = str_replace(' ', '&nbsp;', $code);
$content = explode("\n", $code);
} else {
// remove main code/span tags

View File

@@ -58,7 +58,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

@@ -242,7 +242,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

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

View File

@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -32,9 +32,6 @@ class Event implements StoppableEventInterface
{
private bool $propagationStopped = false;
/**
* {@inheritdoc}
*/
public function isPropagationStopped(): bool
{
return $this->propagationStopped;

View File

@@ -21,11 +21,13 @@ interface EventDispatcherInterface extends PsrEventDispatcherInterface
/**
* Dispatches an event to all registered listeners.
*
* @param object $event The event to pass to the event handlers/listeners
* @template T of object
*
* @param T $event The event to pass to the event handlers/listeners
* @param string|null $eventName The name of the event to dispatch. If not supplied,
* the class of $event should be used instead.
*
* @return object The passed $event MUST be returned
* @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

@@ -1,4 +1,4 @@
Copyright (c) 2018-2022 Fabien Potencier
Copyright (c) 2018-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -3,7 +3,7 @@ Symfony EventDispatcher Contracts
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
Can be used to build on semantics that the Symfony components proved useful and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.

View File

@@ -16,19 +16,16 @@
}
],
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"psr/event-dispatcher": "^1"
},
"suggest": {
"symfony/event-dispatcher-implementation": ""
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\EventDispatcher\Debug;
use Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -36,13 +37,13 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
* @var \SplObjectStorage<WrappedListener, array{string, string}>|null
*/
private ?\SplObjectStorage $callStack = null;
private $dispatcher;
private EventDispatcherInterface $dispatcher;
private array $wrappedListeners = [];
private array $orphanedEvents = [];
private $requestStack;
private ?RequestStack $requestStack;
private string $currentRequestHash = '';
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null)
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, ?LoggerInterface $logger = null, ?RequestStack $requestStack = null)
{
$this->dispatcher = $dispatcher;
$this->stopwatch = $stopwatch;
@@ -51,7 +52,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
}
/**
* {@inheritdoc}
* @return void
*/
public function addListener(string $eventName, callable|array $listener, int $priority = 0)
{
@@ -59,7 +60,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
}
/**
* {@inheritdoc}
* @return void
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
@@ -67,7 +68,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
}
/**
* {@inheritdoc}
* @return void
*/
public function removeListener(string $eventName, callable|array $listener)
{
@@ -81,28 +82,22 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
}
}
return $this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->removeListener($eventName, $listener);
}
/**
* {@inheritdoc}
* @return void
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->removeSubscriber($subscriber);
$this->dispatcher->removeSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function getListeners(string $eventName = null): array
public function getListeners(?string $eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority(string $eventName, callable|array $listener): ?int
{
// we might have wrapped listeners for the event (if called while dispatching)
@@ -118,24 +113,16 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners(string $eventName = null): bool
public function hasListeners(?string $eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function dispatch(object $event, string $eventName = null): object
public function dispatch(object $event, ?string $eventName = null): object
{
$eventName = $eventName ?? \get_class($event);
$eventName ??= $event::class;
if (null === $this->callStack) {
$this->callStack = new \SplObjectStorage();
}
$this->callStack ??= new \SplObjectStorage();
$currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
@@ -166,7 +153,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 [];
@@ -184,14 +171,12 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
return $called;
}
public function getNotCalledListeners(Request $request = null): array
public function getNotCalledListeners(?Request $request = null): array
{
try {
$allListeners = $this->getListeners();
$allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority();
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
}
$this->logger?->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
// unable to retrieve the uncalled listeners
return [];
@@ -211,23 +196,24 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
}
$notCalled = [];
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) {
foreach ($listeners as [$listener, $priority]) {
if (!\in_array($listener, $calledListeners, true)) {
if (!$listener instanceof WrappedListener) {
$listener = new WrappedListener($listener, null, $this->stopwatch, $this);
$listener = new WrappedListener($listener, null, $this->stopwatch, $this, $priority);
}
$notCalled[] = $listener->getInfo($eventName);
}
}
}
uasort($notCalled, [$this, 'sortNotCalledListeners']);
uasort($notCalled, $this->sortNotCalledListeners(...));
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)] ?? [];
@@ -240,6 +226,9 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
return array_merge(...array_values($this->orphanedEvents));
}
/**
* @return void
*/
public function reset()
{
$this->callStack = null;
@@ -260,6 +249,8 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
/**
* Called before dispatching the event.
*
* @return void
*/
protected function beforeDispatch(string $eventName, object $event)
{
@@ -267,6 +258,8 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
/**
* Called after dispatching the event.
*
* @return void
*/
protected function afterDispatch(string $eventName, object $event)
{
@@ -308,9 +301,7 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
}
if ($listener->wasCalled()) {
if (null !== $this->logger) {
$this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
}
$this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context);
} else {
$this->callStack->detach($listener);
}
@@ -320,16 +311,14 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
}
if ($listener->stoppedPropagation()) {
if (null !== $this->logger) {
$this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
}
$this->logger?->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
$skipped = true;
}
}
}
private function sortNotCalledListeners(array $a, array $b)
private function sortNotCalledListeners(array $a, array $b): int
{
if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
return $cmp;
@@ -353,4 +342,34 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
return 1;
}
private function getListenersWithPriority(): array
{
$result = [];
$allListeners = new \ReflectionProperty(EventDispatcher::class, 'listeners');
foreach ($allListeners->getValue($this->dispatcher) as $eventName => $listenersByPriority) {
foreach ($listenersByPriority as $priority => $listeners) {
foreach ($listeners as $listener) {
$result[$eventName][] = [$listener, $priority];
}
}
}
return $result;
}
private function getListenersWithoutPriority(): array
{
$result = [];
foreach ($this->getListeners() as $eventName => $listeners) {
foreach ($listeners as $listener) {
$result[$eventName][] = [$listener, null];
}
}
return $result;
}
}

View File

@@ -26,26 +26,29 @@ final class WrappedListener
private string $name;
private bool $called = false;
private bool $stoppedPropagation = false;
private $stopwatch;
private $dispatcher;
private Stopwatch $stopwatch;
private ?EventDispatcherInterface $dispatcher;
private string $pretty;
private $stub;
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)
public function __construct(callable|array $listener, ?string $name, Stopwatch $stopwatch, ?EventDispatcherInterface $dispatcher = null, ?int $priority = null)
{
$this->listener = $listener;
$this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null);
$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 = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0];
[$this->name, $this->callableRef] = $this->parseListener($listener);
$this->pretty = $this->name.'::'.$listener[1];
$this->callableRef .= '::'.$listener[1];
} elseif ($listener instanceof \Closure) {
$r = new \ReflectionFunction($listener);
if (str_contains($r->name, '{closure}')) {
if (str_contains($r->name, '{closure')) {
$this->pretty = $this->name = 'closure';
} elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
$this->name = $class->name;
@@ -58,6 +61,7 @@ final class WrappedListener
} else {
$this->name = get_debug_type($listener);
$this->pretty = $this->name.'::__invoke';
$this->callableRef = $listener::class.'::__invoke';
}
if (null !== $name) {
@@ -89,11 +93,11 @@ final class WrappedListener
public function getInfo(string $eventName): array
{
$this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
$this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->callableRef ?? $this->listener) : $this->pretty.'()';
return [
'event' => $eventName,
'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null),
'priority' => $this->priority ??= $this->dispatcher?->getListenerPriority($eventName, $this->listener),
'pretty' => $this->pretty,
'stub' => $this->stub,
];
@@ -104,18 +108,37 @@ final class WrappedListener
$dispatcher = $this->dispatcher ?: $dispatcher;
$this->called = true;
$this->priority = $dispatcher->getListenerPriority($eventName, $this->listener);
$this->priority ??= $dispatcher->getListenerPriority($eventName, $this->listener);
$e = $this->stopwatch->start($this->name, 'event_listener');
($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
if ($e->isStarted()) {
$e->stop();
try {
($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
} finally {
if ($e->isStarted()) {
$e->stop();
}
}
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
$this->stoppedPropagation = true;
}
}
private function parseListener(array $listener): array
{
if ($listener[0] instanceof \Closure) {
foreach ((new \ReflectionFunction($listener[0]))->getAttributes(\Closure::class) as $attribute) {
if ($name = $attribute->getArguments()['name'] ?? false) {
return [$name, $attribute->getArguments()['class'] ?? $name];
}
}
}
if (\is_object($listener[0])) {
return [get_debug_type($listener[0]), $listener[0]::class];
}
return [$listener[0], $listener[0]];
}
}

View File

@@ -48,6 +48,9 @@ class RegisterListenersPass implements CompilerPassInterface
return $this;
}
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) {
@@ -73,7 +76,7 @@ class RegisterListenersPass implements CompilerPassInterface
continue;
}
$event['method'] = $event['method'] ?? '__invoke';
$event['method'] ??= '__invoke';
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
}
@@ -83,17 +86,21 @@ class RegisterListenersPass implements CompilerPassInterface
$event['method'] = 'on'.preg_replace_callback([
'/(?<=\b|_)[a-z]/i',
'/[^a-z0-9]/i',
], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
], fn ($matches) => strtoupper($matches[0]), $event['event']);
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) {
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));
}
$event['method'] = '__invoke';
}
}
$dispatcherDefinition = $globalDispatcherDefinition;
if (isset($event['dispatcher'])) {
$dispatcherDefinition = $container->getDefinition($event['dispatcher']);
$dispatcherDefinition = $container->findDefinition($event['dispatcher']);
}
$dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
@@ -132,7 +139,7 @@ class RegisterListenersPass implements CompilerPassInterface
continue;
}
$dispatcherDefinitions[$attributes['dispatcher']] = $container->getDefinition($attributes['dispatcher']);
$dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($attributes['dispatcher']);
}
if (!$dispatcherDefinitions) {
@@ -191,7 +198,7 @@ class ExtractingEventDispatcher extends EventDispatcher implements EventSubscrib
public static array $aliases = [];
public static string $subscriber;
public function addListener(string $eventName, callable|array $listener, int $priority = 0)
public function addListener(string $eventName, callable|array $listener, int $priority = 0): void
{
$this->listeners[] = [$eventName, $listener[1], $priority];
}

View File

@@ -42,12 +42,9 @@ class EventDispatcher implements EventDispatcherInterface
}
}
/**
* {@inheritdoc}
*/
public function dispatch(object $event, string $eventName = null): object
public function dispatch(object $event, ?string $eventName = null): object
{
$eventName = $eventName ?? \get_class($event);
$eventName ??= $event::class;
if (isset($this->optimized)) {
$listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
@@ -62,10 +59,7 @@ class EventDispatcher implements EventDispatcherInterface
return $event;
}
/**
* {@inheritdoc}
*/
public function getListeners(string $eventName = null): array
public function getListeners(?string $eventName = null): array
{
if (null !== $eventName) {
if (empty($this->listeners[$eventName])) {
@@ -88,9 +82,6 @@ class EventDispatcher implements EventDispatcherInterface
return array_filter($this->sorted);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority(string $eventName, callable|array $listener): ?int
{
if (empty($this->listeners[$eventName])) {
@@ -99,14 +90,14 @@ class EventDispatcher implements EventDispatcherInterface
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
$listener[1] ??= '__invoke';
}
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
foreach ($listeners as &$v) {
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
$v[0] = $v[0]();
$v[1] = $v[1] ?? '__invoke';
$v[1] ??= '__invoke';
}
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
return $priority;
@@ -117,10 +108,7 @@ class EventDispatcher implements EventDispatcherInterface
return null;
}
/**
* {@inheritdoc}
*/
public function hasListeners(string $eventName = null): bool
public function hasListeners(?string $eventName = null): bool
{
if (null !== $eventName) {
return !empty($this->listeners[$eventName]);
@@ -136,7 +124,7 @@ class EventDispatcher implements EventDispatcherInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function addListener(string $eventName, callable|array $listener, int $priority = 0)
{
@@ -145,7 +133,7 @@ class EventDispatcher implements EventDispatcherInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function removeListener(string $eventName, callable|array $listener)
{
@@ -155,14 +143,14 @@ class EventDispatcher implements EventDispatcherInterface
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
$listener[1] ??= '__invoke';
}
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
foreach ($listeners as $k => &$v) {
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
$v[0] = $v[0]();
$v[1] = $v[1] ?? '__invoke';
$v[1] ??= '__invoke';
}
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
@@ -176,7 +164,7 @@ class EventDispatcher implements EventDispatcherInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
@@ -194,7 +182,7 @@ class EventDispatcher implements EventDispatcherInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
@@ -218,6 +206,8 @@ class EventDispatcher implements EventDispatcherInterface
* @param callable[] $listeners The event listeners
* @param string $eventName The name of the event to dispatch
* @param object $event The event object to pass to the event handlers/listeners
*
* @return void
*/
protected function callListeners(iterable $listeners, string $eventName, object $event)
{
@@ -234,16 +224,16 @@ class EventDispatcher implements EventDispatcherInterface
/**
* Sorts the internal list of listeners for the given event by priority.
*/
private function sortListeners(string $eventName)
private function sortListeners(string $eventName): void
{
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = [];
foreach ($this->listeners[$eventName] as &$listeners) {
foreach ($listeners as $k => &$listener) {
foreach ($listeners as &$listener) {
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
$listener[1] ??= '__invoke';
}
$this->sorted[$eventName][] = $listener;
}
@@ -265,12 +255,12 @@ class EventDispatcher implements EventDispatcherInterface
$closure = static function (...$args) use (&$listener, &$closure) {
if ($listener[0] instanceof \Closure) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
$listener[1] ??= '__invoke';
}
($closure = \Closure::fromCallable($listener))(...$args);
($closure = $listener(...))(...$args);
};
} else {
$closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener);
$closure = $listener instanceof WrappedListener ? $listener : $listener(...);
}
}
}

View File

@@ -27,6 +27,8 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface
*
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*
* @return void
*/
public function addListener(string $eventName, callable $listener, int $priority = 0);
@@ -35,14 +37,21 @@ interface EventDispatcherInterface extends ContractsEventDispatcherInterface
*
* The subscriber is asked for all the events it is
* interested in and added as a listener for these events.
*
* @return void
*/
public function addSubscriber(EventSubscriberInterface $subscriber);
/**
* Removes an event listener from the specified events.
*
* @return void
*/
public function removeListener(string $eventName, callable $listener);
/**
* @return void
*/
public function removeSubscriber(EventSubscriberInterface $subscriber);
/**
@@ -50,7 +59,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 +71,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

@@ -29,7 +29,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
protected $arguments;
/**
* Encapsulate an event with $subject and $args.
* 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

View File

@@ -18,23 +18,20 @@ namespace Symfony\Component\EventDispatcher;
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
private $dispatcher;
private EventDispatcherInterface $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
public function dispatch(object $event, string $eventName = null): object
public function dispatch(object $event, ?string $eventName = null): object
{
return $this->dispatcher->dispatch($event, $eventName);
}
/**
* {@inheritdoc}
* @return never
*/
public function addListener(string $eventName, callable|array $listener, int $priority = 0)
{
@@ -42,7 +39,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
}
/**
* {@inheritdoc}
* @return never
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
@@ -50,7 +47,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
}
/**
* {@inheritdoc}
* @return never
*/
public function removeListener(string $eventName, callable|array $listener)
{
@@ -58,33 +55,24 @@ class ImmutableEventDispatcher implements EventDispatcherInterface
}
/**
* {@inheritdoc}
* @return never
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function getListeners(string $eventName = null): array
public function getListeners(?string $eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority(string $eventName, callable|array $listener): ?int
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners(string $eventName = null): bool
public function hasListeners(?string $eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2023 Fabien Potencier
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -16,30 +16,27 @@
}
],
"require": {
"php": ">=8.0.2",
"symfony/event-dispatcher-contracts": "^2|^3"
"php": ">=8.1",
"symfony/event-dispatcher-contracts": "^2.5|^3"
},
"require-dev": {
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/expression-language": "^5.4|^6.0",
"symfony/config": "^5.4|^6.0",
"symfony/error-handler": "^5.4|^6.0",
"symfony/http-foundation": "^5.4|^6.0",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/stopwatch": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
"symfony/config": "^5.4|^6.0|^7.0",
"symfony/error-handler": "^5.4|^6.0|^7.0",
"symfony/http-foundation": "^5.4|^6.0|^7.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/stopwatch": "^5.4|^6.0|^7.0",
"psr/log": "^1|^2|^3"
},
"conflict": {
"symfony/dependency-injection": "<5.4"
"symfony/dependency-injection": "<5.4",
"symfony/service-contracts": "<2.5"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "2.0|3.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"autoload": {
"psr-4": { "Symfony\\Component\\EventDispatcher\\": "" },
"exclude-from-classmap": [

View File

@@ -286,7 +286,11 @@ class HeaderUtils
}
foreach ($partMatches as $matches) {
$parts[] = '' === $separators ? self::unquote($matches[0][0]) : self::groupParts($matches, $separators, false);
if ('' === $separators && '' !== $unquoted = self::unquote($matches[0][0])) {
$parts[] = $unquoted;
} elseif ($groupedParts = self::groupParts($matches, $separators, false)) {
$parts[] = $groupedParts;
}
}
return $parts;

View File

@@ -226,14 +226,11 @@ class MockArraySessionStorage implements SessionStorageInterface
/**
* Generates a session ID.
*
* This doesn't need to be particularly cryptographically secure since this is just
* a mock.
*
* @return string
*/
protected function generateId()
{
return hash('sha256', uniqid('ss_mock_', true));
return bin2hex(random_bytes(16));
}
protected function loadSession()

View File

@@ -22,7 +22,7 @@
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"predis/predis": "~1.0",
"predis/predis": "^1.0|^2.0",
"symfony/cache": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4",

View File

@@ -66,7 +66,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
$sessionMetadata = [];
$sessionAttributes = [];
$flashes = [];
if ($request->hasSession()) {
if (!$request->attributes->getBoolean('_stateless') && $request->hasSession()) {
$session = $request->getSession();
if ($session->isStarted()) {
$sessionMetadata['Created'] = date(\DATE_RFC822, $session->getMetadataBag()->getCreated());

View File

@@ -16,7 +16,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Base class for events thrown in the HttpKernel component.
* Base class for events dispatched in the HttpKernel component.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/

View File

@@ -97,7 +97,7 @@ class ProfilerListener implements EventSubscriberInterface
return;
}
$session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null;
$session = !$request->attributes->getBoolean('_stateless') && $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null;
if ($session instanceof Session) {
$usageIndexValue = $usageIndexReference = &$session->getUsageIndex();

View File

@@ -78,11 +78,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static $freshCache = [];
public const VERSION = '5.4.39';
public const VERSION_ID = 50439;
public const VERSION = '5.4.42';
public const VERSION_ID = 50442;
public const MAJOR_VERSION = 5;
public const MINOR_VERSION = 4;
public const RELEASE_VERSION = 39;
public const RELEASE_VERSION = 42;
public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2024';

View File

@@ -124,11 +124,18 @@ class Message extends RawMessage
public function ensureValidity()
{
if (!$this->headers->has('To') && !$this->headers->has('Cc') && !$this->headers->has('Bcc')) {
$to = (null !== $header = $this->headers->get('To')) ? $header->getBody() : null;
$cc = (null !== $header = $this->headers->get('Cc')) ? $header->getBody() : null;
$bcc = (null !== $header = $this->headers->get('Bcc')) ? $header->getBody() : null;
if (!$to && !$cc && !$bcc) {
throw new LogicException('An email must have a "To", "Cc", or "Bcc" header.');
}
if (!$this->headers->has('From') && !$this->headers->has('Sender')) {
$from = (null !== $header = $this->headers->get('From')) ? $header->getBody() : null;
$sender = (null !== $header = $this->headers->get('Sender')) ? $header->getBody() : null;
if (!$from && !$sender) {
throw new LogicException('An email must have a "From" or a "Sender" header.');
}

View File

@@ -280,10 +280,6 @@ final class Idn
switch ($data['status']) {
case 'disallowed':
$info->errors |= self::ERROR_DISALLOWED;
// no break.
case 'valid':
$str .= mb_chr($codePoint, 'utf-8');
@@ -294,7 +290,7 @@ final class Idn
break;
case 'mapped':
$str .= $data['mapping'];
$str .= $transitional && 0x1E9E === $codePoint ? 'ss' : $data['mapping'];
break;
@@ -346,6 +342,18 @@ final class Idn
$validationOptions = $options;
if ('xn--' === substr($label, 0, 4)) {
// Step 4.1. If the label contains any non-ASCII code point (i.e., a code point greater than U+007F),
// record that there was an error, and continue with the next label.
if (preg_match('/[^\x00-\x7F]/', $label)) {
$info->errors |= self::ERROR_PUNYCODE;
continue;
}
// Step 4.2. Attempt to convert the rest of the label to Unicode according to Punycode [RFC3492]. If
// that conversion fails, record that there was an error, and continue
// with the next label. Otherwise replace the original label in the string by the results of the
// conversion.
try {
$label = self::punycodeDecode(substr($label, 4));
} catch (\Exception $e) {
@@ -516,6 +524,8 @@ final class Idn
if ('-' === substr($label, -1, 1)) {
$info->errors |= self::ERROR_TRAILING_HYPHEN;
}
} elseif ('xn--' === substr($label, 0, 4)) {
$info->errors |= self::ERROR_PUNYCODE;
}
// Step 4. The label must not contain a U+002E (.) FULL STOP.

View File

@@ -48,6 +48,8 @@ namespace Symfony\Polyfill\Mbstring;
* - mb_strstr - Finds first occurrence of a string within another
* - mb_strwidth - Return width of string
* - mb_substr_count - Count the number of substring occurrences
* - mb_ucfirst - Make a string's first character uppercase
* - mb_lcfirst - Make a string's first character lowercase
*
* Not implemented:
* - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
@@ -80,6 +82,21 @@ final class Mbstring
public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
{
if (\is_array($s)) {
if (PHP_VERSION_ID < 70200) {
trigger_error('mb_convert_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING);
return null;
}
$r = [];
foreach ($s as $str) {
$r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding);
}
return $r;
}
if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) {
$fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
} else {
@@ -410,7 +427,7 @@ final class Mbstring
public static function mb_check_encoding($var = null, $encoding = null)
{
if (PHP_VERSION_ID < 70200 && \is_array($var)) {
if (\PHP_VERSION_ID < 70200 && \is_array($var)) {
trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING);
return null;
@@ -437,7 +454,6 @@ final class Mbstring
}
return true;
}
public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
@@ -827,7 +843,7 @@ final class Mbstring
return $code;
}
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null): string
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
{
if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
@@ -835,17 +851,8 @@ final class Mbstring
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
}
try {
$validEncoding = @self::mb_check_encoding('', $encoding);
} catch (\ValueError $e) {
throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
}
// BC for PHP 7.3 and lower
if (!$validEncoding) {
throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
} else {
self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given');
}
if (self::mb_strlen($pad_string, $encoding) <= 0) {
@@ -871,6 +878,34 @@ final class Mbstring
}
}
public static function mb_ucfirst(string $string, ?string $encoding = null): string
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
}
$firstChar = mb_substr($string, 0, 1, $encoding);
$firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding);
return $firstChar.mb_substr($string, 1, null, $encoding);
}
public static function mb_lcfirst(string $string, ?string $encoding = null): string
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
} else {
self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
}
$firstChar = mb_substr($string, 0, 1, $encoding);
$firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding);
return $firstChar.mb_substr($string, 1, null, $encoding);
}
private static function getSubpart($pos, $part, $haystack, $encoding)
{
if (false === $pos) {
@@ -944,4 +979,18 @@ final class Mbstring
return $encoding;
}
private static function assertEncoding(string $encoding, string $errorFormat): void
{
try {
$validEncoding = @self::mb_check_encoding('', $encoding);
} catch (\ValueError $e) {
throw new \ValueError(\sprintf($errorFormat, $encoding));
}
// BC for PHP 7.3 and lower
if (!$validEncoding) {
throw new \ValueError(\sprintf($errorFormat, $encoding));
}
}
}

View File

@@ -136,6 +136,14 @@ if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
if (!function_exists('mb_ucfirst')) {
function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
}
if (!function_exists('mb_lcfirst')) {
function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
}
if (extension_loaded('mbstring')) {
return;
}

View File

@@ -132,6 +132,14 @@ if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
if (!function_exists('mb_ucfirst')) {
function mb_ucfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
}
if (!function_exists('mb_lcfirst')) {
function mb_lcfirst($string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
}
if (extension_loaded('mbstring')) {
return;
}

View File

@@ -141,6 +141,7 @@ final class Php72
if ('\\' === \DIRECTORY_SEPARATOR) {
$stat = @fstat($stream);
// Check if formatted mode is S_IFCHR
return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
}

View File

@@ -1,19 +0,0 @@
Copyright (c) 2021-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,37 +0,0 @@
<?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\Polyfill\Php81;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Php81
{
public static function array_is_list(array $array): bool
{
if ([] === $array || $array === array_values($array)) {
return true;
}
$nextKey = -1;
foreach ($array as $k => $v) {
if ($k !== ++$nextKey) {
return false;
}
}
return true;
}
}

View File

@@ -1,18 +0,0 @@
Symfony Polyfill / Php81
========================
This component provides features added to PHP 8.1 core:
- [`array_is_list`](https://php.net/array_is_list)
- [`enum_exists`](https://php.net/enum-exists)
- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant
- [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types)
- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used)
More information can be found in the
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
License
=======
This library is released under the [MIT license](LICENSE).

View File

@@ -1,20 +0,0 @@
<?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.
*/
if (\PHP_VERSION_ID < 80100) {
#[Attribute(Attribute::TARGET_METHOD)]
final class ReturnTypeWillChange
{
public function __construct()
{
}
}
}

View File

@@ -1,28 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php81 as p;
if (\PHP_VERSION_ID >= 80100) {
return;
}
if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) {
define('MYSQLI_REFRESH_REPLICA', 64);
}
if (!function_exists('array_is_list')) {
function array_is_list(array $array): bool { return p\Php81::array_is_list($array); }
}
if (!function_exists('enum_exists')) {
function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; }
}

View File

@@ -1,33 +0,0 @@
{
"name": "symfony/polyfill-php81",
"type": "library",
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"keywords": ["polyfill", "shim", "compatibility", "portable"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.1"
},
"autoload": {
"psr-4": { "Symfony\\Polyfill\\Php81\\": "" },
"files": [ "bootstrap.php" ],
"classmap": [ "Resources/stubs" ]
},
"minimum-stability": "dev",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
}
}

View File

@@ -294,6 +294,7 @@ class Router implements RouterInterface, RequestMatcherInterface
}
$cache->write($dumper->dump(), $this->getRouteCollection()->getResources());
unset(self::$cache[$cache->getPath()]);
}
);
@@ -325,6 +326,7 @@ class Router implements RouterInterface, RequestMatcherInterface
$dumper = $this->getGeneratorDumperInstance();
$cache->write($dumper->dump(), $this->getRouteCollection()->getResources());
unset(self::$cache[$cache->getPath()]);
}
);

View File

@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -11,10 +11,15 @@
namespace Symfony\Contracts\Service\Attribute;
use Symfony\Contracts\Service\ServiceSubscriberTrait;
use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* Use with {@see ServiceSubscriberTrait} to mark a method's return type
* For use as the return value for {@see ServiceSubscriberInterface}.
*
* @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi'))
*
* Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type
* as a subscribed service.
*
* @author Kevin Bond <kevinbond@gmail.com>
@@ -22,12 +27,21 @@ use Symfony\Contracts\Service\ServiceSubscriberTrait;
#[\Attribute(\Attribute::TARGET_METHOD)]
final class SubscribedService
{
/** @var object[] */
public array $attributes;
/**
* @param string|null $key The key to use for the service
* If null, use "ClassName::methodName"
* @param string|null $key The key to use for the service
* @param class-string|null $type The service class
* @param bool $nullable Whether the service is optional
* @param object|object[] $attributes One or more dependency injection attributes to use
*/
public function __construct(
public ?string $key = null
public ?string $key = null,
public ?string $type = null,
public bool $nullable = false,
array|object $attributes = [],
) {
$this->attributes = \is_array($attributes) ? $attributes : [$attributes];
}
}

View File

@@ -3,7 +3,7 @@ Symfony Service Contracts
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
Can be used to build on semantics that the Symfony components proved useful and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.

View File

@@ -26,5 +26,8 @@ namespace Symfony\Contracts\Service;
*/
interface ResetInterface
{
/**
* @return void
*/
public function reset();
}

View File

@@ -26,34 +26,24 @@ class_exists(NotFoundExceptionInterface::class);
*/
trait ServiceLocatorTrait
{
private $factories;
private $loading = [];
private $providedTypes;
private array $factories;
private array $loading = [];
private array $providedTypes;
/**
* @param callable[] $factories
* @param array<string, callable> $factories
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function has(string $id)
public function has(string $id): bool
{
return isset($this->factories[$id]);
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function get(string $id)
public function get(string $id): mixed
{
if (!isset($this->factories[$id])) {
throw $this->createNotFoundException($id);
@@ -75,12 +65,9 @@ trait ServiceLocatorTrait
}
}
/**
* {@inheritdoc}
*/
public function getProvidedServices(): array
{
if (null === $this->providedTypes) {
if (!isset($this->providedTypes)) {
$this->providedTypes = [];
foreach ($this->factories as $name => $factory) {

View File

@@ -18,9 +18,18 @@ use Psr\Container\ContainerInterface;
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Mateusz Sip <mateusz.sip@gmail.com>
*
* @template-covariant T of mixed
*/
interface ServiceProviderInterface extends ContainerInterface
{
/**
* @return T
*/
public function get(string $id): mixed;
public function has(string $id): bool;
/**
* Returns an associative array of service types keyed by the identifiers provided by the current container.
*
@@ -30,7 +39,7 @@ interface ServiceProviderInterface extends ContainerInterface
* * ['foo' => '?'] means the container provides service name "foo" of unspecified type
* * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null
*
* @return string[] The provided service types, keyed by service names
* @return array<string, string> The provided service types, keyed by service names
*/
public function getProvidedServices(): array;
}

View File

@@ -11,6 +11,8 @@
namespace Symfony\Contracts\Service;
use Symfony\Contracts\Service\Attribute\SubscribedService;
/**
* A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
*
@@ -29,7 +31,8 @@ namespace Symfony\Contracts\Service;
interface ServiceSubscriberInterface
{
/**
* Returns an array of service types required by such instances, optionally keyed by the service names used internally.
* Returns an array of service types (or {@see SubscribedService} objects) required
* by such instances, optionally keyed by the service names used internally.
*
* For mandatory dependencies:
*
@@ -47,7 +50,13 @@ interface ServiceSubscriberInterface
* * ['?Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface']
*
* @return string[] The required service types, optionally keyed by service names
* additionally, an array of {@see SubscribedService}'s can be returned:
*
* * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)]
* * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)]
* * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))]
*
* @return string[]|SubscribedService[] The required service types, optionally keyed by service names
*/
public static function getSubscribedServices();
public static function getSubscribedServices(): array;
}

View File

@@ -12,91 +12,65 @@
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\Attribute\Required;
use Symfony\Contracts\Service\Attribute\SubscribedService;
trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class);
/**
* Implementation of ServiceSubscriberInterface that determines subscribed services from
* method return types. Service ids are available as "ClassName::methodName".
* Implementation of ServiceSubscriberInterface that determines subscribed services
* from methods that have the #[SubscribedService] attribute.
*
* Service ids are available as "ClassName::methodName" so that the implementation
* of subscriber methods can be just `return $this->container->get(__METHOD__);`.
*
* @property ContainerInterface $container
*
* @author Kevin Bond <kevinbond@gmail.com>
*
* @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead
*/
trait ServiceSubscriberTrait
{
/** @var ContainerInterface */
protected $container;
/**
* {@inheritdoc}
*/
public static function getSubscribedServices(): array
{
$services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];
$attributeOptIn = false;
if (\PHP_VERSION_ID >= 80000) {
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
if (self::class !== $method->getDeclaringClass()->name) {
continue;
}
if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) {
continue;
}
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name));
}
if (!$returnType = $method->getReturnType()) {
throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class));
}
$serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType;
if ($returnType->allowsNull()) {
$serviceId = '?'.$serviceId;
}
$services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId;
$attributeOptIn = true;
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
if (self::class !== $method->getDeclaringClass()->name) {
continue;
}
}
if (!$attributeOptIn) {
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
continue;
}
if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) {
continue;
}
if (self::class !== $method->getDeclaringClass()->name) {
continue;
}
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name));
}
if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) {
continue;
}
if (!$returnType = $method->getReturnType()) {
throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class));
}
if ($returnType->isBuiltin()) {
continue;
}
/* @var SubscribedService $attribute */
$attribute = $attribute->newInstance();
$attribute->key ??= self::class.'::'.$method->name;
$attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType;
$attribute->nullable = $returnType->allowsNull();
if (\PHP_VERSION_ID >= 80000) {
trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class);
}
$services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType);
if ($attribute->attributes) {
$services[] = $attribute;
} else {
$services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type;
}
}
return $services;
}
/**
* @required
*
* @return ContainerInterface|null
*/
public function setContainer(ContainerInterface $container)
#[Required]
public function setContainer(ContainerInterface $container): ?ContainerInterface
{
$ret = null;
if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) {

View File

@@ -16,23 +16,23 @@
}
],
"require": {
"php": ">=7.2.5",
"psr/container": "^1.1",
"symfony/deprecation-contracts": "^2.1|^3"
"php": ">=8.1",
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"suggest": {
"symfony/service-implementation": ""
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\Service\\": "" }
"psr-4": { "Symfony\\Contracts\\Service\\": "" },
"exclude-from-classmap": [
"/Test/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "2.5-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",

View File

@@ -74,7 +74,7 @@ abstract class AbstractString implements \Stringable, \JsonSerializable
foreach ($values as $k => $v) {
if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) {
$keys = $keys ?? array_keys($values);
$keys ??= array_keys($values);
$keys[$i] = $j;
}
@@ -383,7 +383,7 @@ abstract class AbstractString implements \Stringable, \JsonSerializable
return '' === $this->string;
}
abstract public function join(array $strings, string $lastGlue = null): static;
abstract public function join(array $strings, ?string $lastGlue = null): static;
public function jsonSerialize(): string
{
@@ -429,16 +429,16 @@ abstract class AbstractString implements \Stringable, \JsonSerializable
abstract public function reverse(): static;
abstract public function slice(int $start = 0, int $length = null): static;
abstract public function slice(int $start = 0, ?int $length = null): static;
abstract public function snake(): static;
abstract public function splice(string $replacement, int $start = 0, int $length = null): static;
abstract public function splice(string $replacement, int $start = 0, ?int $length = null): static;
/**
* @return static[]
*/
public function split(string $delimiter, int $limit = null, int $flags = null): array
public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
{
if (null === $flags) {
throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.');
@@ -448,19 +448,11 @@ abstract class AbstractString implements \Stringable, \JsonSerializable
$delimiter .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
try {
if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Splitting failed with '.$k.'.');
}
}
throw new RuntimeException('Splitting failed with unknown error code.');
throw new RuntimeException('Splitting failed with error: '.preg_last_error_msg());
}
} finally {
restore_error_handler();
@@ -503,7 +495,7 @@ abstract class AbstractString implements \Stringable, \JsonSerializable
abstract public function title(bool $allWords = false): static;
public function toByteString(string $toEncoding = null): ByteString
public function toByteString(?string $toEncoding = null): ByteString
{
$b = new ByteString();
@@ -515,20 +507,14 @@ abstract class AbstractString implements \Stringable, \JsonSerializable
return $b;
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
try {
$b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
} catch (InvalidArgumentException $e) {
if (!\function_exists('iconv')) {
throw $e;
}
$b->string = iconv('UTF-8', $toEncoding, $this->string);
$b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
} catch (\ValueError $e) {
if (!\function_exists('iconv')) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
} finally {
restore_error_handler();
$b->string = iconv('UTF-8', $toEncoding, $this->string);
}
return $b;
@@ -558,7 +544,7 @@ abstract class AbstractString implements \Stringable, \JsonSerializable
*/
public function trimPrefix($prefix): static
{
if (\is_array($prefix) || $prefix instanceof \Traversable) {
if (\is_array($prefix) || $prefix instanceof \Traversable) { // don't use is_iterable(), it's slow
foreach ($prefix as $s) {
$t = $this->trimPrefix($s);
@@ -592,7 +578,7 @@ abstract class AbstractString implements \Stringable, \JsonSerializable
*/
public function trimSuffix($suffix): static
{
if (\is_array($suffix) || $suffix instanceof \Traversable) {
if (\is_array($suffix) || $suffix instanceof \Traversable) { // don't use is_iterable(), it's slow
foreach ($suffix as $s) {
$t = $this->trimSuffix($s);

View File

@@ -37,20 +37,16 @@ abstract class AbstractUnicodeString extends AbstractString
private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
// the subset of folded case mappings that is not in lower case mappings
private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'İ', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'i̇', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];
// the subset of upper case mappings that map one code point to many code points
private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ'];
private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂'];
private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];
// the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', '', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', '', 'ᴘ', 'ᴛ', '', '', '', '', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', '', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', '', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', '', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', '', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '', '', '', '', '', '“', '”', '„', '‟', '', '″', '〝', '〞', '«', '»', '', '', '', '', '', '', '—', '―', '︱', '︲', '', '‖', '', '⁅', '⁆', '', '、', '。', '〈', '〉', '《', '》', '', '', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '', '', '', '', '∥', '≪', '≫', '⦅', '⦆'];
private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];
private static $transliterators = [];
private static $tableZero;
private static $tableWide;
private static array $transliterators = [];
private static array $tableZero;
private static array $tableWide;
public static function fromCodePoints(int ...$codes): static
{
@@ -121,10 +117,10 @@ abstract class AbstractUnicodeString extends AbstractString
$s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s);
$s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
} elseif (\function_exists('transliterator_transliterate')) {
if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) {
if (null === $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule)) {
if ('any-latin/bgn' === $rule) {
$rule = 'any-latin';
$transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule);
$transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule);
}
if (null === $transliterator) {
@@ -159,7 +155,9 @@ abstract class AbstractUnicodeString extends AbstractString
public function camel(): static
{
$str = clone $this;
$str->string = str_replace(' ', '', preg_replace_callback('/\b.(?![A-Z]{2,})/u', static function ($m) use (&$i) {
$str->string = str_replace(' ', '', preg_replace_callback('/\b.(?!\p{Lu})/u', static function ($m) {
static $i = 0;
return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
}, preg_replace('/[^\pL0-9]++/u', ' ', $this->string)));
@@ -192,7 +190,7 @@ abstract class AbstractUnicodeString extends AbstractString
if (!$compat || !\defined('Normalizer::NFKC_CF')) {
$str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
$str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8');
$str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $str->string), 'UTF-8');
} else {
$str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
}
@@ -200,7 +198,7 @@ abstract class AbstractUnicodeString extends AbstractString
return $str;
}
public function join(array $strings, string $lastGlue = null): static
public function join(array $strings, ?string $lastGlue = null): static
{
$str = clone $this;
@@ -230,19 +228,11 @@ abstract class AbstractUnicodeString extends AbstractString
$regexp .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
try {
if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
}
} finally {
restore_error_handler();
@@ -322,14 +312,14 @@ abstract class AbstractUnicodeString extends AbstractString
$replace = 'preg_replace';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
try {
if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
if ($lastError === $v && str_ends_with($k, '_ERROR')) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
@@ -368,9 +358,7 @@ abstract class AbstractUnicodeString extends AbstractString
$limit = $allWords ? -1 : 1;
$str->string = preg_replace_callback('/\b./u', static function (array $m): string {
return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
}, $str->string, $limit);
$str->string = preg_replace_callback('/\b./u', static fn (array $m): string => mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'), $str->string, $limit);
return $str;
}
@@ -467,7 +455,7 @@ abstract class AbstractUnicodeString extends AbstractString
$width = 0;
$s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);
if (false !== strpos($s, "\r")) {
if (str_contains($s, "\r")) {
$s = str_replace(["\r\n", "\r"], "\n", $s);
}
@@ -558,9 +546,7 @@ abstract class AbstractUnicodeString extends AbstractString
return -1;
}
if (null === self::$tableZero) {
self::$tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php';
}
self::$tableZero ??= require __DIR__.'/Resources/data/wcswidth_table_zero.php';
if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) {
$lbound = 0;
@@ -577,9 +563,7 @@ abstract class AbstractUnicodeString extends AbstractString
}
}
if (null === self::$tableWide) {
self::$tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php';
}
self::$tableWide ??= require __DIR__.'/Resources/data/wcswidth_table_wide.php';
if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) {
$lbound = 0;

View File

@@ -42,13 +42,13 @@ class ByteString extends AbstractString
* Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
*/
public static function fromRandom(int $length = 16, string $alphabet = null): self
public static function fromRandom(int $length = 16, ?string $alphabet = null): self
{
if ($length <= 0) {
throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length));
}
$alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC;
$alphabet ??= self::ALPHABET_ALPHANUMERIC;
$alphabetSize = \strlen($alphabet);
$bits = (int) ceil(log($alphabetSize, 2.0));
if ($bits <= 0 || $bits > 56) {
@@ -205,7 +205,7 @@ class ByteString extends AbstractString
return '' === $this->string || preg_match('//u', $this->string);
}
public function join(array $strings, string $lastGlue = null): static
public function join(array $strings, ?string $lastGlue = null): static
{
$str = clone $this;
@@ -236,19 +236,11 @@ class ByteString extends AbstractString
$regexp .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
try {
if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
}
} finally {
restore_error_handler();
@@ -308,14 +300,14 @@ class ByteString extends AbstractString
$replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
try {
if (null === $string = $replace($fromRegexp, $to, $this->string)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
if ($lastError === $v && str_ends_with($k, '_ERROR')) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
@@ -340,7 +332,7 @@ class ByteString extends AbstractString
return $str;
}
public function slice(int $start = 0, int $length = null): static
public function slice(int $start = 0, ?int $length = null): static
{
$str = clone $this;
$str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);
@@ -356,7 +348,7 @@ class ByteString extends AbstractString
return $str;
}
public function splice(string $replacement, int $start = 0, int $length = null): static
public function splice(string $replacement, int $start = 0, ?int $length = null): static
{
$str = clone $this;
$str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
@@ -364,9 +356,9 @@ class ByteString extends AbstractString
return $str;
}
public function split(string $delimiter, int $limit = null, int $flags = null): array
public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
{
if (1 > $limit = $limit ?? \PHP_INT_MAX) {
if (1 > $limit ??= \PHP_INT_MAX) {
throw new InvalidArgumentException('Split limit must be a positive integer.');
}
@@ -410,12 +402,12 @@ class ByteString extends AbstractString
return $str;
}
public function toUnicodeString(string $fromEncoding = null): UnicodeString
public function toUnicodeString(?string $fromEncoding = null): UnicodeString
{
return new UnicodeString($this->toCodePointString($fromEncoding)->string);
}
public function toCodePointString(string $fromEncoding = null): CodePointString
public function toCodePointString(?string $fromEncoding = null): CodePointString
{
$u = new CodePointString();
@@ -425,7 +417,7 @@ class ByteString extends AbstractString
return $u;
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
try {
try {

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