Merge branch '4.3' into 4.4
* 4.3: [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 forward caught exception [Validator][ConstraintValidator] Stop passing unnecessary timezone argument to \DateTime add tags before processing them [MonologBridge] Fix debug processor datetime type
This commit is contained in:
commit
e77059825c
|
@ -29,7 +29,7 @@ matrix:
|
|||
env: php_extra="7.2 7.4snapshot"
|
||||
- php: 7.3
|
||||
env: deps=high
|
||||
- php: 7.4snapshot
|
||||
- php: 7.4
|
||||
env: deps=low
|
||||
fast_finish: true
|
||||
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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
|
|||
$debugProcessorChild->countErrors();
|
||||
}
|
||||
|
||||
private function getRecord($level = Logger::WARNING, $message = 'test')
|
||||
private function getRecord($level = Logger::WARNING, $message = 'test'): array
|
||||
{
|
||||
return [
|
||||
'message' => $message,
|
||||
|
|
|
@ -30,6 +30,7 @@ use Symfony\Component\Messenger\Envelope;
|
|||
use Symfony\Component\Messenger\Stamp\StampInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
|
||||
use Symfony\Component\WebLink\GenericLinkProvider;
|
||||
|
@ -352,7 +353,7 @@ trait ControllerTrait
|
|||
/**
|
||||
* Get a user from the Security Token Storage.
|
||||
*
|
||||
* @return object|null
|
||||
* @return UserInterface|object|null
|
||||
*
|
||||
* @throws \LogicException If SecurityBundle is not available
|
||||
*
|
||||
|
|
|
@ -32,7 +32,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 ExceptionListenerPass());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -35,12 +35,13 @@ trait PhpFilesTrait
|
|||
private $files = [];
|
||||
|
||||
private static $startTime;
|
||||
private static $valuesCache = [];
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,7 +101,6 @@ trait PhpFilesTrait
|
|||
} elseif (!\is_object($value)) {
|
||||
$values[$id] = $value;
|
||||
} elseif (!$value instanceof LazyValue) {
|
||||
// calling a Closure is for @deprecated BC and should be removed in Symfony 5.0
|
||||
$values[$id] = $value();
|
||||
} elseif (false === $values[$id] = include $value->file) {
|
||||
unset($values[$id], $this->values[$id]);
|
||||
|
@ -123,14 +123,20 @@ trait PhpFilesTrait
|
|||
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]);
|
||||
|
@ -159,7 +165,13 @@ trait PhpFilesTrait
|
|||
$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);
|
||||
|
@ -213,12 +225,14 @@ trait PhpFilesTrait
|
|||
|
||||
$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);
|
||||
|
@ -229,6 +243,7 @@ trait PhpFilesTrait
|
|||
@opcache_invalidate($file, true);
|
||||
@opcache_compile_file($file);
|
||||
}
|
||||
unset(self::$valuesCache[$file]);
|
||||
}
|
||||
|
||||
if (!$ok && !is_writable($this->directory)) {
|
||||
|
@ -262,6 +277,8 @@ trait PhpFilesTrait
|
|||
|
||||
protected function doUnlink($file)
|
||||
{
|
||||
unset(self::$valuesCache[$file]);
|
||||
|
||||
if (self::isSupported()) {
|
||||
@opcache_invalidate($file, true);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -276,13 +276,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();
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
data:
|
||||
foo: !bar "foo\r\nline with trailing spaces:\n \nbar\ninteger like line:\n123456789\nempty line:\n\nbaz"
|
Reference in New Issue