Merge branch '2.8' into 3.0
* 2.8: [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 Use levenshtein level for better Bundle matching [WebProfilerBundle] Fix CORS ajax security issues [DX][DI] Make Autowiring exceptions more future friendly
This commit is contained in:
commit
90dcd92691
@ -398,8 +398,11 @@ UPGRADE FROM 2.x to 3.0
|
|||||||
</service>
|
</service>
|
||||||
```
|
```
|
||||||
|
|
||||||
* The `ChoiceToBooleanArrayTransformer`, `ChoicesToBooleanArrayTransformer`,
|
* The `max_length` option was removed. Use the `attr` option instead by setting it to
|
||||||
`FixRadioInputListener`, and `FixCheckboxInputListener` classes were removed.
|
an `array` with a `maxlength` key.
|
||||||
|
|
||||||
|
* The `ChoiceToBooleanArrayTransformer`, `ChoicesToBooleanArrayTransformer`,
|
||||||
|
`FixRadioInputListener`, and `FixCheckboxInputListener` classes were removed.
|
||||||
|
|
||||||
* The `choice_list` option of `ChoiceType` was removed.
|
* The `choice_list` option of `ChoiceType` was removed.
|
||||||
|
|
||||||
@ -1742,8 +1745,7 @@ UPGRADE FROM 2.x to 3.0
|
|||||||
|
|
||||||
### WebProfiler
|
### WebProfiler
|
||||||
|
|
||||||
* The `profiler:import` and `profiler:export` commands have been deprecated and
|
* The `profiler:import` and `profiler:export` commands have been removed.
|
||||||
will be removed in 3.0.
|
|
||||||
|
|
||||||
* All the profiler storages different than `FileProfilerStorage` have been
|
* All the profiler storages different than `FileProfilerStorage` have been
|
||||||
removed. The removed classes are:
|
removed. The removed classes are:
|
||||||
|
@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command;
|
|||||||
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ class ServerStatusCommand extends ServerCommand
|
|||||||
$this
|
$this
|
||||||
->setDefinition(array(
|
->setDefinition(array(
|
||||||
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'),
|
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')
|
->setName('server:status')
|
||||||
->setDescription('Outputs the status of the built-in web server for the given address')
|
->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);
|
$io = new SymfonyStyle($input, $output);
|
||||||
$address = $input->getArgument('address');
|
$address = $input->getArgument('address');
|
||||||
|
|
||||||
|
if (false === strpos($address, ':')) {
|
||||||
|
$address = $address.':'.$input->getOption('port');
|
||||||
|
}
|
||||||
|
|
||||||
// remove an orphaned lock file
|
// remove an orphaned lock file
|
||||||
if (file_exists($this->getLockFile($address)) && !$this->isServerRunning($address)) {
|
if (file_exists($this->getLockFile($address)) && !$this->isServerRunning($address)) {
|
||||||
unlink($this->getLockFile($address));
|
unlink($this->getLockFile($address));
|
||||||
|
@ -142,6 +142,7 @@ class ControllerNameParser
|
|||||||
$lev = levenshtein($nonExistentBundleName, $bundleName);
|
$lev = levenshtein($nonExistentBundleName, $bundleName);
|
||||||
if ($lev <= strlen($nonExistentBundleName) / 3 && ($alternative === null || $lev < $shortest)) {
|
if ($lev <= strlen($nonExistentBundleName) / 3 && ($alternative === null || $lev < $shortest)) {
|
||||||
$alternative = $bundleName;
|
$alternative = $bundleName;
|
||||||
|
$shortest = $lev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ class ControllerNameParserTest extends TestCase
|
|||||||
{
|
{
|
||||||
$parser = $this->createParser();
|
$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('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('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: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 {
|
try {
|
||||||
$parser->build('TestBundle\FooBundle\Controller\DefaultController::index');
|
$parser->build('TestBundle\FooBundle\Controller\DefaultController::index');
|
||||||
@ -132,8 +132,9 @@ class ControllerNameParserTest extends TestCase
|
|||||||
public function getInvalidBundleNameTests()
|
public function getInvalidBundleNameTests()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
array('FoodBundle:Default:index', 'FooBundle:Default:index'),
|
'Alternative will be found using levenshtein' => array('FoodBundle:Default:index', 'FooBundle:Default:index'),
|
||||||
array('CrazyBundle:Default:index', false),
|
'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(
|
$bundles = array(
|
||||||
'SensioFooBundle' => $this->getBundle('TestBundle\Fabpot\FooBundle', 'FabpotFooBundle'),
|
'SensioFooBundle' => $this->getBundle('TestBundle\Fabpot\FooBundle', 'FabpotFooBundle'),
|
||||||
'SensioCmsFooBundle' => $this->getBundle('TestBundle\Sensio\Cms\FooBundle', 'SensioCmsFooBundle'),
|
'SensioCmsFooBundle' => $this->getBundle('TestBundle\Sensio\Cms\FooBundle', 'SensioCmsFooBundle'),
|
||||||
|
'FoooooBundle' => $this->getBundle('TestBundle\FooBundle', 'FoooooBundle'),
|
||||||
'FooBundle' => $this->getBundle('TestBundle\FooBundle', 'FooBundle'),
|
'FooBundle' => $this->getBundle('TestBundle\FooBundle', 'FooBundle'),
|
||||||
'FabpotFooBundle' => $this->getBundle('TestBundle\Fabpot\FooBundle', 'FabpotFooBundle'),
|
'FabpotFooBundle' => $this->getBundle('TestBundle\Fabpot\FooBundle', 'FabpotFooBundle'),
|
||||||
);
|
);
|
||||||
|
@ -82,6 +82,20 @@
|
|||||||
|
|
||||||
requestStack = [],
|
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() {
|
renderAjaxRequests = function() {
|
||||||
var requestCounter = document.querySelectorAll('.sf-toolbar-ajax-requests');
|
var requestCounter = document.querySelectorAll('.sf-toolbar-ajax-requests');
|
||||||
if (!requestCounter.length) {
|
if (!requestCounter.length) {
|
||||||
@ -241,8 +255,8 @@
|
|||||||
stackElement.duration = new Date() - stackElement.start;
|
stackElement.duration = new Date() - stackElement.start;
|
||||||
stackElement.loading = false;
|
stackElement.loading = false;
|
||||||
stackElement.error = self.status < 200 || self.status >= 400;
|
stackElement.error = self.status < 200 || self.status >= 400;
|
||||||
stackElement.profile = self.getResponseHeader("X-Debug-Token");
|
|
||||||
stackElement.profilerUrl = self.getResponseHeader("X-Debug-Token-Link");
|
extractHeaders(self, stackElement);
|
||||||
|
|
||||||
Sfjs.renderAjaxRequests();
|
Sfjs.renderAjaxRequests();
|
||||||
}
|
}
|
||||||
|
@ -580,6 +580,8 @@ class ErrorHandler
|
|||||||
}
|
}
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
// Handled below
|
// Handled below
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
// Handled below
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
|
if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
|
||||||
|
@ -44,8 +44,8 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
$this->completeDefinition($id, $definition);
|
$this->completeDefinition($id, $definition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Error $e) {
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
} catch (\Throwable $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
spl_autoload_unregister($throwingAutoloader);
|
spl_autoload_unregister($throwingAutoloader);
|
||||||
@ -225,13 +225,16 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
|
private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
|
||||||
{
|
{
|
||||||
if (isset($this->notGuessableTypes[$typeHint->name])) {
|
if (isset($this->notGuessableTypes[$typeHint->name])) {
|
||||||
throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Several services implementing this type have been declared: "%s".', $typeHint->name, $id, implode('", "', $this->types[$typeHint->name])));
|
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
|
||||||
|
$matchingServices = implode(', ', $this->types[$typeHint->name]);
|
||||||
|
|
||||||
|
throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$noAvailableDefinitionMessage = sprintf('Unable to autowire argument of type "%s" for the service "%s". This type cannot be instantiated automatically and no service implementing this type is declared.', $typeHint->name, $id);
|
|
||||||
|
|
||||||
if (!$typeHint->isInstantiable()) {
|
if (!$typeHint->isInstantiable()) {
|
||||||
throw new RuntimeException($noAvailableDefinitionMessage);
|
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
|
||||||
|
throw new RuntimeException(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));
|
||||||
}
|
}
|
||||||
|
|
||||||
$argumentId = sprintf('autowired.%s', $typeHint->name);
|
$argumentId = sprintf('autowired.%s', $typeHint->name);
|
||||||
@ -244,7 +247,9 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
try {
|
try {
|
||||||
$this->completeDefinition($argumentId, $argumentDefinition);
|
$this->completeDefinition($argumentId, $argumentDefinition);
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
throw new RuntimeException($noAvailableDefinitionMessage, 0, $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);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Reference($argumentId);
|
return new Reference($argumentId);
|
||||||
|
@ -103,7 +103,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||||
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". Several services implementing this type have been declared: "c1", "c2".
|
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". Multiple services exist for this interface (c1, c2, c3).
|
||||||
*/
|
*/
|
||||||
public function testTypeCollision()
|
public function testTypeCollision()
|
||||||
{
|
{
|
||||||
@ -111,6 +111,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$container->register('c1', __NAMESPACE__.'\CollisionA');
|
$container->register('c1', __NAMESPACE__.'\CollisionA');
|
||||||
$container->register('c2', __NAMESPACE__.'\CollisionB');
|
$container->register('c2', __NAMESPACE__.'\CollisionB');
|
||||||
|
$container->register('c3', __NAMESPACE__.'\CollisionB');
|
||||||
$aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
|
$aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
|
||||||
$aDefinition->setAutowired(true);
|
$aDefinition->setAutowired(true);
|
||||||
|
|
||||||
@ -120,7 +121,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||||
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" for the service "a". Several services implementing this type have been declared: "a1", "a2".
|
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\Foo" for the service "a". Multiple services exist for this class (a1, a2).
|
||||||
*/
|
*/
|
||||||
public function testTypeNotGuessable()
|
public function testTypeNotGuessable()
|
||||||
{
|
{
|
||||||
@ -137,7 +138,7 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||||
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\A" for the service "a". Several services implementing this type have been declared: "a1", "a2".
|
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\A" for the service "a". Multiple services exist for this class (a1, a2).
|
||||||
*/
|
*/
|
||||||
public function testTypeNotGuessableWithSubclass()
|
public function testTypeNotGuessableWithSubclass()
|
||||||
{
|
{
|
||||||
@ -152,6 +153,21 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
|||||||
$pass->process($container);
|
$pass->process($container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". No services were found matching this interface and it cannot be auto-registered.
|
||||||
|
*/
|
||||||
|
public function testTypeNotGuessableNoServicesFound()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
|
||||||
|
$aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
|
||||||
|
$aDefinition->setAutowired(true);
|
||||||
|
|
||||||
|
$pass = new AutowirePass();
|
||||||
|
$pass->process($container);
|
||||||
|
}
|
||||||
|
|
||||||
public function testTypeNotGuessableWithTypeSet()
|
public function testTypeNotGuessableWithTypeSet()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
@ -207,21 +223,6 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals(__NAMESPACE__.'\Lille', $lilleDefinition->getClass());
|
$this->assertEquals(__NAMESPACE__.'\Lille', $lilleDefinition->getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
|
||||||
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "a". This type cannot be instantiated automatically and no service implementing this type is declared.
|
|
||||||
*/
|
|
||||||
public function testCreateNonInstanciable()
|
|
||||||
{
|
|
||||||
$container = new ContainerBuilder();
|
|
||||||
|
|
||||||
$aDefinition = $container->register('a', __NAMESPACE__.'\CannotBeAutowired');
|
|
||||||
$aDefinition->setAutowired(true);
|
|
||||||
|
|
||||||
$pass = new AutowirePass();
|
|
||||||
$pass->process($container);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testResolveParameter()
|
public function testResolveParameter()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
|
@ -124,6 +124,9 @@ class StringUtil
|
|||||||
|
|
||||||
// chateaux (chateau)
|
// chateaux (chateau)
|
||||||
array('xuae', 4, false, true, 'eau'),
|
array('xuae', 4, false, true, 'eau'),
|
||||||
|
|
||||||
|
// people (person)
|
||||||
|
array('elpoep', 6, true, true, 'person'),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,6 +107,8 @@ class StringUtilTest extends \PHPUnit_Framework_TestCase
|
|||||||
array('objectives', 'objective'),
|
array('objectives', 'objective'),
|
||||||
array('oxen', 'ox'),
|
array('oxen', 'ox'),
|
||||||
array('parties', 'party'),
|
array('parties', 'party'),
|
||||||
|
array('people', 'person'),
|
||||||
|
array('persons', 'person'),
|
||||||
array('phenomena', array('phenomenon', 'phenomenum')),
|
array('phenomena', array('phenomenon', 'phenomenum')),
|
||||||
array('photos', 'photo'),
|
array('photos', 'photo'),
|
||||||
array('pianos', 'piano'),
|
array('pianos', 'piano'),
|
||||||
|
@ -308,6 +308,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()
|
public function provideCallbacks()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
|
@ -122,6 +122,8 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
|
|||||||
$this->dumpLine(-1);
|
$this->dumpLine(-1);
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
// Re-thrown below
|
// Re-thrown below
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
// Re-thrown below
|
||||||
}
|
}
|
||||||
if ($output) {
|
if ($output) {
|
||||||
$this->setOutput($prevOutput);
|
$this->setOutput($prevOutput);
|
||||||
|
@ -23,6 +23,7 @@ class Parser
|
|||||||
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
|
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
|
||||||
|
|
||||||
private $offset = 0;
|
private $offset = 0;
|
||||||
|
private $totalNumberOfLines;
|
||||||
private $lines = array();
|
private $lines = array();
|
||||||
private $currentLineNb = -1;
|
private $currentLineNb = -1;
|
||||||
private $currentLine = '';
|
private $currentLine = '';
|
||||||
@ -31,11 +32,13 @@ class Parser
|
|||||||
/**
|
/**
|
||||||
* Constructor.
|
* 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->offset = $offset;
|
||||||
|
$this->totalNumberOfLines = $totalNumberOfLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +63,10 @@ class Parser
|
|||||||
$value = $this->cleanup($value);
|
$value = $this->cleanup($value);
|
||||||
$this->lines = explode("\n", $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')) {
|
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
|
||||||
$mbEncoding = mb_internal_encoding();
|
$mbEncoding = mb_internal_encoding();
|
||||||
mb_internal_encoding('UTF-8');
|
mb_internal_encoding('UTF-8');
|
||||||
@ -81,7 +88,7 @@ class Parser
|
|||||||
$isRef = $mergeNode = false;
|
$isRef = $mergeNode = false;
|
||||||
if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
|
if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
|
||||||
if ($context && 'mapping' == $context) {
|
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';
|
$context = 'sequence';
|
||||||
|
|
||||||
@ -93,7 +100,7 @@ class Parser
|
|||||||
// array
|
// array
|
||||||
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
|
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
|
||||||
$c = $this->getRealCurrentLineNb() + 1;
|
$c = $this->getRealCurrentLineNb() + 1;
|
||||||
$parser = new self($c);
|
$parser = new self($c, $this->totalNumberOfLines);
|
||||||
$parser->refs = &$this->refs;
|
$parser->refs = &$this->refs;
|
||||||
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
$data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||||
} else {
|
} else {
|
||||||
@ -102,7 +109,7 @@ class Parser
|
|||||||
) {
|
) {
|
||||||
// this is a compact notation element, add to next block and parse
|
// this is a compact notation element, add to next block and parse
|
||||||
$c = $this->getRealCurrentLineNb();
|
$c = $this->getRealCurrentLineNb();
|
||||||
$parser = new self($c);
|
$parser = new self($c, $this->totalNumberOfLines);
|
||||||
$parser->refs = &$this->refs;
|
$parser->refs = &$this->refs;
|
||||||
|
|
||||||
$block = $values['value'];
|
$block = $values['value'];
|
||||||
@ -120,7 +127,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('"', "'")))) {
|
} 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) {
|
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';
|
$context = 'mapping';
|
||||||
|
|
||||||
@ -167,7 +174,7 @@ class Parser
|
|||||||
$value = $this->getNextEmbedBlock();
|
$value = $this->getNextEmbedBlock();
|
||||||
}
|
}
|
||||||
$c = $this->getRealCurrentLineNb() + 1;
|
$c = $this->getRealCurrentLineNb() + 1;
|
||||||
$parser = new self($c);
|
$parser = new self($c, $this->totalNumberOfLines);
|
||||||
$parser->refs = &$this->refs;
|
$parser->refs = &$this->refs;
|
||||||
$parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
$parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||||
|
|
||||||
@ -218,7 +225,7 @@ class Parser
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$c = $this->getRealCurrentLineNb() + 1;
|
$c = $this->getRealCurrentLineNb() + 1;
|
||||||
$parser = new self($c);
|
$parser = new self($c, $this->totalNumberOfLines);
|
||||||
$parser->refs = &$this->refs;
|
$parser->refs = &$this->refs;
|
||||||
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
$value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
|
||||||
// Spec: Keys MUST be unique; first one wins.
|
// Spec: Keys MUST be unique; first one wins.
|
||||||
@ -241,7 +248,7 @@ class Parser
|
|||||||
} else {
|
} else {
|
||||||
// multiple documents are not supported
|
// multiple documents are not supported
|
||||||
if ('---' === $this->currentLine) {
|
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)
|
// 1-liner optionally followed by newline(s)
|
||||||
@ -488,7 +495,7 @@ class Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!array_key_exists($value, $this->refs)) {
|
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];
|
return $this->refs[$value];
|
||||||
@ -580,6 +587,8 @@ class Parser
|
|||||||
if ($notEOF) {
|
if ($notEOF) {
|
||||||
$blockLines[] = '';
|
$blockLines[] = '';
|
||||||
$this->moveToPreviousLine();
|
$this->moveToPreviousLine();
|
||||||
|
} elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
|
||||||
|
$blockLines[] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// folded style
|
// folded style
|
||||||
@ -686,6 +695,11 @@ class Parser
|
|||||||
return '' !== $ltrimmedLine && $ltrimmedLine[0] === '#';
|
return '' !== $ltrimmedLine && $ltrimmedLine[0] === '#';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isCurrentLineLastLineInDocument()
|
||||||
|
{
|
||||||
|
return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanups a YAML string to be parsed.
|
* Cleanups a YAML string to be parsed.
|
||||||
*
|
*
|
||||||
@ -763,7 +777,7 @@ class Parser
|
|||||||
*/
|
*/
|
||||||
private function isStringUnIndentedCollectionItem()
|
private function isStringUnIndentedCollectionItem()
|
||||||
{
|
{
|
||||||
return 0 === strpos($this->currentLine, '- ');
|
return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -596,7 +596,7 @@ EOF;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
|
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
|
||||||
* @expectedExceptionMessage Multiple documents are not supported.
|
* @expectedExceptionMessageRegExp /^Multiple documents are not supported.+/
|
||||||
*/
|
*/
|
||||||
public function testMultipleDocumentsNotSupportedException()
|
public function testMultipleDocumentsNotSupportedException()
|
||||||
{
|
{
|
||||||
@ -628,6 +628,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
|
* @expectedException \Symfony\Component\Yaml\Exception\ParseException
|
||||||
*/
|
*/
|
||||||
@ -977,6 +1005,7 @@ EOT
|
|||||||
foo
|
foo
|
||||||
# bar
|
# bar
|
||||||
baz
|
baz
|
||||||
|
|
||||||
EOT
|
EOT
|
||||||
,
|
,
|
||||||
),
|
),
|
||||||
@ -1005,7 +1034,7 @@ EOT;
|
|||||||
$expected = array(
|
$expected = array(
|
||||||
'foo' => array(
|
'foo' => array(
|
||||||
'bar' => array(
|
'bar' => array(
|
||||||
'scalar-block' => 'line1 line2>',
|
'scalar-block' => "line1 line2>\n",
|
||||||
),
|
),
|
||||||
'baz' => array(
|
'baz' => array(
|
||||||
'foobar' => null,
|
'foobar' => null,
|
||||||
|
Reference in New Issue
Block a user