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"
- php: 7.3
env: deps=high
- php: 7.4snapshot
- php: 7.4
env: deps=low
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) : '';
$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
$debugProcessorChild->countErrors();
}
private function getRecord($level = Logger::WARNING, $message = 'test')
private function getRecord($level = Logger::WARNING, $message = 'test'): array
{
return [
'message' => $message,

View File

@ -30,6 +30,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Messenger\Envelope;
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;
@ -351,7 +352,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
*

View File

@ -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());

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

@ -267,13 +267,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

@ -1242,7 +1242,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'));
}
@ -1273,7 +1272,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

@ -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($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)
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

@ -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

@ -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

@ -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"