Merge branch '3.1' into 3.2

* 3.1:
  Fix getMock usage
  Remove dead code
  [Form] DateTimeToLocalizedStringTransformer does not use TZ when using only date
  [Validator] Fix caching of constraints derived from non-serializable parents
  [TwigBundle] Fix bug where namespaced paths don't take parent bundles in account
  [FrameworkBundle] Fix relative paths used as cache keys
  respect groups when merging constraints
  fix IPv6 address handling in server commands
This commit is contained in:
Nicolas Grekas 2017-01-12 20:27:47 +01:00
commit ec858cea91
16 changed files with 241 additions and 52 deletions

View File

@ -54,7 +54,9 @@ abstract class ServerCommand extends ContainerAwareCommand
return true;
}
list($hostname, $port) = explode(':', $address);
$pos = strrpos($address, ':');
$hostname = substr($address, 0, $pos);
$port = substr($address, $pos + 1);
$fp = @fsockopen($hostname, $port, $errno, $errstr, 5);

View File

@ -24,6 +24,8 @@ class TemplateLocator implements FileLocatorInterface
protected $locator;
protected $cache;
private $cacheHits = array();
/**
* Constructor.
*
@ -71,12 +73,15 @@ class TemplateLocator implements FileLocatorInterface
$key = $this->getCacheKey($template);
if (isset($this->cacheHits[$key])) {
return $this->cacheHits[$key];
}
if (isset($this->cache[$key])) {
return $this->cache[$key];
return $this->cacheHits[$key] = realpath($this->cache[$key]) ?: $this->cache[$key];
}
try {
return $this->cache[$key] = $this->locator->locate($template->getPath(), $currentPath);
return $this->cacheHits[$key] = $this->locator->locate($template->getPath(), $currentPath);
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException(sprintf('Unable to find template "%s" : "%s".', $template, $e->getMessage()), 0, $e);
}

View File

@ -0,0 +1,5 @@
<?php
return array(
'bundle:controller:name.format.engine' => __DIR__.'/../Fixtures/Resources/views/this.is.a.template.format.engine',
);

View File

@ -38,6 +38,17 @@ class TemplateLocatorTest extends TestCase
$this->assertEquals('/path/to/template', $locator->locate($template));
}
public function testLocateATemplateFromCacheDir()
{
$template = new TemplateReference('bundle', 'controller', 'name', 'format', 'engine');
$fileLocator = $this->getFileLocator();
$locator = new TemplateLocator($fileLocator, __DIR__.'/../../Fixtures');
$this->assertEquals(realpath(__DIR__.'/../../Fixtures/Resources/views/this.is.a.template.format.engine'), $locator->locate($template));
}
public function testThrowsExceptionWhenTemplateNotFound()
{
$template = new TemplateReference('bundle', 'controller', 'name', 'format', 'engine');

View File

@ -92,23 +92,23 @@ class TwigExtension extends Extension
$container->getDefinition('twig.cache_warmer')->replaceArgument(2, $config['paths']);
$container->getDefinition('twig.template_iterator')->replaceArgument(2, $config['paths']);
// register bundles as Twig namespaces
foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) {
$dir = $container->getParameter('kernel.root_dir').'/Resources/'.$name.'/views';
if (is_dir($dir)) {
$this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $name);
}
$container->addResource(new FileExistenceResource($dir));
$bundleHierarchy = $this->getBundleHierarchy($container);
$dir = $bundle['path'].'/Resources/views';
if (is_dir($dir)) {
$this->addTwigPath($twigFilesystemLoaderDefinition, $dir, $name);
foreach ($bundleHierarchy as $name => $bundle) {
$namespace = $this->normalizeBundleName($name);
foreach ($bundle['children'] as $child) {
foreach ($bundleHierarchy[$child]['paths'] as $path) {
$twigFilesystemLoaderDefinition->addMethodCall('addPath', array($path, $namespace));
}
}
foreach ($bundle['paths'] as $path) {
$twigFilesystemLoaderDefinition->addMethodCall('addPath', array($path, $namespace));
}
$container->addResource(new FileExistenceResource($dir));
}
$dir = $container->getParameter('kernel.root_dir').'/Resources/views';
if (is_dir($dir)) {
if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/views')) {
$twigFilesystemLoaderDefinition->addMethodCall('addPath', array($dir));
}
$container->addResource(new FileExistenceResource($dir));
@ -149,13 +149,65 @@ class TwigExtension extends Extension
));
}
private function addTwigPath($twigFilesystemLoaderDefinition, $dir, $bundle)
private function getBundleHierarchy(ContainerBuilder $container)
{
$bundleHierarchy = array();
foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) {
if (!array_key_exists($name, $bundleHierarchy)) {
$bundleHierarchy[$name] = array(
'paths' => array(),
'parents' => array(),
'children' => array(),
);
}
if (is_dir($dir = $container->getParameter('kernel.root_dir').'/Resources/'.$name.'/views')) {
$bundleHierarchy[$name]['paths'][] = $dir;
}
$container->addResource(new FileExistenceResource($dir));
if (is_dir($dir = $bundle['path'].'/Resources/views')) {
$bundleHierarchy[$name]['paths'][] = $dir;
}
$container->addResource(new FileExistenceResource($dir));
if (null === $bundle['parent']) {
continue;
}
$bundleHierarchy[$name]['parents'][] = $bundle['parent'];
if (!array_key_exists($bundle['parent'], $bundleHierarchy)) {
$bundleHierarchy[$bundle['parent']] = array(
'paths' => array(),
'parents' => array(),
'children' => array(),
);
}
$bundleHierarchy[$bundle['parent']]['children'] = array_merge($bundleHierarchy[$name]['children'], array($name), $bundleHierarchy[$bundle['parent']]['children']);
foreach ($bundleHierarchy[$bundle['parent']]['parents'] as $parent) {
$bundleHierarchy[$name]['parents'][] = $parent;
$bundleHierarchy[$parent]['children'] = array_merge($bundleHierarchy[$name]['children'], array($name), $bundleHierarchy[$parent]['children']);
}
foreach ($bundleHierarchy[$name]['children'] as $child) {
$bundleHierarchy[$child]['parents'] = array_merge($bundleHierarchy[$child]['parents'], $bundleHierarchy[$name]['parents']);
}
}
return $bundleHierarchy;
}
private function normalizeBundleName($name)
{
$name = $bundle;
if ('Bundle' === substr($name, -6)) {
$name = substr($name, 0, -6);
}
$twigFilesystemLoaderDefinition->addMethodCall('addPath', array($dir, $name));
return $name;
}
/**

View File

@ -167,8 +167,22 @@ class TwigExtensionTest extends TestCase
array('namespaced_path1', 'namespace1'),
array('namespaced_path2', 'namespace2'),
array('namespaced_path3', 'namespace3'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildChildTwigBundle/Resources/views', 'ChildChildChildChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildChildTwigBundle/Resources/views', 'ChildChildChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle/Resources/views', 'ChildChildChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildChildTwigBundle/Resources/views', 'Twig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle/Resources/views', 'Twig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'Twig'),
array(__DIR__.'/Fixtures/Bundle/ChildTwigBundle/Resources/views', 'Twig'),
array(__DIR__.'/Fixtures/Resources/TwigBundle/views', 'Twig'),
array(realpath(__DIR__.'/../..').'/Resources/views', 'Twig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildChildTwigBundle/Resources/views', 'ChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle/Resources/views', 'ChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'ChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildTwigBundle/Resources/views', 'ChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildChildTwigBundle/Resources/views', 'ChildChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle/Resources/views', 'ChildChildTwig'),
array(__DIR__.'/Fixtures/Bundle/ChildChildTwigBundle/Resources/views', 'ChildChildTwig'),
array(__DIR__.'/Fixtures/Resources/views'),
), $paths);
}
@ -244,8 +258,40 @@ class TwigExtensionTest extends TestCase
'kernel.root_dir' => __DIR__.'/Fixtures',
'kernel.charset' => 'UTF-8',
'kernel.debug' => false,
'kernel.bundles' => array('TwigBundle' => 'Symfony\\Bundle\\TwigBundle\\TwigBundle'),
'kernel.bundles_metadata' => array('TwigBundle' => array('namespace' => 'Symfony\\Bundle\\TwigBundle', 'parent' => null, 'path' => realpath(__DIR__.'/../..'))),
'kernel.bundles' => array(
'TwigBundle' => 'Symfony\\Bundle\\TwigBundle\\TwigBundle',
'ChildTwigBundle' => 'Symfony\\Bundle\\TwigBundle\\Tests\\DependencyInjection\\Fixtures\\Bundle\\ChildTwigBundle\\ChildTwigBundle',
'ChildChildTwigBundle' => 'Symfony\\Bundle\\TwigBundle\\Tests\\DependencyInjection\\Fixtures\\Bundle\\ChildChildTwigBundle\\ChildChildTwigBundle',
'ChildChildChildTwigBundle' => 'Symfony\\Bundle\\TwigBundle\\Tests\\DependencyInjection\\Fixtures\\Bundle\\ChildChildChildTwigBundle\\ChildChildChildTwigBundle',
'ChildChildChildChildTwigBundle' => 'Symfony\\Bundle\\TwigBundle\\Tests\\DependencyInjection\\Fixtures\\Bundle\\ChildChildChildChildTwigBundle\\ChildChildChildChildTwigBundle',
),
'kernel.bundles_metadata' => array(
'ChildChildChildChildTwigBundle' => array(
'namespace' => 'Symfony\\Bundle\\TwigBundle\\Tests\\DependencyInjection\\Fixtures\\Bundle\\ChildChildChildChildTwigBundle\\ChildChildChildChildTwigBundle',
'parent' => 'ChildChildChildTwigBundle',
'path' => __DIR__.'/Fixtures/Bundle/ChildChildChildChildTwigBundle',
),
'TwigBundle' => array(
'namespace' => 'Symfony\\Bundle\\TwigBundle',
'parent' => null,
'path' => realpath(__DIR__.'/../..'),
),
'ChildTwigBundle' => array(
'namespace' => 'Symfony\\Bundle\\TwigBundle\\Tests\\DependencyInjection\\Fixtures\\Bundle\\ChildTwigBundle\\ChildTwigBundle',
'parent' => 'TwigBundle',
'path' => __DIR__.'/Fixtures/Bundle/ChildTwigBundle',
),
'ChildChildChildTwigBundle' => array(
'namespace' => 'Symfony\\Bundle\\TwigBundle\\Tests\\DependencyInjection\\Fixtures\\Bundle\\ChildChildChildTwigBundle\\ChildChildChildTwigBundle',
'parent' => 'ChildChildTwigBundle',
'path' => __DIR__.'/Fixtures/Bundle/ChildChildChildTwigBundle',
),
'ChildChildTwigBundle' => array(
'namespace' => 'Symfony\\Bundle\\TwigBundle\\Tests\\DependencyInjection\\Fixtures\\Bundle\\ChildChildTwigBundle\\ChildChildTwigBundle',
'parent' => 'ChildTwigBundle',
'path' => __DIR__.'/Fixtures/Bundle/ChildChildTwigBundle',
),
),
)));
return $container;

View File

@ -129,11 +129,11 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
try {
if ($dateOnly) {
// we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight
return new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->inputTimezone));
$dateTime = new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->outputTimezone));
} else {
// read timestamp into DateTime object - the formatter delivers a timestamp
$dateTime = new \DateTime(sprintf('@%s', $timestamp));
}
// read timestamp into DateTime object - the formatter delivers a timestamp
$dateTime = new \DateTime(sprintf('@%s', $timestamp));
// set timezone separately, as it would be ignored if set via the constructor,
// see http://php.net/manual/en/datetime.construct.php
$dateTime->setTimezone(new \DateTimeZone($this->outputTimezone));

View File

@ -237,6 +237,15 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
$this->assertDateTimeEquals($dateTime, $transformer->reverseTransform('03.02.2010, 04:05'));
}
public function testReverseTransformOnlyDateWithDifferentTimezones()
{
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Berlin', 'Pacific/Tahiti', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd');
$dateTime = new \DateTime('2017-01-10 11:00', new \DateTimeZone('Europe/Berlin'));
$this->assertDateTimeEquals($dateTime, $transformer->reverseTransform('2017-01-10'));
}
public function testReverseTransformWithDifferentPatterns()
{
$transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'MM*yyyy*dd HH|mm|ss');

View File

@ -312,7 +312,10 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
$member = clone $member;
foreach ($member->getConstraints() as $constraint) {
$member->constraintsByGroup[$this->getDefaultGroup()][] = $constraint;
if (in_array($constraint::DEFAULT_GROUP, $constraint->groups, true)) {
$member->constraintsByGroup[$this->getDefaultGroup()][] = $constraint;
}
$constraint->addImplicitGroupName($this->getDefaultGroup());
}

View File

@ -99,8 +99,11 @@ class LazyLoadingMetadataFactory implements MetadataFactoryInterface
return $this->loadedClasses[$class];
}
if (null !== $this->cache && false !== ($this->loadedClasses[$class] = $this->cache->read($class))) {
return $this->loadedClasses[$class];
if (null !== $this->cache && false !== ($metadata = $this->cache->read($class))) {
// Include constraints from the parent class
$this->mergeConstraints($metadata);
return $this->loadedClasses[$class] = $metadata;
}
if (!class_exists($class) && !interface_exists($class)) {
@ -109,6 +112,22 @@ class LazyLoadingMetadataFactory implements MetadataFactoryInterface
$metadata = new ClassMetadata($class);
if (null !== $this->loader) {
$this->loader->loadClassMetadata($metadata);
}
if (null !== $this->cache) {
$this->cache->write($metadata);
}
// Include constraints from the parent class
$this->mergeConstraints($metadata);
return $this->loadedClasses[$class] = $metadata;
}
private function mergeConstraints(ClassMetadata $metadata)
{
// Include constraints from the parent class
if ($parent = $metadata->getReflectionClass()->getParentClass()) {
$metadata->mergeConstraints($this->getMetadataFor($parent->name));
@ -139,16 +158,6 @@ class LazyLoadingMetadataFactory implements MetadataFactoryInterface
}
$metadata->mergeConstraints($this->getMetadataFor($interface->name));
}
if (null !== $this->loader) {
$this->loader->loadClassMetadata($metadata);
}
if (null !== $this->cache) {
$this->cache->write($metadata);
}
return $this->loadedClasses[$class] = $metadata;
}
/**

View File

@ -135,6 +135,7 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase
{
$parent = new ClassMetadata(self::PARENTCLASS);
$parent->addPropertyConstraint('firstName', new ConstraintA());
$parent->addPropertyConstraint('firstName', new ConstraintB(array('groups' => 'foo')));
$this->metadata->mergeConstraints($parent);
$this->metadata->addPropertyConstraint('firstName', new ConstraintA());
@ -148,9 +149,13 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase
'Default',
'Entity',
)));
$constraintB = new ConstraintB(array(
'groups' => array('foo'),
));
$constraints = array(
$constraintA1,
$constraintB,
$constraintA2,
);
@ -166,6 +171,9 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase
$constraintA1,
$constraintA2,
),
'foo' => array(
$constraintB,
),
);
$members = $this->metadata->getPropertyMetadata('firstName');

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Validator\Tests\Mapping\Factory;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
@ -30,8 +31,8 @@ class LazyLoadingMetadataFactoryTest extends \PHPUnit_Framework_TestCase
$metadata = $factory->getMetadataFor(self::PARENT_CLASS);
$constraints = array(
new ConstraintA(array('groups' => array('Default', 'EntityInterfaceA', 'EntityParent'))),
new ConstraintA(array('groups' => array('Default', 'EntityParent'))),
new ConstraintA(array('groups' => array('Default', 'EntityInterfaceA', 'EntityParent'))),
);
$this->assertEquals($constraints, $metadata->getConstraints());
@ -43,6 +44,15 @@ class LazyLoadingMetadataFactoryTest extends \PHPUnit_Framework_TestCase
$metadata = $factory->getMetadataFor(self::CLASS_NAME);
$constraints = array(
new ConstraintA(array('groups' => array(
'Default',
'Entity',
))),
new ConstraintA(array('groups' => array(
'Default',
'EntityParent',
'Entity',
))),
new ConstraintA(array('groups' => array(
'Default',
'EntityInterfaceA',
@ -51,7 +61,7 @@ class LazyLoadingMetadataFactoryTest extends \PHPUnit_Framework_TestCase
))),
new ConstraintA(array('groups' => array(
'Default',
'EntityParent',
'EntityInterfaceB',
'Entity',
))),
new ConstraintA(array('groups' => array(
@ -60,15 +70,6 @@ class LazyLoadingMetadataFactoryTest extends \PHPUnit_Framework_TestCase
'EntityInterfaceB',
'Entity',
))),
new ConstraintA(array('groups' => array(
'Default',
'EntityInterfaceB',
'Entity',
))),
new ConstraintA(array('groups' => array(
'Default',
'Entity',
))),
);
$this->assertEquals($constraints, $metadata->getConstraints());
@ -80,8 +81,8 @@ class LazyLoadingMetadataFactoryTest extends \PHPUnit_Framework_TestCase
$factory = new LazyLoadingMetadataFactory(new TestLoader(), $cache);
$parentClassConstraints = array(
new ConstraintA(array('groups' => array('Default', 'EntityInterfaceA', 'EntityParent'))),
new ConstraintA(array('groups' => array('Default', 'EntityParent'))),
new ConstraintA(array('groups' => array('Default', 'EntityInterfaceA', 'EntityParent'))),
);
$interfaceAConstraints = array(
new ConstraintA(array('groups' => array('Default', 'EntityInterfaceA'))),
@ -122,17 +123,51 @@ class LazyLoadingMetadataFactoryTest extends \PHPUnit_Framework_TestCase
$metadata = new ClassMetadata(self::PARENT_CLASS);
$metadata->addConstraint(new ConstraintA());
$parentClass = self::PARENT_CLASS;
$interfaceClass = self::INTERFACE_A_CLASS;
$loader->expects($this->never())
->method('loadClassMetadata');
$cache->expects($this->never())
->method('has');
$cache->expects($this->once())
$cache->expects($this->exactly(2))
->method('read')
->will($this->returnValue($metadata));
->withConsecutive(
array(self::PARENT_CLASS),
array(self::INTERFACE_A_CLASS)
)
->willReturnCallback(function ($name) use ($metadata, $parentClass, $interfaceClass) {
if ($parentClass == $name) {
return $metadata;
}
return new ClassMetadata($interfaceClass);
});
$this->assertEquals($metadata, $factory->getMetadataFor(self::PARENT_CLASS));
}
public function testMetadataCacheWithRuntimeConstraint()
{
$cache = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Cache\CacheInterface')->getMock();
$factory = new LazyLoadingMetadataFactory(new TestLoader(), $cache);
$cache
->expects($this->any())
->method('write')
->will($this->returnCallback(function ($metadata) { serialize($metadata); }))
;
$cache->expects($this->any())
->method('read')
->will($this->returnValue(false));
$metadata = $factory->getMetadataFor(self::PARENT_CLASS);
$metadata->addConstraint(new Callback(function () {}));
$metadata = $factory->getMetadataFor(self::CLASS_NAME);
}
}
class TestLoader implements LoaderInterface