Merge branch '3.4' into 4.3

* 3.4:
  [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:
Nicolas Grekas 2019-12-10 11:30:59 +01:00
commit 7f2d9c2b1e
16 changed files with 100 additions and 28 deletions

View File

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

View File

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

View File

@ -19,6 +19,30 @@ use Symfony\Component\HttpFoundation\RequestStack;
class DebugProcessorTest extends TestCase 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() public function testDebugProcessor()
{ {
$processor = new DebugProcessor(); $processor = new DebugProcessor();
@ -75,7 +99,7 @@ class DebugProcessorTest extends TestCase
$debugProcessorChild->countErrors(); $debugProcessorChild->countErrors();
} }
private function getRecord($level = Logger::WARNING, $message = 'test') private function getRecord($level = Logger::WARNING, $message = 'test'): array
{ {
return [ return [
'message' => $message, 'message' => $message,

View File

@ -30,6 +30,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
@ -351,7 +352,7 @@ trait ControllerTrait
/** /**
* Get a user from the Security Token Storage. * Get a user from the Security Token Storage.
* *
* @return object|null * @return UserInterface|object|null
* *
* @throws \LogicException If SecurityBundle is not available * @throws \LogicException If SecurityBundle is not available
* *

View File

@ -32,7 +32,8 @@ class TwigBundle extends Bundle
{ {
parent::build($container); 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 TwigEnvironmentPass());
$container->addCompilerPass(new TwigLoaderPass()); $container->addCompilerPass(new TwigLoaderPass());
$container->addCompilerPass(new ExceptionListenerPass()); $container->addCompilerPass(new ExceptionListenerPass());

View File

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

View File

@ -267,13 +267,13 @@ class ResponseHeaderBag extends HeaderBag
*/ */
protected function computeCacheControlValue() 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->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 // conservative by default
return 'private, must-revalidate'; return 'no-cache, private';
} }
$header = $this->getCacheControlHeader(); $header = $this->getCacheControlHeader();

View File

@ -51,9 +51,9 @@ class ResponseHeaderBagTest extends TestCase
$this->assertTrue($bag->hasCacheControlDirective('public')); $this->assertTrue($bag->hasCacheControlDirective('public'));
$bag = new ResponseHeaderBag(['ETag' => 'abcde']); $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('private'));
$this->assertTrue($bag->hasCacheControlDirective('must-revalidate')); $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
$this->assertFalse($bag->hasCacheControlDirective('max-age')); $this->assertFalse($bag->hasCacheControlDirective('max-age'));
$bag = new ResponseHeaderBag(['Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT']); $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); $response->headers->set('Age', $this->age);
if ($this->isNotCacheableResponseEmbedded) { if ($this->isNotCacheableResponseEmbedded) {
$response->setExpires($response->getDate());
if ($this->flagDirectives['no-store']) { if ($this->flagDirectives['no-store']) {
$response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate'); $response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate');
} else { } else {

View File

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

View File

@ -174,14 +174,14 @@ class PropertyAccessor implements PropertyAccessorInterface
$value = $zval[self::VALUE]; $value = $zval[self::VALUE];
} }
} catch (\TypeError $e) { } 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 // It wasn't thrown in this class so rethrow it
throw $e; throw $e;
} }
} }
private static function throwInvalidArgumentException($message, $trace, $i, $propertyPath) private static function throwInvalidArgumentException($message, $trace, $i, $propertyPath, \Throwable $previous = null)
{ {
// the type mismatch is not caused by invalid arguments (but e.g. by an incompatible return type hint of the writer method) // 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 ')) { if (0 !== strpos($message, 'Argument ')) {
@ -195,7 +195,7 @@ class PropertyAccessor implements PropertyAccessorInterface
$type = substr($message, 2 + $j, strpos($message, ' given', $j) - $j - 2); $type = substr($message, 2 + $j, strpos($message, ' given', $j) - $j - 2);
$message = substr($message, $pos, $j - $pos); $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

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

View File

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

View File

@ -70,7 +70,7 @@ class Dumper
$blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : ''; $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : '';
$output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator); $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); $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row);
} }
@ -80,6 +80,19 @@ class Dumper
if ($value instanceof TaggedValue) { if ($value instanceof TaggedValue) {
$output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); $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())) { if ($inline - 1 <= 0 || null === $value->getValue() || is_scalar($value->getValue())) {
$output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n";
} else { } else {

View File

@ -487,6 +487,39 @@ YAML;
$this->assertSame($expected, $this->dumper->dump($data, 2)); $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() public function testDumpMultiLineStringAsScalarBlock()
{ {
$data = [ $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"