1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
<?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';
}
}
|