[Validator][DoctrineBridge][FWBundle] Automatic data validation
This commit is contained in:
parent
af28965c24
commit
2d64e703c2
@ -0,0 +1,58 @@
|
||||
<?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\Bridge\Doctrine\Tests\Fixtures;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @UniqueEntity(fields={"alreadyMappedUnique"})
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class DoctrineLoaderEntity
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(length=20)
|
||||
*/
|
||||
public $maxLength;
|
||||
|
||||
/**
|
||||
* @ORM\Column(length=20)
|
||||
* @Assert\Length(min=5)
|
||||
*/
|
||||
public $mergedMaxLength;
|
||||
|
||||
/**
|
||||
* @ORM\Column(length=20)
|
||||
* @Assert\Length(min=1, max=10)
|
||||
*/
|
||||
public $alreadyMappedMaxLength;
|
||||
|
||||
/**
|
||||
* @ORM\Column(unique=true)
|
||||
*/
|
||||
public $unique;
|
||||
|
||||
/**
|
||||
* @ORM\Column(unique=true)
|
||||
*/
|
||||
public $alreadyMappedUnique;
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
<?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\Bridge\Doctrine\Tests\Validator;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
|
||||
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEntity;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Bridge\Doctrine\Validator\DoctrineLoader;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\Entity;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Validator\ValidatorBuilder;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class DoctrineLoaderTest extends TestCase
|
||||
{
|
||||
public function testLoadClassMetadata()
|
||||
{
|
||||
if (!method_exists(ValidatorBuilder::class, 'addLoader')) {
|
||||
$this->markTestSkipped('Auto-mapping requires symfony/validation 4.2+');
|
||||
}
|
||||
|
||||
$validator = Validation::createValidatorBuilder()
|
||||
->enableAnnotationMapping()
|
||||
->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager()))
|
||||
->getValidator()
|
||||
;
|
||||
|
||||
$classMetadata = $validator->getMetadataFor(new DoctrineLoaderEntity());
|
||||
|
||||
$classConstraints = $classMetadata->getConstraints();
|
||||
$this->assertCount(2, $classConstraints);
|
||||
$this->assertInstanceOf(UniqueEntity::class, $classConstraints[0]);
|
||||
$this->assertInstanceOf(UniqueEntity::class, $classConstraints[1]);
|
||||
$this->assertSame(['alreadyMappedUnique'], $classConstraints[0]->fields);
|
||||
$this->assertSame('unique', $classConstraints[1]->fields);
|
||||
|
||||
$maxLengthMetadata = $classMetadata->getPropertyMetadata('maxLength');
|
||||
$this->assertCount(1, $maxLengthMetadata);
|
||||
$maxLengthConstraints = $maxLengthMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $maxLengthConstraints);
|
||||
$this->assertInstanceOf(Length::class, $maxLengthConstraints[0]);
|
||||
$this->assertSame(20, $maxLengthConstraints[0]->max);
|
||||
|
||||
$mergedMaxLengthMetadata = $classMetadata->getPropertyMetadata('mergedMaxLength');
|
||||
$this->assertCount(1, $mergedMaxLengthMetadata);
|
||||
$mergedMaxLengthConstraints = $mergedMaxLengthMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $mergedMaxLengthConstraints);
|
||||
$this->assertInstanceOf(Length::class, $mergedMaxLengthConstraints[0]);
|
||||
$this->assertSame(20, $mergedMaxLengthConstraints[0]->max);
|
||||
$this->assertSame(5, $mergedMaxLengthConstraints[0]->min);
|
||||
|
||||
$alreadyMappedMaxLengthMetadata = $classMetadata->getPropertyMetadata('alreadyMappedMaxLength');
|
||||
$this->assertCount(1, $alreadyMappedMaxLengthMetadata);
|
||||
$alreadyMappedMaxLengthConstraints = $alreadyMappedMaxLengthMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $alreadyMappedMaxLengthConstraints);
|
||||
$this->assertInstanceOf(Length::class, $alreadyMappedMaxLengthConstraints[0]);
|
||||
$this->assertSame(10, $alreadyMappedMaxLengthConstraints[0]->max);
|
||||
$this->assertSame(1, $alreadyMappedMaxLengthConstraints[0]->min);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider regexpProvider
|
||||
*/
|
||||
public function testClassValidator(bool $expected, string $classValidatorRegexp = null)
|
||||
{
|
||||
$doctrineLoader = new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), $classValidatorRegexp);
|
||||
|
||||
$classMetadata = new ClassMetadata(DoctrineLoaderEntity::class);
|
||||
$this->assertSame($expected, $doctrineLoader->loadClassMetadata($classMetadata));
|
||||
}
|
||||
|
||||
public function regexpProvider()
|
||||
{
|
||||
return [
|
||||
[true, null],
|
||||
[true, '{^'.preg_quote(DoctrineLoaderEntity::class).'$|^'.preg_quote(Entity::class).'$}'],
|
||||
[false, '{^'.preg_quote(Entity::class).'$}'],
|
||||
];
|
||||
}
|
||||
}
|
121
src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php
Normal file
121
src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?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\Bridge\Doctrine\Validator;
|
||||
|
||||
use Doctrine\Common\Persistence\Mapping\MappingException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints\Length;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
|
||||
|
||||
/**
|
||||
* Guesses and loads the appropriate constraints using Doctrine's metadata.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
final class DoctrineLoader implements LoaderInterface
|
||||
{
|
||||
private $entityManager;
|
||||
private $classValidatorRegexp;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, string $classValidatorRegexp = null)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->classValidatorRegexp = $classValidatorRegexp;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadClassMetadata(ClassMetadata $metadata): bool
|
||||
{
|
||||
$className = $metadata->getClassName();
|
||||
if (null !== $this->classValidatorRegexp && !preg_match($this->classValidatorRegexp, $className)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$doctrineMetadata = $this->entityManager->getClassMetadata($className);
|
||||
} catch (MappingException | OrmMappingException $exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$doctrineMetadata instanceof ClassMetadataInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Available keys:
|
||||
- type
|
||||
- scale
|
||||
- length
|
||||
- unique
|
||||
- nullable
|
||||
- precision
|
||||
*/
|
||||
$existingUniqueFields = $this->getExistingUniqueFields($metadata);
|
||||
|
||||
// Type and nullable aren't handled here, use the PropertyInfo Loader instead.
|
||||
foreach ($doctrineMetadata->fieldMappings as $mapping) {
|
||||
if (true === $mapping['unique'] && !isset($existingUniqueFields[$mapping['fieldName']])) {
|
||||
$metadata->addConstraint(new UniqueEntity(['fields' => $mapping['fieldName']]));
|
||||
}
|
||||
|
||||
if (null === $mapping['length']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = $this->getLengthConstraint($metadata, $mapping['fieldName']);
|
||||
if (null === $constraint) {
|
||||
$metadata->addPropertyConstraint($mapping['fieldName'], new Length(['max' => $mapping['length']]));
|
||||
} elseif (null === $constraint->max) {
|
||||
// If a Length constraint exists and no max length has been explicitly defined, set it
|
||||
$constraint->max = $mapping['length'];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getLengthConstraint(ClassMetadata $metadata, string $fieldName): ?Length
|
||||
{
|
||||
foreach ($metadata->getPropertyMetadata($fieldName) as $propertyMetadata) {
|
||||
foreach ($propertyMetadata->getConstraints() as $constraint) {
|
||||
if ($constraint instanceof Length) {
|
||||
return $constraint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getExistingUniqueFields(ClassMetadata $metadata): array
|
||||
{
|
||||
$fields = [];
|
||||
foreach ($metadata->getConstraints() as $constraint) {
|
||||
if (!$constraint instanceof UniqueEntity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_string($constraint->fields)) {
|
||||
$fields[$constraint->fields] = true;
|
||||
} elseif (\is_array($constraint->fields) && 1 === \count($constraint->fields)) {
|
||||
$fields[$constraint->fields[0]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
@ -792,6 +792,45 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('auto_mapping')
|
||||
->useAttributeAsKey('namespace')
|
||||
->normalizeKeys(false)
|
||||
->beforeNormalization()
|
||||
->ifArray()
|
||||
->then(function (array $values): array {
|
||||
foreach ($values as $k => $v) {
|
||||
if (isset($v['service'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($v['namespace'])) {
|
||||
$values[$k]['services'] = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\is_array($v)) {
|
||||
$values[$v]['services'] = [];
|
||||
unset($values[$k]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$tmp = $v;
|
||||
unset($values[$k]);
|
||||
$values[$k]['services'] = $tmp;
|
||||
}
|
||||
|
||||
return $values;
|
||||
})
|
||||
->end()
|
||||
->arrayPrototype()
|
||||
->fixXmlConfig('service')
|
||||
->children()
|
||||
->arrayNode('services')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
|
@ -107,6 +107,7 @@ use Symfony\Component\Stopwatch\Stopwatch;
|
||||
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
|
||||
use Symfony\Component\Translation\Translator;
|
||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
|
||||
use Symfony\Component\Validator\ObjectInitializerInterface;
|
||||
use Symfony\Component\WebLink\HttpHeaderSerializer;
|
||||
use Symfony\Component\Workflow;
|
||||
@ -280,7 +281,8 @@ class FrameworkExtension extends Extension
|
||||
$container->removeDefinition('console.command.messenger_debug');
|
||||
}
|
||||
|
||||
$this->registerValidationConfiguration($config['validation'], $container, $loader);
|
||||
$propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']);
|
||||
$this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled);
|
||||
$this->registerEsiConfiguration($config['esi'], $container, $loader);
|
||||
$this->registerSsiConfiguration($config['ssi'], $container, $loader);
|
||||
$this->registerFragmentsConfiguration($config['fragments'], $container, $loader);
|
||||
@ -301,7 +303,7 @@ class FrameworkExtension extends Extension
|
||||
$this->registerSerializerConfiguration($config['serializer'], $container, $loader);
|
||||
}
|
||||
|
||||
if ($this->isConfigEnabled($container, $config['property_info'])) {
|
||||
if ($propertyInfoEnabled) {
|
||||
$this->registerPropertyInfoConfiguration($container, $loader);
|
||||
}
|
||||
|
||||
@ -1152,7 +1154,7 @@ class FrameworkExtension extends Extension
|
||||
}
|
||||
}
|
||||
|
||||
private function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
|
||||
private function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader, bool $propertyInfoEnabled)
|
||||
{
|
||||
if (!$this->validatorConfigEnabled = $this->isConfigEnabled($container, $config)) {
|
||||
return;
|
||||
@ -1203,6 +1205,11 @@ class FrameworkExtension extends Extension
|
||||
if (!$container->getParameter('kernel.debug')) {
|
||||
$validatorBuilder->addMethodCall('setMetadataCache', [new Reference('validator.mapping.cache.symfony')]);
|
||||
}
|
||||
|
||||
$container->setParameter('validator.auto_mapping', $config['auto_mapping']);
|
||||
if (!$propertyInfoEnabled || !$config['auto_mapping'] || !class_exists(PropertyInfoLoader::class)) {
|
||||
$container->removeDefinition('validator.property_info_loader');
|
||||
}
|
||||
}
|
||||
|
||||
private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files)
|
||||
|
@ -53,6 +53,7 @@ use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass;
|
||||
use Symfony\Component\Validator\DependencyInjection\AddAutoMappingConfigurationPass;
|
||||
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
|
||||
use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass;
|
||||
|
||||
@ -124,6 +125,7 @@ class FrameworkBundle extends Bundle
|
||||
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
$this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class);
|
||||
$this->addCompilerPassIfExists($container, MessengerPass::class);
|
||||
$this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class);
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(true));
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
|
||||
|
||||
|
@ -190,6 +190,7 @@
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="static-method" type="xsd:string" />
|
||||
<xsd:element name="mapping" type="file_mapping" />
|
||||
<xsd:element name="auto-mapping" type="auto_mapping" />
|
||||
</xsd:choice>
|
||||
|
||||
<xsd:attribute name="enabled" type="xsd:boolean" />
|
||||
@ -207,6 +208,13 @@
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="auto_mapping">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="service" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="namespace" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="email-validation-mode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="html5" />
|
||||
|
@ -60,5 +60,12 @@
|
||||
<argument></argument>
|
||||
<tag name="validator.constraint_validator" alias="Symfony\Component\Validator\Constraints\EmailValidator" />
|
||||
</service>
|
||||
|
||||
<service id="validator.property_info_loader" class="Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader">
|
||||
<argument type="service" id="property_info" />
|
||||
<argument type="service" id="property_info" />
|
||||
|
||||
<tag name="validator.auto_mapper" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -232,6 +232,7 @@ class ConfigurationTest extends TestCase
|
||||
'mapping' => [
|
||||
'paths' => [],
|
||||
],
|
||||
'auto_mapping' => [],
|
||||
],
|
||||
'annotations' => [
|
||||
'cache' => 'php_array',
|
||||
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
$container->loadFromExtension('framework', [
|
||||
'property_info' => ['enabled' => true],
|
||||
'validation' => [
|
||||
'auto_mapping' => [
|
||||
'App\\' => ['foo', 'bar'],
|
||||
'Symfony\\' => ['a', 'b'],
|
||||
'Foo\\',
|
||||
],
|
||||
],
|
||||
]);
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:framework="http://symfony.com/schema/dic/symfony">
|
||||
|
||||
<framework:config>
|
||||
<framework:property-info enabled="true" />
|
||||
<framework:validation>
|
||||
<framework:auto-mapping namespace="App\">
|
||||
<framework:service>foo</framework:service>
|
||||
<framework:service>bar</framework:service>
|
||||
</framework:auto-mapping>
|
||||
<framework:auto-mapping namespace="Symfony\">
|
||||
<framework:service>a</framework:service>
|
||||
<framework:service>b</framework:service>
|
||||
</framework:auto-mapping>
|
||||
<framework:auto-mapping namespace="Foo\" />
|
||||
</framework:validation>
|
||||
</framework:config>
|
||||
</container>
|
@ -0,0 +1,7 @@
|
||||
framework:
|
||||
property_info: { enabled: true }
|
||||
validation:
|
||||
auto_mapping:
|
||||
'App\': ['foo', 'bar']
|
||||
'Symfony\': ['a', 'b']
|
||||
'Foo\': []
|
@ -50,6 +50,8 @@ use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
|
||||
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
|
||||
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Workflow;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
@ -1033,6 +1035,23 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||
$this->assertContains('validation.yaml', $calls[4][1][0][2]);
|
||||
}
|
||||
|
||||
public function testValidationAutoMapping()
|
||||
{
|
||||
if (!class_exists(PropertyInfoLoader::class)) {
|
||||
$this->markTestSkipped('Auto-mapping requires symfony/validation 4.2+');
|
||||
}
|
||||
|
||||
$container = $this->createContainerFromFile('validation_auto_mapping');
|
||||
$parameter = [
|
||||
'App\\' => ['services' => ['foo', 'bar']],
|
||||
'Symfony\\' => ['services' => ['a', 'b']],
|
||||
'Foo\\' => ['services' => []],
|
||||
];
|
||||
|
||||
$this->assertSame($parameter, $container->getParameter('validator.auto_mapping'));
|
||||
$this->assertTrue($container->hasDefinition('validator.property_info_loader'));
|
||||
}
|
||||
|
||||
public function testFormsCanBeEnabledWithoutCsrfProtection()
|
||||
{
|
||||
$container = $this->createContainerFromFile('form_no_csrf');
|
||||
|
@ -0,0 +1,93 @@
|
||||
<?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\Validator\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Injects the automapping configuration as last argument of loaders tagged with the "validator.auto_mapper" tag.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class AddAutoMappingConfigurationPass implements CompilerPassInterface
|
||||
{
|
||||
private $validatorBuilderService;
|
||||
private $tag;
|
||||
|
||||
public function __construct(string $validatorBuilderService = 'validator.builder', string $tag = 'validator.auto_mapper')
|
||||
{
|
||||
$this->validatorBuilderService = $validatorBuilderService;
|
||||
$this->tag = $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasParameter('validator.auto_mapping') || !$container->hasDefinition($this->validatorBuilderService)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = $container->getParameter('validator.auto_mapping');
|
||||
|
||||
$globalNamespaces = [];
|
||||
$servicesToNamespaces = [];
|
||||
foreach ($config as $namespace => $value) {
|
||||
if ([] === $value['services']) {
|
||||
$globalNamespaces[] = $namespace;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($value['services'] as $service) {
|
||||
$servicesToNamespaces[$service][] = $namespace;
|
||||
}
|
||||
}
|
||||
|
||||
$validatorBuilder = $container->getDefinition($this->validatorBuilderService);
|
||||
foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) {
|
||||
$regexp = $this->getRegexp(array_merge($globalNamespaces, $servicesToNamespaces[$id] ?? []));
|
||||
|
||||
$container->getDefinition($id)->setArgument('$classValidatorRegexp', $regexp);
|
||||
$validatorBuilder->addMethodCall('addLoader', [new Reference($id)]);
|
||||
}
|
||||
|
||||
$container->getParameterBag()->remove('validator.auto_mapping');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a regexp to check if a class is auto-mapped.
|
||||
*/
|
||||
private function getRegexp(array $patterns): string
|
||||
{
|
||||
$regexps = [];
|
||||
foreach ($patterns as $pattern) {
|
||||
// Escape namespace
|
||||
$regex = preg_quote(ltrim($pattern, '\\'));
|
||||
|
||||
// Wildcards * and **
|
||||
$regex = strtr($regex, ['\\*\\*' => '.*?', '\\*' => '[^\\\\]*?']);
|
||||
|
||||
// If this class does not end by a slash, anchor the end
|
||||
if ('\\' !== substr($regex, -1)) {
|
||||
$regex .= '$';
|
||||
}
|
||||
|
||||
$regexps[] = '^'.$regex;
|
||||
}
|
||||
|
||||
return sprintf('{%s}', implode('|', $regexps));
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
<?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\Validator\Mapping\Loader;
|
||||
|
||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\Type as PropertyInfoType;
|
||||
use Symfony\Component\Validator\Constraints\All;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\NotNull;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Guesses and loads the appropriate constraints using PropertyInfo.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
final class PropertyInfoLoader implements LoaderInterface
|
||||
{
|
||||
private $listExtractor;
|
||||
private $typeExtractor;
|
||||
private $classValidatorRegexp;
|
||||
|
||||
public function __construct(PropertyListExtractorInterface $listExtractor, PropertyTypeExtractorInterface $typeExtractor, string $classValidatorRegexp = null)
|
||||
{
|
||||
$this->listExtractor = $listExtractor;
|
||||
$this->typeExtractor = $typeExtractor;
|
||||
$this->classValidatorRegexp = $classValidatorRegexp;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadClassMetadata(ClassMetadata $metadata)
|
||||
{
|
||||
$className = $metadata->getClassName();
|
||||
if (null !== $this->classValidatorRegexp && !preg_match($this->classValidatorRegexp, $className)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$properties = $this->listExtractor->getProperties($className)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$types = $this->typeExtractor->getTypes($className, $property);
|
||||
if (null === $types) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasTypeConstraint = false;
|
||||
$hasNotNullConstraint = false;
|
||||
$hasNotBlankConstraint = false;
|
||||
$allConstraint = null;
|
||||
foreach ($metadata->getPropertyMetadata($property) as $propertyMetadata) {
|
||||
foreach ($propertyMetadata->getConstraints() as $constraint) {
|
||||
if ($constraint instanceof Type) {
|
||||
$hasTypeConstraint = true;
|
||||
} elseif ($constraint instanceof NotNull) {
|
||||
$hasNotNullConstraint = true;
|
||||
} elseif ($constraint instanceof NotBlank) {
|
||||
$hasNotBlankConstraint = true;
|
||||
} elseif ($constraint instanceof All) {
|
||||
$allConstraint = $constraint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$builtinTypes = [];
|
||||
$nullable = false;
|
||||
$scalar = true;
|
||||
foreach ($types as $type) {
|
||||
$builtinTypes[] = $type->getBuiltinType();
|
||||
|
||||
if ($scalar && !\in_array($type->getBuiltinType(), [PropertyInfoType::BUILTIN_TYPE_INT, PropertyInfoType::BUILTIN_TYPE_FLOAT, PropertyInfoType::BUILTIN_TYPE_STRING, PropertyInfoType::BUILTIN_TYPE_BOOL], true)) {
|
||||
$scalar = false;
|
||||
}
|
||||
|
||||
if (!$nullable && $type->isNullable()) {
|
||||
$nullable = true;
|
||||
}
|
||||
}
|
||||
if (!$hasTypeConstraint) {
|
||||
if (1 === \count($builtinTypes)) {
|
||||
if ($types[0]->isCollection() && (null !== $collectionValueType = $types[0]->getCollectionValueType())) {
|
||||
$this->handleAllConstraint($property, $allConstraint, $collectionValueType, $metadata);
|
||||
}
|
||||
|
||||
$metadata->addPropertyConstraint($property, $this->getTypeConstraint($builtinTypes[0], $types[0]));
|
||||
} elseif ($scalar) {
|
||||
$metadata->addPropertyConstraint($property, new Type(['type' => 'scalar']));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$nullable && !$hasNotBlankConstraint && !$hasNotNullConstraint) {
|
||||
$metadata->addPropertyConstraint($property, new NotNull());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getTypeConstraint(string $builtinType, PropertyInfoType $type): Type
|
||||
{
|
||||
if (PropertyInfoType::BUILTIN_TYPE_OBJECT === $builtinType && null !== $className = $type->getClassName()) {
|
||||
return new Type(['type' => $className]);
|
||||
}
|
||||
|
||||
return new Type(['type' => $builtinType]);
|
||||
}
|
||||
|
||||
private function handleAllConstraint(string $property, ?All $allConstraint, PropertyInfoType $propertyInfoType, ClassMetadata $metadata)
|
||||
{
|
||||
$containsTypeConstraint = false;
|
||||
$containsNotNullConstraint = false;
|
||||
if (null !== $allConstraint) {
|
||||
foreach ($allConstraint->constraints as $constraint) {
|
||||
if ($constraint instanceof Type) {
|
||||
$containsTypeConstraint = true;
|
||||
} elseif ($constraint instanceof NotNull) {
|
||||
$containsNotNullConstraint = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$constraints = [];
|
||||
if (!$containsNotNullConstraint && !$propertyInfoType->isNullable()) {
|
||||
$constraints[] = new NotNull();
|
||||
}
|
||||
|
||||
if (!$containsTypeConstraint) {
|
||||
$constraints[] = $this->getTypeConstraint($propertyInfoType->getBuiltinType(), $propertyInfoType);
|
||||
}
|
||||
|
||||
if (null === $allConstraint) {
|
||||
$metadata->addPropertyConstraint($property, new All(['constraints' => $constraints]));
|
||||
} else {
|
||||
$allConstraint->constraints = array_merge($allConstraint->constraints, $constraints);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
<?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\Validator\Tests\DependencyInjection;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Validator\DependencyInjection\AddAutoMappingConfigurationPass;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\PropertyInfoLoaderEntity;
|
||||
use Symfony\Component\Validator\ValidatorBuilder;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class AddAutoMappingConfigurationPassTest extends TestCase
|
||||
{
|
||||
public function testNoConfigParameter()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
(new AddAutoMappingConfigurationPass())->process($container);
|
||||
$this->assertCount(1, $container->getDefinitions());
|
||||
}
|
||||
|
||||
public function testNoValidatorBuilder()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
(new AddAutoMappingConfigurationPass())->process($container);
|
||||
$this->assertCount(1, $container->getDefinitions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider mappingProvider
|
||||
*/
|
||||
public function testProcess(string $namespace, array $services, string $expectedRegexp)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->setParameter('validator.auto_mapping', [
|
||||
'App\\' => ['services' => []],
|
||||
$namespace => ['services' => $services],
|
||||
]);
|
||||
|
||||
$container->register('validator.builder', ValidatorBuilder::class);
|
||||
foreach ($services as $service) {
|
||||
$container->register($service)->addTag('validator.auto_mapper');
|
||||
}
|
||||
|
||||
(new AddAutoMappingConfigurationPass())->process($container);
|
||||
|
||||
foreach ($services as $service) {
|
||||
$this->assertSame($expectedRegexp, $container->getDefinition($service)->getArgument('$classValidatorRegexp'));
|
||||
}
|
||||
$this->assertCount(\count($services), $container->getDefinition('validator.builder')->getMethodCalls());
|
||||
}
|
||||
|
||||
public function mappingProvider(): array
|
||||
{
|
||||
return [
|
||||
['Foo\\', ['foo', 'baz'], '{^App\\\\|^Foo\\\\}'],
|
||||
[PropertyInfoLoaderEntity::class, ['class'], '{^App\\\\|^Symfony\\\\Component\\\\Validator\\\\Tests\\\\Fixtures\\\\PropertyInfoLoaderEntity$}'],
|
||||
['Symfony\Component\Validator\Tests\Fixtures\\', ['trailing_antislash'], '{^App\\\\|^Symfony\\\\Component\\\\Validator\\\\Tests\\\\Fixtures\\\\}'],
|
||||
['Symfony\Component\Validator\Tests\Fixtures\\*', ['trailing_star'], '{^App\\\\|^Symfony\\\\Component\\\\Validator\\\\Tests\\\\Fixtures\\\\[^\\\\]*?$}'],
|
||||
['Symfony\Component\Validator\Tests\Fixtures\\**', ['trailing_double_star'], '{^App\\\\|^Symfony\\\\Component\\\\Validator\\\\Tests\\\\Fixtures\\\\.*?$}'],
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<?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\Validator\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class PropertyInfoLoaderEntity
|
||||
{
|
||||
public $nullableString;
|
||||
public $string;
|
||||
public $scalar;
|
||||
public $object;
|
||||
public $collection;
|
||||
|
||||
/**
|
||||
* @Assert\Type(type="int")
|
||||
*/
|
||||
public $alreadyMappedType;
|
||||
|
||||
/**
|
||||
* @Assert\NotNull
|
||||
*/
|
||||
public $alreadyMappedNotNull;
|
||||
|
||||
/**
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
public $alreadyMappedNotBlank;
|
||||
|
||||
/**
|
||||
* @Assert\All({
|
||||
* @Assert\Type(type="string"),
|
||||
* @Assert\Iban
|
||||
* })
|
||||
*/
|
||||
public $alreadyPartiallyMappedCollection;
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
<?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\Validator\Tests\Mapping\Loader;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
use Symfony\Component\Validator\Constraints\All;
|
||||
use Symfony\Component\Validator\Constraints\Iban;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\NotNull;
|
||||
use Symfony\Component\Validator\Constraints\Type as TypeConstraint;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\Entity;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\PropertyInfoLoaderEntity;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class PropertyInfoLoaderTest extends TestCase
|
||||
{
|
||||
public function testLoadClassMetadata()
|
||||
{
|
||||
$propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class);
|
||||
$propertyInfoStub
|
||||
->method('getProperties')
|
||||
->willReturn([
|
||||
'nullableString',
|
||||
'string',
|
||||
'scalar',
|
||||
'object',
|
||||
'collection',
|
||||
'alreadyMappedType',
|
||||
'alreadyMappedNotNull',
|
||||
'alreadyMappedNotBlank',
|
||||
'alreadyPartiallyMappedCollection',
|
||||
])
|
||||
;
|
||||
$propertyInfoStub
|
||||
->method('getTypes')
|
||||
->will($this->onConsecutiveCalls(
|
||||
[new Type(Type::BUILTIN_TYPE_STRING, true)],
|
||||
[new Type(Type::BUILTIN_TYPE_STRING)],
|
||||
[new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_BOOL)],
|
||||
[new Type(Type::BUILTIN_TYPE_OBJECT, true, Entity::class)],
|
||||
[new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, Entity::class))],
|
||||
[new Type(Type::BUILTIN_TYPE_FLOAT, true)], // The existing constraint is float
|
||||
[new Type(Type::BUILTIN_TYPE_STRING, true)],
|
||||
[new Type(Type::BUILTIN_TYPE_STRING, true)],
|
||||
[new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, null, new Type(Type::BUILTIN_TYPE_FLOAT))]
|
||||
))
|
||||
;
|
||||
|
||||
$propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub);
|
||||
|
||||
$validator = Validation::createValidatorBuilder()
|
||||
->enableAnnotationMapping()
|
||||
->addLoader($propertyInfoLoader)
|
||||
->getValidator()
|
||||
;
|
||||
|
||||
$classMetadata = $validator->getMetadataFor(new PropertyInfoLoaderEntity());
|
||||
|
||||
$nullableStringMetadata = $classMetadata->getPropertyMetadata('nullableString');
|
||||
$this->assertCount(1, $nullableStringMetadata);
|
||||
$nullableStringConstraints = $nullableStringMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $nullableStringConstraints);
|
||||
$this->assertInstanceOf(TypeConstraint::class, $nullableStringConstraints[0]);
|
||||
$this->assertSame('string', $nullableStringConstraints[0]->type);
|
||||
|
||||
$stringMetadata = $classMetadata->getPropertyMetadata('string');
|
||||
$this->assertCount(1, $stringMetadata);
|
||||
$stringConstraints = $stringMetadata[0]->getConstraints();
|
||||
$this->assertCount(2, $stringConstraints);
|
||||
$this->assertInstanceOf(TypeConstraint::class, $stringConstraints[0]);
|
||||
$this->assertSame('string', $stringConstraints[0]->type);
|
||||
$this->assertInstanceOf(NotNull::class, $stringConstraints[1]);
|
||||
|
||||
$scalarMetadata = $classMetadata->getPropertyMetadata('scalar');
|
||||
$this->assertCount(1, $scalarMetadata);
|
||||
$scalarConstraints = $scalarMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $scalarConstraints);
|
||||
$this->assertInstanceOf(TypeConstraint::class, $scalarConstraints[0]);
|
||||
$this->assertSame('scalar', $scalarConstraints[0]->type);
|
||||
|
||||
$objectMetadata = $classMetadata->getPropertyMetadata('object');
|
||||
$this->assertCount(1, $objectMetadata);
|
||||
$objectConstraints = $objectMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $objectConstraints);
|
||||
$this->assertInstanceOf(TypeConstraint::class, $objectConstraints[0]);
|
||||
$this->assertSame(Entity::class, $objectConstraints[0]->type);
|
||||
|
||||
$collectionMetadata = $classMetadata->getPropertyMetadata('collection');
|
||||
$this->assertCount(1, $collectionMetadata);
|
||||
$collectionConstraints = $collectionMetadata[0]->getConstraints();
|
||||
$this->assertCount(2, $collectionConstraints);
|
||||
$this->assertInstanceOf(All::class, $collectionConstraints[0]);
|
||||
$this->assertInstanceOf(NotNull::class, $collectionConstraints[0]->constraints[0]);
|
||||
$this->assertInstanceOf(TypeConstraint::class, $collectionConstraints[0]->constraints[1]);
|
||||
$this->assertSame(Entity::class, $collectionConstraints[0]->constraints[1]->type);
|
||||
|
||||
$alreadyMappedTypeMetadata = $classMetadata->getPropertyMetadata('alreadyMappedType');
|
||||
$this->assertCount(1, $alreadyMappedTypeMetadata);
|
||||
$alreadyMappedTypeConstraints = $alreadyMappedTypeMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $alreadyMappedTypeMetadata);
|
||||
$this->assertInstanceOf(TypeConstraint::class, $alreadyMappedTypeConstraints[0]);
|
||||
|
||||
$alreadyMappedNotNullMetadata = $classMetadata->getPropertyMetadata('alreadyMappedNotNull');
|
||||
$this->assertCount(1, $alreadyMappedNotNullMetadata);
|
||||
$alreadyMappedNotNullConstraints = $alreadyMappedNotNullMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $alreadyMappedNotNullMetadata);
|
||||
$this->assertInstanceOf(NotNull::class, $alreadyMappedNotNullConstraints[0]);
|
||||
|
||||
$alreadyMappedNotBlankMetadata = $classMetadata->getPropertyMetadata('alreadyMappedNotBlank');
|
||||
$this->assertCount(1, $alreadyMappedNotBlankMetadata);
|
||||
$alreadyMappedNotBlankConstraints = $alreadyMappedNotBlankMetadata[0]->getConstraints();
|
||||
$this->assertCount(1, $alreadyMappedNotBlankMetadata);
|
||||
$this->assertInstanceOf(NotBlank::class, $alreadyMappedNotBlankConstraints[0]);
|
||||
|
||||
$alreadyPartiallyMappedCollectionMetadata = $classMetadata->getPropertyMetadata('alreadyPartiallyMappedCollection');
|
||||
$this->assertCount(1, $alreadyPartiallyMappedCollectionMetadata);
|
||||
$alreadyPartiallyMappedCollectionConstraints = $alreadyPartiallyMappedCollectionMetadata[0]->getConstraints();
|
||||
$this->assertCount(2, $alreadyPartiallyMappedCollectionConstraints);
|
||||
$this->assertInstanceOf(All::class, $alreadyPartiallyMappedCollectionConstraints[0]);
|
||||
$this->assertInstanceOf(TypeConstraint::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[0]);
|
||||
$this->assertSame('string', $alreadyPartiallyMappedCollectionConstraints[0]->constraints[0]->type);
|
||||
$this->assertInstanceOf(Iban::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[1]);
|
||||
$this->assertInstanceOf(NotNull::class, $alreadyPartiallyMappedCollectionConstraints[0]->constraints[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider regexpProvider
|
||||
*/
|
||||
public function testClassValidator(bool $expected, string $classValidatorRegexp = null)
|
||||
{
|
||||
$propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class);
|
||||
$propertyInfoStub
|
||||
->method('getProperties')
|
||||
->willReturn(['string'])
|
||||
;
|
||||
$propertyInfoStub
|
||||
->method('getTypes')
|
||||
->willReturn([new Type(Type::BUILTIN_TYPE_STRING)])
|
||||
;
|
||||
|
||||
$propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $classValidatorRegexp);
|
||||
|
||||
$classMetadata = new ClassMetadata(PropertyInfoLoaderEntity::class);
|
||||
$this->assertSame($expected, $propertyInfoLoader->loadClassMetadata($classMetadata));
|
||||
}
|
||||
|
||||
public function regexpProvider()
|
||||
{
|
||||
return [
|
||||
[true, null],
|
||||
[true, '{^'.preg_quote(PropertyInfoLoaderEntity::class).'$|^'.preg_quote(Entity::class).'$}'],
|
||||
[false, '{^'.preg_quote(Entity::class).'$}'],
|
||||
];
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@
|
||||
"symfony/expression-language": "~3.4|~4.0",
|
||||
"symfony/cache": "~3.4|~4.0",
|
||||
"symfony/property-access": "~3.4|~4.0",
|
||||
"symfony/property-info": "~3.4|~4.0",
|
||||
"symfony/translation": "~4.2",
|
||||
"doctrine/annotations": "~1.0",
|
||||
"doctrine/cache": "~1.0",
|
||||
@ -56,6 +57,7 @@
|
||||
"symfony/config": "",
|
||||
"egulias/email-validator": "Strict (RFC compliant) email validation",
|
||||
"symfony/property-access": "For accessing properties within comparison constraints",
|
||||
"symfony/property-info": "To automatically add NotNull and Type constraints",
|
||||
"symfony/expression-language": "For using the Expression validator"
|
||||
},
|
||||
"autoload": {
|
||||
|
Reference in New Issue
Block a user