Merge branch '3.4' into 4.0
* 3.4: fixed deprecations in tests fixed Twig URL [Cache] Add missing `@internal` tag on ProxyTrait fix formatting arguments in plaintext format Fix PSR exception context key Don't assume that file binary exists on *nix OS Fix that ESI/SSI processing can turn a \"private\" response \"public\" [Form] Fixed trimming choice values fix rendering exception stack traces [Routing] Fix loading multiple class annotations for invokable classes
This commit is contained in:
commit
bb45c75d7b
@ -1,7 +1,7 @@
|
||||
Twig Bridge
|
||||
===========
|
||||
|
||||
Provides integration for [Twig](http://twig.sensiolabs.org/) with various
|
||||
Provides integration for [Twig](https://twig.symfony.com/) with various
|
||||
Symfony components.
|
||||
|
||||
Resources
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% if trace.function %}
|
||||
at {{ trace.class ~ trace.type ~ trace.function }}({{ trace.args|format_args }})
|
||||
at {{ trace.class ~ trace.type ~ trace.function }}({{ trace.args|format_args_as_text }})
|
||||
{%- endif -%}
|
||||
{% if trace.file|default('') is not empty and trace.line|default('') is not empty %}
|
||||
{{- trace.function ? '\n (' : 'at '}}{{ trace.file|format_file(trace.line)|striptags|replace({ (' at line ' ~ trace.line): '' }) }}:{{ trace.line }}{{ trace.function ? ')' }}
|
||||
|
@ -1,5 +1,4 @@
|
||||
{% if exception.trace|length %}
|
||||
<pre class="stacktrace">
|
||||
{{ exception.class }}:
|
||||
{% if exception.message is not empty %}
|
||||
{{- exception.message }}
|
||||
@ -8,5 +7,4 @@
|
||||
{% for trace in exception.trace %}
|
||||
{{ include('@Twig/Exception/trace.txt.twig', { trace: trace }, with_context = false) }}
|
||||
{% endfor %}
|
||||
</pre>
|
||||
{% endif %}
|
||||
|
@ -17,7 +17,13 @@
|
||||
<tbody id="trace-text-{{ index }}">
|
||||
<tr>
|
||||
<td>
|
||||
{{ include('@Twig/Exception/traces.txt.twig', { exception: exception }, with_context = false) }}
|
||||
{% if exception.trace|length %}
|
||||
<pre class="stacktrace">
|
||||
{%- filter escape('html') -%}
|
||||
{{- include('@Twig/Exception/traces.txt.twig', { exception: exception, format: 'html' }, with_context = false) }}
|
||||
{% endfilter %}
|
||||
</pre>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -16,6 +16,8 @@ use Symfony\Component\Cache\ResettableInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait ProxyTrait
|
||||
{
|
||||
|
@ -40,10 +40,10 @@ class ErrorListener implements EventSubscriberInterface
|
||||
$error = $event->getError();
|
||||
|
||||
if (!$inputString = $this->getInputString($event)) {
|
||||
return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('error' => $error, 'message' => $error->getMessage()));
|
||||
return $this->logger->error('An error occurred while using the console. Message: "{message}"', array('exception' => $error, 'message' => $error->getMessage()));
|
||||
}
|
||||
|
||||
$this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('error' => $error, 'command' => $inputString, 'message' => $error->getMessage()));
|
||||
$this->logger->error('Error thrown while running command "{command}". Message: "{message}"', array('exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()));
|
||||
}
|
||||
|
||||
public function onConsoleTerminate(ConsoleTerminateEvent $event)
|
||||
|
@ -34,7 +34,7 @@ class ErrorListenerTest extends TestCase
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('error')
|
||||
->with('Error thrown while running command "{command}". Message: "{message}"', array('error' => $error, 'command' => 'test:run --foo=baz buzz', 'message' => 'An error occurred'))
|
||||
->with('Error thrown while running command "{command}". Message: "{message}"', array('exception' => $error, 'command' => 'test:run --foo=baz buzz', 'message' => 'An error occurred'))
|
||||
;
|
||||
|
||||
$listener = new ErrorListener($logger);
|
||||
@ -49,7 +49,7 @@ class ErrorListenerTest extends TestCase
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('error')
|
||||
->with('An error occurred while using the console. Message: "{message}"', array('error' => $error, 'message' => 'An error occurred'))
|
||||
->with('An error occurred while using the console. Message: "{message}"', array('exception' => $error, 'message' => 'An error occurred'))
|
||||
;
|
||||
|
||||
$listener = new ErrorListener($logger);
|
||||
|
@ -319,6 +319,7 @@ class ChoiceType extends AbstractType
|
||||
// See https://github.com/symfony/symfony/pull/5582
|
||||
'data_class' => null,
|
||||
'choice_translation_domain' => true,
|
||||
'trim' => false,
|
||||
));
|
||||
|
||||
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
|
||||
|
@ -1880,4 +1880,176 @@ class ChoiceTypeTest extends BaseTypeTest
|
||||
'multiple, expanded' => array(true, true, array(array())),
|
||||
);
|
||||
}
|
||||
|
||||
public function testInheritTranslationDomainFromParent()
|
||||
{
|
||||
$view = $this->factory
|
||||
->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE, null, array(
|
||||
'translation_domain' => 'domain',
|
||||
))
|
||||
->add('child', static::TESTED_TYPE)
|
||||
->getForm()
|
||||
->createView();
|
||||
|
||||
$this->assertEquals('domain', $view['child']->vars['translation_domain']);
|
||||
}
|
||||
|
||||
public function testPassTranslationDomainToView()
|
||||
{
|
||||
$view = $this->factory->create(static::TESTED_TYPE, null, array(
|
||||
'translation_domain' => 'domain',
|
||||
))
|
||||
->createView();
|
||||
|
||||
$this->assertSame('domain', $view->vars['translation_domain']);
|
||||
}
|
||||
|
||||
public function testPreferOwnTranslationDomain()
|
||||
{
|
||||
$view = $this->factory
|
||||
->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE, null, array(
|
||||
'translation_domain' => 'parent_domain',
|
||||
))
|
||||
->add('child', static::TESTED_TYPE, array(
|
||||
'translation_domain' => 'domain',
|
||||
))
|
||||
->getForm()
|
||||
->createView();
|
||||
|
||||
$this->assertEquals('domain', $view['child']->vars['translation_domain']);
|
||||
}
|
||||
|
||||
public function testDefaultTranslationDomain()
|
||||
{
|
||||
$view = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE)
|
||||
->add('child', static::TESTED_TYPE)
|
||||
->getForm()
|
||||
->createView();
|
||||
|
||||
$this->assertNull($view['child']->vars['translation_domain']);
|
||||
}
|
||||
|
||||
public function testPassMultipartFalseToView()
|
||||
{
|
||||
$view = $this->factory->create(static::TESTED_TYPE, null)
|
||||
->createView();
|
||||
|
||||
$this->assertFalse($view->vars['multipart']);
|
||||
}
|
||||
|
||||
public function testPassLabelToView()
|
||||
{
|
||||
$view = $this->factory->createNamed('__test___field', static::TESTED_TYPE, null, array(
|
||||
'label' => 'My label',
|
||||
))
|
||||
->createView();
|
||||
|
||||
$this->assertSame('My label', $view->vars['label']);
|
||||
}
|
||||
|
||||
public function testPassIdAndNameToViewWithGrandParent()
|
||||
{
|
||||
$builder = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE)
|
||||
->add('child', FormTypeTest::TESTED_TYPE);
|
||||
$builder->get('child')->add('grand_child', static::TESTED_TYPE);
|
||||
$view = $builder->getForm()->createView();
|
||||
|
||||
$this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->vars['id']);
|
||||
$this->assertEquals('grand_child', $view['child']['grand_child']->vars['name']);
|
||||
$this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->vars['full_name']);
|
||||
}
|
||||
|
||||
public function testPassIdAndNameToViewWithParent()
|
||||
{
|
||||
$view = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE)
|
||||
->add('child', static::TESTED_TYPE)
|
||||
->getForm()
|
||||
->createView();
|
||||
|
||||
$this->assertEquals('parent_child', $view['child']->vars['id']);
|
||||
$this->assertEquals('child', $view['child']->vars['name']);
|
||||
$this->assertEquals('parent[child]', $view['child']->vars['full_name']);
|
||||
}
|
||||
|
||||
public function testPassDisabledAsOption()
|
||||
{
|
||||
$form = $this->factory->create(static::TESTED_TYPE, null, array(
|
||||
'disabled' => true,
|
||||
));
|
||||
|
||||
$this->assertTrue($form->isDisabled());
|
||||
}
|
||||
|
||||
public function testPassIdAndNameToView()
|
||||
{
|
||||
$view = $this->factory->createNamed('name', static::TESTED_TYPE, null)
|
||||
->createView();
|
||||
|
||||
$this->assertEquals('name', $view->vars['id']);
|
||||
$this->assertEquals('name', $view->vars['name']);
|
||||
$this->assertEquals('name', $view->vars['full_name']);
|
||||
}
|
||||
|
||||
public function testStripLeadingUnderscoresAndDigitsFromId()
|
||||
{
|
||||
$view = $this->factory->createNamed('_09name', static::TESTED_TYPE, null)
|
||||
->createView();
|
||||
|
||||
$this->assertEquals('name', $view->vars['id']);
|
||||
$this->assertEquals('_09name', $view->vars['name']);
|
||||
$this->assertEquals('_09name', $view->vars['full_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTrimCases
|
||||
*/
|
||||
public function testTrimIsDisabled($multiple, $expanded)
|
||||
{
|
||||
$form = $this->factory->create(static::TESTED_TYPE, null, array(
|
||||
'multiple' => $multiple,
|
||||
'expanded' => $expanded,
|
||||
'choices' => array(
|
||||
'a' => '1',
|
||||
),
|
||||
));
|
||||
|
||||
$submittedData = ' 1';
|
||||
|
||||
$form->submit($multiple ? (array) $submittedData : $submittedData);
|
||||
|
||||
// When the choice does not exist the transformation fails
|
||||
$this->assertFalse($form->isSynchronized());
|
||||
$this->assertNull($form->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTrimCases
|
||||
*/
|
||||
public function testSubmitValueWithWhiteSpace($multiple, $expanded)
|
||||
{
|
||||
$valueWhitWhiteSpace = '1 ';
|
||||
|
||||
$form = $this->factory->create(static::TESTED_TYPE, null, array(
|
||||
'multiple' => $multiple,
|
||||
'expanded' => $expanded,
|
||||
'choices' => array(
|
||||
'a' => $valueWhitWhiteSpace,
|
||||
),
|
||||
));
|
||||
|
||||
$form->submit($multiple ? (array) $valueWhitWhiteSpace : $valueWhitWhiteSpace);
|
||||
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
$this->assertSame($multiple ? (array) $valueWhitWhiteSpace : $valueWhitWhiteSpace, $form->getData());
|
||||
}
|
||||
|
||||
public function provideTrimCases()
|
||||
{
|
||||
return array(
|
||||
'Simple' => array(false, false),
|
||||
'Multiple' => array(true, false),
|
||||
'Simple expanded' => array(false, true),
|
||||
'Multiple expanded' => array(true, true),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,21 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg');
|
||||
static $supported = null;
|
||||
|
||||
if (null !== $supported) {
|
||||
return $supported;
|
||||
}
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR || !function_exists('passthru') || !function_exists('escapeshellarg')) {
|
||||
return $supported = false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
passthru('command -v file', $exitStatus);
|
||||
$binPath = trim(ob_get_clean());
|
||||
|
||||
return $supported = 0 === $exitStatus && '' !== $binPath;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -502,13 +502,19 @@ class Response
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the response is worth caching under any circumstance.
|
||||
* Returns true if the response may safely be kept in a shared (surrogate) cache.
|
||||
*
|
||||
* Responses marked "private" with an explicit Cache-Control directive are
|
||||
* considered uncacheable.
|
||||
*
|
||||
* Responses with neither a freshness lifetime (Expires, max-age) nor cache
|
||||
* validator (Last-Modified, ETag) are considered uncacheable.
|
||||
* validator (Last-Modified, ETag) are considered uncacheable because there is
|
||||
* no way to tell when or how to remove them from the cache.
|
||||
*
|
||||
* Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
|
||||
* for example "status codes that are defined as cacheable by default [...]
|
||||
* can be reused by a cache with heuristic expiration unless otherwise indicated"
|
||||
* (https://tools.ietf.org/html/rfc7231#section-6.1)
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
|
@ -72,7 +72,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
|
||||
$response->setLastModified(null);
|
||||
}
|
||||
|
||||
if (!$response->isFresh()) {
|
||||
if (!$response->isFresh() || !$response->isCacheable()) {
|
||||
$this->cacheable = false;
|
||||
}
|
||||
|
||||
|
@ -175,8 +175,26 @@ class ResponseCacheStrategyTest extends TestCase
|
||||
$cacheStrategy->update($masterResponse);
|
||||
|
||||
$this->assertTrue($masterResponse->headers->hasCacheControlDirective('private'));
|
||||
// Not sure if we should pass "max-age: 60" in this case, as long as the response is private and
|
||||
// that's the more conservative of both the master and embedded response...?
|
||||
$this->assertFalse($masterResponse->headers->hasCacheControlDirective('public'));
|
||||
}
|
||||
|
||||
public function testEmbeddingPublicResponseDoesNotMakeMainResponsePublic()
|
||||
{
|
||||
$cacheStrategy = new ResponseCacheStrategy();
|
||||
|
||||
$masterResponse = new Response();
|
||||
$masterResponse->setPrivate(); // this is the default, but let's be explicit
|
||||
$masterResponse->setMaxAge(100);
|
||||
|
||||
$embeddedResponse = new Response();
|
||||
$embeddedResponse->setPublic();
|
||||
$embeddedResponse->setSharedMaxAge(100);
|
||||
|
||||
$cacheStrategy->add($embeddedResponse);
|
||||
$cacheStrategy->update($masterResponse);
|
||||
|
||||
$this->assertTrue($masterResponse->headers->hasCacheControlDirective('private'));
|
||||
$this->assertFalse($masterResponse->headers->hasCacheControlDirective('public'));
|
||||
}
|
||||
|
||||
public function testResponseIsExiprableWhenEmbeddedResponseCombinesExpiryAndValidation()
|
||||
|
@ -119,10 +119,15 @@ abstract class AnnotationClassLoader implements LoaderInterface
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === $collection->count() && $class->hasMethod('__invoke') && $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) {
|
||||
$globals['path'] = '';
|
||||
$globals['name'] = '';
|
||||
$this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
|
||||
if (0 === $collection->count() && $class->hasMethod('__invoke')) {
|
||||
foreach ($this->reader->getClassAnnotations($class) as $annot) {
|
||||
if ($annot instanceof $this->routeAnnotationClass) {
|
||||
$globals['path'] = '';
|
||||
$globals['name'] = '';
|
||||
|
||||
$this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
|
@ -191,9 +191,9 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
|
||||
);
|
||||
|
||||
$this->reader
|
||||
->expects($this->exactly(2))
|
||||
->method('getClassAnnotation')
|
||||
->will($this->returnValue($this->getAnnotatedRoute($classRouteData)))
|
||||
->expects($this->exactly(1))
|
||||
->method('getClassAnnotations')
|
||||
->will($this->returnValue(array($this->getAnnotatedRoute($classRouteData))))
|
||||
;
|
||||
$this->reader
|
||||
->expects($this->once())
|
||||
@ -205,8 +205,49 @@ class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest
|
||||
$route = $routeCollection->get($classRouteData['name']);
|
||||
|
||||
$this->assertSame($classRouteData['path'], $route->getPath(), '->load preserves class route path');
|
||||
$this->assertEquals(array_merge($classRouteData['schemes'], $classRouteData['schemes']), $route->getSchemes(), '->load preserves class route schemes');
|
||||
$this->assertEquals(array_merge($classRouteData['methods'], $classRouteData['methods']), $route->getMethods(), '->load preserves class route methods');
|
||||
$this->assertEquals($classRouteData['schemes'], $route->getSchemes(), '->load preserves class route schemes');
|
||||
$this->assertEquals($classRouteData['methods'], $route->getMethods(), '->load preserves class route methods');
|
||||
}
|
||||
|
||||
public function testInvokableClassMultipleRouteLoad()
|
||||
{
|
||||
$classRouteData1 = array(
|
||||
'name' => 'route1',
|
||||
'path' => '/1',
|
||||
'schemes' => array('https'),
|
||||
'methods' => array('GET'),
|
||||
);
|
||||
|
||||
$classRouteData2 = array(
|
||||
'name' => 'route2',
|
||||
'path' => '/2',
|
||||
'schemes' => array('https'),
|
||||
'methods' => array('GET'),
|
||||
);
|
||||
|
||||
$this->reader
|
||||
->expects($this->exactly(1))
|
||||
->method('getClassAnnotations')
|
||||
->will($this->returnValue(array($this->getAnnotatedRoute($classRouteData1), $this->getAnnotatedRoute($classRouteData2))))
|
||||
;
|
||||
$this->reader
|
||||
->expects($this->once())
|
||||
->method('getMethodAnnotations')
|
||||
->will($this->returnValue(array()))
|
||||
;
|
||||
|
||||
$routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass');
|
||||
$route = $routeCollection->get($classRouteData1['name']);
|
||||
|
||||
$this->assertSame($classRouteData1['path'], $route->getPath(), '->load preserves class route path');
|
||||
$this->assertEquals($classRouteData1['schemes'], $route->getSchemes(), '->load preserves class route schemes');
|
||||
$this->assertEquals($classRouteData1['methods'], $route->getMethods(), '->load preserves class route methods');
|
||||
|
||||
$route = $routeCollection->get($classRouteData2['name']);
|
||||
|
||||
$this->assertSame($classRouteData2['path'], $route->getPath(), '->load preserves class route path');
|
||||
$this->assertEquals($classRouteData2['schemes'], $route->getSchemes(), '->load preserves class route schemes');
|
||||
$this->assertEquals($classRouteData2['methods'], $route->getMethods(), '->load preserves class route methods');
|
||||
}
|
||||
|
||||
public function testInvokableClassWithMethodRouteLoad()
|
||||
|
@ -29,7 +29,7 @@ class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest
|
||||
|
||||
public function testLoad()
|
||||
{
|
||||
$this->reader->expects($this->exactly(4))->method('getClassAnnotation');
|
||||
$this->reader->expects($this->exactly(3))->method('getClassAnnotation');
|
||||
|
||||
$this->reader
|
||||
->expects($this->any())
|
||||
@ -37,6 +37,12 @@ class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest
|
||||
->will($this->returnValue(array()))
|
||||
;
|
||||
|
||||
$this->reader
|
||||
->expects($this->any())
|
||||
->method('getClassAnnotations')
|
||||
->will($this->returnValue(array()))
|
||||
;
|
||||
|
||||
$this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses');
|
||||
}
|
||||
|
||||
@ -45,7 +51,6 @@ class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest
|
||||
$this->expectAnnotationsToBeReadFrom(array(
|
||||
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass',
|
||||
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass',
|
||||
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass',
|
||||
'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\FooClass',
|
||||
));
|
||||
|
||||
@ -55,6 +60,12 @@ class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest
|
||||
->will($this->returnValue(array()))
|
||||
;
|
||||
|
||||
$this->reader
|
||||
->expects($this->any())
|
||||
->method('getClassAnnotations')
|
||||
->will($this->returnValue(array()))
|
||||
;
|
||||
|
||||
$this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses');
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user