Merge branch '5.0'

* 5.0:
  [Validator] Fix auto-mapping constraints should not be validated
  [Debug] Updated the README to deprecate the component
  [Cache] fix memory leak when using PhpFilesAdapter
  [Yaml] Implement multiline string as scalar block for tagged values
  [HttpFoundation] Use `Cache-Control: must-revalidate` only if explicit lifetime has been given
  [FrameworkBundle] Use UserInterface to @return in getUser method
  [CI] Replace php7.4snapshot with php7.4 in Travis configuration
  [ExpressionLanguage][Node][BinaryNode] Process division by zero
  Fixing bad order of operations with null coalescing operator
  forward caught exception
  [Validator][ConstraintValidator] Stop passing unnecessary timezone argument to \DateTime
  add tags before processing them
  [FrameworkBundle][ContainerLintCommand] Reinitialize bundles when the container is reprepared
  [Process] change the syntax of portable prepared command lines
  [MonologBridge] Fix debug processor datetime type
This commit is contained in:
Nicolas Grekas 2019-12-10 12:07:17 +01:00
commit c2181b0887
30 changed files with 293 additions and 88 deletions

View File

@ -29,7 +29,7 @@ matrix:
env: php_extra="7.4snapshot"
- php: 7.3
env: deps=high
- php: 7.4snapshot
- php: 7.4
env: deps=low
fast_finish: true

View File

@ -21,11 +21,12 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderNoAutoMappingEntity;
use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderParentEntity;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Bridge\Doctrine\Validator\DoctrineLoader;
use Symfony\Component\Validator\Constraints\DisableAutoMapping;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
use Symfony\Component\Validator\Mapping\CascadingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\AutoMappingTrait;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
use Symfony\Component\Validator\Mapping\TraversalStrategy;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Validation;
@ -140,11 +141,12 @@ class DoctrineLoaderTest extends TestCase
$this->assertInstanceOf(Length::class, $textFieldConstraints[0]);
$this->assertSame(1000, $textFieldConstraints[0]->max);
/** @var PropertyMetadata[] $noAutoMappingMetadata */
$noAutoMappingMetadata = $classMetadata->getPropertyMetadata('noAutoMapping');
$this->assertCount(1, $noAutoMappingMetadata);
$noAutoMappingConstraints = $noAutoMappingMetadata[0]->getConstraints();
$this->assertCount(1, $noAutoMappingConstraints);
$this->assertInstanceOf(DisableAutoMapping::class, $noAutoMappingConstraints[0]);
$this->assertCount(0, $noAutoMappingConstraints);
$this->assertSame(AutoMappingStrategy::DISABLED, $noAutoMappingMetadata[0]->getAutoMappingStrategy());
}
public function testFieldMappingsConfiguration()
@ -205,13 +207,15 @@ class DoctrineLoaderTest extends TestCase
$classMetadata = $validator->getMetadataFor(new DoctrineLoaderNoAutoMappingEntity());
$classConstraints = $classMetadata->getConstraints();
$this->assertCount(1, $classConstraints);
$this->assertInstanceOf(DisableAutoMapping::class, $classConstraints[0]);
$this->assertCount(0, $classConstraints);
$this->assertSame(AutoMappingStrategy::DISABLED, $classMetadata->getAutoMappingStrategy());
$maxLengthMetadata = $classMetadata->getPropertyMetadata('maxLength');
$this->assertEmpty($maxLengthMetadata);
/** @var PropertyMetadata[] $autoMappingExplicitlyEnabledMetadata */
$autoMappingExplicitlyEnabledMetadata = $classMetadata->getPropertyMetadata('autoMappingExplicitlyEnabled');
$this->assertCount(2, $autoMappingExplicitlyEnabledMetadata[0]->constraints);
$this->assertCount(1, $autoMappingExplicitlyEnabledMetadata[0]->constraints);
$this->assertSame(AutoMappingStrategy::ENABLED, $autoMappingExplicitlyEnabledMetadata[0]->getAutoMappingStrategy());
}
}

View File

@ -16,10 +16,9 @@ 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\DisableAutoMapping;
use Symfony\Component\Validator\Constraints\EnableAutoMapping;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\AutoMappingTrait;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
@ -76,13 +75,16 @@ final class DoctrineLoader implements LoaderInterface
$enabledForProperty = $enabledForClass;
$lengthConstraint = null;
foreach ($metadata->getPropertyMetadata($mapping['fieldName']) as $propertyMetadata) {
// Enabling or disabling auto-mapping explicitly always takes precedence
if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) {
continue 2;
}
if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) {
$enabledForProperty = true;
}
foreach ($propertyMetadata->getConstraints() as $constraint) {
// Enabling or disabling auto-mapping explicitly always takes precedence
if ($constraint instanceof DisableAutoMapping) {
continue 3;
} elseif ($constraint instanceof EnableAutoMapping) {
$enabledForProperty = true;
} elseif ($constraint instanceof Length) {
if ($constraint instanceof Length) {
$lengthConstraint = $constraint;
}
}

View File

@ -35,7 +35,7 @@
"symfony/proxy-manager-bridge": "^4.4|^5.0",
"symfony/security-core": "^5.0",
"symfony/expression-language": "^4.4|^5.0",
"symfony/validator": "^5.0",
"symfony/validator": "^5.0.1",
"symfony/translation": "^4.4|^5.0",
"symfony/var-dumper": "^4.4|^5.0",
"doctrine/annotations": "~1.7",
@ -55,7 +55,7 @@
"symfony/property-info": "<5",
"symfony/security-bundle": "<5",
"symfony/security-core": "<5",
"symfony/validator": "<5"
"symfony/validator": "<5.0.1"
},
"suggest": {
"symfony/form": "",

View File

@ -33,7 +33,7 @@ class DebugProcessor implements DebugLoggerInterface, ResetInterface
$hash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
$this->records[$hash][] = [
'timestamp' => $record['datetime']->getTimestamp(),
'timestamp' => $record['datetime'] instanceof \DateTimeInterface ? $record['datetime']->getTimestamp() : strtotime($record['datetime']),
'message' => $record['message'],
'priority' => $record['level'],
'priorityName' => $record['level_name'],

View File

@ -19,6 +19,30 @@ use Symfony\Component\HttpFoundation\RequestStack;
class DebugProcessorTest extends TestCase
{
/**
* @dataProvider providerDatetimeFormatTests
*/
public function testDatetimeFormat(array $record, $expectedTimestamp)
{
$processor = new DebugProcessor();
$processor($record);
$records = $processor->getLogs();
self::assertCount(1, $records);
self::assertSame($expectedTimestamp, $records[0]['timestamp']);
}
public function providerDatetimeFormatTests(): array
{
$record = $this->getRecord();
return [
[array_merge($record, ['datetime' => new \DateTime('2019-01-01T00:01:00+00:00')]), 1546300860],
[array_merge($record, ['datetime' => '2019-01-01T00:01:00+00:00']), 1546300860],
[array_merge($record, ['datetime' => 'foo']), false],
];
}
public function testDebugProcessor()
{
$processor = new DebugProcessor();
@ -75,7 +99,7 @@ class DebugProcessorTest extends TestCase
$this->assertEquals(0, $debugProcessorChild->countErrors());
}
private function getRecord($level = Logger::WARNING, $message = 'test')
private function getRecord($level = Logger::WARNING, $message = 'test'): array
{
return [
'message' => $message,

View File

@ -69,7 +69,11 @@ final class ContainerLintCommand extends Command
$kernel = $this->getApplication()->getKernel();
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
$buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
$buildContainer = \Closure::bind(function (): ContainerBuilder {
$this->initializeBundles();
return $this->buildContainer();
}, $kernel, \get_class($kernel));
$container = $buildContainer();
} else {
(new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));

View File

@ -351,7 +351,7 @@ abstract class AbstractController implements ServiceSubscriberInterface
/**
* Get a user from the Security Token Storage.
*
* @return mixed
* @return UserInterface|object|null
*
* @throws \LogicException If SecurityBundle is not available
*

View File

@ -31,7 +31,8 @@ class TwigBundle extends Bundle
{
parent::build($container);
$container->addCompilerPass(new ExtensionPass());
// ExtensionPass must be run before the FragmentRendererPass as it adds tags that are processed later
$container->addCompilerPass(new ExtensionPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10);
$container->addCompilerPass(new TwigEnvironmentPass());
$container->addCompilerPass(new TwigLoaderPass());
$container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING);

View File

@ -57,7 +57,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
{
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
return \function_exists('opcache_invalidate') && ('cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)) && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN);
return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN));
}
/**
@ -116,6 +116,8 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
$values[$id] = null;
} elseif (!\is_object($value)) {
$values[$id] = $value;
} elseif (!$value instanceof LazyValue) {
$values[$id] = $value();
} elseif (false === $values[$id] = include $value->file) {
unset($values[$id], $this->values[$id]);
$missingIds[] = $id;
@ -137,14 +139,20 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
try {
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
if (\is_array($expiresAt = include $file)) {
if (isset(self::$valuesCache[$file])) {
[$expiresAt, $this->values[$id]] = self::$valuesCache[$file];
} elseif (\is_array($expiresAt = include $file)) {
if ($this->appendOnly) {
self::$valuesCache[$file] = $expiresAt;
}
[$expiresAt, $this->values[$id]] = $expiresAt;
} elseif ($now < $expiresAt) {
$this->values[$id] = new LazyValue($file);
}
if ($now >= $expiresAt) {
unset($this->values[$id], $missingIds[$k]);
unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
}
} catch (\ErrorException $e) {
unset($missingIds[$k]);
@ -173,7 +181,13 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
$getExpiry = true;
if (\is_array($expiresAt = include $file)) {
if (isset(self::$valuesCache[$file])) {
[$expiresAt, $value] = self::$valuesCache[$file];
} elseif (\is_array($expiresAt = include $file)) {
if ($this->appendOnly) {
self::$valuesCache[$file] = $expiresAt;
}
[$expiresAt, $value] = $expiresAt;
} elseif ($this->appendOnly) {
$value = new LazyValue($file);
@ -227,12 +241,14 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
$encodedKey = rawurlencode($key);
if (!$isStaticValue) {
if ($isStaticValue) {
$value = "return [{$expiry}, {$value}];";
} elseif ($this->appendOnly) {
$value = "return [{$expiry}, static function () { return {$value}; }];";
} else {
// We cannot use a closure here because of https://bugs.php.net/76982
$value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value);
$value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};";
} else {
$value = "return [{$expiry}, {$value}];";
}
$file = $this->files[$key] = $this->getFile($key, true);
@ -243,6 +259,7 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
@opcache_invalidate($file, true);
@opcache_compile_file($file);
}
unset(self::$valuesCache[$file]);
}
if (!$ok && !is_writable($this->directory)) {
@ -276,6 +293,8 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
protected function doUnlink($file)
{
unset(self::$valuesCache[$file]);
if (self::isSupported()) {
@opcache_invalidate($file, true);
}

View File

@ -0,0 +1,31 @@
<?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\Cache\Tests\Adapter;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
/**
* @group time-sensitive
*/
class PhpFilesAdapterAppendOnlyTest extends PhpFilesAdapterTest
{
protected $skippedTests = [
'testDefaultLifeTime' => 'PhpFilesAdapter does not allow configuring a default lifetime.',
'testExpiration' => 'PhpFilesAdapter in append-only mode does not expiration.',
];
public function createCachePool(): CacheItemPoolInterface
{
return new PhpFilesAdapter('sf-cache', 0, null, true);
}
}

View File

@ -147,8 +147,16 @@ class BinaryNode extends Node
case '*':
return $left * $right;
case '/':
if (0 == $right) {
throw new \DivisionByZeroError('Division by zero');
}
return $left / $right;
case '%':
if (0 == $right) {
throw new \DivisionByZeroError('Modulo by zero');
}
return $left % $right;
case 'matches':
return preg_match($right, $left);

View File

@ -262,13 +262,13 @@ class ResponseHeaderBag extends HeaderBag
*/
protected function computeCacheControlValue()
{
if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
return 'no-cache, private';
}
if (!$this->cacheControl) {
if ($this->has('Last-Modified') || $this->has('Expires')) {
return 'private, must-revalidate'; // allows for heuristic expiration (RFC 7234 Section 4.2.2) in the case of "Last-Modified"
}
// conservative by default
return 'private, must-revalidate';
return 'no-cache, private';
}
$header = $this->getCacheControlHeader();

View File

@ -51,9 +51,9 @@ class ResponseHeaderBagTest extends TestCase
$this->assertTrue($bag->hasCacheControlDirective('public'));
$bag = new ResponseHeaderBag(['ETag' => 'abcde']);
$this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
$this->assertEquals('no-cache, private', $bag->get('Cache-Control'));
$this->assertTrue($bag->hasCacheControlDirective('private'));
$this->assertTrue($bag->hasCacheControlDirective('must-revalidate'));
$this->assertTrue($bag->hasCacheControlDirective('no-cache'));
$this->assertFalse($bag->hasCacheControlDirective('max-age'));
$bag = new ResponseHeaderBag(['Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT']);

View File

@ -110,8 +110,6 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
$response->headers->set('Age', $this->age);
if ($this->isNotCacheableResponseEmbedded) {
$response->setExpires($response->getDate());
if ($this->flagDirectives['no-store']) {
$response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate');
} else {

View File

@ -1240,7 +1240,6 @@ class HttpCacheTest extends HttpCacheTestCase
$this->request('GET', '/', [], [], true);
$this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent());
$this->assertNull($this->response->getTtl());
$this->assertTrue($this->response->mustRevalidate());
$this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
$this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache'));
}
@ -1271,7 +1270,6 @@ class HttpCacheTest extends HttpCacheTestCase
// This can neither be cached nor revalidated, so it should be private/no cache
$this->assertEmpty($this->response->getContent());
$this->assertNull($this->response->getTtl());
$this->assertTrue($this->response->mustRevalidate());
$this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
$this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache'));
}

View File

@ -1591,12 +1591,12 @@ class Process implements \IteratorAggregate
private function replacePlaceholders(string $commandline, array $env)
{
return preg_replace_callback('/"\$([_a-zA-Z]++[_a-zA-Z0-9]*+)"/', function ($matches) use ($commandline, $env) {
return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
throw new InvalidArgumentException(sprintf('Command line is missing a value for key %s: %s.', $matches[0], $commandline));
throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": %s.', $matches[1], $commandline));
}
return '\\' === \DIRECTORY_SEPARATOR ? $this->escapeArgument($env[$matches[1]]) : $matches[0];
return $this->escapeArgument($env[$matches[1]]);
}, $commandline);
}

View File

@ -1463,7 +1463,7 @@ EOTXT;
public function testPreparedCommand()
{
$p = Process::fromShellCommandline('echo "$abc"DEF');
$p = Process::fromShellCommandline('echo "${:abc}"DEF');
$p->run(null, ['abc' => 'ABC']);
$this->assertSame('ABCDEF', rtrim($p->getOutput()));
@ -1471,7 +1471,7 @@ EOTXT;
public function testPreparedCommandMulti()
{
$p = Process::fromShellCommandline('echo "$abc""$def"');
$p = Process::fromShellCommandline('echo "${:abc}""${:def}"');
$p->run(null, ['abc' => 'ABC', 'def' => 'DEF']);
$this->assertSame('ABCDEF', rtrim($p->getOutput()));
@ -1479,7 +1479,7 @@ EOTXT;
public function testPreparedCommandWithQuoteInIt()
{
$p = Process::fromShellCommandline('php -r "$code" "$def"');
$p = Process::fromShellCommandline('php -r "${:code}" "${:def}"');
$p->run(null, ['code' => 'echo $argv[1];', 'def' => '"DEF"']);
$this->assertSame('"DEF"', rtrim($p->getOutput()));
@ -1488,16 +1488,16 @@ EOTXT;
public function testPreparedCommandWithMissingValue()
{
$this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Command line is missing a value for key "$abc": echo "$abc".');
$p = Process::fromShellCommandline('echo "$abc"');
$this->expectExceptionMessage('Command line is missing a value for parameter "abc": echo "${:abc}".');
$p = Process::fromShellCommandline('echo "${:abc}"');
$p->run(null, ['bcd' => 'BCD']);
}
public function testPreparedCommandWithNoValues()
{
$this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Command line is missing a value for key "$abc": echo "$abc".');
$p = Process::fromShellCommandline('echo "$abc"');
$this->expectExceptionMessage('Command line is missing a value for parameter "abc": echo "${:abc}".');
$p = Process::fromShellCommandline('echo "${:abc}"');
$p->run(null, []);
}

View File

@ -174,14 +174,14 @@ class PropertyAccessor implements PropertyAccessorInterface
$value = $zval[self::VALUE];
}
} catch (\TypeError $e) {
self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath);
self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e);
// It wasn't thrown in this class so rethrow it
throw $e;
}
}
private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath): void
private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath, \Throwable $previous = null): void
{
// the type mismatch is not caused by invalid arguments (but e.g. by an incompatible return type hint of the writer method)
if (0 !== strpos($message, 'Argument ')) {
@ -195,7 +195,7 @@ class PropertyAccessor implements PropertyAccessorInterface
$type = substr($message, 2 + $j, strpos($message, ' given', $j) - $j - 2);
$message = substr($message, $pos, $j - $pos);
throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $message, 'NULL' === $type ? 'null' : $type, $propertyPath));
throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $message, 'NULL' === $type ? 'null' : $type, $propertyPath), 0, $previous);
}
}

View File

@ -41,7 +41,7 @@ class ProblemNormalizer implements NormalizerInterface, CacheableSupportsMethodI
public function normalize($exception, string $format = null, array $context = [])
{
$context += $this->defaultContext;
$debug = $this->debug && $context['debug'] ?? true;
$debug = $this->debug && ($context['debug'] ?? true);
$data = [
'type' => $context['type'],

View File

@ -93,10 +93,7 @@ abstract class ConstraintValidator implements ConstraintValidatorInterface
// neither the native nor the stub IntlDateFormatter support
// DateTimeImmutable as of yet
if (!$value instanceof \DateTime) {
$value = new \DateTime(
$value->format('Y-m-d H:i:s.u e'),
$value->getTimezone()
);
$value = new \DateTime($value->format('Y-m-d H:i:s.u e'));
}
return $formatter->format($value);

View File

@ -0,0 +1,42 @@
<?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;
/**
* Specifies how the auto-mapping feature should behave.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
final class AutoMappingStrategy
{
/**
* Nothing explicitly set, rely on auto-mapping configured regex.
*/
public const NONE = 0;
/**
* Explicitly enabled.
*/
public const ENABLED = 1;
/**
* Explicitly disabled.
*/
public const DISABLED = 2;
/**
* Not instantiable.
*/
private function __construct()
{
}
}

View File

@ -12,6 +12,13 @@
namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraint;
<<<<<<< HEAD
=======
use Symfony\Component\Validator\Constraints\DisableAutoMapping;
use Symfony\Component\Validator\Constraints\EnableAutoMapping;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
>>>>>>> 4.4
use Symfony\Component\Validator\Constraints\Traverse;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
@ -73,6 +80,19 @@ class GenericMetadata implements MetadataInterface
*/
public $traversalStrategy = TraversalStrategy::NONE;
/**
* Is auto-mapping enabled?
*
* @var int
*
* @see AutoMappingStrategy
*
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getAutoMappingStrategy()} instead.
*/
public $autoMappingStrategy = AutoMappingStrategy::NONE;
/**
* Returns the names of the properties that should be serialized.
*
@ -85,6 +105,7 @@ class GenericMetadata implements MetadataInterface
'constraintsByGroup',
'cascadingStrategy',
'traversalStrategy',
'autoMappingStrategy',
];
}
@ -137,6 +158,13 @@ class GenericMetadata implements MetadataInterface
return $this;
}
if ($constraint instanceof DisableAutoMapping || $constraint instanceof EnableAutoMapping) {
$this->autoMappingStrategy = $constraint instanceof EnableAutoMapping ? AutoMappingStrategy::ENABLED : AutoMappingStrategy::DISABLED;
// The constraint is not added
return $this;
}
$this->constraints[] = $constraint;
foreach ($constraint->groups as $group) {
@ -205,4 +233,12 @@ class GenericMetadata implements MetadataInterface
{
return $this->traversalStrategy;
}
/**
* @see AutoMappingStrategy
*/
public function getAutoMappingStrategy(): int
{
return $this->autoMappingStrategy;
}
}

View File

@ -11,8 +11,7 @@
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Constraints\DisableAutoMapping;
use Symfony\Component\Validator\Constraints\EnableAutoMapping;
use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
@ -25,14 +24,8 @@ trait AutoMappingTrait
private function isAutoMappingEnabledForClass(ClassMetadata $metadata, string $classValidatorRegexp = null): bool
{
// Check if AutoMapping constraint is set first
foreach ($metadata->getConstraints() as $constraint) {
if ($constraint instanceof DisableAutoMapping) {
return false;
}
if ($constraint instanceof EnableAutoMapping) {
return true;
}
if (AutoMappingStrategy::NONE !== $strategy = $metadata->getAutoMappingStrategy()) {
return AutoMappingStrategy::ENABLED === $strategy;
}
// Fallback on the config

View File

@ -16,11 +16,10 @@ 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\DisableAutoMapping;
use Symfony\Component\Validator\Constraints\EnableAutoMapping;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
@ -77,16 +76,16 @@ final class PropertyInfoLoader implements LoaderInterface
$hasNotBlankConstraint = false;
$allConstraint = null;
foreach ($metadata->getPropertyMetadata($property) as $propertyMetadata) {
// Enabling or disabling auto-mapping explicitly always takes precedence
if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) {
continue 2;
}
if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) {
$enabledForProperty = true;
}
foreach ($propertyMetadata->getConstraints() as $constraint) {
// Enabling or disabling auto-mapping explicitly always takes precedence
if ($constraint instanceof DisableAutoMapping) {
continue 3;
}
if ($constraint instanceof EnableAutoMapping) {
$enabledForProperty = true;
}
if ($constraint instanceof Type) {
$hasTypeConstraint = true;
} elseif ($constraint instanceof NotNull) {

View File

@ -53,10 +53,7 @@ abstract class AbstractComparisonValidatorTestCase extends ConstraintValidatorTe
foreach ($comparison as $i => $value) {
if ($value instanceof \DateTime) {
$comparison[$i] = new \DateTimeImmutable(
$value->format('Y-m-d H:i:s.u e'),
$value->getTimezone()
);
$comparison[$i] = new \DateTimeImmutable($value->format('Y-m-d H:i:s.u e'));
$add = true;
} elseif ('DateTime' === $value) {
$comparison[$i] = 'DateTimeImmutable';

View File

@ -15,13 +15,14 @@ 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\DisableAutoMapping;
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\AutoMappingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\PropertyInfoLoaderEntity;
use Symfony\Component\Validator\Tests\Fixtures\PropertyInfoLoaderNoAutoMappingEntity;
@ -164,11 +165,12 @@ class PropertyInfoLoaderTest extends TestCase
$readOnlyMetadata = $classMetadata->getPropertyMetadata('readOnly');
$this->assertEmpty($readOnlyMetadata);
/** @var PropertyMetadata[] $noAutoMappingMetadata */
$noAutoMappingMetadata = $classMetadata->getPropertyMetadata('noAutoMapping');
$this->assertCount(1, $noAutoMappingMetadata);
$this->assertSame(AutoMappingStrategy::DISABLED, $noAutoMappingMetadata[0]->getAutoMappingStrategy());
$noAutoMappingConstraints = $noAutoMappingMetadata[0]->getConstraints();
$this->assertCount(1, $noAutoMappingConstraints);
$this->assertInstanceOf(DisableAutoMapping::class, $noAutoMappingConstraints[0]);
$this->assertCount(0, $noAutoMappingConstraints, 'DisableAutoMapping constraint is not added in the list');
}
/**
@ -222,8 +224,10 @@ class PropertyInfoLoaderTest extends TestCase
->getValidator()
;
/** @var ClassMetadata $classMetadata */
$classMetadata = $validator->getMetadataFor(new PropertyInfoLoaderNoAutoMappingEntity());
$this->assertEmpty($classMetadata->getPropertyMetadata('string'));
$this->assertCount(3, $classMetadata->getPropertyMetadata('autoMappingExplicitlyEnabled')[0]->constraints);
$this->assertCount(2, $classMetadata->getPropertyMetadata('autoMappingExplicitlyEnabled')[0]->constraints);
$this->assertSame(AutoMappingStrategy::ENABLED, $classMetadata->getPropertyMetadata('autoMappingExplicitlyEnabled')[0]->getAutoMappingStrategy());
}
}

View File

@ -70,7 +70,7 @@ class Dumper
$blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : '';
$output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator);
foreach (preg_split('/\n|\r\n/', $value) as $row) {
foreach (explode("\n", $value) as $row) {
$output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row);
}
@ -80,6 +80,19 @@ class Dumper
if ($value instanceof TaggedValue) {
$output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag());
if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) {
// If the first line starts with a space character, the spec requires a blockIndicationIndicator
// http://www.yaml.org/spec/1.2/spec.html#id2793979
$blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : '';
$output .= sprintf(" |%s\n", $blockIndentationIndicator);
foreach (explode("\n", $value->getValue()) as $row) {
$output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row);
}
continue;
}
if ($inline - 1 <= 0 || null === $value->getValue() || is_scalar($value->getValue())) {
$output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n";
} else {

View File

@ -487,6 +487,39 @@ YAML;
$this->assertSame($expected, $this->dumper->dump($data, 2));
}
public function testDumpingMultiLineStringAsScalarBlockTaggedValue()
{
$data = [
'foo' => new TaggedValue('bar', "foo\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz"),
];
$expected = <<<YAML
foo: !bar |
foo
line with trailing spaces:
bar
integer like line:
123456789
empty line:
baz
YAML;
$this->assertSame($expected, $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
}
public function testDumpingInlinedMultiLineIfRnBreakLineInTaggedValue()
{
$data = [
'data' => [
'foo' => new TaggedValue('bar', "foo\r\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz"),
],
];
$this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block_for_tagged_values.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK));
}
public function testDumpMultiLineStringAsScalarBlock()
{
$data = [

View File

@ -0,0 +1,2 @@
data:
foo: !bar "foo\r\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz"