Merge branch '4.1'

* 4.1:
  [Console] simplified code
  removed useless phpdoc
  improve docblocks around group sequences
  [Cache] prevent getting older entries when the version key is evicted
  [WebProfilerBundle] added a note in the README
  [Yaml] Skip parser test with root user
  [Filesystem] Skip tests on readable file when run with root user
  [FWBundle] Fix an error in WebTestCase::createClient's PHPDoc
  [HttpFoundation][Security] forward locale and format to subrequests
  [Console] Send the right exit code to console.terminate listeners
  [HttpFoundation] fix hidding warnings from session handlers
  Caching missed templates on cache warmup
This commit is contained in:
Fabien Potencier 2018-09-30 05:40:00 +02:00
commit deaf53c231
20 changed files with 155 additions and 74 deletions

View File

@ -23,7 +23,7 @@ abstract class WebTestCase extends KernelTestCase
/**
* Creates a Client.
*
* @param array $options An array of options to pass to the createKernel class
* @param array $options An array of options to pass to the createKernel method
* @param array $server An array of server parameters
*
* @return Client A Client instance

View File

@ -63,7 +63,7 @@ class TemplateIterator implements \IteratorAggregate
$this->templates = array_merge(
$this->templates,
$this->findTemplatesInDirectory($bundle->getPath().'/Resources/views', $name),
$this->findTemplatesInDirectory($this->rootDir.'/'.$bundle->getName().'/views', $name),
$this->findTemplatesInDirectory($this->rootDir.'/Resources/'.$bundle->getName().'/views', $name),
$this->findTemplatesInDirectory($this->defaultPath.'/bundles/'.$bundle->getName(), $name)
);
}

View File

@ -31,6 +31,7 @@ class TemplateIteratorTest extends TestCase
sort($sorted);
$this->assertEquals(
array(
'@Bar/base.html.twig',
'@Bar/index.html.twig',
'@Bar/layout.html.twig',
'@Foo/index.html.twig',

View File

@ -107,14 +107,7 @@ trait AbstractTrait
{
$this->deferred = array();
if ($cleared = $this->versioningIsEnabled) {
$namespaceVersion = 2;
try {
foreach ($this->doFetch(array('/'.$this->namespace)) as $v) {
$namespaceVersion = 1 + (int) $v;
}
} catch (\Exception $e) {
}
$namespaceVersion .= '/';
$namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), ':', 5);
try {
$cleared = $this->doSave(array('/'.$this->namespace => $namespaceVersion), 0);
} catch (\Exception $e) {
@ -254,6 +247,10 @@ trait AbstractTrait
foreach ($this->doFetch(array('/'.$this->namespace)) as $v) {
$this->namespaceVersion = $v;
}
if ('1:' === $this->namespaceVersion) {
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), ':', 5);
$this->doSave(array('@'.$this->namespace => $this->namespaceVersion), 0);
}
} catch (\Exception $e) {
}
}

View File

@ -159,6 +159,8 @@ class Application
} else {
$exitCode = 1;
}
return $exitCode;
} finally {
// if the exception handler changed, keep it
// otherwise, unregister $renderException

View File

@ -985,6 +985,31 @@ class ApplicationTest extends TestCase
$this->assertSame(4, $exitCode, '->run() returns integer exit code extracted from raised exception');
}
public function testRunDispatchesIntegerExitCode()
{
$passedRightValue = false;
// We can assume here that some other test asserts that the event is dispatched at all
$dispatcher = new EventDispatcher();
$self = $this;
$dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) use ($self, &$passedRightValue) {
$passedRightValue = (4 === $event->getExitCode());
});
$application = new Application();
$application->setDispatcher($dispatcher);
$application->setAutoExit(false);
$application->register('test')->setCode(function (InputInterface $input, OutputInterface $output) {
throw new \Exception('', 4);
});
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'test'));
$this->assertTrue($passedRightValue, '-> exit code 4 was passed in the console.terminate event');
}
public function testRunReturnsExitCodeOneForExceptionCodeZero()
{
$exception = new \Exception('', 0);
@ -1000,6 +1025,31 @@ class ApplicationTest extends TestCase
$this->assertSame(1, $exitCode, '->run() returns exit code 1 when exception code is 0');
}
public function testRunDispatchesExitCodeOneForExceptionCodeZero()
{
$passedRightValue = false;
// We can assume here that some other test asserts that the event is dispatched at all
$dispatcher = new EventDispatcher();
$self = $this;
$dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) use ($self, &$passedRightValue) {
$passedRightValue = (1 === $event->getExitCode());
});
$application = new Application();
$application->setDispatcher($dispatcher);
$application->setAutoExit(false);
$application->register('test')->setCode(function (InputInterface $input, OutputInterface $output) {
throw new \Exception();
});
$tester = new ApplicationTester($application);
$tester->run(array('command' => 'test'));
$this->assertTrue($passedRightValue, '-> exit code 1 was passed in the console.terminate event');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage An option with shortcut "e" already exists.

View File

@ -50,6 +50,10 @@ class FilesystemTest extends FilesystemTestCase
$this->markTestSkipped('This test cannot run on Windows.');
}
if (!getenv('USER') || 'root' === getenv('USER')) {
$this->markTestSkipped('This test will fail if run under superuser');
}
$sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
$targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';
@ -124,6 +128,10 @@ class FilesystemTest extends FilesystemTestCase
$this->markTestSkipped('This test cannot run on Windows.');
}
if (!getenv('USER') || 'root' === getenv('USER')) {
$this->markTestSkipped('This test will fail if run under superuser');
}
$sourceFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_source_file';
$targetFilePath = $this->workspace.\DIRECTORY_SEPARATOR.'copy_target_file';

View File

@ -137,7 +137,7 @@ class FormValidator extends ConstraintValidator
/**
* Returns the validation groups of the given form.
*
* @return array The validation groups
* @return string|GroupSequence|(string|GroupSequence)[] The validation groups
*/
private static function getValidationGroups(FormInterface $form)
{
@ -172,10 +172,10 @@ class FormValidator extends ConstraintValidator
/**
* Post-processes the validation groups option for a given form.
*
* @param array|callable $groups The validation groups
* @param FormInterface $form The validated form
* @param string|GroupSequence|(string|GroupSequence)[]|callable $groups The validation groups
* @param FormInterface $form The validated form
*
* @return array The validation groups
* @return (string|GroupSequence)[] The validation groups
*/
private static function resolveValidationGroups($groups, FormInterface $form)
{

View File

@ -1332,7 +1332,7 @@ class Request
* * _format request attribute
* * $default
*
* @param string $default The default format
* @param string|null $default The default format
*
* @return string The request format
*/

View File

@ -241,29 +241,22 @@ class NativeSessionStorage implements SessionStorageInterface
unset($_SESSION[$key]);
}
// Register custom error handler to catch a possible failure warning during session write
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline);
}, E_WARNING);
// Register error handler to add information about the current save handler
$previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
if (E_WARNING === $type && 0 === strpos($msg, 'session_write_close():')) {
$handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
$msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler));
}
return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
});
try {
$e = null;
session_write_close();
} catch (\ErrorException $e) {
} finally {
restore_error_handler();
$_SESSION = $session;
}
if (null !== $e) {
// The default PHP error message is not very helpful, as it does not give any information on the current save handler.
// Therefore, we catch this error and trigger a warning with a better error message
$handler = $this->getSaveHandler();
if ($handler instanceof SessionHandlerProxy) {
$handler = $handler->getHandler();
}
trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', \get_class($handler)), E_USER_WARNING);
}
$this->closed = true;
$this->started = false;

View File

@ -125,6 +125,13 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
}
$setSession($subRequest, $request);
if ($request->get('_format')) {
$subRequest->attributes->set('_format', $request->get('_format'));
}
if ($request->getDefaultLocale() !== $request->getLocale()) {
$subRequest->setLocale($request->getLocale());
}
return $subRequest;
}

View File

@ -145,6 +145,26 @@ class InlineFragmentRendererTest extends TestCase
$this->assertEquals('Foo', ob_get_clean());
}
public function testLocaleAndFormatAreIsKeptInSubrequest()
{
$expectedSubRequest = Request::create('/');
$expectedSubRequest->attributes->set('_format', 'foo');
$expectedSubRequest->setLocale('fr');
if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) {
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
}
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
$expectedSubRequest->server->set('HTTP_FORWARDED', 'for="127.0.0.1";host="localhost";proto=http');
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
$request = Request::create('/');
$request->attributes->set('_format', 'foo');
$request->setLocale('fr');
$strategy->render('/', $request);
}
public function testESIHeaderIsKeptInSubrequest()
{
$expectedSubRequest = Request::create('/');

View File

@ -101,6 +101,13 @@ class HttpUtils
$newRequest->attributes->set(Security::LAST_USERNAME, $request->attributes->get(Security::LAST_USERNAME));
}
if ($request->get('_format')) {
$newRequest->attributes->set('_format', $request->get('_format'));
}
if ($request->getDefaultLocale() !== $request->getLocale()) {
$newRequest->setLocale($request->getLocale());
}
return $newRequest;
}

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Constraints\GroupSequence;
/**
* Defines the interface for a group sequence provider.
*/
@ -20,7 +22,7 @@ interface GroupSequenceProviderInterface
* Returns which validation groups should be used for a certain state
* of the object.
*
* @return array An array of validation groups
* @return string[]|GroupSequence An array of validation groups
*/
public function getGroupSequence();
}

View File

@ -392,7 +392,7 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
/**
* Sets the default group sequence for this class.
*
* @param array $groupSequence An array of group names
* @param string[]|GroupSequence $groupSequence An array of group names
*
* @return $this
*

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
@ -39,12 +40,9 @@ interface ContextualValidatorInterface
* If no constraint is passed, the constraint
* {@link \Symfony\Component\Validator\Constraints\Valid} is assumed.
*
* @param mixed $value The value to validate
* @param Constraint|Constraint[] $constraints The constraint(s) to validate
* against
* @param array|null $groups The validation groups to
* validate. If none is given,
* "Default" is assumed
* @param mixed $value The value to validate
* @param Constraint|Constraint[] $constraints The constraint(s) to validate against
* @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed
*
* @return $this
*/
@ -54,10 +52,9 @@ interface ContextualValidatorInterface
* Validates a property of an object against the constraints specified
* for this property.
*
* @param object $object The object
* @param string $propertyName The name of the validated property
* @param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
* @param object $object The object
* @param string $propertyName The name of the validated property
* @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed
*
* @return $this
*/
@ -67,12 +64,10 @@ interface ContextualValidatorInterface
* Validates a value against the constraints specified for an object's
* property.
*
* @param object|string $objectOrClass The object or its class name
* @param string $propertyName The name of the property
* @param mixed $value The value to validate against the
* property's constraints
* @param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
* @param object|string $objectOrClass The object or its class name
* @param string $propertyName The name of the property
* @param mixed $value The value to validate against the property's constraints
* @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed
*
* @return $this
*/

View File

@ -272,9 +272,9 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
/**
* Normalizes the given group or list of groups to an array.
*
* @param mixed $groups The groups to normalize
* @param string|GroupSequence|(string|GroupSequence)[] $groups The groups to normalize
*
* @return array A group array
* @return (string|GroupSequence)[] A group array
*/
protected function normalizeGroups($groups)
{
@ -295,7 +295,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
*
* @param object $object The object to cascade
* @param string $propertyPath The current property path
* @param string[] $groups The validated groups
* @param (string|GroupSequence)[] $groups The validated groups
* @param int $traversalStrategy The strategy for traversing the
* cascaded object
* @param ExecutionContextInterface $context The current execution context
@ -355,10 +355,10 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
* objects are iterated as well. Nested arrays are always iterated,
* regardless of the value of $recursive.
*
* @param iterable $collection The collection
* @param string $propertyPath The current property path
* @param string[] $groups The validated groups
* @param ExecutionContextInterface $context The current execution context
* @param iterable $collection The collection
* @param string $propertyPath The current property path
* @param (string|GroupSequence)[] $groups The validated groups
* @param ExecutionContextInterface $context The current execution context
*
* @see ClassNode
* @see CollectionNode
@ -424,7 +424,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
* the object
* @param string $propertyPath The property path leading
* to the object
* @param string[] $groups The groups in which the
* @param (string|GroupSequence)[] $groups The groups in which the
* object should be validated
* @param string[]|null $cascadedGroups The groups in which
* cascaded objects should
@ -608,7 +608,7 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
* value
* @param string $propertyPath The property path leading
* to the value
* @param string[] $groups The groups in which the
* @param (string|GroupSequence)[] $groups The groups in which the
* value should be validated
* @param string[]|null $cascadedGroups The groups in which
* cascaded objects should

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
@ -29,12 +30,9 @@ interface ValidatorInterface extends MetadataFactoryInterface
* If no constraint is passed, the constraint
* {@link \Symfony\Component\Validator\Constraints\Valid} is assumed.
*
* @param mixed $value The value to validate
* @param Constraint|Constraint[] $constraints The constraint(s) to validate
* against
* @param array|null $groups The validation groups to
* validate. If none is given,
* "Default" is assumed
* @param mixed $value The value to validate
* @param Constraint|Constraint[] $constraints The constraint(s) to validate against
* @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed
*
* @return ConstraintViolationListInterface A list of constraint violations
* If the list is empty, validation
@ -46,10 +44,9 @@ interface ValidatorInterface extends MetadataFactoryInterface
* Validates a property of an object against the constraints specified
* for this property.
*
* @param object $object The object
* @param string $propertyName The name of the validated property
* @param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
* @param object $object The object
* @param string $propertyName The name of the validated property
* @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed
*
* @return ConstraintViolationListInterface A list of constraint violations
* If the list is empty, validation
@ -61,12 +58,10 @@ interface ValidatorInterface extends MetadataFactoryInterface
* Validates a value against the constraints specified for an object's
* property.
*
* @param object|string $objectOrClass The object or its class name
* @param string $propertyName The name of the property
* @param mixed $value The value to validate against the
* property's constraints
* @param array|null $groups The validation groups to validate. If
* none is given, "Default" is assumed
* @param object|string $objectOrClass The object or its class name
* @param string $propertyName The name of the property
* @param mixed $value The value to validate against the property's constraints
* @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed
*
* @return ConstraintViolationListInterface A list of constraint violations
* If the list is empty, validation

View File

@ -1945,6 +1945,10 @@ YAML;
$this->markTestSkipped('chmod is not supported on Windows');
}
if (!getenv('USER') || 'root' === getenv('USER')) {
$this->markTestSkipped('This test will fail if run under superuser');
}
$file = __DIR__.'/Fixtures/not_readable.yml';
chmod($file, 0200);