diff --git a/core/lib/Drupal/Component/Serialization/Yaml.php b/core/lib/Drupal/Component/Serialization/Yaml.php index 7d0f0c5..5aa3fbb 100644 --- a/core/lib/Drupal/Component/Serialization/Yaml.php +++ b/core/lib/Drupal/Component/Serialization/Yaml.php @@ -7,36 +7,66 @@ namespace Drupal\Component\Serialization; -use Drupal\Component\Serialization\Exception\InvalidDataTypeException; -use Symfony\Component\Yaml\Yaml as Symfony; +use Drupal\Component\Utility\Settings; /** - * Default serialization for YAML using the Symfony component. + * Default serialization for YAML. + * + * Automatically uses the optimal YAML implementation, unless overridden in + * Settings. */ class Yaml implements SerializationInterface { /** + * The YAML implementation to use. + * + * @var \Drupal\Component\Serialization\SerializationInterface + */ + protected static $instance; + + /** + * Determines the optimal implementation to use for encoding and parsing Yaml. + * + * The selection is made based on the enabled PHP extensions, with the most + * performant available option chosen. + */ + public function __construct() { + if (isset(static::$instance)) { + return; + } + $settings = Settings::getSingleton(); + // If there is a settings.php override, use that. + if ($settings && ($class = $settings->get('yaml_parser_class'))) { + static::$instance = new $class(); + } + // If the PECL YAML extension installed, use that. + elseif (extension_loaded('yaml')) { + static::$instance = new YamlPecl(); + } + else { + // Otherwise, fallback to the Symfony implementation. + static::$instance = new YamlSymfony(); + } + } + + /** * {@inheritdoc} */ public static function encode($data) { - try { - return Symfony::dump($data, PHP_INT_MAX, 2, TRUE); - } - catch (\Exception $e) { - throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e); + if (!isset(static::$instance)) { + new static(); } + return static::$instance->encode($data); } /** * {@inheritdoc} */ public static function decode($raw) { - try { - return Symfony::parse($raw, TRUE); - } - catch (\Exception $e) { - throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e); + if (!isset(static::$instance)) { + new static(); } + return static::$instance->decode($raw); } /** diff --git a/core/lib/Drupal/Component/Serialization/YamlPecl.php b/core/lib/Drupal/Component/Serialization/YamlPecl.php new file mode 100644 index 0000000..60b0e14 --- /dev/null +++ b/core/lib/Drupal/Component/Serialization/YamlPecl.php @@ -0,0 +1,60 @@ +infoParser->parse('core/modules/system/tests/fixtures/common_test.info.txt'); $this->assertEqual($info_values['simple_string'], 'A simple string', 'Simple string value was parsed correctly.', 'System'); $this->assertEqual($info_values['version'], \Drupal::VERSION, 'Constant value was parsed correctly.', 'System'); - $this->assertEqual($info_values['double_colon'], 'dummyClassName::', 'Value containing double-colon was parsed correctly.', 'System'); + $this->assertEqual($info_values['double_colon'], 'dummyClassName::foo', 'Value containing double-colon was parsed correctly.', 'System'); } } diff --git a/core/modules/system/tests/fixtures/common_test.info.txt b/core/modules/system/tests/fixtures/common_test.info.txt index 7e57dfe..ff535b1 100644 --- a/core/modules/system/tests/fixtures/common_test.info.txt +++ b/core/modules/system/tests/fixtures/common_test.info.txt @@ -4,4 +4,4 @@ type: module description: 'testing info file parsing' simple_string: 'A simple string' version: "VERSION" -double_colon: dummyClassName:: +double_colon: dummyClassName::foo diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php new file mode 100644 index 0000000..baa5fe6 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php @@ -0,0 +1,161 @@ + 'YAML', + 'description' => "Tests encoding and decoding of YAML is consistent between PECL and Symfony implementations.", + 'group' => 'Serialization API', + ); + } + + protected function setUp() { + if (!extension_loaded('yaml')) { + $this->markTestSkipped('The PECL Yaml extension is not available.'); + } + } + + /** + * Tests encoding with Symfony and decoding with PECL and vice versa. + * + * @dataProvider providerYamlData + * @covers \Drupal\Component\Serialization\Yaml::encode + * @covers \Drupal\Component\Serialization\Yaml::decode + * @covers \Drupal\Component\Serialization\YamlPecl::encode + * @covers \Drupal\Component\Serialization\YamlPecl::decode + * @covers \Drupal\Component\Serialization\YamlSymfony::encode + * @covers \Drupal\Component\Serialization\YamlSymfony::decode + */ + public function testEncodeDecode($data, $parser_class, $dumper_class) { + $dumper = new $dumper_class; + $parser = new $parser_class; + + $dumped = $dumper->encode($data); + + $parsed = $parser->decode($dumped); + + $this->assertEquals($data, $parsed); + } + + /** + * Tests decoding YAML node anchors with both Symfony and PECL. + * + * @dataProvider providerYamlNodeAnchors + */ + public function testDecodeNodeAnchors($data) { + $symfony = new YamlSymfony(); + $pecl = new YamlPecl(); + $this->assertEquals($symfony->decode($data), $pecl->decode($data)); + } + + /** + * Data provider for YAML instance tests. + * + * @return array + */ + public function providerYamlData() { + $object = array( + 'foo' => 'bar', + 'id' => 'schnitzel', + 'ponies' => array('nope', 'thanks'), + 'how' => array( + 'about' => 'if', + 'i' => 'ask', + 'nicely' + ), + 'the' => array( + 'answer' => array( + 'still' => 'would', + 'be' => 'Y', + ), + ), + 'how_many_times' => 123, + 'should_i_ask' => FALSE, + 1, + FALSE, + array(1, FALSE), + array(10), + array(0 => '123456'), + ); + + // Test parsing with Symfony and dumping with PECL. + $data[] = array( + $object, + 'Drupal\Component\Serialization\YamlSymfony', + 'Drupal\Component\Serialization\YamlPecl' + ); + // Test parsing with PECL and dumping with Symfony. + $data[] = array( + $object, + 'Drupal\Component\Serialization\YamlPecl', + 'Drupal\Component\Serialization\YamlSymfony' + ); + return $data; + } + + /** + * Data provider for YAML instance tests. + * + * @return array + */ + public function providerYamlNodeAnchors() { + $yaml = <<assertEquals($symfony->decode($data), $pecl->decode($data)); + } + + /** + * Data provider for YAML files in core test. + * + * @return array + */ + public function providerYamlFilesInCore() { + $files = array(); + $dirs = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__ . '/../../../../../', \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)); + foreach ($dirs as $dir) { + $pathname = $dir->getPathname(); + // Exclude vendor. + if ($dir->getExtension() == 'yml' && strpos($pathname, '/../../../../../vendor') === FALSE) { + $files[] = array($dir->getRealPath()); + } + } + return $files; + } + +}