Merge branch '3.4' into 4.1
* 3.4: [Debug] ignore underscore vs backslash namespaces in DebugClassLoader [TwigBridge][Form] Prevent multiple rendering of form collection prototypes [FrameworkBundle] fix describing routes with no controllers [DI] move RegisterServiceSubscribersPass before DecoratorServicePass Update ValidationListener.php [Yaml] ensures that the mb_internal_encoding is reset to its initial value [WebLink] Fixed documentation link [Security] getTargetPath of TargetPathTrait must return string or null [Hackday][Serializer] Deserialization ignores argument type hint from phpdoc for array in constructor argument [Security] defer log message in guard authenticator merge conflicts Fix HeaderBag::get phpdoc
This commit is contained in:
commit
547bf26eee
@ -28,7 +28,7 @@
|
||||
{%- endblock form_widget_compound -%}
|
||||
|
||||
{%- block collection_widget -%}
|
||||
{% if prototype is defined %}
|
||||
{% if prototype is defined and not prototype.rendered %}
|
||||
{%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%}
|
||||
{% endif %}
|
||||
{{- block('form_widget') -}}
|
||||
|
@ -56,7 +56,7 @@ class TextDescriptor extends Descriptor
|
||||
|
||||
if ($showControllers) {
|
||||
$controller = $route->getDefault('_controller');
|
||||
$row[] = $this->formatCallable($controller);
|
||||
$row[] = $controller ? $this->formatCallable($controller) : '';
|
||||
}
|
||||
|
||||
$tableRows[] = $row;
|
||||
|
@ -214,7 +214,7 @@ class DebugClassLoader
|
||||
$len = 0;
|
||||
$ns = '';
|
||||
} else {
|
||||
$ns = \substr($class, 0, $len);
|
||||
$ns = \str_replace('_', '\\', \substr($class, 0, $len));
|
||||
}
|
||||
|
||||
// Detect annotations on the class
|
||||
@ -245,13 +245,13 @@ class DebugClassLoader
|
||||
if (!isset(self::$checkedClasses[$use])) {
|
||||
$this->checkClass($use);
|
||||
}
|
||||
if (isset(self::$deprecated[$use]) && \strncmp($ns, $use, $len)) {
|
||||
if (isset(self::$deprecated[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
|
||||
$type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
|
||||
$verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
|
||||
|
||||
$deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
|
||||
}
|
||||
if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
|
||||
if (isset(self::$internal[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
|
||||
$deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class);
|
||||
}
|
||||
}
|
||||
|
@ -52,11 +52,11 @@ class PassConfig
|
||||
new ValidateEnvPlaceholdersPass(),
|
||||
new ResolveChildDefinitionsPass(),
|
||||
new ServiceLocatorTagPass(),
|
||||
new RegisterServiceSubscribersPass(),
|
||||
new DecoratorServicePass(),
|
||||
new ResolveParameterPlaceHoldersPass(false),
|
||||
new ResolveFactoryClassPass(),
|
||||
new CheckDefinitionValidityPass(),
|
||||
new RegisterServiceSubscribersPass(),
|
||||
new ResolveNamedArgumentsPass(),
|
||||
new AutowireRequiredMethodsPass(),
|
||||
new ResolveBindingsPass(),
|
||||
|
@ -94,39 +94,40 @@ class ServiceLocator implements PsrContainerInterface
|
||||
$class = isset($class[2]['object']) ? \get_class($class[2]['object']) : null;
|
||||
$externalId = $this->externalId ?: $class;
|
||||
|
||||
$msg = sprintf('Service "%s" not found: ', $id);
|
||||
$msg = array();
|
||||
$msg[] = sprintf('Service "%s" not found:', $id);
|
||||
|
||||
if (!$this->container) {
|
||||
$class = null;
|
||||
} elseif ($this->container->has($id) || isset($this->container->getRemovedIds()[$id])) {
|
||||
$msg .= 'even though it exists in the app\'s container, ';
|
||||
$msg[] = 'even though it exists in the app\'s container,';
|
||||
} else {
|
||||
try {
|
||||
$this->container->get($id);
|
||||
$class = null;
|
||||
} catch (ServiceNotFoundException $e) {
|
||||
if ($e->getAlternatives()) {
|
||||
$msg .= sprintf(' did you mean %s? Anyway, ', $this->formatAlternatives($e->getAlternatives(), 'or'));
|
||||
$msg[] = sprintf('did you mean %s? Anyway,', $this->formatAlternatives($e->getAlternatives(), 'or'));
|
||||
} else {
|
||||
$class = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($externalId) {
|
||||
$msg .= sprintf('the container inside "%s" is a smaller service locator that %s', $externalId, $this->formatAlternatives());
|
||||
$msg[] = sprintf('the container inside "%s" is a smaller service locator that %s', $externalId, $this->formatAlternatives());
|
||||
} else {
|
||||
$msg .= sprintf('the current service locator %s', $this->formatAlternatives());
|
||||
$msg[] = sprintf('the current service locator %s', $this->formatAlternatives());
|
||||
}
|
||||
|
||||
if (!$class) {
|
||||
// no-op
|
||||
} elseif (is_subclass_of($class, ServiceSubscriberInterface::class)) {
|
||||
$msg .= sprintf(' Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "%s::getSubscribedServices()".', preg_replace('/([^\\\\]++\\\\)++/', '', $class));
|
||||
$msg[] = sprintf('Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "%s::getSubscribedServices()".', preg_replace('/([^\\\\]++\\\\)++/', '', $class));
|
||||
} else {
|
||||
$msg .= 'Try using dependency injection instead.';
|
||||
$msg[] = 'Try using dependency injection instead.';
|
||||
}
|
||||
|
||||
return $msg;
|
||||
return implode(' ', $msg);
|
||||
}
|
||||
|
||||
private function formatAlternatives(array $alternatives = null, $separator = 'and')
|
||||
|
@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
|
||||
|
||||
/**
|
||||
* This class tests the integration of the different compiler passes.
|
||||
@ -120,6 +121,21 @@ class IntegrationTest extends TestCase
|
||||
$this->assertFalse($container->hasDefinition('c'), 'Service C was not inlined.');
|
||||
}
|
||||
|
||||
public function testCanDecorateServiceSubscriber()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register(ServiceSubscriberStub::class)
|
||||
->addTag('container.service_subscriber')
|
||||
->setPublic(true);
|
||||
|
||||
$container->register(DecoratedServiceSubscriber::class)
|
||||
->setDecoratedService(ServiceSubscriberStub::class);
|
||||
|
||||
$container->compile();
|
||||
|
||||
$this->assertInstanceOf(DecoratedServiceSubscriber::class, $container->get(ServiceSubscriberStub::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getYamlCompileTests
|
||||
*/
|
||||
@ -220,6 +236,18 @@ class IntegrationTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceSubscriberStub implements ServiceSubscriberInterface
|
||||
{
|
||||
public static function getSubscribedServices()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
class DecoratedServiceSubscriber
|
||||
{
|
||||
}
|
||||
|
||||
class IntegrationTestStub extends IntegrationTestStubParent
|
||||
{
|
||||
}
|
||||
|
@ -115,6 +115,20 @@ class ServiceLocatorTest extends TestCase
|
||||
$subscriber->getFoo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
|
||||
* @expectedExceptionMessage Service "foo" not found: even though it exists in the app's container, the container inside "foo" is a smaller service locator that is empty... Try using dependency injection instead.
|
||||
*/
|
||||
public function testGetThrowsServiceNotFoundException()
|
||||
{
|
||||
$container = new Container();
|
||||
$container->set('foo', new \stdClass());
|
||||
|
||||
$locator = new ServiceLocator(array());
|
||||
$locator = $locator->withContext('foo', $container);
|
||||
$locator->get('foo');
|
||||
}
|
||||
|
||||
public function testInvoke()
|
||||
{
|
||||
$locator = new ServiceLocator(array(
|
||||
|
@ -51,7 +51,7 @@ class ValidationListener implements EventSubscriberInterface
|
||||
$form = $event->getForm();
|
||||
|
||||
if ($form->isRoot()) {
|
||||
// Validate the form in group "Default"
|
||||
// Form groups are validated internally (FormValidator). Here we don't set groups as they are retrieved into the validator.
|
||||
foreach ($this->validator->validate($form) as $violation) {
|
||||
// Allow the "invalid" constraint to be put onto
|
||||
// non-synchronized forms
|
||||
|
@ -101,9 +101,9 @@ class HeaderBag implements \IteratorAggregate, \Countable
|
||||
/**
|
||||
* Returns a header value by name.
|
||||
*
|
||||
* @param string $key The header name
|
||||
* @param string|string[]|null $default The default value
|
||||
* @param bool $first Whether to return the first value or all header values
|
||||
* @param string $key The header name
|
||||
* @param string|null $default The default value
|
||||
* @param bool $first Whether to return the first value or all header values
|
||||
*
|
||||
* @return string|string[]|null The first header value or default value if $first is true, an array of values otherwise
|
||||
*/
|
||||
|
@ -96,14 +96,22 @@ class GuardAuthenticationListener implements ListenerInterface
|
||||
$request = $event->getRequest();
|
||||
try {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Calling getCredentials() on guard authenticator.', array('firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)));
|
||||
$this->logger->debug('Checking support on guard authenticator.', array('firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
// abort the execution of the authenticator if it doesn't support the request
|
||||
if (!$guardAuthenticator->supports($request)) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Guard authenticator does not support the request.', array('firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Calling getCredentials() on guard authenticator.', array('firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
// allow the authenticator to fetch authentication info from the request
|
||||
$credentials = $guardAuthenticator->getCredentials($request);
|
||||
|
||||
|
@ -38,7 +38,7 @@ trait TargetPathTrait
|
||||
* @param SessionInterface $session
|
||||
* @param string $providerKey The name of your firewall
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
private function getTargetPath(SessionInterface $session, $providerKey)
|
||||
{
|
||||
|
@ -358,25 +358,9 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
|
||||
unset($data[$key]);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (null !== $constructorParameter->getClass()) {
|
||||
if (!$this->serializer instanceof DenormalizerInterface) {
|
||||
throw new LogicException(sprintf('Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer', $constructorParameter->getClass(), static::class));
|
||||
}
|
||||
$parameterClass = $constructorParameter->getClass()->getName();
|
||||
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $paramName));
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $key), 0, $e);
|
||||
} catch (MissingConstructorArgumentsException $e) {
|
||||
if (!$constructorParameter->getType()->allowsNull()) {
|
||||
throw $e;
|
||||
}
|
||||
$parameterData = null;
|
||||
}
|
||||
|
||||
// Don't run set for a parameter passed to the constructor
|
||||
$params[] = $parameterData;
|
||||
$params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
|
||||
unset($data[$key]);
|
||||
} elseif (array_key_exists($key, $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? array())) {
|
||||
$params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
|
||||
@ -397,6 +381,31 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
|
||||
return new $class();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, $parameterName, $parameterData, array $context, $format = null)
|
||||
{
|
||||
try {
|
||||
if (null !== $parameter->getClass()) {
|
||||
if (!$this->serializer instanceof DenormalizerInterface) {
|
||||
throw new LogicException(sprintf('Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer', $parameter->getClass(), static::class));
|
||||
}
|
||||
$parameterClass = $parameter->getClass()->getName();
|
||||
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName));
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e);
|
||||
} catch (MissingConstructorArgumentsException $e) {
|
||||
if (!$parameter->getType()->allowsNull()) {
|
||||
throw $e;
|
||||
}
|
||||
$parameterData = null;
|
||||
}
|
||||
|
||||
return $parameterData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parentContext
|
||||
* @param string $attribute
|
||||
|
@ -357,6 +357,18 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), \gettype($data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function denormalizeParameter(\ReflectionClass $class, \ReflectionParameter $parameter, $parameterName, $parameterData, array $context, $format = null)
|
||||
{
|
||||
if (null === $this->propertyTypeExtractor || null === $types = $this->propertyTypeExtractor->getTypes($class->getName(), $parameterName)) {
|
||||
return parent::denormalizeParameter($class, $parameter, $parameterName, $parameterData, $context, $format);
|
||||
}
|
||||
|
||||
return $this->validateAndDenormalize($class->getName(), $parameterName, $parameterData, $format, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type[]|null
|
||||
*/
|
||||
|
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Serializer\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
|
||||
class DeserializeNestedArrayOfObjectsTest extends TestCase
|
||||
{
|
||||
public function provider()
|
||||
{
|
||||
return array(
|
||||
//from property PhpDoc
|
||||
array(Zoo::class),
|
||||
//from argument constructor PhpDoc
|
||||
array(ZooImmutable::class),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provider
|
||||
*/
|
||||
public function testPropertyPhpDoc($class)
|
||||
{
|
||||
//GIVEN
|
||||
$json = <<<EOF
|
||||
{
|
||||
"animals": [
|
||||
{"name": "Bug"}
|
||||
]
|
||||
}
|
||||
EOF;
|
||||
$serializer = new Serializer(array(
|
||||
new ObjectNormalizer(null, null, null, new PhpDocExtractor()),
|
||||
new ArrayDenormalizer(),
|
||||
), array('json' => new JsonEncoder()));
|
||||
//WHEN
|
||||
/** @var Zoo $zoo */
|
||||
$zoo = $serializer->deserialize($json, $class, 'json');
|
||||
//THEN
|
||||
self::assertCount(1, $zoo->getAnimals());
|
||||
self::assertInstanceOf(Animal::class, $zoo->getAnimals()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
class Zoo
|
||||
{
|
||||
/** @var Animal[] */
|
||||
private $animals = array();
|
||||
|
||||
/**
|
||||
* @return Animal[]
|
||||
*/
|
||||
public function getAnimals()
|
||||
{
|
||||
return $this->animals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Animal[] $animals
|
||||
*/
|
||||
public function setAnimals(array $animals)
|
||||
{
|
||||
$this->animals = $animals;
|
||||
}
|
||||
}
|
||||
|
||||
class ZooImmutable
|
||||
{
|
||||
/** @var Animal[] */
|
||||
private $animals = array();
|
||||
|
||||
/**
|
||||
* @param Animal[] $animals
|
||||
*/
|
||||
public function __construct(array $animals = array())
|
||||
{
|
||||
$this->animals = $animals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Animal[]
|
||||
*/
|
||||
public function getAnimals()
|
||||
{
|
||||
return $this->animals;
|
||||
}
|
||||
}
|
||||
|
||||
class Animal
|
||||
{
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
echo '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ It can also be used with extensions defined in the [HTML5 link type extensions w
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/weblink/introduction.html)
|
||||
* [Documentation](https://symfony.com/doc/current/components/web_link.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
|
@ -78,35 +78,37 @@ class Inline
|
||||
mb_internal_encoding('ASCII');
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$tag = self::parseTag($value, $i, $flags);
|
||||
switch ($value[$i]) {
|
||||
case '[':
|
||||
$result = self::parseSequence($value, $flags, $i, $references);
|
||||
++$i;
|
||||
break;
|
||||
case '{':
|
||||
$result = self::parseMapping($value, $flags, $i, $references);
|
||||
++$i;
|
||||
break;
|
||||
default:
|
||||
$result = self::parseScalar($value, $flags, null, $i, null === $tag, $references);
|
||||
}
|
||||
try {
|
||||
$i = 0;
|
||||
$tag = self::parseTag($value, $i, $flags);
|
||||
switch ($value[$i]) {
|
||||
case '[':
|
||||
$result = self::parseSequence($value, $flags, $i, $references);
|
||||
++$i;
|
||||
break;
|
||||
case '{':
|
||||
$result = self::parseMapping($value, $flags, $i, $references);
|
||||
++$i;
|
||||
break;
|
||||
default:
|
||||
$result = self::parseScalar($value, $flags, null, $i, null === $tag, $references);
|
||||
}
|
||||
|
||||
if (null !== $tag && '' !== $tag) {
|
||||
return new TaggedValue($tag, $result);
|
||||
}
|
||||
if (null !== $tag && '' !== $tag) {
|
||||
return new TaggedValue($tag, $result);
|
||||
}
|
||||
|
||||
// some comments are allowed at the end
|
||||
if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
|
||||
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
|
||||
}
|
||||
// some comments are allowed at the end
|
||||
if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
|
||||
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
|
||||
}
|
||||
|
||||
if (isset($mbEncoding)) {
|
||||
mb_internal_encoding($mbEncoding);
|
||||
return $result;
|
||||
} finally {
|
||||
if (isset($mbEncoding)) {
|
||||
mb_internal_encoding($mbEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user