Update Composer packages

This commit is contained in:
Paul Nicoué 2025-01-12 18:56:44 +01:00
parent a80f1abaa4
commit 0a904482ae
41 changed files with 922 additions and 679 deletions

View file

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

View file

@ -20,7 +20,7 @@ if (!function_exists('trigger_deprecation')) {
*
* @author Nicolas Grekas <p@tchwork.com>
*/
function trigger_deprecation(string $package, string $version, string $message, ...$args): void
function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void
{
@trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
}

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

@ -33,9 +33,6 @@
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"

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

@ -30,9 +30,6 @@
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"

View file

@ -11,6 +11,7 @@
namespace Symfony\Component\Yaml\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\CI\GithubActionReporter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
@ -32,34 +33,31 @@ use Symfony\Component\Yaml\Yaml;
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
#[AsCommand(name: 'lint:yaml', description: 'Lint a YAML file and outputs encountered errors')]
class LintCommand extends Command
{
protected static $defaultName = 'lint:yaml';
protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors';
private Parser $parser;
private ?string $format = null;
private bool $displayCorrectFiles;
private ?\Closure $directoryIteratorProvider;
private ?\Closure $isReadableProvider;
private $parser;
private $format;
private $displayCorrectFiles;
private $directoryIteratorProvider;
private $isReadableProvider;
public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null)
public function __construct(?string $name = null, ?callable $directoryIteratorProvider = null, ?callable $isReadableProvider = null)
{
parent::__construct($name);
$this->directoryIteratorProvider = $directoryIteratorProvider;
$this->isReadableProvider = $isReadableProvider;
$this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...);
$this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...);
}
/**
* {@inheritdoc}
* @return void
*/
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')
->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())))
->addOption('exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to exclude')
->addOption('parse-tags', null, InputOption::VALUE_NEGATABLE, 'Parse custom tags', null)
->setHelp(<<<EOF
@ -88,7 +86,7 @@ EOF
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$filenames = (array) $input->getArgument('filename');
@ -96,10 +94,6 @@ EOF
$this->format = $input->getOption('format');
$flags = $input->getOption('parse-tags');
if ('github' === $this->format && !class_exists(GithubActionReporter::class)) {
throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.');
}
if (null === $this->format) {
// Autodetect format according to CI environment
$this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt';
@ -133,7 +127,7 @@ EOF
return $this->display($io, $filesInfo);
}
private function validate(string $content, int $flags, string $file = null)
private function validate(string $content, int $flags, ?string $file = null): array
{
$prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) {
if (\E_USER_DEPRECATED === $level) {
@ -156,16 +150,12 @@ EOF
private function display(SymfonyStyle $io, array $files): int
{
switch ($this->format) {
case 'txt':
return $this->displayTxt($io, $files);
case 'json':
return $this->displayJson($io, $files);
case 'github':
return $this->displayTxt($io, $files, true);
default:
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
}
return match ($this->format) {
'txt' => $this->displayTxt($io, $files),
'json' => $this->displayJson($io, $files),
'github' => $this->displayTxt($io, $files, true),
default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))),
};
}
private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int
@ -186,7 +176,7 @@ EOF
$io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
$io->text(sprintf('<error> >> %s</error>', $info['message']));
if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) {
if (str_contains($info['message'], 'PARSE_CUSTOM_TAGS')) {
$suggestTagOption = true;
}
@ -215,7 +205,7 @@ EOF
++$errors;
}
if (isset($v['message']) && false !== strpos($v['message'], 'PARSE_CUSTOM_TAGS')) {
if (isset($v['message']) && str_contains($v['message'], 'PARSE_CUSTOM_TAGS')) {
$v['message'] .= ' Use the --parse-tags option if you want parse custom tags.';
}
});
@ -244,21 +234,15 @@ EOF
private function getParser(): Parser
{
if (!$this->parser) {
$this->parser = new Parser();
}
return $this->parser;
return $this->parser ??= new Parser();
}
private function getDirectoryIterator(string $directory): iterable
{
$default = function ($directory) {
return new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
};
$default = fn ($directory) => new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
if (null !== $this->directoryIteratorProvider) {
return ($this->directoryIteratorProvider)($directory, $default);
@ -269,9 +253,7 @@ EOF
private function isReadable(string $fileOrDirectory): bool
{
$default = function ($fileOrDirectory) {
return is_readable($fileOrDirectory);
};
$default = is_readable(...);
if (null !== $this->isReadableProvider) {
return ($this->isReadableProvider)($fileOrDirectory, $default);
@ -283,7 +265,12 @@ EOF
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues(['txt', 'json', 'github']);
$suggestions->suggestValues($this->getAvailableFormatOptions());
}
}
private function getAvailableFormatOptions(): array
{
return ['txt', 'json', 'github'];
}
}

View file

@ -24,10 +24,8 @@ class Dumper
{
/**
* The amount of spaces to use for indentation of nested nodes.
*
* @var int
*/
protected $indentation;
private int $indentation;
public function __construct(int $indentation = 4)
{
@ -46,7 +44,7 @@ class Dumper
* @param int $indent The level of indentation (used internally)
* @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
*/
public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): string
public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags = 0): string
{
$output = '';
$prefix = $indent ? str_repeat(' ', $indent) : '';
@ -68,7 +66,11 @@ class Dumper
$output .= "\n";
}
if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r")) {
if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) {
$key = (string) $key;
}
if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && str_contains($value, "\n") && !str_contains($value, "\r")) {
$blockIndentationIndicator = $this->getBlockIndentationIndicator($value);
if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) {
@ -95,7 +97,7 @@ class Dumper
if ($value instanceof TaggedValue) {
$output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag());
if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) {
if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && str_contains($value->getValue(), "\n") && !str_contains($value->getValue(), "\r\n")) {
$blockIndentationIndicator = $this->getBlockIndentationIndicator($value->getValue());
$output .= sprintf(' |%s', $blockIndentationIndicator);
@ -140,7 +142,7 @@ class Dumper
{
$output = sprintf('%s!%s', $prefix ? $prefix.' ' : '', $value->getTag());
if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) {
if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && str_contains($value->getValue(), "\n") && !str_contains($value->getValue(), "\r\n")) {
$blockIndentationIndicator = $this->getBlockIndentationIndicator($value->getValue());
$output .= sprintf(' |%s', $blockIndentationIndicator);

View file

@ -18,10 +18,10 @@ namespace Symfony\Component\Yaml\Exception;
*/
class ParseException extends RuntimeException
{
private $parsedFile;
private $parsedLine;
private $snippet;
private $rawMessage;
private ?string $parsedFile;
private int $parsedLine;
private ?string $snippet;
private string $rawMessage;
/**
* @param string $message The error message
@ -29,7 +29,7 @@ class ParseException extends RuntimeException
* @param string|null $snippet The snippet of code near the problem
* @param string|null $parsedFile The file name where the error occurred
*/
public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null)
public function __construct(string $message, int $parsedLine = -1, ?string $snippet = null, ?string $parsedFile = null, ?\Throwable $previous = null)
{
$this->parsedFile = $parsedFile;
$this->parsedLine = $parsedLine;
@ -43,16 +43,16 @@ class ParseException extends RuntimeException
/**
* Gets the snippet of code near the error.
*
* @return string
*/
public function getSnippet()
public function getSnippet(): string
{
return $this->snippet;
}
/**
* Sets the snippet of code near the error.
*
* @return void
*/
public function setSnippet(string $snippet)
{
@ -65,16 +65,16 @@ class ParseException extends RuntimeException
* Gets the filename where the error occurred.
*
* This method returns null if a string is parsed.
*
* @return string
*/
public function getParsedFile()
public function getParsedFile(): string
{
return $this->parsedFile;
}
/**
* Sets the filename where the error occurred.
*
* @return void
*/
public function setParsedFile(string $parsedFile)
{
@ -85,16 +85,16 @@ class ParseException extends RuntimeException
/**
* Gets the line where the error occurred.
*
* @return int
*/
public function getParsedLine()
public function getParsedLine(): int
{
return $this->parsedLine;
}
/**
* Sets the line where the error occurred.
*
* @return void
*/
public function setParsedLine(int $parsedLine)
{
@ -103,12 +103,12 @@ class ParseException extends RuntimeException
$this->updateRepr();
}
private function updateRepr()
private function updateRepr(): void
{
$this->message = $this->rawMessage;
$dot = false;
if ('.' === substr($this->message, -1)) {
if (str_ends_with($this->message, '.')) {
$this->message = substr($this->message, 0, -1);
$dot = true;
}

View file

@ -26,15 +26,15 @@ class Inline
{
public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
public static $parsedLineNumber = -1;
public static $parsedFilename;
public static int $parsedLineNumber = -1;
public static ?string $parsedFilename = null;
private static $exceptionOnInvalidType = false;
private static $objectSupport = false;
private static $objectForMap = false;
private static $constantSupport = false;
private static bool $exceptionOnInvalidType = false;
private static bool $objectSupport = false;
private static bool $objectForMap = false;
private static bool $constantSupport = false;
public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null)
public static function initialize(int $flags, ?int $parsedLineNumber = null, ?string $parsedFilename = null): void
{
self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
@ -50,20 +50,13 @@ class Inline
/**
* Converts a YAML string to a PHP value.
*
* @param string|null $value A YAML string
* @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior
* @param array $references Mapping of variable names to values
*
* @return mixed
* @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior
* @param array $references Mapping of variable names to values
*
* @throws ParseException
*/
public static function parse(string $value = null, int $flags = 0, array &$references = [])
public static function parse(string $value, int $flags = 0, array &$references = []): mixed
{
if (null === $value) {
return '';
}
self::initialize($flags);
$value = trim($value);
@ -72,42 +65,31 @@ class Inline
return '';
}
if (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
$i = 0;
$tag = self::parseTag($value, $i, $flags);
switch ($value[$i]) {
case '[':
$result = self::parseSequence($value, $flags, $i, $references);
++$i;
break;
case '{':
$result = self::parseMapping($value, $flags, $i, $references);
++$i;
break;
default:
$result = self::parseScalar($value, $flags, null, $i, true, $references);
}
try {
$i = 0;
$tag = self::parseTag($value, $i, $flags);
switch ($value[$i]) {
case '[':
$result = self::parseSequence($value, $flags, $i, $references);
++$i;
break;
case '{':
$result = self::parseMapping($value, $flags, $i, $references);
++$i;
break;
default:
$result = self::parseScalar($value, $flags, null, $i, true, $references);
}
// some comments are allowed at the end
if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) {
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if (null !== $tag && '' !== $tag) {
return new TaggedValue($tag, $result);
}
return $result;
} finally {
if (isset($mbEncoding)) {
mb_internal_encoding($mbEncoding);
}
// some comments are allowed at the end
if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) {
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if (null !== $tag && '' !== $tag) {
return new TaggedValue($tag, $result);
}
return $result;
}
/**
@ -118,7 +100,7 @@ class Inline
*
* @throws DumpException When trying to dump PHP resource
*/
public static function dump($value, int $flags = 0): string
public static function dump(mixed $value, int $flags = 0): string
{
switch (true) {
case \is_resource($value):
@ -128,9 +110,13 @@ class Inline
return self::dumpNull($flags);
case $value instanceof \DateTimeInterface:
return $value->format('c');
return $value->format(match (true) {
!$length = \strlen(rtrim($value->format('u'), '0')) => 'c',
$length < 4 => 'Y-m-d\TH:i:s.vP',
default => 'Y-m-d\TH:i:s.uP',
});
case $value instanceof \UnitEnum:
return sprintf('!php/const %s::%s', \get_class($value), $value->name);
return sprintf('!php/const %s::%s', $value::class, $value->name);
case \is_object($value):
if ($value instanceof TaggedValue) {
return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
@ -141,13 +127,7 @@ class Inline
}
if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
$output = [];
foreach ($value as $key => $val) {
$output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
}
return sprintf('{ %s }', implode(', ', $output));
return self::dumpHashArray($value, $flags);
}
if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
@ -176,8 +156,8 @@ class Inline
$repr = str_ireplace('INF', '.Inf', $repr);
} elseif (floor($value) == $value && $repr == $value) {
// Preserve float data type since storing a whole number will result in integer value.
if (false === strpos($repr, 'E')) {
$repr = $repr.'.0';
if (!str_contains($repr, 'E')) {
$repr .= '.0';
}
}
} else {
@ -195,6 +175,14 @@ class Inline
case Escaper::requiresDoubleQuoting($value):
return Escaper::escapeWithDoubleQuotes($value);
case Escaper::requiresSingleQuoting($value):
$singleQuoted = Escaper::escapeWithSingleQuotes($value);
if (!str_contains($value, "'")) {
return $singleQuoted;
}
// Attempt double-quoting the string instead to see if it's more efficient.
$doubleQuoted = Escaper::escapeWithDoubleQuotes($value);
return \strlen($doubleQuoted) < \strlen($singleQuoted) ? $doubleQuoted : $singleQuoted;
case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value):
case Parser::preg_match(self::getHexRegex(), $value):
case Parser::preg_match(self::getTimestampRegex(), $value):
@ -206,10 +194,8 @@ class Inline
/**
* Check if given array is hash or just normal indexed array.
*
* @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check
*/
public static function isHash($value): bool
public static function isHash(array|\ArrayObject|\stdClass $value): bool
{
if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
return true;
@ -244,9 +230,23 @@ class Inline
return sprintf('[%s]', implode(', ', $output));
}
// hash
return self::dumpHashArray($value, $flags);
}
/**
* Dumps hash array to a YAML string.
*
* @param array|\ArrayObject|\stdClass $value The hash array to dump
* @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
*/
private static function dumpHashArray(array|\ArrayObject|\stdClass $value, int $flags): string
{
$output = [];
foreach ($value as $key => $val) {
if (\is_int($key) && Yaml::DUMP_NUMERIC_KEY_AS_STRING & $flags) {
$key = (string) $key;
}
$output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
}
@ -265,11 +265,9 @@ class Inline
/**
* Parses a YAML scalar.
*
* @return mixed
*
* @throws ParseException When malformed inline YAML string is parsed
*/
public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null)
public static function parseScalar(string $scalar, int $flags = 0, ?array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], ?bool &$isQuoted = null): mixed
{
if (\in_array($scalar[$i], ['"', "'"], true)) {
// quoted scalar
@ -379,12 +377,12 @@ class Inline
$value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted);
// the value can be an array if a reference has been resolved to an array var
if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) {
if (\is_string($value) && !$isQuoted && str_contains($value, ': ')) {
// embedded mapping?
try {
$pos = 0;
$value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
} catch (\InvalidArgumentException $e) {
} catch (\InvalidArgumentException) {
// no, it's not
}
}
@ -412,11 +410,9 @@ class Inline
/**
* Parses a YAML mapping.
*
* @return array|\stdClass
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = [])
private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []): array|\stdClass
{
$output = [];
$len = \strlen($mapping);
@ -448,7 +444,7 @@ class Inline
throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping);
}
if ('!php/const' === $key) {
if ('!php/const' === $key || '!php/enum' === $key) {
$key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false);
$key = self::evaluateScalar($key, $flags);
}
@ -531,7 +527,7 @@ class Inline
if ('<<' === $key) {
$output += $value;
} elseif ($allowOverwrite || !isset($output[$key])) {
if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) {
if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && !self::isBinaryString($value) && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) {
$references[$matches['ref']] = $matches['value'];
$value = $matches['value'];
}
@ -558,16 +554,14 @@ class Inline
/**
* Evaluates scalars and replaces magic values.
*
* @return mixed
*
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
*/
private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null)
private static function evaluateScalar(string $scalar, int $flags, array &$references = [], ?bool &$isQuotedString = null): mixed
{
$isQuotedString = false;
$scalar = trim($scalar);
if (0 === strpos($scalar, '*')) {
if (str_starts_with($scalar, '*')) {
if (false !== $pos = strpos($scalar, '#')) {
$value = substr($scalar, 1, $pos - 2);
} else {
@ -599,7 +593,7 @@ class Inline
return false;
case '!' === $scalar[0]:
switch (true) {
case 0 === strpos($scalar, '!!str '):
case str_starts_with($scalar, '!!str '):
$s = (string) substr($scalar, 6);
if (\in_array($s[0] ?? '', ['"', "'"], true)) {
@ -608,14 +602,12 @@ class Inline
}
return $s;
case 0 === strpos($scalar, '! '):
case str_starts_with($scalar, '! '):
return substr($scalar, 2);
case 0 === strpos($scalar, '!php/object'):
case str_starts_with($scalar, '!php/object'):
if (self::$objectSupport) {
if (!isset($scalar[12])) {
trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/object tag without a value is deprecated.');
return false;
throw new ParseException('Missing value for tag "!php/object".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return unserialize(self::parseScalar(substr($scalar, 12)));
@ -626,12 +618,10 @@ class Inline
}
return null;
case 0 === strpos($scalar, '!php/const'):
case str_starts_with($scalar, '!php/const'):
if (self::$constantSupport) {
if (!isset($scalar[11])) {
trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/const tag without a value is deprecated.');
return '';
throw new ParseException('Missing value for tag "!php/const".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
$i = 0;
@ -646,9 +636,43 @@ class Inline
}
return null;
case 0 === strpos($scalar, '!!float '):
case str_starts_with($scalar, '!php/enum'):
if (self::$constantSupport) {
if (!isset($scalar[11])) {
throw new ParseException('Missing value for tag "!php/enum".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
$i = 0;
$enum = self::parseScalar(substr($scalar, 10), 0, null, $i, false);
if ($useValue = str_ends_with($enum, '->value')) {
$enum = substr($enum, 0, -7);
}
if (!\defined($enum)) {
throw new ParseException(sprintf('The enum "%s" is not defined.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
$value = \constant($enum);
if (!$value instanceof \UnitEnum) {
throw new ParseException(sprintf('The string "%s" is not the name of a valid enum.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (!$useValue) {
return $value;
}
if (!$value instanceof \BackedEnum) {
throw new ParseException(sprintf('The enum "%s" defines no value next to its name.', $enum), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return $value->value;
}
if (self::$exceptionOnInvalidType) {
throw new ParseException(sprintf('The string "%s" could not be parsed as an enum. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return null;
case str_starts_with($scalar, '!!float '):
return (float) substr($scalar, 8);
case 0 === strpos($scalar, '!!binary '):
case str_starts_with($scalar, '!!binary '):
return self::evaluateBinaryScalar(substr($scalar, 9));
}
@ -668,22 +692,7 @@ class Inline
switch (true) {
case ctype_digit($scalar):
if (preg_match('/^0[0-7]+$/', $scalar)) {
trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0. Use "%s" to represent the octal number.', '0o'.substr($scalar, 1));
return octdec($scalar);
}
$cast = (int) $scalar;
return ($scalar === (string) $cast) ? $cast : $scalar;
case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
if (preg_match('/^-0[0-7]+$/', $scalar)) {
trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0. Use "%s" to represent the octal number.', '-0o'.substr($scalar, 2));
return -octdec(substr($scalar, 1));
}
$cast = (int) $scalar;
return ($scalar === (string) $cast) ? $cast : $scalar;
@ -701,17 +710,21 @@ class Inline
return (float) str_replace('_', '', $scalar);
case Parser::preg_match(self::getTimestampRegex(), $scalar):
// When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
$time = new \DateTime($scalar, new \DateTimeZone('UTC'));
$time = new \DateTimeImmutable($scalar, new \DateTimeZone('UTC'));
if (Yaml::PARSE_DATETIME & $flags) {
return $time;
}
if ('' !== rtrim($time->format('u'), '0')) {
return (float) $time->format('U.u');
}
try {
if (false !== $scalar = $time->getTimestamp()) {
return $scalar;
}
} catch (\ValueError $e) {
} catch (\ValueError) {
// no-op
}
@ -739,7 +752,7 @@ class Inline
}
// Is followed by a scalar and is a built-in tag
if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) {
if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || \in_array($tag, ['str', 'php/const', 'php/enum', 'php/object'], true))) {
// Manage in {@link self::evaluateScalar()}
return null;
}

View file

@ -27,17 +27,17 @@ class Parser
public const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
public const REFERENCE_PATTERN = '#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u';
private $filename;
private $offset = 0;
private $numberOfParsedLines = 0;
private $totalNumberOfLines;
private $lines = [];
private $currentLineNb = -1;
private $currentLine = '';
private $refs = [];
private $skippedLineNumbers = [];
private $locallySkippedLineNumbers = [];
private $refsBeingParsed = [];
private ?string $filename = null;
private int $offset = 0;
private int $numberOfParsedLines = 0;
private ?int $totalNumberOfLines = null;
private array $lines = [];
private int $currentLineNb = -1;
private string $currentLine = '';
private array $refs = [];
private array $skippedLineNumbers = [];
private array $locallySkippedLineNumbers = [];
private array $refsBeingParsed = [];
/**
* Parses a YAML file into a PHP value.
@ -45,11 +45,9 @@ class Parser
* @param string $filename The path to the YAML file to be parsed
* @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior
*
* @return mixed
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public function parseFile(string $filename, int $flags = 0)
public function parseFile(string $filename, int $flags = 0): mixed
{
if (!is_file($filename)) {
throw new ParseException(sprintf('File "%s" does not exist.', $filename));
@ -74,11 +72,9 @@ class Parser
* @param string $value A YAML string
* @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior
*
* @return mixed
*
* @throws ParseException If the YAML is not valid
*/
public function parse(string $value, int $flags = 0)
public function parse(string $value, int $flags = 0): mixed
{
if (false === preg_match('//u', $value)) {
throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
@ -86,19 +82,9 @@ class Parser
$this->refs = [];
$mbEncoding = null;
if (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8');
}
try {
$data = $this->doParse($value, $flags);
} finally {
if (null !== $mbEncoding) {
mb_internal_encoding($mbEncoding);
}
$this->refsBeingParsed = [];
$this->offset = 0;
$this->lines = [];
@ -113,7 +99,7 @@ class Parser
return $data;
}
private function doParse(string $value, int $flags)
private function doParse(string $value, int $flags): mixed
{
$this->currentLineNb = -1;
$this->currentLine = '';
@ -121,10 +107,7 @@ class Parser
$this->lines = explode("\n", $value);
$this->numberOfParsedLines = \count($this->lines);
$this->locallySkippedLineNumbers = [];
if (null === $this->totalNumberOfLines) {
$this->totalNumberOfLines = $this->numberOfParsedLines;
}
$this->totalNumberOfLines ??= $this->numberOfParsedLines;
if (!$this->moveToNextLine()) {
return null;
@ -175,7 +158,7 @@ class Parser
}
// array
if (isset($values['value']) && 0 === strpos(ltrim($values['value'], ' '), '-')) {
if (isset($values['value']) && str_starts_with(ltrim($values['value'], ' '), '-')) {
// Inline first child
$currentLineNumber = $this->getRealCurrentLineNb();
@ -184,7 +167,7 @@ class Parser
$sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true);
$data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags);
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
} elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || str_starts_with(ltrim($values['value'], ' '), '#')) {
$data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags);
} elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
$data[] = new TaggedValue(
@ -199,9 +182,8 @@ class Parser
|| self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
)
) {
// this is a compact notation element, add to next block and parse
$block = $values['value'];
if ($this->isNextLineIndented()) {
if ($this->isNextLineIndented() || isset($matches['value']) && '>-' === $matches['value']) {
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
}
@ -215,9 +197,14 @@ class Parser
array_pop($this->refsBeingParsed);
}
} elseif (
// @todo in 7.0 remove legacy "(?:!?!php/const:)?"
self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(( |\t)++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
&& (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
&& (!str_contains($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
) {
if (str_starts_with($values['key'], '!php/const:')) {
trigger_deprecation('symfony/yaml', '6.2', 'YAML syntax for key "%s" is deprecated and replaced by "!php/const %s".', $values['key'], substr($values['key'], 11));
}
if ($context && 'sequence' == $context) {
throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
@ -311,7 +298,7 @@ class Parser
$subTag = null;
if ($mergeNode) {
// Merge keys
} elseif (!isset($values['value']) || '' === $values['value'] || 0 === strpos($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
} elseif (!isset($values['value']) || '' === $values['value'] || str_starts_with($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
// hash
// if next line is less indented or equal, then it means that the current value is null
if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
@ -459,7 +446,7 @@ class Parser
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
if (false !== strpos($line, ': ')) {
if (str_contains($line, ': ')) {
throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
@ -469,7 +456,7 @@ class Parser
$value .= ' ';
}
if ('' !== $trimmedLine && '\\' === substr($line, -1)) {
if ('' !== $trimmedLine && str_ends_with($line, '\\')) {
$value .= ltrim(substr($line, 0, -1));
} elseif ('' !== $trimmedLine) {
$value .= $trimmedLine;
@ -478,7 +465,7 @@ class Parser
if ('' === $trimmedLine) {
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === substr($line, -1)) {
} elseif (str_ends_with($line, '\\')) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true;
} else {
@ -489,7 +476,7 @@ class Parser
try {
return Inline::parse(trim($value));
} catch (ParseException $e) {
} catch (ParseException) {
// fall-through to the ParseException thrown below
}
}
@ -515,7 +502,7 @@ class Parser
return empty($data) ? null : $data;
}
private function parseBlock(int $offset, string $yaml, int $flags)
private function parseBlock(int $offset, string $yaml, int $flags): mixed
{
$skippedLineNumbers = $this->skippedLineNumbers;
@ -557,9 +544,6 @@ class Parser
return $realCurrentLineNumber;
}
/**
* Returns the current line indentation.
*/
private function getCurrentLineIndentation(): int
{
if (' ' !== ($this->currentLine[0] ?? '')) {
@ -577,7 +561,7 @@ class Parser
*
* @throws ParseException When indentation problem are detected
*/
private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string
private function getNextEmbedBlock(?int $indentation = null, bool $inSequence = false): string
{
$oldLineIndentation = $this->getCurrentLineIndentation();
@ -654,12 +638,12 @@ class Parser
}
if ($this->isCurrentLineBlank()) {
$data[] = substr($this->currentLine, $newIndent);
$data[] = substr($this->currentLine, $newIndent ?? 0);
continue;
}
if ($indent >= $newIndent) {
$data[] = substr($this->currentLine, $newIndent);
$data[] = substr($this->currentLine, $newIndent ?? 0);
} elseif ($this->isCurrentLineComment()) {
$data[] = $this->currentLine;
} elseif (0 == $indent) {
@ -714,13 +698,11 @@ class Parser
* @param int $flags A bit field of Yaml::PARSE_* constants to customize the YAML parser behavior
* @param string $context The parser context (either sequence or mapping)
*
* @return mixed
*
* @throws ParseException When reference does not exist
*/
private function parseValue(string $value, int $flags, string $context)
private function parseValue(string $value, int $flags, string $context): mixed
{
if (0 === strpos($value, '*')) {
if (str_starts_with($value, '*')) {
if (false !== $pos = strpos($value, '#')) {
$value = substr($value, 1, $pos - 2);
} else {
@ -807,7 +789,7 @@ class Parser
$parsedValue = Inline::parse($value, $flags, $this->refs);
if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && str_contains($parsedValue, ': ')) {
throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
@ -861,8 +843,8 @@ class Parser
while (
$notEOF && (
$isCurrentLineBlank ||
self::preg_match($pattern, $this->currentLine, $matches)
$isCurrentLineBlank
|| self::preg_match($pattern, $this->currentLine, $matches)
)
) {
if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
@ -949,6 +931,10 @@ class Parser
} while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
if ($EOF) {
for ($i = 0; $i < $movements; ++$i) {
$this->moveToPreviousLine();
}
return false;
}
@ -961,25 +947,16 @@ class Parser
return $ret;
}
/**
* Returns true if the current line is blank or if it is a comment line.
*/
private function isCurrentLineEmpty(): bool
{
return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
}
/**
* Returns true if the current line is blank.
*/
private function isCurrentLineBlank(): bool
{
return '' === $this->currentLine || '' === trim($this->currentLine, ' ');
}
/**
* Returns true if the current line is a comment line.
*/
private function isCurrentLineComment(): bool
{
// checking explicitly the first char of the trim is faster than loops or strpos
@ -993,11 +970,6 @@ class Parser
return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
}
/**
* Cleanups a YAML string to be parsed.
*
* @param string $value The input YAML string
*/
private function cleanup(string $value): string
{
$value = str_replace(["\r\n", "\r"], "\n", $value);
@ -1029,9 +1001,6 @@ class Parser
return $value;
}
/**
* Returns true if the next line starts unindented collection.
*/
private function isNextLineUnIndentedCollection(): bool
{
$currentIndentation = $this->getCurrentLineIndentation();
@ -1058,12 +1027,9 @@ class Parser
return $ret;
}
/**
* Returns true if the string is un-indented collection item.
*/
private function isStringUnIndentedCollectionItem(): bool
{
return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
return '-' === rtrim($this->currentLine) || str_starts_with($this->currentLine, '- ');
}
/**
@ -1075,34 +1041,12 @@ class Parser
*
* @throws ParseException on a PCRE internal error
*
* @see preg_last_error()
*
* @internal
*/
public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int
public static function preg_match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
{
if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
switch (preg_last_error()) {
case \PREG_INTERNAL_ERROR:
$error = 'Internal PCRE error.';
break;
case \PREG_BACKTRACK_LIMIT_ERROR:
$error = 'pcre.backtrack_limit reached.';
break;
case \PREG_RECURSION_LIMIT_ERROR:
$error = 'pcre.recursion_limit reached.';
break;
case \PREG_BAD_UTF8_ERROR:
$error = 'Malformed UTF-8 data.';
break;
case \PREG_BAD_UTF8_OFFSET_ERROR:
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
break;
default:
$error = 'Error.';
}
throw new ParseException($error);
throw new ParseException(preg_last_error_msg());
}
return $ret;

View file

@ -17,10 +17,10 @@ namespace Symfony\Component\Yaml\Tag;
*/
final class TaggedValue
{
private $tag;
private $value;
private string $tag;
private mixed $value;
public function __construct(string $tag, $value)
public function __construct(string $tag, mixed $value)
{
$this->tag = $tag;
$this->value = $value;
@ -31,7 +31,7 @@ final class TaggedValue
return $this->tag;
}
public function getValue()
public function getValue(): mixed
{
return $this->value;
}

View file

@ -45,9 +45,7 @@ class Unescaper
*/
public function unescapeDoubleQuotedString(string $value): string
{
$callback = function ($match) {
return $this->unescapeCharacter($match[0]);
};
$callback = fn ($match) => $this->unescapeCharacter($match[0]);
// evaluate the string
return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value);
@ -60,56 +58,34 @@ class Unescaper
*/
private function unescapeCharacter(string $value): string
{
switch ($value[1]) {
case '0':
return "\x0";
case 'a':
return "\x7";
case 'b':
return "\x8";
case 't':
return "\t";
case "\t":
return "\t";
case 'n':
return "\n";
case 'v':
return "\xB";
case 'f':
return "\xC";
case 'r':
return "\r";
case 'e':
return "\x1B";
case ' ':
return ' ';
case '"':
return '"';
case '/':
return '/';
case '\\':
return '\\';
case 'N':
// U+0085 NEXT LINE
return "\xC2\x85";
case '_':
// U+00A0 NO-BREAK SPACE
return "\xC2\xA0";
case 'L':
// U+2028 LINE SEPARATOR
return "\xE2\x80\xA8";
case 'P':
// U+2029 PARAGRAPH SEPARATOR
return "\xE2\x80\xA9";
case 'x':
return self::utf8chr(hexdec(substr($value, 2, 2)));
case 'u':
return self::utf8chr(hexdec(substr($value, 2, 4)));
case 'U':
return self::utf8chr(hexdec(substr($value, 2, 8)));
default:
throw new ParseException(sprintf('Found unknown escape character "%s".', $value));
}
return match ($value[1]) {
'0' => "\x0",
'a' => "\x7",
'b' => "\x8",
't' => "\t",
"\t" => "\t",
'n' => "\n",
'v' => "\xB",
'f' => "\xC",
'r' => "\r",
'e' => "\x1B",
' ' => ' ',
'"' => '"',
'/' => '/',
'\\' => '\\',
// U+0085 NEXT LINE
'N' => "\xC2\x85",
// U+00A0 NO-BREAK SPACE
'_' => "\xC2\xA0",
// U+2028 LINE SEPARATOR
'L' => "\xE2\x80\xA8",
// U+2029 PARAGRAPH SEPARATOR
'P' => "\xE2\x80\xA9",
'x' => self::utf8chr(hexdec(substr($value, 2, 2))),
'u' => self::utf8chr(hexdec(substr($value, 2, 4))),
'U' => self::utf8chr(hexdec(substr($value, 2, 8))),
default => throw new ParseException(sprintf('Found unknown escape character "%s".', $value)),
};
}
/**

View file

@ -34,6 +34,7 @@ class Yaml
public const PARSE_CUSTOM_TAGS = 512;
public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024;
public const DUMP_NULL_AS_TILDE = 2048;
public const DUMP_NUMERIC_KEY_AS_STRING = 4096;
/**
* Parses a YAML file into a PHP value.
@ -46,11 +47,9 @@ class Yaml
* @param string $filename The path to the YAML file to be parsed
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public static function parseFile(string $filename, int $flags = 0)
public static function parseFile(string $filename, int $flags = 0): mixed
{
$yaml = new Parser();
@ -69,11 +68,9 @@ class Yaml
* @param string $input A string containing YAML
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed
*
* @throws ParseException If the YAML is not valid
*/
public static function parse(string $input, int $flags = 0)
public static function parse(string $input, int $flags = 0): mixed
{
$yaml = new Parser();
@ -91,7 +88,7 @@ class Yaml
* @param int $indent The amount of spaces to use for indentation of nested nodes
* @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string
*/
public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string
public static function dump(mixed $input, int $inline = 2, int $indent = 4, int $flags = 0): string
{
$yaml = new Dumper($indent);

View file

@ -16,18 +16,15 @@
}
],
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8"
},
"require-dev": {
"symfony/console": "^5.3|^6.0"
"symfony/console": "^5.4|^6.0|^7.0"
},
"conflict": {
"symfony/console": "<5.3"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
"symfony/console": "<5.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Yaml\\": "" },