Merge branch '3.4'

* 3.4:
  bumped Symfony version to 3.4.0
  updated VERSION for 3.4.0-BETA1
  updated CHANGELOG for 3.4.0-BETA1
  Do not process bindings in AbstractRecursivePass
  don't bind scalar values to controller method arguments
  Add extra autowiring aliases
  adding AdapterInterface alias for cache.app
  Adding a new debug:autowiring command
  [HttpFoundation] Make sessions secure and lazy
  [Routing] Ensure uniqueness without repeated check
  [Console] Sync ConsoleLogger::interpolate with the one in HttpKernel
This commit is contained in:
Nicolas Grekas 2017-10-18 17:12:27 -07:00
commit 14de848cc4
50 changed files with 1925 additions and 373 deletions

217
CHANGELOG-3.4.md Normal file
View File

@ -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 <supports> 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 <notes> 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 <notes> 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)

View File

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

View File

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

View File

@ -22,10 +22,12 @@ CHANGELOG
* Removed the "framework.validation.cache" configuration option. Configure the "cache.validator" service under "framework.cache.pools" instead.
* Removed `PhpStringTokenParser`, use `Symfony\Component\Translation\Extractor\PhpStringTokenParser` instead.
* Removed `PhpExtractor`, use `Symfony\Component\Translation\Extractor\PhpExtractor` instead.
* Removed the `use_strict_mode` session option, it's is now enabled by default
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`

View File

@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\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 <ryan@knpuniversity.com>
*
* @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 <info>%command.name%</info> command displays all classes and interfaces that
you can use as type-hints for autowiring:
<info>php %command.full_name%</info>
You can also pass a search term to filter the list:
<info>php %command.full_name% log</info>
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 <comment>%s</comment>)', $search));
}
$io->newLine();
$tableRows = array();
foreach ($serviceIds as $serviceId) {
$tableRows[] = array(sprintf('<fg=cyan>%s</fg=cyan>', $serviceId));
if ($builder->hasAlias($serviceId)) {
$tableRows[] = array(sprintf(' alias to %s', $builder->getAlias($serviceId)));
}
}
$io->table(array(), $tableRows);
}
}

View File

@ -413,11 +413,10 @@ class Configuration implements ConfigurationInterface
->scalarNode('gc_divisor')->end()
->scalarNode('gc_probability')->defaultValue(1)->end()
->scalarNode('gc_maxlifetime')->end()
->booleanNode('use_strict_mode')->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()

View File

@ -728,7 +728,7 @@ class FrameworkExtension extends Extension
// session storage
$container->setAlias('session.storage', $config['storage_id'])->setPrivate(true);
$options = array();
foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'use_strict_mode') as $key) {
foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor') as $key) {
if (isset($config[$key])) {
$options[$key] = $config[$key];
}
@ -742,14 +742,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']);

View File

@ -111,5 +111,6 @@
<service id="cache.global_clearer" parent="cache.default_clearer" public="true" />
<service id="cache.app_clearer" alias="cache.default_clearer" public="true" />
<service id="Psr\Cache\CacheItemPoolInterface" alias="cache.app" />
<service id="Symfony\Component\Cache\Adapter\AdapterInterface" alias="cache.app" />
</services>
</container>

View File

@ -55,6 +55,10 @@
<tag name="console.command" command="debug:container" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand">
<tag name="console.command" command="debug:autowiring" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand">
<argument type="service" id="event_dispatcher" />
<tag name="console.command" command="debug:event-dispatcher" />

View File

@ -77,6 +77,7 @@
<service id="Symfony\Component\Routing\RouterInterface" alias="router" />
<service id="Symfony\Component\Routing\Generator\UrlGeneratorInterface" alias="router" />
<service id="Symfony\Component\Routing\Matcher\UrlMatcherInterface" alias="router" />
<service id="Symfony\Component\Routing\RequestContextAwareInterface" alias="router" />
<service id="router.request_context" class="Symfony\Component\Routing\RequestContext">
<argument>%router.request_context.base_url%</argument>

View File

@ -48,12 +48,14 @@
<argument type="service" id="session.storage.metadata_bag" />
</service>
<service id="session.handler.native_file" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler">
<argument>%session.save_path%</argument>
<service id="session.handler.native_file" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler">
<argument type="service">
<service class="Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler">
<argument>%session.save_path%</argument>
</service>
</argument>
</service>
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler" />
<service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener">
<tag name="kernel.event_subscriber" />
<argument type="service">

View File

@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\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());
}
}

View File

@ -52,6 +52,7 @@
<argument type="service" id="event_dispatcher" />
</call>
</service>
<service id="Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface" alias="security.authentication.manager" />
<service id="security.authentication.trust_resolver" class="Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver">
<argument>%security.authentication.trust_resolver.anonymous_class%</argument>

View File

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

View File

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

View File

@ -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'])) {

View File

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

View File

@ -17,8 +17,7 @@ CHANGELOG
* checking for cacheable HTTP methods using the `Request::isMethodSafe()`
method (by not passing `false` as its argument) is not supported anymore and
throws a `\BadMethodCallException`
* the `NativeSessionHandler` class has been removed
* the `AbstractProxy`, `NativeProxy` and `SessionHandlerProxy` classes have been removed
* the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed
* setting session save handlers that do not implement `\SessionHandlerInterface` in
`NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a
`\TypeError`
@ -26,8 +25,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

View File

@ -0,0 +1,165 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\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 <p@tchwork.com>
*/
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);
}
}

View File

@ -19,7 +19,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
*
* @author Drak <drak@zikula.org>
*/
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);

View File

@ -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 \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
@ -83,14 +83,6 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
), $options);
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* {@inheritdoc}
*/
@ -102,7 +94,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
protected function doDestroy($sessionId)
{
$this->getCollection()->deleteOne(array(
$this->options['id_field'] => $sessionId,
@ -126,7 +118,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
protected function doWrite($sessionId, $data)
{
$expiry = new \MongoDB\BSON\UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000);
@ -148,7 +140,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,

View File

@ -16,16 +16,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
*
* @author Drak <drak@zikula.org>
*/
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;
}

View File

@ -38,7 +38,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
* @author Michael Williams <michael.williams@funsational.com>
* @author Tobias Schultze <http://tobion.de>
*/
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 {

View File

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
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);
}
}

View File

@ -1,91 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
/**
* Wraps another SessionHandlerInterface to only write the session when it has been modified.
*
* @author Adrien Brault <adrien.brault@gmail.com>
*/
class WriteCheckSessionHandler implements \SessionHandlerInterface
{
/**
* @var \SessionHandlerInterface
*/
private $wrappedSessionHandler;
/**
* @var array sessionId => session
*/
private $readSessions;
public function __construct(\SessionHandlerInterface $wrappedSessionHandler)
{
$this->wrappedSessionHandler = $wrappedSessionHandler;
}
/**
* {@inheritdoc}
*/
public function close()
{
return $this->wrappedSessionHandler->close();
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
return $this->wrappedSessionHandler->destroy($sessionId);
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
return $this->wrappedSessionHandler->gc($maxlifetime);
}
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return $this->wrappedSessionHandler->open($savePath, $sessionName);
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
$session = $this->wrappedSessionHandler->read($sessionId);
$this->readSessions[$sessionId] = $session;
return $session;
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) {
return true;
}
return $this->wrappedSessionHandler->write($sessionId, $data);
}
}

View File

@ -12,6 +12,9 @@
namespace Symfony\Component\HttpFoundation\Session\Storage;
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;
/**
* This provides a base class for session attribute storage.
@ -23,7 +26,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* @var SessionBagInterface[]
*/
protected $bags;
protected $bags = array();
/**
* @var bool
@ -36,7 +39,7 @@ class NativeSessionStorage implements SessionStorageInterface
protected $closed = false;
/**
* @var \SessionHandlerInterface
* @var AbstractProxy|\SessionHandlerInterface
*/
protected $saveHandler;
@ -85,13 +88,18 @@ class NativeSessionStorage implements SessionStorageInterface
* sid_bits_per_character, "5"
* trans_sid_hosts, $_SERVER['HTTP_HOST']
* trans_sid_tags, "a=href,area=href,frame=src,form="
*
* @param array $options Session configuration options
* @param \SessionHandlerInterface|null $handler
* @param MetadataBag $metaBag MetadataBag
*/
public function __construct(array $options = array(), \SessionHandlerInterface $handler = null, MetadataBag $metaBag = null)
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,
'use_strict_mode' => 1,
);
session_register_shutdown();
@ -104,7 +112,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* Gets the save handler instance.
*
* @return \SessionHandlerInterface
* @return AbstractProxy|\SessionHandlerInterface
*/
public function getSaveHandler()
{
@ -143,21 +151,15 @@ class NativeSessionStorage implements SessionStorageInterface
*/
public function getId()
{
return session_id();
return $this->saveHandler->getId();
}
/**
* {@inheritdoc}
*
* @throws \LogicException When the session is active
*/
public function setId($id)
{
if (\PHP_SESSION_ACTIVE === session_status()) {
throw new \LogicException('Cannot change the ID of an active session');
}
session_id($id);
$this->saveHandler->setId($id);
}
/**
@ -165,21 +167,15 @@ class NativeSessionStorage implements SessionStorageInterface
*/
public function getName()
{
return session_name();
return $this->saveHandler->getName();
}
/**
* {@inheritdoc}
*
* @throws \LogicException When the session is active
*/
public function setName($name)
{
if (\PHP_SESSION_ACTIVE === session_status()) {
throw new \LogicException('Cannot change the name of an active session');
}
session_name($name);
$this->saveHandler->setName($name);
}
/**
@ -218,20 +214,38 @@ 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 \ErrorException($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();
restore_error_handler();
} catch (\ErrorException $e) {
} finally {
restore_error_handler();
$_SESSION = $session;
}
if (null !== $e) {
// The default PHP error message is not very helpful, as it does not give any information on the current save handler.
// Therefore, we catch this error and trigger a warning with a better error message
$handler = $this->getSaveHandler();
if ($handler instanceof SessionHandlerProxy) {
$handler = $handler->getHandler();
}
restore_error_handler();
trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING);
}
@ -258,8 +272,6 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*
* @throws \LogicException When the session is already started
*/
public function registerBag(SessionBagInterface $bag)
{
@ -279,7 +291,7 @@ class NativeSessionStorage implements SessionStorageInterface
throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
}
if (!$this->started && \PHP_SESSION_ACTIVE === session_status()) {
if (!$this->started && $this->saveHandler->isActive()) {
$this->loadSession();
} elseif (!$this->started) {
$this->start();
@ -372,16 +384,36 @@ class NativeSessionStorage implements SessionStorageInterface
* @see http://php.net/sessionhandlerinterface
* @see http://php.net/sessionhandler
* @see http://github.com/drak/NativeSession
*
* @param \SessionHandlerInterface|null $saveHandler
*
* @throws \InvalidArgumentException
*/
public function setSaveHandler(\SessionHandlerInterface $saveHandler = null)
public function setSaveHandler($saveHandler = null)
{
$this->saveHandler = $saveHandler ?: new \SessionHandler();
if (!$saveHandler instanceof AbstractProxy &&
!$saveHandler instanceof \SessionHandlerInterface &&
null !== $saveHandler) {
throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
}
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));
}
session_set_save_handler($this->saveHandler, false);
// Wrap $saveHandler in proxy and prevent double wrapping of proxy
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
$saveHandler = new SessionHandlerProxy($saveHandler);
} elseif (!$saveHandler instanceof AbstractProxy) {
$saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
}
$this->saveHandler = $saveHandler;
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);
}
}
/**
@ -400,12 +432,11 @@ class NativeSessionStorage implements SessionStorageInterface
$session = &$_SESSION;
}
$bags = $this->bags;
$bags[] = $this->metadataBag;
$bags = array_merge($this->bags, array($this->metadataBag));
foreach ($bags as $bag) {
$key = $bag->getStorageKey();
$session[$key] = $session[$key] ?? array();
$session[$key] = isset($session[$key]) ? $session[$key] : array();
$bag->initialize($session[$key]);
}

View File

@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
/**
* @author Drak <drak@zikula.org>
*/
abstract class AbstractProxy
{
/**
* Flag if handler wraps an internal PHP session handler (using \SessionHandler).
*
* @var bool
*/
protected $wrapper = false;
/**
* @var string
*/
protected $saveHandlerName;
/**
* Gets the session.save_handler name.
*
* @return string
*/
public function getSaveHandlerName()
{
return $this->saveHandlerName;
}
/**
* Is this proxy handler and instance of \SessionHandlerInterface.
*
* @return bool
*/
public function isSessionHandlerInterface()
{
return $this instanceof \SessionHandlerInterface;
}
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @return bool
*/
public function isWrapper()
{
return $this->wrapper;
}
/**
* Has a session started?
*
* @return bool
*/
public function isActive()
{
return \PHP_SESSION_ACTIVE === session_status();
}
/**
* Gets the session ID.
*
* @return string
*/
public function getId()
{
return session_id();
}
/**
* Sets the session ID.
*
* @param string $id
*
* @throws \LogicException
*/
public function setId($id)
{
if ($this->isActive()) {
throw new \LogicException('Cannot change the ID of an active session');
}
session_id($id);
}
/**
* Gets the session name.
*
* @return string
*/
public function getName()
{
return session_name();
}
/**
* Sets the session name.
*
* @param string $name
*
* @throws \LogicException
*/
public function setName($name)
{
if ($this->isActive()) {
throw new \LogicException('Cannot change the name of an active session');
}
session_name($name);
}
}

View File

@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
/**
* @author Drak <drak@zikula.org>
*/
class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface
{
/**
* @var \SessionHandlerInterface
*/
protected $handler;
public function __construct(\SessionHandlerInterface $handler)
{
$this->handler = $handler;
$this->wrapper = ($handler instanceof \SessionHandler);
$this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
}
/**
* @return \SessionHandlerInterface
*/
public function getHandler()
{
return $this->handler;
}
// \SessionHandlerInterface
/**
* {@inheritdoc}
*/
public function open($savePath, $sessionName)
{
return (bool) $this->handler->open($savePath, $sessionName);
}
/**
* {@inheritdoc}
*/
public function close()
{
return (bool) $this->handler->close();
}
/**
* {@inheritdoc}
*/
public function read($sessionId)
{
return (string) $this->handler->read($sessionId);
}
/**
* {@inheritdoc}
*/
public function write($sessionId, $data)
{
return (bool) $this->handler->write($sessionId, $data);
}
/**
* {@inheritdoc}
*/
public function destroy($sessionId)
{
return (bool) $this->handler->destroy($sessionId);
}
/**
* {@inheritdoc}
*/
public function gc($maxlifetime)
{
return (bool) $this->handler->gc($maxlifetime);
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\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));
}
}
}

View File

@ -0,0 +1,152 @@
<?php
use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler;
$parent = __DIR__;
while (!@file_exists($parent.'/vendor/autoload.php')) {
if (!@file_exists($parent)) {
// open_basedir restriction in effect
break;
}
if ($parent === dirname($parent)) {
echo "vendor/autoload.php not found\n";
exit(1);
}
$parent = dirname($parent);
}
require $parent.'/vendor/autoload.php';
error_reporting(-1);
ini_set('html_errors', 0);
ini_set('display_errors', 1);
ini_set('session.gc_probability', 0);
ini_set('session.serialize_handler', 'php');
ini_set('session.cookie_lifetime', 0);
ini_set('session.cookie_domain', '');
ini_set('session.cookie_secure', '');
ini_set('session.cookie_httponly', '');
ini_set('session.use_cookies', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cache_expire', 180);
ini_set('session.cookie_path', '/');
ini_set('session.cookie_domain', '');
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_httponly', 1);
ini_set('session.use_strict_mode', 1);
ini_set('session.lazy_write', 1);
ini_set('session.name', 'sid');
ini_set('session.save_path', __DIR__);
ini_set('session.cache_limiter', 'private_no_expire');
header_remove('X-Powered-By');
header('Content-Type: text/plain; charset=utf-8');
register_shutdown_function(function () {
echo "\n";
header_remove('Last-Modified');
session_write_close();
print_r(headers_list());
echo "shutdown\n";
});
ob_start();
class TestSessionHandler extends AbstractSessionHandler
{
private $data;
public function __construct($data = '')
{
$this->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;
}
}

View File

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

View File

@ -0,0 +1,8 @@
<?php
require __DIR__.'/common.inc';
session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
session_start();
unset($_SESSION['abc']);

View File

@ -0,0 +1,14 @@
open
validateId
read
doRead: abc|i:123;
read
123
updateTimestamp
close
Array
(
[0] => Content-Type: text/plain; charset=utf-8
[1] => Cache-Control: private, max-age=10800
)
shutdown

View File

@ -0,0 +1,8 @@
<?php
require __DIR__.'/common.inc';
session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
session_start();
echo $_SESSION['abc'];

View File

@ -0,0 +1,24 @@
open
validateId
read
doRead: abc|i:123;
read
destroy
doDestroy
close
open
validateId
read
doRead: abc|i:123;
read
write
doWrite: abc|i:123;
close
Array
(
[0] => 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

View File

@ -0,0 +1,10 @@
<?php
require __DIR__.'/common.inc';
session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
session_start();
session_regenerate_id(true);
ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); });

View File

@ -0,0 +1,20 @@
open
validateId
read
doRead:
read
Array
(
[0] => 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

View File

@ -0,0 +1,24 @@
<?php
require __DIR__.'/common.inc';
use Symfony\Component\HttpFoundation\Session\Flash\FlashBag;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
$storage = new NativeSessionStorage();
$storage->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); });

View File

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

View File

@ -0,0 +1,8 @@
<?php
require __DIR__.'/common.inc';
session_set_save_handler(new TestSessionHandler('abc|i:123;'), false);
session_start();
setcookie('abc', 'def');

View File

@ -153,6 +153,10 @@ class PdoSessionHandlerTest extends TestCase
public function testReadLockedConvertsStreamToString()
{
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();
$insertStmt = $this->getMockBuilder('PDOStatement')->getMock();

View File

@ -0,0 +1,189 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\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));
}
}

View File

@ -1,95 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler;
/**
* @author Adrien Brault <adrien.brault@gmail.com>
*/
class WriteCheckSessionHandlerTest extends TestCase
{
public function test()
{
$wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
$writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
$wrappedSessionHandlerMock
->expects($this->once())
->method('close')
->with()
->will($this->returnValue(true))
;
$this->assertTrue($writeCheckSessionHandler->close());
}
public function testWrite()
{
$wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
$writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
$wrappedSessionHandlerMock
->expects($this->once())
->method('write')
->with('foo', 'bar')
->will($this->returnValue(true))
;
$this->assertTrue($writeCheckSessionHandler->write('foo', 'bar'));
}
public function testSkippedWrite()
{
$wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
$writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
$wrappedSessionHandlerMock
->expects($this->once())
->method('read')
->with('foo')
->will($this->returnValue('bar'))
;
$wrappedSessionHandlerMock
->expects($this->never())
->method('write')
;
$this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
$this->assertTrue($writeCheckSessionHandler->write('foo', 'bar'));
}
public function testNonSkippedWrite()
{
$wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
$writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock);
$wrappedSessionHandlerMock
->expects($this->once())
->method('read')
->with('foo')
->will($this->returnValue('bar'))
;
$wrappedSessionHandlerMock
->expects($this->once())
->method('write')
->with('foo', 'baZZZ')
->will($this->returnValue(true))
;
$this->assertEquals('bar', $writeCheckSessionHandler->read('foo'));
$this->assertTrue($writeCheckSessionHandler->write('foo', 'baZZZ'));
}
}

View File

@ -14,8 +14,10 @@ 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\NativeFileSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
/**
* Test class for NativeSessionStorage.
@ -98,7 +100,6 @@ class NativeSessionStorageTest extends TestCase
$storage->start();
$id = $storage->getId();
$this->assertEquals(session_id(), $id);
$this->assertInternalType('string', $id);
$this->assertNotSame('', $id);
@ -106,44 +107,6 @@ class NativeSessionStorageTest extends TestCase
$this->assertSame($id, $storage->getId(), 'ID stays after saving session');
}
public function testId()
{
$storage = $this->getStorage();
$this->assertEquals(session_id(), $storage->getId());
$storage->setId('foo');
$this->assertEquals('foo', $storage->getId());
$this->assertEquals(session_id(), $storage->getId());
}
/**
* @expectedException \LogicException
*/
public function testIdException()
{
session_start();
$this->getStorage()->setId('foo');
}
public function testName()
{
$storage = $this->getStorage();
$this->assertEquals(session_name(), $storage->getName());
$storage->setName('foo');
$this->assertEquals('foo', $storage->getName());
$this->assertEquals(session_name(), $storage->getName());
}
/**
* @expectedException \LogicException
*/
public function testNameException()
{
session_start();
$this->getStorage()->setName('foo');
}
public function testRegenerate()
{
$storage = $this->getStorage();
@ -189,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()
@ -222,9 +185,9 @@ class NativeSessionStorageTest extends TestCase
}
/**
* @expectedException \TypeError
* @expectedException \InvalidArgumentException
*/
public function testSetSaveHandlerError()
public function testSetSaveHandlerException()
{
$storage = $this->getStorage();
$storage->setSaveHandler(new \stdClass());
@ -235,13 +198,17 @@ class NativeSessionStorageTest extends TestCase
$this->iniSet('session.save_handler', 'files');
$storage = $this->getStorage();
$storage->setSaveHandler();
$this->assertInstanceOf(\SessionHandler::class, $storage->getSaveHandler());
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
$storage->setSaveHandler(null);
$this->assertInstanceOf(\SessionHandler::class, $storage->getSaveHandler());
$storage->setSaveHandler(new \SessionHandler());
$this->assertInstanceOf(\SessionHandler::class, $storage->getSaveHandler());
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
$storage->setSaveHandler(new SessionHandlerProxy(new NativeFileSessionHandler()));
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
$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());
$storage->setSaveHandler(new NullSessionHandler());
$this->assertInstanceOf(\SessionHandlerInterface::class, $storage->getSaveHandler());
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler());
}
/**
@ -251,12 +218,12 @@ class NativeSessionStorageTest extends TestCase
{
$storage = $this->getStorage();
$this->assertNotSame(\PHP_SESSION_ACTIVE, session_status());
$this->assertFalse($storage->getSaveHandler()->isActive());
$this->assertFalse($storage->isStarted());
session_start();
$this->assertTrue(isset($_SESSION));
$this->assertSame(\PHP_SESSION_ACTIVE, session_status());
$this->assertTrue($storage->getSaveHandler()->isActive());
// PHP session might have started, but the storage driver has not, so false is correct here
$this->assertFalse($storage->isStarted());

View File

@ -0,0 +1,113 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
/**
* Test class for AbstractProxy.
*
* @author Drak <drak@zikula.org>
*/
class AbstractProxyTest extends TestCase
{
/**
* @var AbstractProxy
*/
protected $proxy;
protected function setUp()
{
$this->proxy = $this->getMockForAbstractClass(AbstractProxy::class);
}
protected function tearDown()
{
$this->proxy = null;
}
public function testGetSaveHandlerName()
{
$this->assertNull($this->proxy->getSaveHandlerName());
}
public function testIsSessionHandlerInterface()
{
$this->assertFalse($this->proxy->isSessionHandlerInterface());
$sh = new SessionHandlerProxy(new \SessionHandler());
$this->assertTrue($sh->isSessionHandlerInterface());
}
public function testIsWrapper()
{
$this->assertFalse($this->proxy->isWrapper());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testIsActive()
{
$this->assertFalse($this->proxy->isActive());
session_start();
$this->assertTrue($this->proxy->isActive());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testName()
{
$this->assertEquals(session_name(), $this->proxy->getName());
$this->proxy->setName('foo');
$this->assertEquals('foo', $this->proxy->getName());
$this->assertEquals(session_name(), $this->proxy->getName());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
* @expectedException \LogicException
*/
public function testNameException()
{
session_start();
$this->proxy->setName('foo');
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testId()
{
$this->assertEquals(session_id(), $this->proxy->getId());
$this->proxy->setId('foo');
$this->assertEquals('foo', $this->proxy->getId());
$this->assertEquals(session_id(), $this->proxy->getId());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
* @expectedException \LogicException
*/
public function testIdException()
{
session_start();
$this->proxy->setId('foo');
}
}

View File

@ -0,0 +1,124 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
/**
* Tests for SessionHandlerProxy class.
*
* @author Drak <drak@zikula.org>
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class SessionHandlerProxyTest extends TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_Matcher
*/
private $mock;
/**
* @var SessionHandlerProxy
*/
private $proxy;
protected function setUp()
{
$this->mock = $this->getMockBuilder('SessionHandlerInterface')->getMock();
$this->proxy = new SessionHandlerProxy($this->mock);
}
protected function tearDown()
{
$this->mock = null;
$this->proxy = null;
}
public function testOpenTrue()
{
$this->mock->expects($this->once())
->method('open')
->will($this->returnValue(true));
$this->assertFalse($this->proxy->isActive());
$this->proxy->open('name', 'id');
$this->assertFalse($this->proxy->isActive());
}
public function testOpenFalse()
{
$this->mock->expects($this->once())
->method('open')
->will($this->returnValue(false));
$this->assertFalse($this->proxy->isActive());
$this->proxy->open('name', 'id');
$this->assertFalse($this->proxy->isActive());
}
public function testClose()
{
$this->mock->expects($this->once())
->method('close')
->will($this->returnValue(true));
$this->assertFalse($this->proxy->isActive());
$this->proxy->close();
$this->assertFalse($this->proxy->isActive());
}
public function testCloseFalse()
{
$this->mock->expects($this->once())
->method('close')
->will($this->returnValue(false));
$this->assertFalse($this->proxy->isActive());
$this->proxy->close();
$this->assertFalse($this->proxy->isActive());
}
public function testRead()
{
$this->mock->expects($this->once())
->method('read');
$this->proxy->read('id');
}
public function testWrite()
{
$this->mock->expects($this->once())
->method('write');
$this->proxy->write('id', 'data');
}
public function testDestroy()
{
$this->mock->expects($this->once())
->method('destroy');
$this->proxy->destroy('id');
}
public function testGc()
{
$this->mock->expects($this->once())
->method('gc');
$this->proxy->gc(86400);
}
}

View File

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

View File

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

View File

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

View File

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