Merge branch '2.7' into 2.8

* 2.7:
  #20411 fix Yaml parsing for very long quoted strings
  [Doctrine Bridge] fix priority for doctrine event listeners
  Use PHP functions as array_map callbacks when possible
  [Validator] revert wrong Phpdoc change
  Use proper line endings
This commit is contained in:
Nicolas Grekas 2017-03-20 09:46:40 +01:00
commit 5d06acadd3
11 changed files with 294 additions and 161 deletions

View File

@ -11,27 +11,30 @@
namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers event listeners and subscribers to the available doctrine connections.
*
* @author Jeremy Mikola <jmikola@gmail.com>
* @author Alexander <iam.asm89@gmail.com>
* @author David Maicher <mail@dmaicher.de>
*/
class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
{
/**
* @var string|string[]
*/
private $connections;
private $container;
private $eventManagers;
private $managerTemplate;
private $tagPrefix;
/**
* Constructor.
*
* @param string $connections Parameter ID for connections
* @param string $managerTemplate sprintf() template for generating the event
* manager's service ID for a connection name
@ -53,105 +56,112 @@ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
return;
}
$taggedSubscribers = $container->findTaggedServiceIds($this->tagPrefix.'.event_subscriber');
$taggedListeners = $container->findTaggedServiceIds($this->tagPrefix.'.event_listener');
if (empty($taggedSubscribers) && empty($taggedListeners)) {
return;
}
$this->container = $container;
$this->connections = $container->getParameter($this->connections);
$sortFunc = function ($a, $b) {
$a = isset($a['priority']) ? $a['priority'] : 0;
$b = isset($b['priority']) ? $b['priority'] : 0;
$this->addTaggedSubscribers($container);
$this->addTaggedListeners($container);
}
return $a > $b ? -1 : 1;
};
private function addTaggedSubscribers(ContainerBuilder $container)
{
$subscriberTag = $this->tagPrefix.'.event_subscriber';
$taggedSubscribers = $this->findAndSortTags($subscriberTag, $container);
if (!empty($taggedSubscribers)) {
$subscribersPerCon = $this->groupByConnection($taggedSubscribers);
foreach ($subscribersPerCon as $con => $subscribers) {
$em = $this->getEventManager($con);
foreach ($taggedSubscribers as $taggedSubscriber) {
$id = $taggedSubscriber[0];
$taggedSubscriberDef = $container->getDefinition($id);
uasort($subscribers, $sortFunc);
foreach ($subscribers as $id => $instance) {
if ($container->getDefinition($id)->isAbstract()) {
throw new \InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event subscriber.', $id));
}
$em->addMethodCall('addEventSubscriber', array(new Reference($id)));
}
if ($taggedSubscriberDef->isAbstract()) {
throw new InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event subscriber.', $id));
}
}
if (!empty($taggedListeners)) {
$listenersPerCon = $this->groupByConnection($taggedListeners, true);
foreach ($listenersPerCon as $con => $listeners) {
$em = $this->getEventManager($con);
uasort($listeners, $sortFunc);
foreach ($listeners as $id => $instance) {
if ($container->getDefinition($id)->isAbstract()) {
throw new \InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event listener.', $id));
}
$em->addMethodCall('addEventListener', array(
array_unique($instance['event']),
isset($instance['lazy']) && $instance['lazy'] ? $id : new Reference($id),
));
$tag = $taggedSubscriber[1];
$connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections);
foreach ($connections as $con) {
if (!isset($this->connections[$con])) {
throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $taggedSubscriber, implode(', ', array_keys($this->connections))));
}
$this->getEventManagerDef($container, $con)->addMethodCall('addEventSubscriber', array(new Reference($id)));
}
}
}
private function groupByConnection(array $services, $isListener = false)
private function addTaggedListeners(ContainerBuilder $container)
{
$grouped = array();
foreach ($allCons = array_keys($this->connections) as $con) {
$grouped[$con] = array();
}
$listenerTag = $this->tagPrefix.'.event_listener';
$taggedListeners = $this->findAndSortTags($listenerTag, $container);
foreach ($services as $id => $instances) {
foreach ($instances as $instance) {
if ($isListener) {
if (!isset($instance['event'])) {
throw new \InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id));
}
$instance['event'] = array($instance['event']);
foreach ($taggedListeners as $taggedListener) {
$id = $taggedListener[0];
$taggedListenerDef = $container->getDefinition($taggedListener[0]);
if ($taggedListenerDef->isAbstract()) {
throw new InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event listener.', $id));
}
if (isset($instance['lazy']) && $instance['lazy']) {
$this->container->getDefinition($id)->setPublic(true);
}
$tag = $taggedListener[1];
if (!isset($tag['event'])) {
throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id));
}
$connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections);
foreach ($connections as $con) {
if (!isset($this->connections[$con])) {
throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections))));
}
$cons = isset($instance['connection']) ? array($instance['connection']) : $allCons;
foreach ($cons as $con) {
if (!isset($grouped[$con])) {
throw new \RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections))));
}
if ($isListener && isset($grouped[$con][$id])) {
$grouped[$con][$id]['event'] = array_merge($grouped[$con][$id]['event'], $instance['event']);
} else {
$grouped[$con][$id] = $instance;
}
if ($lazy = isset($tag['lazy']) && $tag['lazy']) {
$taggedListenerDef->setPublic(true);
}
// we add one call per event per service so we have the correct order
$this->getEventManagerDef($container, $con)->addMethodCall('addEventListener', array(
$tag['event'],
$lazy ? $id : new Reference($id),
));
}
}
return $grouped;
}
private function getEventManager($name)
private function getEventManagerDef(ContainerBuilder $container, $name)
{
if (null === $this->eventManagers) {
$this->eventManagers = array();
foreach ($this->connections as $n => $id) {
$this->eventManagers[$n] = $this->container->getDefinition(sprintf($this->managerTemplate, $n));
}
if (!isset($this->eventManagers[$name])) {
$this->eventManagers[$name] = $container->getDefinition(sprintf($this->managerTemplate, $name));
}
return $this->eventManagers[$name];
}
/**
* Finds and orders all service tags with the given name by their priority.
*
* The order of additions must be respected for services having the same priority,
* and knowing that the \SplPriorityQueue class does not respect the FIFO method,
* we should not use this class.
*
* @see https://bugs.php.net/bug.php?id=53710
* @see https://bugs.php.net/bug.php?id=60926
*
* @param string $tagName
* @param ContainerBuilder $container
*
* @return array
*/
private function findAndSortTags($tagName, ContainerBuilder $container)
{
$sortedTags = array();
foreach ($container->findTaggedServiceIds($tagName) as $serviceId => $tags) {
foreach ($tags as $attributes) {
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
$sortedTags[$priority][] = array($serviceId, $attributes);
}
}
if ($sortedTags) {
krsort($sortedTags);
$sortedTags = call_user_func_array('array_merge', $sortedTags);
}
return $sortedTags;
}
}

View File

@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class RegisterEventListenersAndSubscribersPassTest extends TestCase
{
@ -56,12 +57,18 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase
$container
->register('a', 'stdClass')
->setPublic(false)
->addTag('doctrine.event_listener', array(
'event' => 'bar',
))
->addTag('doctrine.event_listener', array(
'event' => 'foo',
'priority' => -5,
))
->addTag('doctrine.event_listener', array(
'event' => 'bar',
'event' => 'foo_bar',
'priority' => 3,
'lazy' => true,
))
;
$container
@ -70,12 +77,34 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase
'event' => 'foo',
))
;
$container
->register('c', 'stdClass')
->addTag('doctrine.event_listener', array(
'event' => 'foo_bar',
'priority' => 4,
))
;
$this->process($container);
$this->assertEquals(array('b', 'a'), $this->getServiceOrder($container, 'addEventListener'));
$methodCalls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls();
$calls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls();
$this->assertEquals(array('foo', 'bar'), $calls[1][1][0]);
$this->assertEquals(
array(
array('addEventListener', array('foo_bar', new Reference('c'))),
array('addEventListener', array('foo_bar', new Reference('a'))),
array('addEventListener', array('bar', new Reference('a'))),
array('addEventListener', array('foo', new Reference('b'))),
array('addEventListener', array('foo', new Reference('a'))),
),
$methodCalls
);
// not lazy so must be reference
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $methodCalls[0][1][1]);
// lazy so id instead of reference and must mark service public
$this->assertSame('a', $methodCalls[1][1][1]);
$this->assertTrue($container->getDefinition('a')->isPublic());
}
public function testProcessEventListenersWithMultipleConnections()
@ -88,15 +117,86 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase
'event' => 'onFlush',
))
;
$container
->register('b', 'stdClass')
->addTag('doctrine.event_listener', array(
'event' => 'onFlush',
'connection' => 'default',
))
;
$container
->register('c', 'stdClass')
->addTag('doctrine.event_listener', array(
'event' => 'onFlush',
'connection' => 'second',
))
;
$this->process($container);
$callsDefault = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls();
$this->assertEquals(
array(
array('addEventListener', array('onFlush', new Reference('a'))),
array('addEventListener', array('onFlush', new Reference('b'))),
),
$container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls()
);
$this->assertEquals('addEventListener', $callsDefault[0][0]);
$this->assertEquals(array('onFlush'), $callsDefault[0][1][0]);
$this->assertEquals(
array(
array('addEventListener', array('onFlush', new Reference('a'))),
array('addEventListener', array('onFlush', new Reference('c'))),
),
$container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls()
);
}
$callsSecond = $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls();
$this->assertEquals($callsDefault, $callsSecond);
public function testProcessEventSubscribersWithMultipleConnections()
{
$container = $this->createBuilder(true);
$container
->register('a', 'stdClass')
->addTag('doctrine.event_subscriber', array(
'event' => 'onFlush',
))
;
$container
->register('b', 'stdClass')
->addTag('doctrine.event_subscriber', array(
'event' => 'onFlush',
'connection' => 'default',
))
;
$container
->register('c', 'stdClass')
->addTag('doctrine.event_subscriber', array(
'event' => 'onFlush',
'connection' => 'second',
))
;
$this->process($container);
$this->assertEquals(
array(
array('addEventSubscriber', array(new Reference('a'))),
array('addEventSubscriber', array(new Reference('b'))),
),
$container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls()
);
$this->assertEquals(
array(
array('addEventSubscriber', array(new Reference('a'))),
array('addEventSubscriber', array(new Reference('c'))),
),
$container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls()
);
}
public function testProcessEventSubscribersWithPriorities()
@ -133,11 +233,17 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase
;
$this->process($container);
$serviceOrder = $this->getServiceOrder($container, 'addEventSubscriber');
$unordered = array_splice($serviceOrder, 0, 3);
sort($unordered);
$this->assertEquals(array('c', 'd', 'e'), $unordered);
$this->assertEquals(array('b', 'a'), $serviceOrder);
$this->assertEquals(
array(
array('addEventSubscriber', array(new Reference('c'))),
array('addEventSubscriber', array(new Reference('d'))),
array('addEventSubscriber', array(new Reference('e'))),
array('addEventSubscriber', array(new Reference('b'))),
array('addEventSubscriber', array(new Reference('a'))),
),
$container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls()
);
}
public function testProcessNoTaggedServices()
@ -157,26 +263,6 @@ class RegisterEventListenersAndSubscribersPassTest extends TestCase
$pass->process($container);
}
private function getServiceOrder(ContainerBuilder $container, $method)
{
$order = array();
foreach ($container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() as $call) {
list($name, $arguments) = $call;
if ($method !== $name) {
continue;
}
if ('addEventListener' === $name) {
$order[] = (string) $arguments[1];
continue;
}
$order[] = (string) $arguments[0];
}
return $order;
}
private function createBuilder($multipleConnections = false)
{
$container = new ContainerBuilder();

View File

@ -226,7 +226,7 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertEquals('translator.default', (string) $container->getAlias('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator');
$options = $container->getDefinition('translator.default')->getArgument(3);
$files = array_map(function ($resource) { return realpath($resource); }, $options['resource_files']['en']);
$files = array_map('realpath', $options['resource_files']['en']);
$ref = new \ReflectionClass('Symfony\Component\Validator\Validation');
$this->assertContains(
strtr(dirname($ref->getFileName()).'/Resources/translations/validators.en.xlf', '/', DIRECTORY_SEPARATOR),

View File

@ -73,7 +73,7 @@ abstract class CompleteConfigurationTest extends TestCase
foreach (array_keys($arguments[1]) as $contextId) {
$contextDef = $container->getDefinition($contextId);
$arguments = $contextDef->getArguments();
$listeners[] = array_map(function ($ref) { return (string) $ref; }, $arguments['index_0']);
$listeners[] = array_map('strval', $arguments['index_0']);
}
$this->assertEquals(array(

View File

@ -42,7 +42,7 @@ class BufferedOutput extends Output
$this->buffer .= $message;
if ($newline) {
$this->buffer .= "\n";
$this->buffer .= PHP_EOL;
}
}
}

View File

@ -26,7 +26,7 @@ class DummyOutput extends BufferedOutput
public function getLogs()
{
$logs = array();
foreach (explode("\n", trim($this->fetch())) as $message) {
foreach (explode(PHP_EOL, trim($this->fetch())) as $message) {
preg_match('/^\[(.*)\] (.*)/', $message, $matches);
$logs[] = sprintf('%s %s', $matches[1], $matches[2]);
}

View File

@ -318,7 +318,7 @@ class FinderTest extends Iterator\RealIteratorTestCase
$finder = $this->buildFinder();
$a = iterator_to_array($finder->directories()->in(self::$tmpDir));
$a = array_values(array_map(function ($a) { return (string) $a; }, $a));
$a = array_values(array_map('strval', $a));
sort($a);
$this->assertEquals($expected, $a, 'implements the \IteratorAggregate interface');
}

View File

@ -299,9 +299,8 @@ class ClassMetadata extends ElementMetadata implements ClassMetadataInterface
* The name of the getter is assumed to be the name of the property with an
* uppercased first letter and either the prefix "get" or "is".
*
* @param string $property The name of the property
* @param Constraint $constraint The constraint
* @param string|null $method The method that is called to retrieve the value being validated (null for auto-detection)
* @param string $property The name of the property
* @param Constraint $constraint The constraint
*
* @return $this
*/

View File

@ -149,8 +149,8 @@ class Inline
case Escaper::requiresDoubleQuoting($value):
return Escaper::escapeWithDoubleQuotes($value);
case Escaper::requiresSingleQuoting($value):
case preg_match(self::getHexRegex(), $value):
case preg_match(self::getTimestampRegex(), $value):
case Parser::preg_match(self::getHexRegex(), $value):
case Parser::preg_match(self::getTimestampRegex(), $value):
return Escaper::escapeWithSingleQuotes($value);
default:
return $value;
@ -244,10 +244,10 @@ class Inline
$i += strlen($output);
// remove comments
if (preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
$output = substr($output, 0, $match[0][1]);
}
} elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
} elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
$output = $match[1];
$i += strlen($output);
} else {
@ -282,7 +282,7 @@ class Inline
*/
private static function parseQuotedScalar($scalar, &$i)
{
if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)));
}
@ -530,16 +530,16 @@ class Inline
return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
case is_numeric($scalar):
case preg_match(self::getHexRegex(), $scalar):
case Parser::preg_match(self::getHexRegex(), $scalar):
return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
case '.inf' === $scalarLower:
case '.nan' === $scalarLower:
return -log(0);
case '-.inf' === $scalarLower:
return log(0);
case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
return (float) str_replace(',', '', $scalar);
case preg_match(self::getTimestampRegex(), $scalar):
case Parser::preg_match(self::getTimestampRegex(), $scalar):
$timeZone = date_default_timezone_get();
date_default_timezone_set('UTC');
$time = strtotime($scalar);

View File

@ -61,7 +61,7 @@ class Parser
*/
public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
{
if (!preg_match('//u', $value)) {
if (false === preg_match('//u', $value)) {
throw new ParseException('The YAML value does not appear to be valid UTF-8.');
}
$this->currentLineNb = -1;
@ -92,13 +92,13 @@ class Parser
}
$isRef = $mergeNode = false;
if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
if ($context && 'mapping' == $context) {
throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
$context = 'sequence';
if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
$isRef = $matches['ref'];
$values['value'] = $matches['value'];
}
@ -108,7 +108,7 @@ class Parser
$data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
} else {
if (isset($values['leadspaces'])
&& preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
&& self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($values['value']), $matches)
) {
// this is a compact notation element, add to next block and parse
$block = $values['value'];
@ -124,7 +124,10 @@ class Parser
if ($isRef) {
$this->refs[$isRef] = end($data);
}
} elseif (preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->currentLine, $values) && (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))) {
} elseif (
self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
&& (false === strpos($values['key'], ' #') || in_array($values['key'][0], array('"', "'")))
) {
if ($context && 'sequence' == $context) {
throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
}
@ -191,7 +194,7 @@ class Parser
$data += $parsed; // array union
}
}
} elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
} elseif (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
$isRef = $matches['ref'];
$values['value'] = $matches['value'];
}
@ -250,27 +253,7 @@ class Parser
return $value;
}
switch (preg_last_error()) {
case PREG_INTERNAL_ERROR:
$error = 'Internal PCRE error.';
break;
case PREG_BACKTRACK_LIMIT_ERROR:
$error = 'pcre.backtrack_limit reached.';
break;
case PREG_RECURSION_LIMIT_ERROR:
$error = 'pcre.recursion_limit reached.';
break;
case PREG_BAD_UTF8_ERROR:
$error = 'Malformed UTF-8 data.';
break;
case PREG_BAD_UTF8_OFFSET_ERROR:
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
break;
default:
$error = 'Unable to parse.';
}
throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine);
throw new ParseException('Unable to parse', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
}
@ -515,7 +498,7 @@ class Parser
return $this->refs[$value];
}
if (preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
$modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
@ -570,7 +553,7 @@ class Parser
// determine indentation if not specified
if (0 === $indentation) {
if (preg_match('/^ +/', $this->currentLine, $matches)) {
if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
$indentation = strlen($matches[0]);
}
}
@ -581,7 +564,7 @@ class Parser
while (
$notEOF && (
$isCurrentLineBlank ||
preg_match($pattern, $this->currentLine, $matches)
self::preg_match($pattern, $this->currentLine, $matches)
)
) {
if ($isCurrentLineBlank && strlen($this->currentLine) > $indentation) {
@ -804,6 +787,49 @@ class Parser
*/
private function isBlockScalarHeader()
{
return (bool) preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
}
/**
* A local wrapper for `preg_match` which will throw a ParseException if there
* is an internal error in the PCRE engine.
*
* This avoids us needing to check for "false" every time PCRE is used
* in the YAML engine
*
* @throws ParseException on a PCRE internal error
*
* @see preg_last_error()
*
* @internal
*/
public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
{
$ret = preg_match($pattern, $subject, $matches, $flags, $offset);
if ($ret === false) {
switch (preg_last_error()) {
case PREG_INTERNAL_ERROR:
$error = 'Internal PCRE error.';
break;
case PREG_BACKTRACK_LIMIT_ERROR:
$error = 'pcre.backtrack_limit reached.';
break;
case PREG_RECURSION_LIMIT_ERROR:
$error = 'pcre.recursion_limit reached.';
break;
case PREG_BAD_UTF8_ERROR:
$error = 'Malformed UTF-8 data.';
break;
case PREG_BAD_UTF8_OFFSET_ERROR:
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
break;
default:
$error = 'Error.';
}
throw new ParseException($error);
}
return $ret;
}
}

View File

@ -17,6 +17,7 @@ use Symfony\Component\Yaml\Parser;
class ParserTest extends TestCase
{
/** @var Parser */
protected $parser;
protected function setUp()
@ -1219,6 +1220,17 @@ YAML
),
);
}
public function testCanParseVeryLongValue()
{
$longStringWithSpaces = str_repeat('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ', 20000);
$trickyVal = array('x' => $longStringWithSpaces);
$yamlString = Yaml::dump($trickyVal);
$arrayFromYaml = $this->parser->parse($yamlString);
$this->assertEquals($trickyVal, $arrayFromYaml);
}
}
class B