Commit a0a530fd authored by catch's avatar catch

Issue #1920902 by neclimdul, larowlan, alexpott, chx, sun, jthorson,...

Issue #1920902 by neclimdul, larowlan, alexpott, chx, sun, jthorson, damiankloip, Berdir, Mixologic, YesCT, hussainweb, rcaracaus, cweagans, catch, jibran, msonnabaum, dawehner, webchick, anavarre, heyrocker: Add a Drupal Yaml wrapper so we can default to PECL Yaml component if it is available
parent 03fc2042
......@@ -61,7 +61,7 @@ public function findAll() {
foreach ($provider_by_files as $file => $provider) {
// If a file is empty or its contents are commented out, return an empty
// array instead of NULL for type consistency.
$all[$provider] = Yaml::decode(file_get_contents($file)) ?: [];
$all[$provider] = $this->decode($file);
$file_cache->set($file, $all[$provider]);
}
}
......@@ -69,6 +69,17 @@ public function findAll() {
return $all;
}
/**
* Decode a YAML file.
*
* @param string $file
* Yaml file path.
* @return array
*/
protected function decode($file) {
return Yaml::decode(file_get_contents($file)) ?: [];
}
/**
* Returns an array of file paths, keyed by provider.
*
......
......@@ -2,42 +2,37 @@
namespace Drupal\Component\Serialization;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Dumper;
/**
* Default serialization for YAML using the Symfony component.
* Provides a YAML serialization implementation.
*
* Proxy implementation that will choose the best library based on availability.
*/
class Yaml implements SerializationInterface {
/**
* The YAML implementation to use.
*
* @var \Drupal\Component\Serialization\SerializationInterface
*/
protected static $serializer;
/**
* {@inheritdoc}
*/
public static function encode($data) {
try {
$yaml = new Dumper();
$yaml->setIndentation(2);
return $yaml->dump($data, PHP_INT_MAX, 0, TRUE, FALSE);
}
catch (\Exception $e) {
throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e);
}
// Instead of using \Drupal\Component\Serialization\Yaml::getSerializer(),
// always using Symfony for writing the data, to reduce the risk of having
// differences if different environments (like production and development)
// do not match in terms of what YAML implementation is available.
return YamlSymfony::encode($data);
}
/**
* {@inheritdoc}
*/
public static function decode($raw) {
try {
$yaml = new Parser();
// Make sure we have a single trailing newline. A very simple config like
// 'foo: bar' with no newline will fail to parse otherwise.
return $yaml->parse($raw, TRUE, FALSE);
}
catch (\Exception $e) {
throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e);
}
$serializer = static::getSerializer();
return $serializer::decode($raw);
}
/**
......@@ -47,4 +42,23 @@ public static function getFileExtension() {
return 'yml';
}
/**
* Determines which implementation to use for parsing YAML.
*/
protected static function getSerializer() {
if (!isset(static::$serializer)) {
// Use the PECL YAML extension if it is available. It has better
// performance for file reads and is YAML compliant.
if (extension_loaded('yaml')) {
static::$serializer = YamlPecl::class;
}
else {
// Otherwise, fallback to the Symfony implementation.
static::$serializer = YamlSymfony::class;
}
}
return static::$serializer;
}
}
<?php
namespace Drupal\Component\Serialization;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
/**
* Provides default serialization for YAML using the PECL extension.
*/
class YamlPecl implements SerializationInterface {
/**
* {@inheritdoc}
*/
public static function encode($data) {
static $init;
if (!isset($init)) {
ini_set('yaml.output_indent', 2);
// Do not break lines at 80 characters.
ini_set('yaml.output_width', -1);
$init = TRUE;
}
return yaml_emit($data, YAML_UTF8_ENCODING, YAML_LN_BREAK);
}
/**
* {@inheritdoc}
*/
public static function decode($raw) {
// yaml_parse() will error with an empty value.
if (!trim($raw)) {
return NULL;
}
// @todo Use ErrorExceptions when https://drupal.org/node/1247666 is in.
// yaml_parse() will throw errors instead of raising an exception. Until
// such time as Drupal supports native PHP ErrorExceptions as the error
// handler, we need to temporarily set the error handler as ::errorHandler()
// and then restore it after decoding has occurred. This allows us to turn
// parsing errors into a throwable exception.
// @see Drupal\Component\Serialization\Exception\InvalidDataTypeException
// @see http://php.net/manual/en/class.errorexception.php
set_error_handler([__CLASS__, 'errorHandler']);
$ndocs = 0;
$data = yaml_parse($raw, 0, $ndocs, [
YAML_BOOL_TAG => '\Drupal\Component\Serialization\YamlPecl::applyBooleanCallbacks',
]);
restore_error_handler();
return $data;
}
/**
* Handles errors for \Drupal\Component\Serialization\YamlPecl::decode().
*
* @param int $severity
* The severity level of the error.
* @param string $message
* The error message to display.
*
* @see \Drupal\Component\Serialization\YamlPecl::decode()
*/
public static function errorHandler($severity, $message) {
restore_error_handler();
throw new InvalidDataTypeException($message, $severity);
}
/**
* {@inheritdoc}
*/
public static function getFileExtension() {
return 'yml';
}
/**
* Applies callbacks after parsing to ignore 1.1 style booleans.
*
* @param mixed $value
* Value from YAML file.
* @param string $tag
* Tag that triggered the callback.
* @param int $flags
* Scalar entity style flags.
*
* @return string|bool
* FALSE, false, TRUE and true are returned as booleans, everything else is
* returned as a string.
*/
public static function applyBooleanCallbacks($value, $tag, $flags) {
// YAML 1.1 spec dictates that 'Y', 'N', 'y' and 'n' are booleans. But, we
// want the 1.2 behavior, so we only consider 'false', 'FALSE', 'true' and
// 'TRUE' as booleans.
if (!in_array(strtolower($value), ['false', 'true'], TRUE)) {
return $value;
}
$map = [
'false' => FALSE,
'true' => TRUE,
];
return $map[strtolower($value)];
}
}
<?php
namespace Drupal\Component\Serialization;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Dumper;
/**
* Default serialization for YAML using the Symfony component.
*/
class YamlSymfony implements SerializationInterface {
/**
* {@inheritdoc}
*/
public static function encode($data) {
try {
$yaml = new Dumper();
$yaml->setIndentation(2);
return $yaml->dump($data, PHP_INT_MAX, 0, TRUE, FALSE);
}
catch (\Exception $e) {
throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public static function decode($raw) {
try {
$yaml = new Parser();
// Make sure we have a single trailing newline. A very simple config like
// 'foo: bar' with no newline will fail to parse otherwise.
return $yaml->parse($raw, TRUE, FALSE);
}
catch (\Exception $e) {
throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public static function getFileExtension() {
return 'yml';
}
}
......@@ -7,9 +7,9 @@
use Drupal\Core\Asset\Exception\InvalidLibraryFileException;
use Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\NestedArray;
/**
......
......@@ -3,12 +3,12 @@
namespace Drupal\Core\Config;
use Drupal\Component\Diff\Diff;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\Entity\ConfigDependencyManager;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
......
......@@ -3,8 +3,8 @@
namespace Drupal\Core\Config;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Core\Serialization\Yaml;
/**
* Defines the file storage.
......
......@@ -4,7 +4,7 @@
namespace Drupal\Core\DependencyInjection;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Serialization\Yaml;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
......
<?php
namespace Drupal\Core\Discovery;
use Drupal\Component\Discovery\YamlDiscovery as ComponentYamlDiscovery;
use Drupal\Core\Serialization\Yaml;
/**
* Provides discovery for YAML files within a given set of directories.
*
* This overrides the Component file decoding with the Core YAML implementation.
*/
class YamlDiscovery extends ComponentYamlDiscovery {
/**
* {@inheritdoc}
*/
protected function decode($file) {
return Yaml::decode(file_get_contents($file)) ?: [];
}
}
......@@ -2,8 +2,8 @@
namespace Drupal\Core\Extension;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Core\Serialization\Yaml;
/**
* Parses dynamic .info.yml files that might change during the page request.
......
......@@ -2,12 +2,12 @@
namespace Drupal\Core\Extension;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DrupalKernelInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Serialization\Yaml;
/**
* Default implementation of the module installer.
......
......@@ -3,8 +3,8 @@
namespace Drupal\Core\Plugin\Discovery;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Component\Discovery\YamlDiscovery as ComponentYamlDiscovery;
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
use Drupal\Core\Discovery\YamlDiscovery as CoreYamlDiscovery;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
......@@ -25,7 +25,7 @@ class YamlDiscovery implements DiscoveryInterface {
/**
* YAML file discovery and parsing handler.
*
* @var \Drupal\Component\Discovery\YamlDiscovery
* @var \Drupal\Core\Discovery\YamlDiscovery
*/
protected $discovery;
......@@ -48,7 +48,7 @@ class YamlDiscovery implements DiscoveryInterface {
* An array of directories to scan.
*/
function __construct($name, array $directories) {
$this->discovery = new ComponentYamlDiscovery($name, $directories);
$this->discovery = new CoreYamlDiscovery($name, $directories);
}
/**
......
......@@ -2,9 +2,9 @@
namespace Drupal\Core\Routing;
use Drupal\Component\Discovery\YamlDiscovery;
use Drupal\Core\Access\CheckProviderInterface;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Discovery\YamlDiscovery;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\DestructableInterface;
......
<?php
namespace Drupal\Core\Serialization;
use Drupal\Core\Site\Settings;
use Drupal\Component\Serialization\Yaml as ComponentYaml;
/**
* Provides a YAML serialization implementation.
*
* Allow settings to override the YAML implementation resolution.
*/
class Yaml extends ComponentYaml {
/**
* {@inheritdoc}
*/
protected static function getSerializer() {
// Allow settings.php to override the YAML serializer.
if (!isset(static::$serializer) &&
$class = Settings::get('yaml_parser_class')) {
static::$serializer = $class;
}
return parent::getSerializer();
}
}
......@@ -3,11 +3,11 @@
namespace Drupal\config\Controller;
use Drupal\Core\Archiver\ArchiveTar;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Diff\DiffFormatter;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Url;
use Drupal\system\FileDownloadController;
use Symfony\Component\DependencyInjection\ContainerInterface;
......
......@@ -2,13 +2,13 @@
namespace Drupal\config\Form;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
......
......@@ -3,7 +3,6 @@
namespace Drupal\config\Form;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use Drupal\Component\Serialization\Yaml;
use Drupal\config\StorageReplaceDataWrapper;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\ConfigImporterException;
......@@ -19,6 +18,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
......
......@@ -2,8 +2,8 @@
namespace Drupal\config\Tests;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Archiver\Tar;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\WebTestBase;
/**
......
......@@ -2,8 +2,8 @@
namespace Drupal\config\Tests;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\InstallerTestBase;
/**
......
......@@ -2,7 +2,7 @@
namespace Drupal\config\Tests;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\WebTestBase;
/**
......@@ -45,7 +45,8 @@ public function testImport() {
];
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertText('The import failed with the following message: Malformed inline YAML string ({{{) at line 1 (near &quot;{{{&quot;)');
// Assert the static portion of the error since different parsers could give different text in their error.
$this->assertText('The import failed with the following message: ');
$import = <<<EOD
label: First
......@@ -216,8 +217,8 @@ public function testExport() {
$this->assertFieldByXPath('//select[@name="config_name"]//option[@selected="selected"]', t('Fallback date format (fallback)'), 'The fallback date format config entity is selected when specified in the URL.');
$fallback_date = \Drupal::entityManager()->getStorage('date_format')->load('fallback');
$data = Yaml::encode($fallback_date->toArray());
$this->assertFieldByXPath('//textarea[@name="export"]', $data, 'The fallback date format config entity export code is displayed.');
$yaml_text = (string) $this->xpath('//textarea[@name="export"]')[0];
$this->assertEqual(Yaml::decode($yaml_text), $fallback_date->toArray(), 'The fallback date format config entity export code is displayed.');
}
}
......@@ -7,7 +7,7 @@
*/
use Drupal\Core\Database\Database;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Serialization\Yaml;
use Drupal\field\Entity\FieldStorageConfig;
$connection = Database::getConnection();
......
......@@ -2,8 +2,8 @@
namespace Drupal\responsive_image\Tests\Update;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Serialization\Yaml;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
......
......@@ -5,8 +5,8 @@
* Test fixture for \Drupal\rest\Tests\Update\RestExportAuthUpdateTest.
*/
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
$config = $connection;
......
......@@ -5,7 +5,6 @@
use Drupal\block\Entity\Block;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\UrlHelper;
......@@ -18,6 +17,7 @@
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Extension\MissingDependencyException;
use Drupal\Core\Render\Element;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\UserSession;
......
......@@ -2,7 +2,7 @@
namespace Drupal\system\Tests\Installer;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\InstallerTestBase;
/**
......
......@@ -2,7 +2,7 @@
namespace Drupal\system\Tests\Installer;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\InstallerTestBase;
/**
......
......@@ -2,7 +2,7 @@
namespace Drupal\system\Tests\Installer;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\InstallerTestBase;
/**
......
......@@ -2,7 +2,7 @@
namespace Drupal\system\Tests\Installer;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Serialization\Yaml;
use Drupal\simpletest\InstallerTestBase;
/**
......
......@@ -55,15 +55,15 @@ public function testStandardConfig() {
$skipped_config = [];
// \Drupal\simpletest\WebTestBase::installParameters() uses
// simpletest@example.com as mail address.
$skipped_config['contact.form.feedback'][] = ' - simpletest@example.com';
$skipped_config['contact.form.feedback'][] = '- simpletest@example.com';
// \Drupal\filter\Entity\FilterFormat::toArray() drops the roles of filter
// formats.
$skipped_config['filter.format.basic_html'][] = 'roles:';
$skipped_config['filter.format.basic_html'][] = ' - authenticated';
$skipped_config['filter.format.basic_html'][] = '- authenticated';
$skipped_config['filter.format.full_html'][] = 'roles:';
$skipped_config['filter.format.full_html'][] = ' - administrator';
$skipped_config['filter.format.full_html'][] = '- administrator';
$skipped_config['filter.format.restricted_html'][] = 'roles:';
$skipped_config['filter.format.restricted_html'][] = ' - anonymous';
$skipped_config['filter.format.restricted_html'][] = '- anonymous';
$this->assertInstalledConfig($skipped_config);
}
......
......@@ -6,8 +6,8 @@
* upgrade path of https://www.drupal.org/node/2354889.
*/
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
......
......@@ -6,8 +6,8 @@
* upgrade path of https://www.drupal.org/node/507488.
*/
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
......
......@@ -6,8 +6,8 @@
* upgrade path of https://www.drupal.org/node/2476947.
*/
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
......
......@@ -6,8 +6,8 @@
* upgrade path of https://www.drupal.org/node/507488.
*/
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
......
......@@ -6,8 +6,8 @@
* upgrade path of https://www.drupal.org/node/2005546.
*/
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
......
......@@ -6,8 +6,8 @@
* upgrade path of https://www.drupal.org/node/2455125.
*/
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
......
......@@ -6,8 +6,8 @@
* the upgrade path of https://www.drupal.org/node/2649914.
*/