From 8fcbc5572b0a20fc3ef4980e49c499d27b10eda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 12 Oct 2017 09:04:00 +0200 Subject: [PATCH 01/11] [Console] Sync ConsoleLogger::interpolate with the one in HttpKernel --- .../Console/Logger/ConsoleLogger.php | 20 +++++++++++++------ .../Tests/Logger/ConsoleLoggerTest.php | 4 +--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Console/Logger/ConsoleLogger.php b/src/Symfony/Component/Console/Logger/ConsoleLogger.php index d7bcdfad00..ee2e185754 100644 --- a/src/Symfony/Component/Console/Logger/ConsoleLogger.php +++ b/src/Symfony/Component/Console/Logger/ConsoleLogger.php @@ -121,15 +121,23 @@ class ConsoleLogger extends AbstractLogger */ private function interpolate($message, array $context) { - // build a replacement array with braces around the context keys - $replace = array(); + if (false === strpos($message, '{')) { + return $message; + } + + $replacements = array(); foreach ($context as $key => $val) { - if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { - $replace[sprintf('{%s}', $key)] = $val; + if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { + $replacements["{{$key}}"] = $val; + } elseif ($val instanceof \DateTimeInterface) { + $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); + } elseif (\is_object($val)) { + $replacements["{{$key}}"] = '[object '.\get_class($val).']'; + } else { + $replacements["{{$key}}"] = '['.\gettype($val).']'; } } - // interpolate replacement values into the message and return - return strtr($message, $replace); + return strtr($message, $replacements); } } diff --git a/src/Symfony/Component/Console/Tests/Logger/ConsoleLoggerTest.php b/src/Symfony/Component/Console/Tests/Logger/ConsoleLoggerTest.php index 342992982a..734a153e8a 100644 --- a/src/Symfony/Component/Console/Tests/Logger/ConsoleLoggerTest.php +++ b/src/Symfony/Component/Console/Tests/Logger/ConsoleLoggerTest.php @@ -166,9 +166,7 @@ class ConsoleLoggerTest extends TestCase } else { $dummy = $this->getMock('Symfony\Component\Console\Tests\Logger\DummyTest', array('__toString')); } - $dummy->expects($this->once()) - ->method('__toString') - ->will($this->returnValue('DUMMY')); + $dummy->method('__toString')->will($this->returnValue('DUMMY')); $this->getLogger()->warning($dummy); From 16f728117876258a52f664daad156bc11382f9a9 Mon Sep 17 00:00:00 2001 From: endroid Date: Sat, 14 Oct 2017 00:01:11 +0000 Subject: [PATCH 02/11] [Routing] Ensure uniqueness without repeated check --- src/Symfony/Component/Routing/RouteCollection.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index f8b18084be..c6229cfde2 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -128,7 +128,9 @@ class RouteCollection implements \IteratorAggregate, \Countable $this->routes[$name] = $route; } - $this->resources = array_merge($this->resources, $collection->getResources()); + foreach ($collection->getResources() as $resource) { + $this->addResource($resource); + } } /** @@ -262,16 +264,21 @@ class RouteCollection implements \IteratorAggregate, \Countable */ public function getResources() { - return array_unique($this->resources); + return array_values($this->resources); } /** - * Adds a resource for this collection. + * Adds a resource for this collection. If the resource already exists + * it is not added. * * @param ResourceInterface $resource A resource instance */ public function addResource(ResourceInterface $resource) { - $this->resources[] = $resource; + $key = (string) $resource; + + if (!isset($this->resources[$key])) { + $this->resources[$key] = $resource; + } } } From 347939c9b3202ec55fcfa545bfdefd18e414789f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 10 Oct 2017 11:13:08 +0200 Subject: [PATCH 03/11] [HttpFoundation] Make sessions secure and lazy --- UPGRADE-3.4.md | 13 +- UPGRADE-4.0.md | 11 +- composer.json | 2 +- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 7 +- .../FrameworkExtension.php | 9 +- .../Resources/config/session.xml | 12 +- .../DependencyInjection/ConfigurationTest.php | 1 + .../Component/HttpFoundation/CHANGELOG.md | 5 +- .../Handler/AbstractSessionHandler.php | 165 +++++++++++++++ .../Handler/MemcachedSessionHandler.php | 26 +-- .../Storage/Handler/MongoDbSessionHandler.php | 45 +++-- .../Storage/Handler/NativeSessionHandler.php | 6 +- .../Storage/Handler/NullSessionHandler.php | 46 +++-- .../Storage/Handler/PdoSessionHandler.php | 44 +++- .../Storage/Handler/StrictSessionHandler.php | 89 +++++++++ .../Handler/WriteCheckSessionHandler.php | 4 + .../Session/Storage/NativeSessionStorage.php | 44 ++-- .../Session/Storage/Proxy/AbstractProxy.php | 4 - .../Storage/Proxy/SessionHandlerProxy.php | 4 - .../Handler/AbstractSessionHandlerTest.php | 61 ++++++ .../Storage/Handler/Fixtures/common.inc | 152 ++++++++++++++ .../Handler/Fixtures/empty_destroys.expected | 17 ++ .../Handler/Fixtures/empty_destroys.php | 8 + .../Handler/Fixtures/read_only.expected | 14 ++ .../Storage/Handler/Fixtures/read_only.php | 8 + .../Handler/Fixtures/regenerate.expected | 24 +++ .../Storage/Handler/Fixtures/regenerate.php | 10 + .../Storage/Handler/Fixtures/storage.expected | 20 ++ .../Storage/Handler/Fixtures/storage.php | 24 +++ .../Handler/Fixtures/with_cookie.expected | 15 ++ .../Storage/Handler/Fixtures/with_cookie.php | 8 + .../Handler/NativeSessionHandlerTest.php | 3 + .../Storage/Handler/PdoSessionHandlerTest.php | 3 + .../Handler/StrictSessionHandlerTest.php | 189 ++++++++++++++++++ .../Handler/WriteCheckSessionHandlerTest.php | 2 + .../Storage/NativeSessionStorageTest.php | 8 +- .../Storage/Proxy/AbstractProxyTest.php | 2 - .../Storage/Proxy/SessionHandlerProxyTest.php | 1 - .../Component/HttpFoundation/composer.json | 3 +- .../NativeSessionTokenStorage.php | 12 +- 41 files changed, 1001 insertions(+), 121 deletions(-) create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php create mode 100644 src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.expected create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.expected create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.expected create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php create mode 100644 src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php diff --git a/UPGRADE-3.4.md b/UPGRADE-3.4.md index 5ef2448bf2..1b9a40c452 100644 --- a/UPGRADE-3.4.md +++ b/UPGRADE-3.4.md @@ -126,6 +126,8 @@ Form FrameworkBundle --------------- + * The `session.use_strict_mode` option has been deprecated and is enabled by default. + * The `cache:clear` command doesn't clear "app" PSR-6 cache pools anymore, but still clears "system" ones. Use the `cache:pool:clear` command to clear "app" pools instead. @@ -235,18 +237,13 @@ HttpFoundation * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler` class has been deprecated and will be removed in 4.0. Use the `\SessionHandler` class instead. - * The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy` class has been - deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly. + * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler` class has been + deprecated and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or + extend `AbstractSessionHandler` instead. * The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` class has been deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly. - * The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy` class has been - deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly. - - * `NativeSessionStorage::setSaveHandler()` now takes an instance of `\SessionHandlerInterface` as argument. - Not passing it is deprecated and will throw a `TypeError` in 4.0. - * Using `Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler` with the legacy mongo extension has been deprecated and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead. diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index b2854c5925..77959fa48b 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -329,6 +329,8 @@ Form FrameworkBundle --------------- + * The `session.use_strict_mode` option has been removed and strict mode is always enabled. + * The `validator.mapping.cache.doctrine.apc` service has been removed. * The "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies" parameter have been removed. Use the `Request::setTrustedProxies()` method in your front controller instead. @@ -542,12 +544,11 @@ HttpFoundation * The ability to check only for cacheable HTTP methods using `Request::isMethodSafe()` is not supported anymore, use `Request::isMethodCacheable()` instead. - * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler`, - `Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy`, - `Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` and - `Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy` classes have been removed. + * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler` class has been + removed. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead. - * `NativeSessionStorage::setSaveHandler()` now requires an instance of `\SessionHandlerInterface` as argument. + * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler` and + `Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` classes have been removed. * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler` does not work with the legacy mongo extension anymore. It requires mongodb/mongodb package and ext-mongodb. diff --git a/composer.json b/composer.json index 3d96b012ce..5b8e7985aa 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php56": "~1.0", - "symfony/polyfill-php70": "~1.0", + "symfony/polyfill-php70": "~1.6", "symfony/polyfill-util": "~1.0" }, "replace": { diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 49fd03165a..db3d480b3e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 3.4.0 ----- + * Session `use_strict_mode` is now enabled by default and the corresponding option has been deprecated * Made the `cache:clear` command to *not* clear "app" PSR-6 cache pools anymore, but to still clear "system" ones; use the `cache:pool:clear` command to clear "app" pools instead * Always register a minimalist logger that writes in `stderr` diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 4d5af046cb..14c61e1395 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -462,11 +462,14 @@ class Configuration implements ConfigurationInterface ->scalarNode('gc_divisor')->end() ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() - ->booleanNode('use_strict_mode')->end() + ->booleanNode('use_strict_mode') + ->defaultTrue() + ->setDeprecated('The "%path%.%node%" option is enabled by default and deprecated since Symfony 3.4. It will be always enabled in 4.0.') + ->end() ->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end() ->integerNode('metadata_update_threshold') ->defaultValue('0') - ->info('seconds to wait between 2 session metadata updates, it will also prevent the session handler to write if the session has not changed') + ->info('seconds to wait between 2 session metadata updates') ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 568bb0913e..5eb3c24dff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -916,14 +916,7 @@ class FrameworkExtension extends Extension $container->getDefinition('session.storage.native')->replaceArgument(1, null); $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null); } else { - $handlerId = $config['handler_id']; - - if ($config['metadata_update_threshold'] > 0) { - $container->getDefinition('session.handler.write_check')->addArgument(new Reference($handlerId)); - $handlerId = 'session.handler.write_check'; - } - - $container->setAlias('session.handler', $handlerId)->setPrivate(true); + $container->setAlias('session.handler', $config['handler_id'])->setPrivate(true); } $container->setParameter('session.save_path', $config['save_path']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index 335c14a8a6..ce7a3dd7ee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -48,11 +48,17 @@ - - %session.save_path% + + + + %session.save_path% + + - + + The "%service_id%" service is deprecated since Symfony 3.4 and will be removed in 4.0. Use the `session.lazy_write` ini setting instead. + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index e6e83d40b5..80f54afd20 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -301,6 +301,7 @@ class ConfigurationTest extends TestCase 'gc_probability' => 1, 'save_path' => '%kernel.cache_dir%/sessions', 'metadata_update_threshold' => '0', + 'use_strict_mode' => true, ), 'request' => array( 'enabled' => false, diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 83a3c8e32e..ee5b6cecf2 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -4,8 +4,9 @@ CHANGELOG 3.4.0 ----- - * deprecated the `NativeSessionHandler` class, - * deprecated the `AbstractProxy`, `NativeProxy` and `SessionHandlerProxy` classes, + * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new + `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper + * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()` * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php new file mode 100644 index 0000000000..c20a23b20e --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * This abstract session handler provides a generic implementation + * of the PHP 7.0 SessionUpdateTimestampHandlerInterface, + * enabling strict and lazy session handling. + * + * @author Nicolas Grekas + */ +abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $sessionName; + private $prefetchId; + private $prefetchData; + private $newSessionId; + private $igbinaryEmptyData; + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + $this->sessionName = $sessionName; + + return true; + } + + /** + * @param string $sessionId + * + * @return string + */ + abstract protected function doRead($sessionId); + + /** + * @param string $sessionId + * @param string $data + * + * @return bool + */ + abstract protected function doWrite($sessionId, $data); + + /** + * @param string $sessionId + * + * @return bool + */ + abstract protected function doDestroy($sessionId); + + /** + * {@inheritdoc} + */ + public function validateId($sessionId) + { + $this->prefetchData = $this->read($sessionId); + $this->prefetchId = $sessionId; + + return '' !== $this->prefetchData; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + if (null !== $this->prefetchId) { + $prefetchId = $this->prefetchId; + $prefetchData = $this->prefetchData; + $this->prefetchId = $this->prefetchData = null; + + if ($prefetchId === $sessionId || '' === $prefetchData) { + $this->newSessionId = '' === $prefetchData ? $sessionId : null; + + return $prefetchData; + } + } + + $data = $this->doRead($sessionId); + $this->newSessionId = '' === $data ? $sessionId : null; + if (\PHP_VERSION_ID < 70000) { + $this->prefetchData = $data; + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + if (\PHP_VERSION_ID < 70000 && $this->prefetchData) { + $readData = $this->prefetchData; + $this->prefetchData = null; + + if ($readData === $data) { + return $this->updateTimestamp($sessionId, $data); + } + } + if (null === $this->igbinaryEmptyData) { + // see https://github.com/igbinary/igbinary/issues/146 + $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize(array()) : ''; + } + if ('' === $data || $this->igbinaryEmptyData === $data) { + return $this->destroy($sessionId); + } + $this->newSessionId = null; + + return $this->doWrite($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + if (\PHP_VERSION_ID < 70000) { + $this->prefetchData = null; + } + if (!headers_sent() && ini_get('session.use_cookies')) { + if (!$this->sessionName) { + throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', get_class($this))); + } + $sessionCookie = sprintf(' %s=', urlencode($this->sessionName)); + $sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId)); + $sessionCookieFound = false; + $otherCookies = array(); + foreach (headers_list() as $h) { + if (0 !== stripos($h, 'Set-Cookie:')) { + continue; + } + if (11 === strpos($h, $sessionCookie, 11)) { + $sessionCookieFound = true; + + if (11 !== strpos($h, $sessionCookieWithId, 11)) { + $otherCookies[] = $h; + } + } else { + $otherCookies[] = $h; + } + } + if ($sessionCookieFound) { + header_remove('Set-Cookie'); + foreach ($otherCookies as $h) { + header('Set-Cookie:'.$h, false); + } + } else { + setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly')); + } + } + + return $this->newSessionId === $sessionId || $this->doDestroy($sessionId); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php index 3bbde5420d..a31642cc83 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; * * @author Drak */ -class MemcachedSessionHandler implements \SessionHandlerInterface +class MemcachedSessionHandler extends AbstractSessionHandler { /** * @var \Memcached Memcached driver @@ -39,7 +39,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface /** * List of available options: * * prefix: The prefix to use for the memcached keys in order to avoid collision - * * expiretime: The time to live in seconds + * * expiretime: The time to live in seconds. * * @param \Memcached $memcached A \Memcached instance * @param array $options An associative array of Memcached options @@ -60,14 +60,6 @@ class MemcachedSessionHandler implements \SessionHandlerInterface $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; } - /** - * {@inheritdoc} - */ - public function open($savePath, $sessionName) - { - return true; - } - /** * {@inheritdoc} */ @@ -79,7 +71,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function read($sessionId) + protected function doRead($sessionId) { return $this->memcached->get($this->prefix.$sessionId) ?: ''; } @@ -87,7 +79,15 @@ class MemcachedSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function write($sessionId, $data) + public function updateTimestamp($sessionId, $data) + { + return $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl); + } + + /** + * {@inheritdoc} + */ + protected function doWrite($sessionId, $data) { return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl); } @@ -95,7 +95,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function destroy($sessionId) + protected function doDestroy($sessionId) { $result = $this->memcached->delete($this->prefix.$sessionId); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index d532fb92c6..7d770421c5 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; * @see https://packagist.org/packages/mongodb/mongodb * @see http://php.net/manual/en/set.mongodb.php */ -class MongoDbSessionHandler implements \SessionHandlerInterface +class MongoDbSessionHandler extends AbstractSessionHandler { /** * @var \Mongo|\MongoClient|\MongoDB\Client @@ -43,7 +43,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface * * id_field: The field name for storing the session id [default: _id] * * data_field: The field name for storing the session data [default: data] * * time_field: The field name for storing the timestamp [default: time] - * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]. * * It is strongly recommended to put an index on the `expiry_field` for * garbage-collection. Alternatively it's possible to automatically expire @@ -92,14 +92,6 @@ class MongoDbSessionHandler implements \SessionHandlerInterface ), $options); } - /** - * {@inheritdoc} - */ - public function open($savePath, $sessionName) - { - return true; - } - /** * {@inheritdoc} */ @@ -111,7 +103,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function destroy($sessionId) + protected function doDestroy($sessionId) { $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; @@ -139,7 +131,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function write($sessionId, $data) + protected function doWrite($sessionId, $data) { $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); @@ -171,7 +163,34 @@ class MongoDbSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function read($sessionId) + public function updateTimestamp($sessionId, $data) + { + $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); + + if ($this->mongo instanceof \MongoDB\Client) { + $methodName = 'updateOne'; + $options = array(); + } else { + $methodName = 'update'; + $options = array('multiple' => false); + } + + $this->getCollection()->$methodName( + array($this->options['id_field'] => $sessionId), + array('$set' => array( + $this->options['time_field'] => $this->createDateTime(), + $this->options['expiry_field'] => $expiry, + )), + $options + ); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doRead($sessionId) { $dbData = $this->getCollection()->findOne(array( $this->options['id_field'] => $sessionId, diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php index daa7dbd15b..9ea4629ca1 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php @@ -11,12 +11,14 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; -@trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED); - /** * @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead. * @see http://php.net/sessionhandler */ class NativeSessionHandler extends \SessionHandler { + public function __construct() + { + @trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED); + } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php index 981d96d93a..8d193155b0 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php @@ -16,16 +16,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; * * @author Drak */ -class NullSessionHandler implements \SessionHandlerInterface +class NullSessionHandler extends AbstractSessionHandler { - /** - * {@inheritdoc} - */ - public function open($savePath, $sessionName) - { - return true; - } - /** * {@inheritdoc} */ @@ -37,15 +29,7 @@ class NullSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function read($sessionId) - { - return ''; - } - - /** - * {@inheritdoc} - */ - public function write($sessionId, $data) + public function validateId($sessionId) { return true; } @@ -53,7 +37,31 @@ class NullSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function destroy($sessionId) + protected function doRead($sessionId) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function updateTimestamp($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy($sessionId) { return true; } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 5cdac63939..19bf6e9bca 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -38,7 +38,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; * @author Michael Williams * @author Tobias Schultze */ -class PdoSessionHandler implements \SessionHandlerInterface +class PdoSessionHandler extends AbstractSessionHandler { /** * No locking is done. This means sessions are prone to loss of data due to @@ -260,11 +260,13 @@ class PdoSessionHandler implements \SessionHandlerInterface */ public function open($savePath, $sessionName) { + $this->sessionExpired = false; + if (null === $this->pdo) { $this->connect($this->dsn ?: $savePath); } - return true; + return parent::open($savePath, $sessionName); } /** @@ -273,7 +275,7 @@ class PdoSessionHandler implements \SessionHandlerInterface public function read($sessionId) { try { - return $this->doRead($sessionId); + return parent::read($sessionId); } catch (\PDOException $e) { $this->rollback(); @@ -296,7 +298,7 @@ class PdoSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function destroy($sessionId) + protected function doDestroy($sessionId) { // delete the record associated with this id $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; @@ -317,7 +319,7 @@ class PdoSessionHandler implements \SessionHandlerInterface /** * {@inheritdoc} */ - public function write($sessionId, $data) + protected function doWrite($sessionId, $data) { $maxlifetime = (int) ini_get('session.gc_maxlifetime'); @@ -372,6 +374,30 @@ class PdoSessionHandler implements \SessionHandlerInterface return true; } + /** + * {@inheritdoc} + */ + public function updateTimestamp($sessionId, $data) + { + $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + + try { + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + /** * {@inheritdoc} */ @@ -491,10 +517,8 @@ class PdoSessionHandler implements \SessionHandlerInterface * * @return string The session data */ - private function doRead($sessionId) + protected function doRead($sessionId) { - $this->sessionExpired = false; - if (self::LOCK_ADVISORY === $this->lockMode) { $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); } @@ -517,7 +541,9 @@ class PdoSessionHandler implements \SessionHandlerInterface return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; } - if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + if (!ini_get('session.use_strict_mode') && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // In strict mode, session fixation is not possible: new sessions always start with a unique + // random id, so that concurrency is not possible and this code path can be skipped. // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block // until other connections to the session are committed. try { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php new file mode 100644 index 0000000000..1bad0641e8 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`. + * + * @author Nicolas Grekas + */ +class StrictSessionHandler extends AbstractSessionHandler +{ + private $handler; + + public function __construct(\SessionHandlerInterface $handler) + { + if ($handler instanceof \SessionUpdateTimestampHandlerInterface) { + throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_class($handler), self::class)); + } + + $this->handler = $handler; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + parent::open($savePath, $sessionName); + + return $this->handler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + protected function doRead($sessionId) + { + return $this->handler->read($sessionId); + } + + /** + * {@inheritdoc} + */ + public function updateTimestamp($sessionId, $data) + { + return $this->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + protected function doWrite($sessionId, $data) + { + return $this->handler->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + protected function doDestroy($sessionId) + { + return $this->handler->destroy($sessionId); + } + + /** + * {@inheritdoc} + */ + public function close() + { + return $this->handler->close(); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php index d49c36cae5..638a633076 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php @@ -11,10 +11,14 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; +@trigger_error(sprintf('The %s class is deprecated since version 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', WriteCheckSessionHandler::class), E_USER_DEPRECATED); + /** * Wraps another SessionHandlerInterface to only write the session when it has been modified. * * @author Adrien Brault + * + * @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead. */ class WriteCheckSessionHandler implements \SessionHandlerInterface { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index 623d38721b..092d21830d 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -11,8 +11,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; -use Symfony\Component\Debug\Exception\ContextErrorException; use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; @@ -26,7 +26,7 @@ class NativeSessionStorage implements SessionStorageInterface /** * @var SessionBagInterface[] */ - protected $bags; + protected $bags = array(); /** * @var bool @@ -100,9 +100,9 @@ class NativeSessionStorage implements SessionStorageInterface public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null) { $options += array( - // disable by default because it's managed by HeaderBag (if used) - 'cache_limiter' => '', + 'cache_limiter' => 'private_no_expire', 'use_cookies' => 1, + 'lazy_write' => 1, ); session_register_shutdown(); @@ -217,15 +217,31 @@ class NativeSessionStorage implements SessionStorageInterface */ public function save() { + $session = $_SESSION; + + foreach ($this->bags as $bag) { + if (empty($_SESSION[$key = $bag->getStorageKey()])) { + unset($_SESSION[$key]); + } + } + if (array($key = $this->metadataBag->getStorageKey()) === array_keys($_SESSION)) { + unset($_SESSION[$key]); + } + // Register custom error handler to catch a possible failure warning during session write - set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext) { - throw new ContextErrorException($errstr, $errno, E_WARNING, $errfile, $errline, $errcontext); + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline); }, E_WARNING); try { + $e = null; session_write_close(); + } catch (\ErrorException $e) { + } finally { restore_error_handler(); - } catch (ContextErrorException $e) { + $_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(); @@ -233,7 +249,6 @@ class NativeSessionStorage implements SessionStorageInterface $handler = $handler->getHandler(); } - restore_error_handler(); trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING); } @@ -386,13 +401,6 @@ class NativeSessionStorage implements SessionStorageInterface throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.'); } - if ($saveHandler instanceof AbstractProxy) { - @trigger_error( - 'Using session save handlers that are instances of AbstractProxy is deprecated since version 3.4 and will be removed in 4.0.', - E_USER_DEPRECATED - ); - } - if (headers_sent($file, $line)) { throw new \RuntimeException(sprintf('Failed to set the session handler because headers have already been sent by "%s" at line %d.', $file, $line)); } @@ -401,11 +409,13 @@ class NativeSessionStorage implements SessionStorageInterface if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { $saveHandler = new SessionHandlerProxy($saveHandler); } elseif (!$saveHandler instanceof AbstractProxy) { - $saveHandler = new SessionHandlerProxy(new \SessionHandler()); + $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler())); } $this->saveHandler = $saveHandler; - if ($this->saveHandler instanceof \SessionHandlerInterface) { + if ($this->saveHandler instanceof SessionHandlerProxy) { + session_set_save_handler($this->saveHandler->getHandler(), false); + } elseif ($this->saveHandler instanceof \SessionHandlerInterface) { session_set_save_handler($this->saveHandler, false); } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php index c1c8b9b1f7..09c92483c7 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php @@ -11,11 +11,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; -@trigger_error('The '.__NAMESPACE__.'\AbstractProxy class is deprecated since version 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED); - /** - * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly. - * * @author Drak */ abstract class AbstractProxy diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php index d6adef82db..359bb877b5 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -11,11 +11,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; -@trigger_error('The '.__NAMESPACE__.'\SessionHandlerProxy class is deprecated since version 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED); - /** - * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly. - * * @author Drak */ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php new file mode 100644 index 0000000000..3ac081e388 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; + +/** + * @requires PHP 7.0 + */ +class AbstractSessionHandlerTest extends TestCase +{ + private static $server; + + public static function setUpBeforeClass() + { + $spec = array( + 1 => array('file', '/dev/null', 'w'), + 2 => array('file', '/dev/null', 'w'), + ); + if (!self::$server = @proc_open('exec php -S localhost:8053', $spec, $pipes, __DIR__.'/Fixtures')) { + self::markTestSkipped('PHP server unable to start.'); + } + sleep(1); + } + + public static function tearDownAfterClass() + { + if (self::$server) { + proc_terminate(self::$server); + proc_close(self::$server); + } + } + + /** + * @dataProvider provideSession + */ + public function testSession($fixture) + { + $context = array('http' => array('header' => "Cookie: sid=123abc\r\n")); + $context = stream_context_create($context); + $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context); + + $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result); + } + + public function provideSession() + { + foreach (glob(__DIR__.'/Fixtures/*.php') as $file) { + yield array(pathinfo($file, PATHINFO_FILENAME)); + } + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc new file mode 100644 index 0000000000..5c183acfff --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc @@ -0,0 +1,152 @@ +data = $data; + } + + public function open($path, $name) + { + echo __FUNCTION__, "\n"; + + return parent::open($path, $name); + } + + public function validateId($sessionId) + { + echo __FUNCTION__, "\n"; + + return parent::validateId($sessionId); + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + echo __FUNCTION__, "\n"; + + return parent::read($sessionId); + } + + /** + * {@inheritdoc} + */ + public function updateTimestamp($sessionId, $data) + { + echo __FUNCTION__, "\n"; + + return true; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + echo __FUNCTION__, "\n"; + + return parent::write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + echo __FUNCTION__, "\n"; + + return parent::destroy($sessionId); + } + + public function close() + { + echo __FUNCTION__, "\n"; + + return true; + } + + public function gc($maxLifetime) + { + echo __FUNCTION__, "\n"; + + return true; + } + + protected function doRead($sessionId) + { + echo __FUNCTION__.': ', $this->data, "\n"; + + return $this->data; + } + + protected function doWrite($sessionId, $data) + { + echo __FUNCTION__.': ', $data, "\n"; + + return true; + } + + protected function doDestroy($sessionId) + { + echo __FUNCTION__, "\n"; + + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected new file mode 100644 index 0000000000..1720bf0558 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected @@ -0,0 +1,17 @@ +open +validateId +read +doRead: abc|i:123; +read + +write +destroy +doDestroy +close +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 + [2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php new file mode 100644 index 0000000000..3cfc1250ad --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php @@ -0,0 +1,8 @@ + Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php new file mode 100644 index 0000000000..3e62fb9ecb --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php @@ -0,0 +1,8 @@ + Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 + [2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php new file mode 100644 index 0000000000..a0f635c871 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php @@ -0,0 +1,10 @@ + bar +) +$_SESSION is not empty +write +destroy +close +$_SESSION is not empty +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php new file mode 100644 index 0000000000..96dca3c2c0 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php @@ -0,0 +1,24 @@ +setSaveHandler(new TestSessionHandler()); +$flash = new FlashBag(); +$storage->registerBag($flash); +$storage->start(); + +$flash->add('foo', 'bar'); + +print_r($flash->get('foo')); +echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty'; +echo "\n"; + +$storage->save(); + +echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty'; + +ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); }); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected new file mode 100644 index 0000000000..47ae4da824 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected @@ -0,0 +1,15 @@ +open +validateId +read +doRead: abc|i:123; +read + +updateTimestamp +close +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 + [2] => Set-Cookie: abc=def +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php new file mode 100644 index 0000000000..ffb5b20a37 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php @@ -0,0 +1,8 @@ +markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); } + if (ini_get('session.use_strict_mode')) { + $this->markTestSkipped('Strict mode needs no locking for new sessions.'); + } $pdo = new MockPdo('pgsql'); $selectStmt = $this->getMockBuilder('PDOStatement')->getMock(); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php new file mode 100644 index 0000000000..9d2c1949f3 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; + +class StrictSessionHandlerTest extends TestCase +{ + public function testOpen() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('open') + ->with('path', 'name')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertInstanceof('SessionUpdateTimestampHandlerInterface', $proxy); + $this->assertInstanceof(AbstractSessionHandler::class, $proxy); + $this->assertTrue($proxy->open('path', 'name')); + } + + public function testCloseSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('close') + ->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->close()); + } + + public function testValidateIdOK() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn('data'); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->validateId('id')); + } + + public function testValidateIdKO() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn(''); + $proxy = new StrictSessionHandler($handler); + + $this->assertFalse($proxy->validateId('id')); + } + + public function testRead() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn('data'); + $proxy = new StrictSessionHandler($handler); + + $this->assertSame('data', $proxy->read('id')); + } + + public function testReadWithValidateIdOK() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn('data'); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->validateId('id')); + $this->assertSame('data', $proxy->read('id')); + } + + public function testReadWithValidateIdMismatch() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->exactly(2))->method('read') + ->withConsecutive(array('id1'), array('id2')) + ->will($this->onConsecutiveCalls('data1', 'data2')); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->validateId('id1')); + $this->assertSame('data2', $proxy->read('id2')); + } + + public function testUpdateTimestamp() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('write') + ->with('id', 'data')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->updateTimestamp('id', 'data')); + } + + public function testWrite() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('write') + ->with('id', 'data')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->write('id', 'data')); + } + + public function testWriteEmptyNewSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn(''); + $handler->expects($this->never())->method('write'); + $handler->expects($this->never())->method('destroy'); + $proxy = new StrictSessionHandler($handler); + + $this->assertFalse($proxy->validateId('id')); + $this->assertSame('', $proxy->read('id')); + $this->assertTrue($proxy->write('id', '')); + } + + public function testWriteEmptyExistingSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn('data'); + $handler->expects($this->never())->method('write'); + $handler->expects($this->once())->method('destroy')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertSame('data', $proxy->read('id')); + $this->assertTrue($proxy->write('id', '')); + } + + public function testDestroy() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('destroy') + ->with('id')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->destroy('id')); + } + + public function testDestroyNewSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn(''); + $handler->expects($this->never())->method('destroy'); + $proxy = new StrictSessionHandler($handler); + + $this->assertSame('', $proxy->read('id')); + $this->assertTrue($proxy->destroy('id')); + } + + public function testDestroyNonEmptyNewSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn(''); + $handler->expects($this->once())->method('write') + ->with('id', 'data')->willReturn(true); + $handler->expects($this->once())->method('destroy') + ->with('id')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertSame('', $proxy->read('id')); + $this->assertTrue($proxy->write('id', 'data')); + $this->assertTrue($proxy->destroy('id')); + } + + public function testGc() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('gc') + ->with(123)->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->gc(123)); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php index 5e41a4743e..898a7d11a5 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php @@ -16,6 +16,8 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHa /** * @author Adrien Brault + * + * @group legacy */ class WriteCheckSessionHandlerTest extends TestCase { diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php index 818c63a9d2..93de175fd3 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -14,7 +14,7 @@ namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; -use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; @@ -152,7 +152,7 @@ class NativeSessionStorageTest extends TestCase $this->iniSet('session.cache_limiter', 'nocache'); $storage = new NativeSessionStorage(); - $this->assertEquals('', ini_get('session.cache_limiter')); + $this->assertEquals('private_no_expire', ini_get('session.cache_limiter')); } public function testExplicitSessionCacheLimiter() @@ -201,9 +201,9 @@ class NativeSessionStorageTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); $storage->setSaveHandler(null); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); - $storage->setSaveHandler(new SessionHandlerProxy(new NativeSessionHandler())); + $storage->setSaveHandler(new SessionHandlerProxy(new NativeFileSessionHandler())); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); - $storage->setSaveHandler(new NativeSessionHandler()); + $storage->setSaveHandler(new NativeFileSessionHandler()); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); $storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler())); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php index f2be722c8f..cbb291f19f 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php @@ -18,8 +18,6 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; /** * Test class for AbstractProxy. * - * @group legacy - * * @author Drak */ class AbstractProxyTest extends TestCase diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php index fdd1dae254..682825356a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -21,7 +21,6 @@ use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; * * @runTestsInSeparateProcesses * @preserveGlobalState disabled - * @group legacy */ class SessionHandlerProxyTest extends TestCase { diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index 4f13511e9a..f6c6f2e623 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -17,7 +17,8 @@ ], "require": { "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-mbstring": "~1.1" + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" }, "require-dev": { "symfony/expression-language": "~2.8|~3.0|~4.0" diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php index 5ce2774114..d441ba6ed3 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php @@ -97,12 +97,18 @@ class NativeSessionTokenStorage implements TokenStorageInterface $this->startSession(); } - $token = isset($_SESSION[$this->namespace][$tokenId]) - ? (string) $_SESSION[$this->namespace][$tokenId] - : null; + if (!isset($_SESSION[$this->namespace][$tokenId])) { + return; + } + + $token = (string) $_SESSION[$this->namespace][$tokenId]; unset($_SESSION[$this->namespace][$tokenId]); + if (!$_SESSION[$this->namespace]) { + unset($_SESSION[$this->namespace]); + } + return $token; } From 41df5123497be20155b615e44edbbb08c4f14129 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 16 Oct 2017 20:25:51 -0400 Subject: [PATCH 04/11] Adding a new debug:autowiring command --- .../Command/DebugAutowiringCommand.php | 97 +++++++++++++++++++ .../Resources/config/console.xml | 4 + .../Functional/DebugAutowiringCommandTest.php | 63 ++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php new file mode 100644 index 0000000000..23d688495d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Command/DebugAutowiringCommand.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * A console command for autowiring information. + * + * @author Ryan Weaver + * + * @internal + */ +class DebugAutowiringCommand extends ContainerDebugCommand +{ + protected static $defaultName = 'debug:autowiring'; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition(array( + new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'), + )) + ->setDescription('Lists classes/interfaces you can use for autowiring') + ->setHelp(<<<'EOF' +The %command.name% command displays all classes and interfaces that +you can use as type-hints for autowiring: + + php %command.full_name% + +You can also pass a search term to filter the list: + + php %command.full_name% log + +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $errorIo = $io->getErrorStyle(); + + $builder = $this->getContainerBuilder(); + $serviceIds = $builder->getServiceIds(); + $serviceIds = array_filter($serviceIds, array($this, 'filterToServiceTypes')); + + if ($search = $input->getArgument('search')) { + $serviceIds = array_filter($serviceIds, function ($serviceId) use ($search) { + return false !== stripos($serviceId, $search); + }); + + if (empty($serviceIds)) { + $errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search)); + + return 1; + } + } + + asort($serviceIds); + + $io->title('Autowirable Services'); + $io->text('The following classes & interfaces can be used as type-hints when autowiring:'); + if ($search) { + $io->text(sprintf('(only showing classes/interfaces matching %s)', $search)); + } + $io->newLine(); + $tableRows = array(); + foreach ($serviceIds as $serviceId) { + $tableRows[] = array(sprintf('%s', $serviceId)); + if ($builder->hasAlias($serviceId)) { + $tableRows[] = array(sprintf(' alias to %s', $builder->getAlias($serviceId))); + } + } + + $io->table(array(), $tableRows); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 7dc4ea6d2f..cebf6014f0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -55,6 +55,10 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php new file mode 100644 index 0000000000..0b7f9290a5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/DebugAutowiringCommandTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Tester\ApplicationTester; + +/** + * @group functional + */ +class DebugAutowiringCommandTest extends WebTestCase +{ + public function testBasicFunctionality() + { + static::bootKernel(array('test_case' => 'ContainerDebug', 'root_config' => 'config.yml')); + + $application = new Application(static::$kernel); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'debug:autowiring')); + + $this->assertContains('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay()); + $this->assertContains('alias to http_kernel', $tester->getDisplay()); + } + + public function testSearchArgument() + { + static::bootKernel(array('test_case' => 'ContainerDebug', 'root_config' => 'config.yml')); + + $application = new Application(static::$kernel); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'debug:autowiring', 'search' => 'kern')); + + $this->assertContains('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay()); + $this->assertNotContains('Symfony\Component\Routing\RouterInterface', $tester->getDisplay()); + } + + public function testSearchNoResults() + { + static::bootKernel(array('test_case' => 'ContainerDebug', 'root_config' => 'config.yml')); + + $application = new Application(static::$kernel); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'debug:autowiring', 'search' => 'foo_fake'), array('capture_stderr_separately' => true)); + + $this->assertContains('No autowirable classes or interfaces found matching "foo_fake"', $tester->getErrorOutput()); + $this->assertEquals(1, $tester->getStatusCode()); + } +} From 454f65a77d707cc31c08d1417f27247b23bd0e89 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 17 Oct 2017 00:33:38 -0400 Subject: [PATCH 05/11] adding AdapterInterface alias for cache.app --- src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index b4d23a576e..9466d992c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -111,5 +111,6 @@ + From 921a8794c60678c08c10bb5bd51298de50edff96 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 17 Oct 2017 16:03:36 +0200 Subject: [PATCH 06/11] Add extra autowiring aliases This adds autowiring for RequestContextAwareInterface for the routing layer and for AuthenticationManagerInterface in the security layer. --- src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml | 1 + src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 94579d93b5..f515d33c0c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -85,6 +85,7 @@ + %router.request_context.base_url% diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 0225df0ee4..4ebbbb8542 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -52,6 +52,7 @@ + %security.authentication.trust_resolver.anonymous_class% From a1df9af20f50762007a64ca75601bf9f5f85f675 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Wed, 18 Oct 2017 01:29:21 -0400 Subject: [PATCH 07/11] don't bind scalar values to controller method arguments --- .../RegisterControllerArgumentLocatorsPass.php | 5 +++++ ...egisterControllerArgumentLocatorsPassTest.php | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index 6a9f0586f7..985dfb71d7 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -135,6 +135,11 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface $binding = $bindings[$bindingName]; list($bindingValue, $bindingId) = $binding->getValues(); + + if (!$bindingValue instanceof Reference) { + continue; + } + $binding->setValues(array($bindingValue, $bindingId, true)); $args[$p->name] = $bindingValue; diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php index 0e524b92d8..4016deb4ab 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -310,6 +310,22 @@ class RegisterControllerArgumentLocatorsPassTest extends TestCase { return array(array(ControllerDummy::class), array('$bar')); } + + public function testDoNotBindScalarValueToControllerArgument() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', ArgumentWithoutTypeController::class) + ->setBindings(array('$someArg' => '%foo%')) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertEmpty($locator); + } } class RegisterTestController From 6a6256c6a8bb6b5734c7ff491c5be0e291bcae2a Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 18 Oct 2017 11:36:53 +0200 Subject: [PATCH 08/11] Do not process bindings in AbstractRecursivePass --- .../Compiler/AbstractRecursivePass.php | 1 - ...xceptionOnInvalidReferenceBehaviorPassTest.php | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index 765b5752da..cbe9b30468 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -67,7 +67,6 @@ abstract class AbstractRecursivePass implements CompilerPassInterface $value->setArguments($this->processValue($value->getArguments())); $value->setProperties($this->processValue($value->getProperties())); $value->setMethodCalls($this->processValue($value->getMethodCalls())); - $value->setBindings($this->processValue($value->getBindings())); $changes = $value->getChanges(); if (isset($changes['factory'])) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php index dc20b39bc4..ac002d834d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Compiler\CheckExceptionOnInvalidReferenceBehaviorPass; use Symfony\Component\DependencyInjection\Reference; @@ -88,6 +89,20 @@ class CheckExceptionOnInvalidReferenceBehaviorPassTest extends TestCase $this->process($container); } + public function testProcessDefinitionWithBindings() + { + $container = new ContainerBuilder(); + + $container + ->register('b') + ->setBindings(array(new BoundArgument(new Reference('a')))) + ; + + $this->process($container); + + $this->addToAssertionCount(1); + } + private function process(ContainerBuilder $container) { $pass = new CheckExceptionOnInvalidReferenceBehaviorPass(); From a024e80f1cc45731e509d65f0bc5dedfeb015dd2 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 18 Oct 2017 14:45:28 -0700 Subject: [PATCH 09/11] updated CHANGELOG for 3.4.0-BETA1 --- CHANGELOG-3.4.md | 217 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 CHANGELOG-3.4.md diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md new file mode 100644 index 0000000000..2790538d54 --- /dev/null +++ b/CHANGELOG-3.4.md @@ -0,0 +1,217 @@ +CHANGELOG for 3.4.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 3.4 minor versions. + +To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash +To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.4.0...v3.4.1 + +* 3.4.0-BETA1 (2017-10-18) + + * feature #24583 Adding a new debug:autowiring command (weaverryan) + * feature #24523 [HttpFoundation] Make sessions secure and lazy (nicolas-grekas) + * feature #22610 [Form] [TwigBridge] Added option to disable usage of default themes when rendering a form (emodric) + * feature #23112 [OptionsResolver] Support array of types in allowed type (pierredup) + * feature #24321 added ability to handle parent classes for PropertyNormalizer (ivoba) + * feature #24505 [HttpKernel] implement reset() in DumpDataCollector (xabbuh) + * feature #24425 [Console][HttpKernel] Handle new SHELL_VERBOSITY env var, also configures the default logger (nicolas-grekas) + * feature #24387 [FORM] Prevent forms from extending itself as a parent (pierredup) + * feature #24484 [DI] Throw accurate failures when accessing removed services (nicolas-grekas) + * feature #24208 [Form] Display option definition from a given form type (yceruto, ogizanagi) + * feature #23499 [Workflow] add guard is_valid() method support (alain-flaus, lyrixx) + * feature #24388 [Security] Look at headers for switch_user username (chalasr) + * feature #23708 Added deprecation to cwd not existing Fixes #18249 (alexbowers) + * feature #24443 [Session] deprecate MemcacheSessionHandler (Tobion) + * feature #24409 [Bridge\Doctrine][FrameworkBundle] Deprecate some remaining uses of ContainerAwareTrait (nicolas-grekas) + * feature #24438 [Session][VarDumper] Deprecate accepting legacy mongo extension (Tobion) + * feature #24389 [DoctrineBridge] Deprecate dbal session handler (Tobion) + * feature #16835 [Security] Add Guard authenticator method (Amo, chalasr) + * feature #24289 [FrameworkBundle][HttpKernel] Reset profiler (derrabus) + * feature #24144 [FrameworkBundle] Expose dotenv in bin/console about (ro0NL) + * feature #24403 [FrameworkBundle][Routing] Show welcome message if no routes are configured (yceruto) + * feature #22679 [Form] Add tel and color types (apetitpa) + * feature #23845 [Validator] Add unique entity violation cause (Ilya Vertakov) + * feature #22132 [Lock] Automaticaly release lock when user forget it (jderusse) + * feature #21751 Bootstrap4 support for Twig form theme (hiddewie, javiereguiluz) + * feature #24383 [FrameworkBundle] Don't clear app pools on cache:clear (nicolas-grekas) + * feature #24148 [Form] Hide label button when its setted to false (TeLiXj) + * feature #24378 [SecurityBundle] Deprecate auto picking the first provider (ogizanagi) + * feature #24260 [Security] Add impersonation support for stateless authentication (chalasr) + * feature #24300 [HttpKernel][FrameworkBundle] Add a minimalist default PSR-3 logger (dunglas) + * feature #21604 [Security] Argon2i Password Encoder (zanbaldwin) + * feature #24372 [DowCrawler] Default to UTF-8 when possible (nicolas-grekas) + * feature #24264 [TwigBundle] Improve the overriding of bundle templates (yceruto) + * feature #24197 [Translation] Moved PhpExtractor and PhpStringTokenParser to Translation component (Nyholm) + * feature #24362 [HttpKernel] Deprecate some compiler passes in favor of tagged iterator args (nicolas-grekas) + * feature #21027 [Asset] Provide default context (ro0NL) + * feature #22200 [DI] Reference tagged services in config (ro0NL) + * feature #24337 Adding a shortcuts for the main security functionality (weaverryan, javiereguiluz) + * feature #24358 [TwigBundle] register an identity translator as fallback (xabbuh) + * feature #24357 [Yaml] include file and line no in deprecation message (xabbuh) + * feature #24330 [FrameworkBundle] register class metadata factory alias (xabbuh) + * feature #24349 [SecurityBundle] Add missing AclSchemaListener deprecation (ogizanagi) + * feature #24202 [Filesystem] deprecate relative paths in makePathRelative() (xabbuh) + * feature #21716 [Serializer] Add Support for `object_to_populate` in CustomNormalizer (chrisguitarguy) + * feature #21960 Remove Validator\TypeTestCase and add validator logic to base TypeTestCase (pierredup) + * feature #22113 [Lock] Include lock component in framework bundle (jderusse) + * feature #24236 [WebProfilerBundle] Render file links for twig templates (ro0NL) + * feature #21239 [Serializer] throw more specific exceptions (xabbuh) + * feature #24256 CsvEncoder handling variable structures and custom header order (Oliver Hoff) + * feature #23471 [Finder] Add a method to check if any results were found (duncan3dc) + * feature #23149 [PhpUnitBridge] Added a CoverageListener to enhance the code coverage report (lyrixx) + * feature #24318 [SecurityBundle] Deprecate ACL related code (chalasr) + * feature #24335 [Security][SecurityBundle] Deprecate the HTTP digest auth (ogizanagi) + * feature #21951 [Security][Firewall] Passing the newly generated security token to the event during user switching (klandaika) + * feature #23485 [Config] extracted the xml parsing from XmlUtils::loadFile into XmlUtils::parse (Basster) + * feature #22890 [HttpKernel] Add ability to configure catching exceptions for Client (kbond) + * feature #24239 [HttpFoundation] Deprecate compatibility with PHP <5.4 sessions (afurculita) + * feature #23882 [Security] Deprecated not being logged out after user change (iltar) + * feature #24200 Added an alias for FlashBagInterface in config (tifabien) + * feature #24295 [DI][DX] Throw exception on some ContainerBuilder methods used from extensions (ogizanagi) + * feature #24253 [Yaml] support parsing files (xabbuh) + * feature #24290 Adding Definition::addError() and a compiler pass to throw errors as exceptions (weaverryan) + * feature #24301 [DI] Add AutowireRequiredMethodsPass to fix bindings for `@required` methods (nicolas-grekas) + * feature #24226 [Cache] Add ResettableInterface to allow resetting any pool's local state (nicolas-grekas) + * feature #24303 [FrameworkBundle] allow forms without translations and validator (xabbuh) + * feature #24291 [SecurityBundle] Reset the authentication token between requests (derrabus) + * feature #24280 [VarDumper] Make `dump()` a little bit more easier to use (freekmurze) + * feature #24277 [Serializer] Getter for extra attributes in ExtraAttributesException (mdeboer) + * feature #24257 [HttpKernel][DI] Enable Kernel to implement CompilerPassInterface (nicolas-grekas) + * feature #23834 [DI] Add "PHP fluent format" for configuring the container (nicolas-grekas) + * feature #24180 [Routing] Add PHP fluent DSL for configuring routes (nicolas-grekas) + * feature #24232 [Bridge\Doctrine] Add "DoctrineType::reset()" method (nicolas-grekas) + * feature #24238 [DI] Turn services and aliases private by default, with BC layer (nicolas-grekas) + * feature #23648 [Form] Add input + regions options to TimezoneType (ro0NL) + * feature #24185 [Form] Display general forms information on debug:form (yceruto) + * feature #23747 [Serializer][FrameworkBundle] Add a DateInterval normalizer (Lctrs) + * feature #24193 [FrameworkBundle] Reset stopwatch between requests (derrabus) + * feature #24160 [HttpKernel] Deprecate bundle inheritance (fabpot) + * feature #24155 [FrameworkBundle][HttpKernel] Add DI tag for resettable services (derrabus) + * feature #23625 Feature #23583 Add current and fallback locales in WDT / Profiler (nemoneph) + * feature #24179 [TwigBundle] Add default templates directory and option to configure it (yceruto) + * feature #24104 Make as many services private as possible (nicolas-grekas) + * feature #18314 [Translation] added support for adding custom message formatter (aitboudad) + * feature #24158 deprecated profiler.matcher configuration (fabpot) + * feature #24131 [Console] Do not display short exception trace for common console exceptions (yceruto) + * feature #24080 Deprecated the web_profiler.position option (javiereguiluz) + * feature #24114 [SecurityBundle] Throw a meaningful exception when an undefined user provider is used inside a firewall (chalasr) + * feature #24122 [DI] rename ResolveDefinitionTemplatesPass to ResolveChildDefinitionsPass (nicolas-grekas) + * feature #23901 [DI] Allow processing env vars (nicolas-grekas) + * feature #24093 [FrameworkBundle] be able to enable workflow support explicitly (xabbuh) + * feature #24064 [TwigBridge] Show Twig's loader paths on debug:twig command (yceruto) + * feature #23978 [Cache] Use options from Memcached DSN (Bukashk0zzz) + * feature #24075 Implemented PruneableInterface on TagAwareAdapter (Toflar) + * feature #21414 [Console] Display file and line on Exception (arno14) + * feature #24068 [HttpKernel] Deprecate EnvParametersResource (ro0NL) + * feature #22542 [Lock] Check TTL expiration in lock acquisition (jderusse) + * feature #24031 [Routing] Add the possibility to define a prefix for all routes of a controller (fabpot) + * feature #23967 [VarDumper] add force-collapse/expand + use it for traces (nicolas-grekas) + * feature #24033 [DI] Add ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE (nicolas-grekas) + * feature #24026 [Security] add impersonator_user to "User was reloaded" log message (gharlan) + * feature #23603 [Cache] Add (pdo|chain) cache (adapter|simple) prune method (robfrawley) + * feature #23694 [Form] Add debug:form command (yceruto) + * feature #24028 [Yaml] mark some classes as final (xabbuh) + * feature #22543 [Lock] Expose an expiringDate and isExpired method in Lock (jderusse) + * feature #23667 [Translation] Create an TranslationReaderInterface and move TranslationLoader to TranslationComponent (Nyholm) + * feature #24024 [config] Add ability to deprecate a node (sanpii) + * feature #23668 [VarDumper] Add period caster (maidmaid) + * feature #23991 [DI] Improve psr4-based service discovery (alternative implementation) (kbond) + * feature #23947 [Translation] Adding the ability do load in xliff2.0 (Nyholm) + * feature #23887 [Console] Allow commands to provide a default name for compile time registration (chalasr, nicolas-grekas) + * feature #23874 [DI] Case sensitive parameter names (ro0NL) + * feature #23936 Remove some sf2 references (fabpot) + * feature #23680 [Webprofiler] Added blocks that allows extension of the profiler page. (Nyholm) + * feature #23665 Create an interface for TranslationWriter (Nyholm) + * feature #23890 [Translation] Adding the ability do dump in xliff2.0 (Nyholm) + * feature #23862 [SecurityBundle] resolve class name parameter inside AddSecurityVotersPass (pjarmalavicius) + * feature #23915 [DI] Allow get available services from service locator (Koc) + * feature #23792 [HttpKernel][FrameworkBundle] Add RebootableInterface, fix and un-deprecate cache:clear with warmup (nicolas-grekas) + * feature #23227 Add support for "controller" keyword for configuring routes controllers (voronkovich) + * feature #23869 [Console] Made console command shortcuts case insensitive (thanosp) + * feature #23855 [DI] Allow dumping inline services in Yaml (nicolas-grekas) + * feature #23836 [FrameworkBundle] Catch Fatal errors in commands registration (chalasr) + * feature #23805 [HttpKernel] Deprecated commands auto-registration (GuilhemN) + * feature #23816 [Debug] Detect internal and deprecated methods (GuilhemN) + * feature #23812 [FrameworkBundle] Allow micro kernel to subscribe events easily (ogizanagi) + * feature #22187 [DependencyInjection] Support local binding (GuilhemN) + * feature #23741 [DI] Generate one file per service factory (nicolas-grekas) + * feature #23807 [Debug] Trigger a deprecation when using an internal class/trait/interface (GuilhemN) + * feature #22587 [Workflow] Add transition completed event (izzyp) + * feature #23624 [FrameworkBundle] Commands as a service (ro0NL) + * feature #21111 [Validator] add groups support to the Valid constraint (xabbuh) + * feature #20361 [Config] Enable cannotBeEmpty along with requiresAtLeastOneElement (ro0NL) + * feature #23712 [DependencyInjection] Deprecate autowiring service auto-registration (GuilhemN) + * feature #23719 Autoconfigure instances of ArgumentValueResolverInterface (BPScott) + * feature #23706 [Webprofiler] Improve sql explain table display (mimol91) + * feature #23724 [Lock] Deprecate Filesystem/LockHandler (jderusse) + * feature #23593 [Workflow] Adding workflow name to the announce event (Nyholm) + * feature #20496 [Form] Allow pass filter callback to delete_empty option. (Koc) + * feature #23451 [Cache] Add (filesystem|phpfiles) cache (adapter|simple) prune method and prune command (robfrawley) + * feature #23519 [TwigBundle] Commands as a service (ro0NL) + * feature #23591 [VarDumper] Add time zone caster (maidmaid) + * feature #23510 [Console] Add a factory command loader for standalone application with lazy-loading needs (ogizanagi) + * feature #23357 [VarDumper] Add interval caster (maidmaid) + * feature #23550 [DebugBundle] Added min_depth to Configuration (james-johnston-thumbtack) + * feature #23570 [FrameworkBundle] Make RouterCacheWarmer implement ServiceSubscriberInterface (nicolas-grekas) + * feature #23437 [TwigBridge] deprecate TwigRenderer (Tobion) + * feature #23515 [VarDumper] Added setMinDepth to VarCloner (james-johnston-thumbtack) + * feature #23404 [Serializer] AbstractObjectNormalizer: Allow to disable type enforcement (ogizanagi) + * feature #21086 [MonologBridge] Add TokenProcessor (maidmaid) + * feature #22576 [Validator] Allow to use a property path to get value to compare in comparison constraints (ogizanagi) + * feature #22689 [DoctrineBridge] Add support for doctrin/dbal v2.6 types (jvasseur) + * feature #22734 [Console] Add support for command lazy-loading (chalasr) + * feature #19034 [Security] make it possible to configure a custom access decision manager service (xabbuh) + * feature #23037 [TwigBundle] Added a RuntimeExtensionInterface to take advantage of autoconfigure (lyrixx) + * feature #22176 [DI] Allow imports in string format for YAML (ro0NL) + * feature #23295 [Security] Lazy load user providers (chalasr) + * feature #23440 [Routing] Add matched and default parameters to redirect responses (artursvonda, Tobion) + * feature #22832 [Debug] Deprecate support for stacked errors (mbabker) + * feature #21469 [HttpFoundation] Find the original request protocol version (thewilkybarkid) + * feature #23431 [Validator] Add min/max amount of pixels to Image constraint (akeeman) + * feature #23223 Add support for microseconds in Stopwatch (javiereguiluz) + * feature #22341 [BrowserKit] Emulate back/forward browser navigation (e-moe) + * feature #22619 [FrameworkBundle][Translation] Move translation compiler pass (lepiaf) + * feature #22620 [FrameworkBundle][HttpKernel] Move httpkernel pass (lepiaf) + * feature #23337 [Component][Serializer][Normalizer] : Deal it with Has Method for the Normalizer/Denormalizer (jordscream) + * feature #22588 [VarDumper] Add filter in VarDumperTestTrait (maidmaid) + * feature #23288 [Yaml] deprecate the !str tag (xabbuh) + * feature #23039 [Validator] Support for parsing PHP constants in yaml loader (mimol91) + * feature #22431 [VarDumper] Add date caster (maidmaid) + * feature #23285 [Stopwatch] Add a reset method (jmgq) + * feature #23320 [WebServer] Allow * to bind all interfaces (as INADDR_ANY) (jpauli, fabpot) + * feature #23272 [FrameworkBundle] disable unusable fragment renderers (xabbuh) + * feature #23332 [Yaml] fix the displayed line number (fabpot, xabbuh) + * feature #23026 [SecurityBundle] Add user impersonation info and exit action to the profiler (yceruto) + * feature #22932 [HttpFoundation] Adds support for the immutable directive in the cache-control header (twoleds) + * feature #22554 [Profiler][Validator] Add a validator panel in profiler (ogizanagi) + * feature #22124 Shift responsibility for keeping Date header to ResponseHeaderBag (mpdude) + * feature #23122 Xml encoder optional type cast (ragboyjr) + * feature #23207 [FrameworkBundle] Allow .yaml file extension everywhere (ogizanagi) + * feature #23076 [Validator] Adds support to check specific DNS record type for URL (iisisrael) + * feature #22629 [Security] Trigger a deprecation when a voter is missing the VoterInterface (iltar) + * feature #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * feature #22909 [Yaml] Deprecate using the non-specific tag (GuilhemN) + * feature #23042 Consistent error handling in remember me services (lstrojny) + * feature #22444 [Serializer] DateTimeNormalizer: allow to provide timezone (ogizanagi) + * feature #23143 [DI] Reference instead of inline for array-params (nicolas-grekas) + * feature #23154 [WebProfilerBundle] Sticky ajax window (ro0NL) + * feature #23161 [FrameworkBundle] Deprecate useless --no-prefix option (chalasr) + * feature #23105 [SecurityBundle][Profiler] Give info about called security listeners in profiler (chalasr) + * feature #23148 [FrameworkBundle] drop hard dependency on the Stopwatch component (xabbuh) + * feature #23131 [FrameworkBundle] Remove dependency on Doctrine cache (fabpot) + * feature #23114 [SecurityBundle] Lazy load security listeners (chalasr) + * feature #23111 [Process] Deprecate ProcessBuilder (nicolas-grekas) + * feature #22675 [FrameworkBundle] KernelTestCase: deprecate not using KERNEL_CLASS (ogizanagi) + * feature #22917 [VarDumper] Cycle prev/next searching in HTML dumps (ro0NL) + * feature #23044 Automatically enable the routing annotation loader (GuilhemN) + * feature #22696 [PropertyInfo] Made ReflectionExtractor's prefix lists instance variables (neemzy) + * feature #23035 Deprecate passing a concrete service in optional cache warmers (romainneutron) + * feature #23036 Implement ServiceSubscriberInterface in optional cache warmers (romainneutron) + * feature #23022 [Di] Remove closure-proxy arguments (nicolas-grekas) + * feature #22903 [DI] Deprecate XML services without ID (ro0NL) + * feature #22597 [Lock] Re-add the Lock component in 3.4 (jderusse) + * feature #22803 [DI] Deprecate Container::initialized() for privates (ro0NL) + * feature #22828 [Finder] Deprecate FilterIterator (ogizanagi) + * feature #22826 [Validator] improve strict option value deprecation (xabbuh) + From c000b454c8d44ef6aaed8fb1b991d91a4d29f522 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 18 Oct 2017 14:45:50 -0700 Subject: [PATCH 10/11] updated VERSION for 3.4.0-BETA1 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 6f4f2b72b6..af68d7c39a 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -65,12 +65,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $projectDir; private $warmupDir; - const VERSION = '3.4.0-DEV'; + const VERSION = '3.4.0-BETA1'; const VERSION_ID = 30400; const MAJOR_VERSION = 3; const MINOR_VERSION = 4; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'DEV'; + const EXTRA_VERSION = 'BETA1'; const END_OF_MAINTENANCE = '11/2020'; const END_OF_LIFE = '11/2021'; From d484f7282a25572a0f1045dcccf7c79260b6fe2e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 18 Oct 2017 15:39:44 -0700 Subject: [PATCH 11/11] bumped Symfony version to 3.4.0 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index af68d7c39a..6f4f2b72b6 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -65,12 +65,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $projectDir; private $warmupDir; - const VERSION = '3.4.0-BETA1'; + const VERSION = '3.4.0-DEV'; const VERSION_ID = 30400; const MAJOR_VERSION = 3; const MINOR_VERSION = 4; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'BETA1'; + const EXTRA_VERSION = 'DEV'; const END_OF_MAINTENANCE = '11/2020'; const END_OF_LIFE = '11/2021';