Merge branch '3.0' into 3.1

* 3.0:
  [Yaml] fix exception contexts
  People - person singularization
  [Yaml] properly handle unindented collections
  [Serializer] Add test for ignored attributes during denormalization
  chomp newlines only at the end of YAML documents
  Fixed server status command when port has been omitted
  Update UPGRADE FROM 2.x to 3.0
  fix removed commands wording in upgrade file
  Catch \Throwable
  Catch \Throwable
  [DependencyInjection] Avoid generating call_user_func in more cases
  [Validator] Support for DateTimeImmutable
  [FrameworkBundle] update upgrade instructions
  Use levenshtein level for better Bundle matching
  [WebProfilerBundle] Fix CORS ajax security issues
  remove methods that were needed for PHP 5.3
  [DX][DI] Make Autowiring exceptions more future friendly
This commit is contained in:
Fabien Potencier 2016-05-24 12:06:56 +02:00
commit 330c09b390
24 changed files with 290 additions and 67 deletions

View File

@ -398,8 +398,11 @@ UPGRADE FROM 2.x to 3.0
</service>
```
* The `ChoiceToBooleanArrayTransformer`, `ChoicesToBooleanArrayTransformer`,
`FixRadioInputListener`, and `FixCheckboxInputListener` classes were removed.
* The `max_length` option was removed. Use the `attr` option instead by setting it to
an `array` with a `maxlength` key.
* The `ChoiceToBooleanArrayTransformer`, `ChoicesToBooleanArrayTransformer`,
`FixRadioInputListener`, and `FixCheckboxInputListener` classes were removed.
* The `choice_list` option of `ChoiceType` was removed.
@ -636,6 +639,11 @@ UPGRADE FROM 2.x to 3.0
be removed in Symfony 3.0. Use the `debug:config`, `debug:container`,
`debug:router`, `debug:translation` and `lint:yaml` commands instead.
* The base `Controller`class is now abstract.
* The visibility of all methods of the base `Controller` class has been changed from
`public` to `protected`.
* The `getRequest` method of the base `Controller` class has been deprecated
since Symfony 2.4 and must be therefore removed in 3.0. The only reliable
way to get the `Request` object is to inject it in the action method.
@ -1737,8 +1745,7 @@ UPGRADE FROM 2.x to 3.0
### WebProfiler
* The `profiler:import` and `profiler:export` commands have been deprecated and
will be removed in 3.0.
* The `profiler:import` and `profiler:export` commands have been removed.
* All the profiler storages different than `FileProfilerStorage` have been
removed. The removed classes are:

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@ -32,6 +33,7 @@ class ServerStatusCommand extends ServerCommand
$this
->setDefinition(array(
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'),
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
))
->setName('server:status')
->setDescription('Outputs the status of the built-in web server for the given address')
@ -46,6 +48,10 @@ class ServerStatusCommand extends ServerCommand
$io = new SymfonyStyle($input, $output);
$address = $input->getArgument('address');
if (false === strpos($address, ':')) {
$address = $address.':'.$input->getOption('port');
}
// remove an orphaned lock file
if (file_exists($this->getLockFile($address)) && !$this->isServerRunning($address)) {
unlink($this->getLockFile($address));

View File

@ -142,6 +142,7 @@ class ControllerNameParser
$lev = levenshtein($nonExistentBundleName, $bundleName);
if ($lev <= strlen($nonExistentBundleName) / 3 && ($alternative === null || $lev < $shortest)) {
$alternative = $bundleName;
$shortest = $lev;
}
}

View File

@ -59,8 +59,8 @@ class ControllerNameParserTest extends TestCase
{
$parser = $this->createParser();
$this->assertEquals('FooBundle:Default:index', $parser->build('TestBundle\FooBundle\Controller\DefaultController::indexAction'), '->parse() converts a class::method string to a short a:b:c notation string');
$this->assertEquals('FooBundle:Sub\Default:index', $parser->build('TestBundle\FooBundle\Controller\Sub\DefaultController::indexAction'), '->parse() converts a class::method string to a short a:b:c notation string');
$this->assertEquals('FoooooBundle:Default:index', $parser->build('TestBundle\FooBundle\Controller\DefaultController::indexAction'), '->parse() converts a class::method string to a short a:b:c notation string');
$this->assertEquals('FoooooBundle:Sub\Default:index', $parser->build('TestBundle\FooBundle\Controller\Sub\DefaultController::indexAction'), '->parse() converts a class::method string to a short a:b:c notation string');
try {
$parser->build('TestBundle\FooBundle\Controller\DefaultController::index');
@ -132,8 +132,9 @@ class ControllerNameParserTest extends TestCase
public function getInvalidBundleNameTests()
{
return array(
array('FoodBundle:Default:index', 'FooBundle:Default:index'),
array('CrazyBundle:Default:index', false),
'Alternative will be found using levenshtein' => array('FoodBundle:Default:index', 'FooBundle:Default:index'),
'Alternative will be found using partial match' => array('FabpotFooBund:Default:index', 'FabpotFooBundle:Default:index'),
'Bundle does not exist at all' => array('CrazyBundle:Default:index', false),
);
}
@ -162,6 +163,7 @@ class ControllerNameParserTest extends TestCase
$bundles = array(
'SensioFooBundle' => $this->getBundle('TestBundle\Fabpot\FooBundle', 'FabpotFooBundle'),
'SensioCmsFooBundle' => $this->getBundle('TestBundle\Sensio\Cms\FooBundle', 'SensioCmsFooBundle'),
'FoooooBundle' => $this->getBundle('TestBundle\FooBundle', 'FoooooBundle'),
'FooBundle' => $this->getBundle('TestBundle\FooBundle', 'FooBundle'),
'FabpotFooBundle' => $this->getBundle('TestBundle\Fabpot\FooBundle', 'FabpotFooBundle'),
);

View File

@ -82,6 +82,20 @@
requestStack = [],
extractHeaders = function(xhr, stackElement) {
// Here we avoid to call xhr.getResponseHeader in order to
// prevent polluting the console with CORS security errors
var allHeaders = xhr.getAllResponseHeaders();
var ret;
if (ret = allHeaders.match(/^x-debug-token:\s+(.*)$/im)) {
stackElement.profile = ret[1];
}
if (ret = allHeaders.match(/^x-debug-token-link:\s+(.*)$/im)) {
stackElement.profilerUrl = ret[1];
}
},
renderAjaxRequests = function() {
var requestCounter = document.querySelectorAll('.sf-toolbar-ajax-requests');
if (!requestCounter.length) {
@ -255,8 +269,7 @@
stackElement.loading = false;
stackElement.error = self.status < 200 || self.status >= 400;
stackElement.statusCode = self.status;
stackElement.profile = self.getResponseHeader("X-Debug-Token");
stackElement.profilerUrl = self.getResponseHeader("X-Debug-Token-Link");
extractHeaders(self, stackElement);
Sfjs.renderAjaxRequests();
}

View File

@ -76,42 +76,6 @@ class ProgressIndicator
$this->display();
}
/**
* Gets the current indicator message.
*
* @return string|null
*
* @internal for PHP 5.3 compatibility
*/
public function getMessage()
{
return $this->message;
}
/**
* Gets the progress bar start time.
*
* @return int The progress bar start time
*
* @internal for PHP 5.3 compatibility
*/
public function getStartTime()
{
return $this->startTime;
}
/**
* Gets the current animated indicator character.
*
* @return string
*
* @internal for PHP 5.3 compatibility
*/
public function getCurrentValue()
{
return $this->indicatorValues[$this->indicatorCurrent % count($this->indicatorValues)];
}
/**
* Starts the indicator output.
*
@ -294,13 +258,13 @@ class ProgressIndicator
{
return array(
'indicator' => function (ProgressIndicator $indicator) {
return $indicator->getCurrentValue();
return $indicator->indicatorValues[$indicator->indicatorCurrent % count($indicator->indicatorValues)];
},
'message' => function (ProgressIndicator $indicator) {
return $indicator->getMessage();
return $indicator->message;
},
'elapsed' => function (ProgressIndicator $indicator) {
return Helper::formatTime(time() - $indicator->getStartTime());
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));

View File

@ -580,6 +580,8 @@ class ErrorHandler
}
} catch (\Exception $exception) {
// Handled below
} catch (\Throwable $exception) {
// Handled below
}
if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {

View File

@ -45,8 +45,8 @@ class AutowirePass implements CompilerPassInterface
$this->completeDefinition($id, $definition);
}
}
} catch (\Error $e) {
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
spl_autoload_unregister($throwingAutoloader);
@ -283,7 +283,7 @@ class AutowirePass implements CompilerPassInterface
try {
$this->completeDefinition($argumentId, $argumentDefinition);
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
$message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface);
throw new RuntimeException($message, 0, $e);

View File

@ -542,6 +542,10 @@ class PhpDumper extends Dumper
return sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName);
}
if (0 === strpos($class, 'new ')) {
return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
}
return sprintf(" call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
}
@ -723,6 +727,10 @@ EOF;
return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '');
}
if (0 === strpos($class, 'new ')) {
return sprintf(" $return{$instantiation}(%s)->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '');
}
return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : '');
}

View File

@ -78,6 +78,15 @@ $container
->register('configured_service', 'stdClass')
->setConfigurator(array(new Reference('configurator_service'), 'configureStdClass'))
;
$container
->register('configurator_service_simple', 'ConfClass')
->addArgument('bar')
->setPublic(false)
;
$container
->register('configured_service_simple', 'stdClass')
->setConfigurator(array(new Reference('configurator_service_simple'), 'configureStdClass'))
;
$container
->register('decorated', 'stdClass')
;
@ -111,5 +120,14 @@ $container
->register('service_from_static_method', 'Bar\FooClass')
->setFactory(array('Bar\FooClass', 'getInstance'))
;
$container
->register('factory_simple', 'SimpleFactoryClass')
->addArgument('foo')
->setPublic(false)
;
$container
->register('factory_service_simple', 'Bar')
->setFactory(array(new Reference('factory_simple'), 'getInstance'))
;
return $container;

View File

@ -14,6 +14,8 @@ digraph sc {
node_request [label="request\nRequest\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_configurator_service [label="configurator_service\nConfClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_configured_service [label="configured_service\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_configurator_service_simple [label="configurator_service_simple\nConfClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_configured_service_simple [label="configured_service_simple\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_decorated [label="decorated\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_decorator_service [label="decorator_service\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_decorator_service_with_name [label="decorator_service_with_name\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
@ -22,6 +24,8 @@ digraph sc {
node_factory_service [label="factory_service\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_new_factory_service [label="new_factory_service\nFooBarBaz\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_service_from_static_method [label="service_from_static_method\nBar\\FooClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_factory_simple [label="factory_simple\nSimpleFactoryClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_factory_service_simple [label="factory_service_simple\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_service_container [label="service_container\nSymfony\\Component\\DependencyInjection\\ContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"];
node_foo2 [label="foo2\n\n", shape=record, fillcolor="#ff9999", style="filled"];
node_foo3 [label="foo3\n\n", shape=record, fillcolor="#ff9999", style="filled"];

View File

@ -40,7 +40,7 @@ class ProjectServiceContainer extends Container
*/
protected function getServiceFromAnonymousFactoryService()
{
return $this->services['service_from_anonymous_factory'] = call_user_func(array(new \Bar\FooClass(), 'getInstance'));
return $this->services['service_from_anonymous_factory'] = (new \Bar\FooClass())->getInstance();
}
/**

View File

@ -28,12 +28,16 @@ class ProjectServiceContainer extends Container
'bar' => 'getBarService',
'baz' => 'getBazService',
'configurator_service' => 'getConfiguratorServiceService',
'configurator_service_simple' => 'getConfiguratorServiceSimpleService',
'configured_service' => 'getConfiguredServiceService',
'configured_service_simple' => 'getConfiguredServiceSimpleService',
'decorated' => 'getDecoratedService',
'decorator_service' => 'getDecoratorServiceService',
'decorator_service_with_name' => 'getDecoratorServiceWithNameService',
'deprecated_service' => 'getDeprecatedServiceService',
'factory_service' => 'getFactoryServiceService',
'factory_service_simple' => 'getFactoryServiceSimpleService',
'factory_simple' => 'getFactorySimpleService',
'foo' => 'getFooService',
'foo.baz' => 'getFoo_BazService',
'foo_bar' => 'getFooBarService',
@ -104,6 +108,23 @@ class ProjectServiceContainer extends Container
return $instance;
}
/**
* Gets the 'configured_service_simple' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* @return \stdClass A stdClass instance.
*/
protected function getConfiguredServiceSimpleService()
{
$this->services['configured_service_simple'] = $instance = new \stdClass();
$this->get('configurator_service_simple')->configureStdClass($instance);
return $instance;
}
/**
* Gets the 'decorated' service.
*
@ -173,6 +194,19 @@ class ProjectServiceContainer extends Container
return $this->services['factory_service'] = $this->get('foo.baz')->getInstance();
}
/**
* Gets the 'factory_service_simple' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* @return \Bar A Bar instance.
*/
protected function getFactoryServiceSimpleService()
{
return $this->services['factory_service_simple'] = $this->get('factory_simple')->getInstance();
}
/**
* Gets the 'foo' service.
*
@ -334,6 +368,40 @@ class ProjectServiceContainer extends Container
return $instance;
}
/**
* Gets the 'configurator_service_simple' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* This service is private.
* If you want to be able to request this service from the container directly,
* make it public, otherwise you might end up with broken code.
*
* @return \ConfClass A ConfClass instance.
*/
protected function getConfiguratorServiceSimpleService()
{
return $this->services['configurator_service_simple'] = new \ConfClass('bar');
}
/**
* Gets the 'factory_simple' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* This service is private.
* If you want to be able to request this service from the container directly,
* make it public, otherwise you might end up with broken code.
*
* @return \SimpleFactoryClass A SimpleFactoryClass instance.
*/
protected function getFactorySimpleService()
{
return $this->services['factory_simple'] = new \SimpleFactoryClass('foo');
}
/**
* Gets the 'inlined' service.
*

View File

@ -30,10 +30,12 @@ class ProjectServiceContainer extends Container
'bar' => 'getBarService',
'baz' => 'getBazService',
'configured_service' => 'getConfiguredServiceService',
'configured_service_simple' => 'getConfiguredServiceSimpleService',
'decorator_service' => 'getDecoratorServiceService',
'decorator_service_with_name' => 'getDecoratorServiceWithNameService',
'deprecated_service' => 'getDeprecatedServiceService',
'factory_service' => 'getFactoryServiceService',
'factory_service_simple' => 'getFactoryServiceSimpleService',
'foo' => 'getFooService',
'foo.baz' => 'getFoo_BazService',
'foo_bar' => 'getFooBarService',
@ -114,6 +116,23 @@ class ProjectServiceContainer extends Container
return $instance;
}
/**
* Gets the 'configured_service_simple' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* @return \stdClass A stdClass instance.
*/
protected function getConfiguredServiceSimpleService()
{
$this->services['configured_service_simple'] = $instance = new \stdClass();
(new \ConfClass('bar'))->configureStdClass($instance);
return $instance;
}
/**
* Gets the 'decorator_service' service.
*
@ -170,6 +189,19 @@ class ProjectServiceContainer extends Container
return $this->services['factory_service'] = $this->get('foo.baz')->getInstance();
}
/**
* Gets the 'factory_service_simple' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* @return \Bar A Bar instance.
*/
protected function getFactoryServiceSimpleService()
{
return $this->services['factory_service_simple'] = (new \SimpleFactoryClass('foo'))->getInstance();
}
/**
* Gets the 'foo' service.
*

View File

@ -84,6 +84,12 @@
<service id="configured_service" class="stdClass">
<configurator service="configurator_service" method="configureStdClass"/>
</service>
<service id="configurator_service_simple" class="ConfClass" public="false">
<argument>bar</argument>
</service>
<service id="configured_service_simple" class="stdClass">
<configurator service="configurator_service_simple" method="configureStdClass"/>
</service>
<service id="decorated" class="stdClass"/>
<service id="decorator_service" class="stdClass" decorates="decorated"/>
<service id="decorator_service_with_name" class="stdClass" decorates="decorated" decoration-inner-name="decorated.pif-pouf"/>
@ -103,6 +109,12 @@
<service id="service_from_static_method" class="Bar\FooClass">
<factory class="Bar\FooClass" method="getInstance"/>
</service>
<service id="factory_simple" class="SimpleFactoryClass" public="false">
<argument>foo</argument>
</service>
<service id="factory_service_simple" class="Bar">
<factory service="factory_simple" method="getInstance"/>
</service>
<service id="alias_for_foo" alias="foo"/>
<service id="alias_for_alias" alias="foo"/>
</services>

View File

@ -67,6 +67,13 @@ services:
configured_service:
class: stdClass
configurator: ['@configurator_service', configureStdClass]
configurator_service_simple:
class: ConfClass
public: false
arguments: ['bar']
configured_service_simple:
class: stdClass
configurator: ['@configurator_service_simple', configureStdClass]
decorated:
class: stdClass
decorator_service:
@ -93,5 +100,12 @@ services:
service_from_static_method:
class: Bar\FooClass
factory: [Bar\FooClass, getInstance]
factory_simple:
class: SimpleFactoryClass
public: false
arguments: ['foo']
factory_service_simple:
class: Bar
factory: ['@factory_simple', getInstance]
alias_for_foo: '@foo'
alias_for_alias: '@foo'

View File

@ -312,6 +312,19 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase
);
}
public function testIgnoredAttributesDenormalize()
{
$this->normalizer->setIgnoredAttributes(array('fooBar', 'bar', 'baz'));
$obj = new ObjectDummy();
$obj->setFoo('foo');
$this->assertEquals(
$obj,
$this->normalizer->denormalize(array('fooBar' => 'fooBar', 'foo' => 'foo', 'baz' => 'baz'), __NAMESPACE__.'\ObjectDummy')
);
}
public function provideCallbacks()
{
return array(

View File

@ -34,7 +34,7 @@ class DateTimeValidator extends DateValidator
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\DateTime');
}
if (null === $value || '' === $value || $value instanceof \DateTime) {
if (null === $value || '' === $value || $value instanceof \DateTimeInterface) {
return;
}

View File

@ -47,7 +47,7 @@ class DateValidator extends ConstraintValidator
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Date');
}
if (null === $value || '' === $value || $value instanceof \DateTime) {
if (null === $value || '' === $value || $value instanceof \DateTimeInterface) {
return;
}

View File

@ -42,6 +42,13 @@ class DateTimeValidatorTest extends AbstractConstraintValidatorTest
$this->assertNoViolation();
}
public function testDateTimeImmutableClassIsValid()
{
$this->validator->validate(new \DateTimeImmutable(), new DateTime());
$this->assertNoViolation();
}
/**
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
*/

View File

@ -42,6 +42,13 @@ class DateValidatorTest extends AbstractConstraintValidatorTest
$this->assertNoViolation();
}
public function testDateTimeImmutableClassIsValid()
{
$this->validator->validate(new \DateTimeImmutable(), new Date());
$this->assertNoViolation();
}
/**
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
*/

View File

@ -128,6 +128,8 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
$this->dumpLine(-1);
} catch (\Exception $exception) {
// Re-thrown below
} catch (\Throwable $exception) {
// Re-thrown below
}
if ($output) {
$this->setOutput($prevOutput);

View File

@ -24,6 +24,7 @@ class Parser
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
private $offset = 0;
private $totalNumberOfLines;
private $lines = array();
private $currentLineNb = -1;
private $currentLine = '';
@ -32,11 +33,13 @@ class Parser
/**
* Constructor.
*
* @param int $offset The offset of YAML document (used for line numbers in error messages)
* @param int $offset The offset of YAML document (used for line numbers in error messages)
* @param int|null $totalNumberOfLines The overall number of lines being parsed
*/
public function __construct($offset = 0)
public function __construct($offset = 0, $totalNumberOfLines = null)
{
$this->offset = $offset;
$this->totalNumberOfLines = $totalNumberOfLines;
}
/**
@ -85,6 +88,10 @@ class Parser
$value = $this->cleanup($value);
$this->lines = explode("\n", $value);
if (null === $this->totalNumberOfLines) {
$this->totalNumberOfLines = count($this->lines);
}
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8');
@ -106,7 +113,7 @@ class Parser
$isRef = $mergeNode = false;
if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
if ($context && 'mapping' == $context) {
throw new ParseException('You cannot define a sequence item when in a mapping');
throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
$context = 'sequence';
@ -118,7 +125,7 @@ class Parser
// array
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
$c = $this->getRealCurrentLineNb() + 1;
$parser = new self($c);
$parser = new self($c, $this->totalNumberOfLines);
$parser->refs = &$this->refs;
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $flags);
} else {
@ -127,7 +134,7 @@ class Parser
) {
// this is a compact notation element, add to next block and parse
$c = $this->getRealCurrentLineNb();
$parser = new self($c);
$parser = new self($c, $this->totalNumberOfLines);
$parser->refs = &$this->refs;
$block = $values['value'];
@ -145,7 +152,7 @@ class Parser
}
} 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('"', "'")))) {
if ($context && 'sequence' == $context) {
throw new ParseException('You cannot define a mapping item when in a sequence');
throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
}
$context = 'mapping';
@ -192,7 +199,7 @@ class Parser
$value = $this->getNextEmbedBlock();
}
$c = $this->getRealCurrentLineNb() + 1;
$parser = new self($c);
$parser = new self($c, $this->totalNumberOfLines);
$parser->refs = &$this->refs;
$parsed = $parser->parse($value, $flags);
@ -243,7 +250,7 @@ class Parser
}
} else {
$c = $this->getRealCurrentLineNb() + 1;
$parser = new self($c);
$parser = new self($c, $this->totalNumberOfLines);
$parser->refs = &$this->refs;
$value = $parser->parse($this->getNextEmbedBlock(), $flags);
// Spec: Keys MUST be unique; first one wins.
@ -266,7 +273,7 @@ class Parser
} else {
// multiple documents are not supported
if ('---' === $this->currentLine) {
throw new ParseException('Multiple documents are not supported.');
throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine);
}
// 1-liner optionally followed by newline(s)
@ -511,7 +518,7 @@ class Parser
}
if (!array_key_exists($value, $this->refs)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine);
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine);
}
return $this->refs[$value];
@ -609,6 +616,8 @@ class Parser
if ($notEOF) {
$blockLines[] = '';
$this->moveToPreviousLine();
} elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
$blockLines[] = '';
}
// folded style
@ -715,6 +724,11 @@ class Parser
return '' !== $ltrimmedLine && $ltrimmedLine[0] === '#';
}
private function isCurrentLineLastLineInDocument()
{
return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
}
/**
* Cleanups a YAML string to be parsed.
*
@ -792,7 +806,7 @@ class Parser
*/
private function isStringUnIndentedCollectionItem()
{
return 0 === strpos($this->currentLine, '- ');
return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
}
/**

View File

@ -633,7 +633,7 @@ EOF;
/**
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
* @expectedExceptionMessage Multiple documents are not supported.
* @expectedExceptionMessageRegExp /^Multiple documents are not supported.+/
*/
public function testMultipleDocumentsNotSupportedException()
{
@ -665,6 +665,34 @@ EOF
);
}
public function testSequenceInMappingStartedBySingleDashLine()
{
$yaml = <<<EOT
a:
-
b:
-
bar: baz
- foo
d: e
EOT;
$expected = array(
'a' => array(
array(
'b' => array(
array(
'bar' => 'baz',
),
),
),
'foo',
),
'd' => 'e',
);
$this->assertSame($expected, $this->parser->parse($yaml));
}
/**
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
*/
@ -1014,6 +1042,7 @@ EOT
foo
# bar
baz
EOT
,
),
@ -1042,7 +1071,7 @@ EOT;
$expected = array(
'foo' => array(
'bar' => array(
'scalar-block' => 'line1 line2>',
'scalar-block' => "line1 line2>\n",
),
'baz' => array(
'foobar' => null,