Merge branch '3.4' into 4.0
* 3.4: [HttpFoundation] Add Session::isEmpty(), fix MockFileSessionStorage to behave like the native one [HttpKernel] Add a better error messages when passing a private or non-tagged controller [VarDumper] Dont use empty(), it chokes on eg GMP objects [Dotenv] Changed preg_match flags from null to 0 remove upgrade instructions for kernel.root_dir [HttpKernel] Arrays with scalar values passed to ESI fragment renderer throw deprecation notice [HttpKernel] add a test for FilterControllerEvents
This commit is contained in:
commit
32dfdb30c6
@ -598,12 +598,6 @@ HttpKernel
|
||||
tags: ['console.command']
|
||||
```
|
||||
|
||||
* Removed the `kernel.root_dir` parameter. Use the `kernel.project_dir` parameter
|
||||
instead.
|
||||
|
||||
* Removed the `Kernel::getRootDir()` method. Use the `Kernel::getProjectDir()`
|
||||
method instead.
|
||||
|
||||
* The `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` methods have been removed.
|
||||
|
||||
* Possibility to pass non-scalar values as URI attributes to the ESI and SSI
|
||||
|
@ -29,7 +29,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface
|
||||
protected $options = array(
|
||||
'check_path' => '/login_check',
|
||||
'use_forward' => false,
|
||||
'require_previous_session' => true,
|
||||
'require_previous_session' => false,
|
||||
);
|
||||
|
||||
protected $defaultSuccessHandlerOptions = array(
|
||||
|
@ -28,7 +28,6 @@ class JsonLoginFactory extends AbstractFactory
|
||||
$this->addOption('password_path', 'password');
|
||||
$this->defaultFailureHandlerOptions = array();
|
||||
$this->defaultSuccessHandlerOptions = array();
|
||||
$this->options['require_previous_session'] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,7 +174,7 @@ final class Dotenv
|
||||
|
||||
private function lexValue()
|
||||
{
|
||||
if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, null, $this->cursor)) {
|
||||
if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) {
|
||||
$this->moveCursor($matches[0]);
|
||||
$this->skipEmptyLines();
|
||||
|
||||
@ -296,7 +296,7 @@ final class Dotenv
|
||||
|
||||
private function skipEmptyLines()
|
||||
{
|
||||
if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, null, $this->cursor)) {
|
||||
if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, 0, $this->cursor)) {
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
|
||||
|
||||
private $flashName;
|
||||
private $attributeName;
|
||||
private $data = array();
|
||||
|
||||
/**
|
||||
* @param SessionStorageInterface $storage A SessionStorageInterface instance
|
||||
@ -108,7 +109,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->storage->getBag($this->attributeName)->clear();
|
||||
$this->getAttributeBag()->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,6 +140,22 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
|
||||
return count($this->getAttributeBag()->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
foreach ($this->data as &$data) {
|
||||
if (!empty($data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -210,7 +227,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
|
||||
*/
|
||||
public function registerBag(SessionBagInterface $bag)
|
||||
{
|
||||
$this->storage->registerBag($bag);
|
||||
$this->storage->registerBag(new SessionBagProxy($bag, $this->data));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -218,7 +235,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
|
||||
*/
|
||||
public function getBag($name)
|
||||
{
|
||||
return $this->storage->getBag($name);
|
||||
return $this->storage->getBag($name)->getBag();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,6 +257,6 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
|
||||
*/
|
||||
private function getAttributeBag()
|
||||
{
|
||||
return $this->storage->getBag($this->attributeName);
|
||||
return $this->storage->getBag($this->attributeName)->getBag();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
<?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\HttpFoundation\Session;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SessionBagProxy implements SessionBagInterface
|
||||
{
|
||||
private $bag;
|
||||
private $data;
|
||||
|
||||
public function __construct(SessionBagInterface $bag, array &$data)
|
||||
{
|
||||
$this->bag = $bag;
|
||||
$this->data = &$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SessionBagInterface
|
||||
*/
|
||||
public function getBag()
|
||||
{
|
||||
return $this->bag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->data[$this->bag->getStorageKey()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->bag->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initialize(array &$array)
|
||||
{
|
||||
$this->data[$this->bag->getStorageKey()] = &$array;
|
||||
|
||||
$this->bag->initialize($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStorageKey()
|
||||
{
|
||||
return $this->bag->getStorageKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return $this->bag->clear();
|
||||
}
|
||||
}
|
@ -91,7 +91,26 @@ class MockFileSessionStorage extends MockArraySessionStorage
|
||||
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
|
||||
}
|
||||
|
||||
file_put_contents($this->getFilePath(), serialize($this->data));
|
||||
$data = $this->data;
|
||||
|
||||
foreach ($this->bags as $bag) {
|
||||
if (empty($data[$key = $bag->getStorageKey()])) {
|
||||
unset($data[$key]);
|
||||
}
|
||||
}
|
||||
if (array($key = $this->metadataBag->getStorageKey()) === array_keys($data)) {
|
||||
unset($data[$key]);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($data) {
|
||||
file_put_contents($this->getFilePath(), serialize($data));
|
||||
} else {
|
||||
$this->destroy();
|
||||
}
|
||||
} finally {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
// this is needed for Silex, where the session object is re-used across requests
|
||||
// in functional tests. In Symfony, the container is rebooted, so we don't have
|
||||
|
@ -221,4 +221,22 @@ class SessionTest extends TestCase
|
||||
{
|
||||
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag());
|
||||
}
|
||||
|
||||
public function testIsEmpty()
|
||||
{
|
||||
$this->assertTrue($this->session->isEmpty());
|
||||
|
||||
$this->session->set('hello', 'world');
|
||||
$this->assertFalse($this->session->isEmpty());
|
||||
|
||||
$this->session->remove('hello');
|
||||
$this->assertTrue($this->session->isEmpty());
|
||||
|
||||
$flash = $this->session->getFlashBag();
|
||||
$flash->set('hello', 'world');
|
||||
$this->assertFalse($this->session->isEmpty());
|
||||
|
||||
$flash->get('hello');
|
||||
$this->assertTrue($this->session->isEmpty());
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Controller;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
@ -86,6 +87,15 @@ class ContainerControllerResolver extends ControllerResolver
|
||||
return $this->container->get($class);
|
||||
}
|
||||
|
||||
return parent::instantiateController($class);
|
||||
try {
|
||||
return parent::instantiateController($class);
|
||||
} catch (\ArgumentCountError $e) {
|
||||
}
|
||||
|
||||
if ($this->container instanceof Container && in_array($class, $this->container->getRemovedIds(), true)) {
|
||||
throw new \LogicException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $class), 0, $e);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\HttpKernel\EventListener;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
@ -60,8 +61,10 @@ abstract class AbstractTestSessionListener implements EventSubscriberInterface
|
||||
$session = $event->getRequest()->getSession();
|
||||
if ($session && $session->isStarted()) {
|
||||
$session->save();
|
||||
$params = session_get_cookie_params();
|
||||
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
|
||||
if (!$session instanceof Session || !\method_exists($session, 'isEmpty') || !$session->isEmpty()) {
|
||||
$params = session_get_cookie_params();
|
||||
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,8 +98,8 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere
|
||||
private function containsNonScalars(array $values): bool
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
if (is_array($value) && $this->containsNonScalars($value)) {
|
||||
return true;
|
||||
if (is_array($value)) {
|
||||
return $this->containsNonScalars($value);
|
||||
} elseif (!is_scalar($value) && null !== $value) {
|
||||
return true;
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ namespace Symfony\Component\HttpKernel\Tests\Controller;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver;
|
||||
|
||||
@ -106,6 +108,38 @@ class ContainerControllerResolverTest extends ControllerResolverTest
|
||||
$this->assertSame(array(NonInstantiableController::class, 'action'), $controller);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
* @expectedExceptionMessage Controller "Symfony\Component\HttpKernel\Tests\Controller\ImpossibleConstructController" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?
|
||||
*/
|
||||
public function testNonConstructController()
|
||||
{
|
||||
$container = $this->getMockBuilder(Container::class)->getMock();
|
||||
$container->expects($this->at(0))
|
||||
->method('has')
|
||||
->with(ImpossibleConstructController::class)
|
||||
->will($this->returnValue(true))
|
||||
;
|
||||
|
||||
$container->expects($this->at(1))
|
||||
->method('has')
|
||||
->with(ImpossibleConstructController::class)
|
||||
->will($this->returnValue(false))
|
||||
;
|
||||
|
||||
$container->expects($this->atLeastOnce())
|
||||
->method('getRemovedIds')
|
||||
->with()
|
||||
->will($this->returnValue(array(ImpossibleConstructController::class)))
|
||||
;
|
||||
|
||||
$resolver = $this->createControllerResolver(null, $container);
|
||||
$request = Request::create('/');
|
||||
$request->attributes->set('_controller', array(ImpossibleConstructController::class, 'action'));
|
||||
|
||||
$resolver->getController($request);
|
||||
}
|
||||
|
||||
public function testNonInstantiableControllerWithCorrespondingService()
|
||||
{
|
||||
$service = new \stdClass();
|
||||
@ -196,3 +230,14 @@ abstract class NonInstantiableController
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class ImpossibleConstructController
|
||||
{
|
||||
public function __construct($toto, $controller)
|
||||
{
|
||||
}
|
||||
|
||||
public function action()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\Event;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpKernel\Tests\TestHttpKernel;
|
||||
|
||||
class FilterControllerArgumentsEventTest extends TestCase
|
||||
{
|
||||
public function testFilterControllerArgumentsEvent()
|
||||
{
|
||||
$filterController = new FilterControllerArgumentsEvent(new TestHttpKernel(), function () {}, array('test'), new Request(), 1);
|
||||
$this->assertEquals($filterController->getArguments(), array('test'));
|
||||
}
|
||||
}
|
@ -73,6 +73,19 @@ class TestSessionListenerTest extends TestCase
|
||||
$this->assertEquals(0, reset($cookies)->getExpiresTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires function \Symfony\Component\HttpFoundation\Session\Session::isEmpty
|
||||
*/
|
||||
public function testEmptySessionDoesNotSendCookie()
|
||||
{
|
||||
$this->sessionHasBeenStarted();
|
||||
$this->sessionIsEmpty();
|
||||
|
||||
$response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST);
|
||||
|
||||
$this->assertSame(array(), $response->headers->getCookies());
|
||||
}
|
||||
|
||||
public function testUnstartedSessionIsNotSave()
|
||||
{
|
||||
$this->sessionHasNotBeenStarted();
|
||||
@ -130,6 +143,13 @@ class TestSessionListenerTest extends TestCase
|
||||
->will($this->returnValue(false));
|
||||
}
|
||||
|
||||
private function sessionIsEmpty()
|
||||
{
|
||||
$this->session->expects($this->once())
|
||||
->method('isEmpty')
|
||||
->will($this->returnValue(true));
|
||||
}
|
||||
|
||||
private function getSession()
|
||||
{
|
||||
$mock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session')
|
||||
|
@ -26,6 +26,14 @@ class EsiFragmentRendererTest extends TestCase
|
||||
$strategy->render('/', Request::create('/'));
|
||||
}
|
||||
|
||||
public function testRenderFallbackWithScalar()
|
||||
{
|
||||
$strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true), new UriSigner('foo'));
|
||||
$request = Request::create('/');
|
||||
$reference = new ControllerReference('main_controller', array('foo' => array(true)), array());
|
||||
$strategy->render($reference, $request);
|
||||
}
|
||||
|
||||
public function testRender()
|
||||
{
|
||||
$strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy());
|
||||
|
@ -113,8 +113,8 @@ class Caster
|
||||
|
||||
if (null === $v) {
|
||||
$type |= self::EXCLUDE_NULL & $filter;
|
||||
}
|
||||
if (empty($v)) {
|
||||
$type |= self::EXCLUDE_EMPTY & $filter;
|
||||
} elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || array() === $v) {
|
||||
$type |= self::EXCLUDE_EMPTY & $filter;
|
||||
}
|
||||
if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !in_array($k, $listedProperties, true)) {
|
||||
|
@ -101,13 +101,16 @@ class VarCloner extends AbstractCloner
|
||||
// Create $stub when the original value $v can not be used directly
|
||||
// If $v is a nested structure, put that structure in array $a
|
||||
switch (true) {
|
||||
case empty($v):
|
||||
case true === $v:
|
||||
case null === $v:
|
||||
case \is_bool($v):
|
||||
case \is_int($v):
|
||||
case \is_float($v):
|
||||
continue 2;
|
||||
|
||||
case \is_string($v):
|
||||
if ('' === $v) {
|
||||
continue 2;
|
||||
}
|
||||
if (!\preg_match('//u', $v)) {
|
||||
$stub = new Stub();
|
||||
$stub->type = Stub::TYPE_STRING;
|
||||
@ -131,6 +134,9 @@ class VarCloner extends AbstractCloner
|
||||
break;
|
||||
|
||||
case \is_array($v):
|
||||
if (!$v) {
|
||||
continue 2;
|
||||
}
|
||||
$stub = $arrayStub;
|
||||
$stub->class = Stub::ARRAY_INDEXED;
|
||||
|
||||
|
Reference in New Issue
Block a user