In PHP 8, but not in PHP 7.4, every class with a __toString() function implicitly implements the Stringable interface. Therefore, the behavior of checks like "instanceof Stringable" differs between these PHP versions when such classes are involved. Make every such class implement the interface so that the behavior will be consistent. The PHP 7.4 fallback for the Stringable interface is provided by symfony/polyfill-php80. Change-Id: I3f0330c2555c7d3bf99b654ed3c0b0303e257ea1
121 lines
2.9 KiB
PHP
121 lines
2.9 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Settings\Source\Format;
|
|
|
|
use InvalidArgumentException;
|
|
use LogicException;
|
|
use Stringable;
|
|
use Symfony\Component\Yaml\Exception\ParseException;
|
|
use Symfony\Component\Yaml\Yaml;
|
|
use UnexpectedValueException;
|
|
use Wikimedia\AtEase\AtEase;
|
|
|
|
class YamlFormat implements Stringable, SettingsFormat {
|
|
|
|
public const PARSER_PHP_YAML = 'php-yaml';
|
|
|
|
public const PARSER_SYMFONY = 'symfony';
|
|
|
|
/** @var string[] */
|
|
private $useParsers;
|
|
|
|
/**
|
|
* @param string[] $useParsers which parsers to try in order.
|
|
*/
|
|
public function __construct( array $useParsers = [ self::PARSER_PHP_YAML, self::PARSER_SYMFONY ] ) {
|
|
$this->useParsers = $useParsers;
|
|
}
|
|
|
|
public function decode( string $data ): array {
|
|
foreach ( $this->useParsers as $parser ) {
|
|
if ( self::isParserAvailable( $parser ) ) {
|
|
return $this->parseWith( $parser, $data );
|
|
}
|
|
}
|
|
throw new LogicException( 'No parser available' );
|
|
}
|
|
|
|
/**
|
|
* Check whether a specific YAML parser is available.
|
|
*
|
|
* @param string $parser one of the PARSER_* constants.
|
|
* @return bool
|
|
*/
|
|
public static function isParserAvailable( string $parser ): bool {
|
|
switch ( $parser ) {
|
|
case self::PARSER_PHP_YAML:
|
|
return function_exists( 'yaml_parse' );
|
|
case self::PARSER_SYMFONY:
|
|
return true;
|
|
default:
|
|
throw new InvalidArgumentException( 'Unknown parser: ' . $parser );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $parser
|
|
* @param string $data
|
|
* @return array
|
|
*/
|
|
private function parseWith( string $parser, string $data ): array {
|
|
switch ( $parser ) {
|
|
case self::PARSER_PHP_YAML:
|
|
return $this->parseWithPhp( $data );
|
|
case self::PARSER_SYMFONY:
|
|
return $this->parseWithSymfony( $data );
|
|
default:
|
|
throw new InvalidArgumentException( 'Unknown parser: ' . $parser );
|
|
}
|
|
}
|
|
|
|
private function parseWithPhp( string $data ): array {
|
|
$previousValue = ini_set( 'yaml.decode_php', false );
|
|
try {
|
|
$ndocs = 0;
|
|
$result = AtEase::quietCall(
|
|
'yaml_parse',
|
|
$data,
|
|
0,
|
|
$ndocs,
|
|
[
|
|
/**
|
|
* Crash if provided YAML has PHP constants in it.
|
|
* We do not want to support that.
|
|
*
|
|
* @return never
|
|
*/
|
|
'!php/const' => static function () {
|
|
throw new UnexpectedValueException(
|
|
'PHP constants are not supported'
|
|
);
|
|
},
|
|
]
|
|
);
|
|
if ( $result === false ) {
|
|
throw new UnexpectedValueException( 'Failed to parse YAML' );
|
|
}
|
|
return $result;
|
|
} finally {
|
|
ini_set( 'yaml.decode_php', $previousValue );
|
|
}
|
|
}
|
|
|
|
private function parseWithSymfony( string $data ): array {
|
|
try {
|
|
return Yaml::parse( $data, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE );
|
|
} catch ( ParseException $e ) {
|
|
throw new UnexpectedValueException(
|
|
'Failed to parse YAML ' . $e->getMessage()
|
|
);
|
|
}
|
|
}
|
|
|
|
public static function supportsFileExtension( string $ext ): bool {
|
|
$ext = strtolower( $ext );
|
|
return $ext === 'yml' || $ext === 'yaml';
|
|
}
|
|
|
|
public function __toString() {
|
|
return 'YAML';
|
|
}
|
|
}
|