File: YamlFormat.php

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (121 lines) | stat: -rw-r--r-- 2,963 bytes parent folder | download
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';
	}
}