diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 6eaec7c81da9a..0000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,13 +0,0 @@ -| Q | A -| ---------------- | ----- -| Bug report? | yes/no -| Feature request? | yes/no -| BC Break report? | yes/no -| RFC? | yes/no -| Symfony version | x.y.z - - diff --git a/.github/ISSUE_TEMPLATE/1_Bug_report.md b/.github/ISSUE_TEMPLATE/1_Bug_report.md new file mode 100644 index 0000000000000..4a64e16edf0a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_Bug_report.md @@ -0,0 +1,21 @@ +--- +name: 🐛 Bug Report +about: Report errors and problems + +--- + +**Symfony version(s) affected**: x.y.z + +**Description** + + +**How to reproduce** + + +**Possible Solution** + + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/2_Feature_request.md b/.github/ISSUE_TEMPLATE/2_Feature_request.md new file mode 100644 index 0000000000000..335321e413607 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_Feature_request.md @@ -0,0 +1,12 @@ +--- +name: 🚀 Feature Request +about: RFC and ideas for new features and improvements + +--- + +**Description** + + +**Example** + diff --git a/.github/ISSUE_TEMPLATE/3_Support_question.md b/.github/ISSUE_TEMPLATE/3_Support_question.md new file mode 100644 index 0000000000000..9480710c15655 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3_Support_question.md @@ -0,0 +1,11 @@ +--- +name: ⛔ Support Question +about: See https://symfony.com/support for questions about using Symfony and its components + +--- + +We use GitHub issues only to discuss about Symfony bugs and new features. For +this kind of questions about using Symfony or third-party bundles, please use +any of the support alternatives shown in https://symfony.com/support + +Thanks! diff --git a/.github/ISSUE_TEMPLATE/4_Documentation_issue.md b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md new file mode 100644 index 0000000000000..0855c3c5f1e12 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4_Documentation_issue.md @@ -0,0 +1,10 @@ +--- +name: ⛔ Documentation Issue +about: See https://github.com/symfony/symfony-docs/issues for documentation issues + +--- + +Symfony Documentation has its own dedicated repository. Please open your +documentation-related issue at https://github.com/symfony/symfony-docs/issues + +Thanks! diff --git a/.github/ISSUE_TEMPLATE/5_Security_issue.md b/.github/ISSUE_TEMPLATE/5_Security_issue.md new file mode 100644 index 0000000000000..9b3165eb1db47 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/5_Security_issue.md @@ -0,0 +1,13 @@ +--- +name: ⛔ Security Issue +about: See https://symfony.com/security to report security-related issues + +--- + +⚠ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW. + +If you have found a security issue in Symfony, please send the details to +security [at] symfony.com and don't disclose it publicly until we can provide a +fix for it. + +More information: https://symfony.com/security diff --git a/.github/build-packages.php b/.github/build-packages.php index b67a699609d66..7ab54f11e3aa6 100644 --- a/.github/build-packages.php +++ b/.github/build-packages.php @@ -49,7 +49,7 @@ $packages[$package->name][$package->version] = $package; - $versions = file_get_contents('https://packagist.org/p/'.$package->name.'.json'); + $versions = @file_get_contents('https://packagist.org/p/'.$package->name.'.json') ?: sprintf('{"packages":{"%s":{"dev-master":%s}}}', $package->name, file_get_contents($dir.'/composer.json')); $versions = json_decode($versions)->packages->{$package->name}; if ($package->version === str_replace('-dev', '.x-dev', $versions->{'dev-master'}->extra->{'branch-alias'}->{'dev-master'})) { diff --git a/.php_cs.dist b/.php_cs.dist index e9eeb14369993..f9f786895fb62 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -22,7 +22,9 @@ return PhpCsFixer\Config::create() ->in(__DIR__.'/src') ->append(array(__FILE__)) ->exclude(array( + 'Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures', // directories containing files with content that is autogenerated by `var_export`, which breaks CS in output code + 'Symfony/Component/Cache/Tests/Marshaller/Fixtures', 'Symfony/Component/DependencyInjection/Tests/Fixtures', 'Symfony/Component/Routing/Tests/Fixtures/dumper', // fixture templates diff --git a/.travis.yml b/.travis.yml index d4c5156a6d2a5..edf86a7e51b47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -124,7 +124,7 @@ before_install: deps=skip skip=1 else - COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') + COMPONENTS=$(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n') fi - | @@ -151,6 +151,7 @@ before_install: tfold ext.libsodium tpecl libsodium sodium.so $INI tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI tfold ext.amqp tpecl amqp-1.9.3 amqp.so $INI + tfold ext.igbinary tpecl igbinary-2.0.6 igbinary.so $INI fi - | @@ -180,7 +181,7 @@ install: SYMFONY_VERSION=$(git ls-remote --heads | grep -o '/[1-9].*' | tail -n 1 | sed s/.//) && git fetch origin $SYMFONY_VERSION && git checkout -m FETCH_HEAD && - COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') + COMPONENTS=$(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n') elif [[ ! $skip ]]; then SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*') fi diff --git a/UPGRADE-4.2.md b/UPGRADE-4.2.md new file mode 100644 index 0000000000000..e49c442e05cb5 --- /dev/null +++ b/UPGRADE-4.2.md @@ -0,0 +1,118 @@ +UPGRADE FROM 4.1 to 4.2 +======================= + +Cache +----- + + * Deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead. + +Config +------ + + * Deprecated constructing a `TreeBuilder` without passing root node information. + +Console +------- + + * Deprecated passing a command as a string to `ProcessHelper::run()`, + pass the command as an array of arguments instead. + + Before: + ```php + $processHelper->run($output, 'ls -l'); + ``` + + After: + ```php + $processHelper->run($output, array('ls', '-l')); + + // alternatively, when a shell wrapper is required + $processHelper->run($output, Process::fromShellCommandline('ls -l')); + ``` + +DoctrineBridge +-------------- + + * The `lazy` attribute on `doctrine.event_listener` tags was removed. + Listeners are now lazy by default. So any `lazy` attributes can safely be removed from those tags. + +Form +---- + + * Deprecated calling `FormRenderer::searchAndRenderBlock` for fields which were already rendered. + Instead of expecting such calls to return empty strings, check if the field has already been rendered. + + Before: + ```twig + {% for field in fieldsWithPotentialDuplicates %} + {{ form_widget(field) }} + {% endfor %} + ``` + + After: + ```twig + {% for field in fieldsWithPotentialDuplicates if not field.rendered %} + {{ form_widget(field) }} + {% endfor %} + ``` + +Process +------- + + * Deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods. + * Deprecated passing commands as strings when creating a `Process` instance. + + Before: + ```php + $process = new Process('ls -l'); + ``` + + After: + ```php + $process = new Process(array('ls', '-l')); + + // alternatively, when a shell wrapper is required + $process = Process::fromShellCommandline('ls -l'); + ``` + +FrameworkBundle +--------------- + + * The `framework.router.utf8` configuration option has been added. If your app's charset + is UTF-8 (see kernel's `getCharset()` method), it is recommended to set it to `true`: + this will generate 404s for non-UTF-8 URLs, which are incompatible with you app anyway, + and will allow dumping optimized routers and using Unicode classes in requirements. + +Messenger +--------- + + * The `handle` method of the `Symfony\Component\Messenger\Middleware\ValidationMiddleware` and `Symfony\Component\Messenger\Asynchronous\Middleware\SendMessageMiddleware` middlewares now requires an `Envelope` object to be given (because they implement the `EnvelopeAwareInterface`). When using these middleware with the provided `MessageBus`, you will not have to do anything. If you use the middlewares any other way, you can use `Envelope::wrap($message)` to create an envelope for your message. + +Security +-------- + + * Using the `has_role()` function in security expressions is deprecated, use the `is_granted()` function instead. + * Not returning an array of 3 elements from `FirewallMapInterface::getListeners()` is deprecated, the 3rd element + must be an instance of `LogoutListener` or `null`. + * Passing custom class names to the + `Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver` to define + custom anonymous and remember me token classes is deprecated. To + use custom tokens, extend the existing `Symfony\Component\Security\Core\Authentication\Token\AnonymousToken` + or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`. + * Accessing the user object that is not an instance of `UserInterface` from `Security::getUser()` is deprecated. + +SecurityBundle +-------------- + + * Passing a `FirewallConfig` instance as 3rd argument to the `FirewallContext` constructor is deprecated, + pass a `LogoutListener` instance instead. + * Using the `security.authentication.trust_resolver.anonymous_class` and + `security.authentication.trust_resolver.rememberme_class` parameters to define + the token classes is deprecated. To use + custom tokens extend the existing AnonymousToken and RememberMeToken. + +Serializer +---------- + + * Relying on the default value (false) of the "as_collection" option is deprecated since 4.2. + You should set it to false explicitly instead as true will be the default value in 5.0. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 61b7237b44923..7fca4a927facd 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -1,9 +1,15 @@ UPGRADE FROM 4.x to 5.0 ======================= +Cache +----- + + * Removed `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead. + Config ------ + * Dropped support for constructing a `TreeBuilder` without passing root node information. * Added the `getChildNodeDefinitions()` method to `ParentNodeDefinitionInterface`. * The `Processor` class has been made final @@ -15,6 +21,20 @@ Console * Removed the `getHorizontalBorderChar()` method in favor of the `getBorderChars()` method in `TableStyle`. * Removed the `setVerticalBorderChar()` method in favor of the `setVerticalBorderChars()` method in `TableStyle`. * Removed the `getVerticalBorderChar()` method in favor of the `getBorderChars()` method in `TableStyle`. + * The `ProcessHelper::run()` method takes the command as an array of arguments. + + Before: + ```php + $processHelper->run($output, 'ls -l'); + ``` + + After: + ```php + $processHelper->run($output, array('ls', '-l')); + + // alternatively, when a shell wrapper is required + $processHelper->run($output, Process::fromShellCommandline('ls -l')); + ``` DependencyInjection ------------------- @@ -22,6 +42,12 @@ DependencyInjection * Removed the `TypedReference::canBeAutoregistered()` and `TypedReference::getRequiringClass()` methods. * Removed support for auto-discovered extension configuration class which does not implement `ConfigurationInterface`. +DoctrineBridge +-------------- + + * Deprecated injecting `ClassMetadataFactory` in `DoctrineExtractor`, an instance of `EntityManagerInterface` should be + injected instead + EventDispatcher --------------- @@ -72,12 +98,35 @@ HttpFoundation * The `getClientSize()` method of the `UploadedFile` class has been removed. * The `getSession()` method of the `Request` class throws an exception when session is null. +Process +------- + + * Removed the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods. + * Commands must be defined as arrays when creating a `Process` instance. + + Before: + ```php + $process = new Process('ls -l'); + ``` + + After: + ```php + $process = new Process(array('ls', '-l')); + + // alternatively, when a shell wrapper is required + $process = Process::fromShellCommandline('ls -l'); + ``` + Security -------- * The `ContextListener::setLogoutOnUserChange()` method has been removed. * The `Symfony\Component\Security\Core\User\AdvancedUserInterface` has been removed. * The `ExpressionVoter::addExpressionLanguageProvider()` method has been removed. + * The `FirewallMapInterface::getListeners()` method must return an array of 3 elements, + the 3rd one must be either a `LogoutListener` instance or `null`. + * The `AuthenticationTrustResolver` constructor arguments have been removed. + * A user object that is not an instance of `UserInterface` cannot be accessed from `Security::getUser()` anymore and returns `null` instead. SecurityBundle -------------- @@ -85,6 +134,10 @@ SecurityBundle * The `logout_on_user_change` firewall option has been removed. * The `switch_user.stateless` firewall option has been removed. * The `SecurityUserValueResolver` class has been removed. + * Passing a `FirewallConfig` instance as 3rd argument to the `FirewallContext` constructor + now throws a `\TypeError`, pass a `LogoutListener` instance instead. + * The `security.authentication.trust_resolver.anonymous_class` parameter has been removed. + * The `security.authentication.trust_resolver.rememberme_class` parameter has been removed. Translation ----------- diff --git a/composer.json b/composer.json index f861cbca31349..fc42f7b955171 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,9 @@ "require": { "php": "^7.1.3", "ext-xml": "*", - "doctrine/common": "~2.4", + "doctrine/collections": "~1.0", + "doctrine/event-manager": "~1.0", + "doctrine/persistence": "~1.0", "fig/link-util": "^1.0", "twig/twig": "^1.35|^2.4.4", "psr/cache": "~1.0", @@ -37,6 +39,7 @@ "symfony/cache": "self.version", "symfony/config": "self.version", "symfony/console": "self.version", + "symfony/contracts": "1.0.0", "symfony/css-selector": "self.version", "symfony/dependency-injection": "self.version", "symfony/debug": "self.version", @@ -91,6 +94,7 @@ "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", "doctrine/orm": "~2.4,>=2.4.5", + "doctrine/reflection": "~1.0", "doctrine/doctrine-bundle": "~1.4", "monolog/monolog": "~1.11", "ocramius/proxy-manager": "~0.4|~1.0|~2.0", @@ -118,7 +122,8 @@ "Symfony\\Bridge\\ProxyManager\\": "src/Symfony/Bridge/ProxyManager/", "Symfony\\Bridge\\Twig\\": "src/Symfony/Bridge/Twig/", "Symfony\\Bundle\\": "src/Symfony/Bundle/", - "Symfony\\Component\\": "src/Symfony/Component/" + "Symfony\\Component\\": "src/Symfony/Component/", + "Symfony\\Contracts\\": "src/Symfony/Contracts/" }, "classmap": [ "src/Symfony/Component/Intl/Resources/stubs" @@ -133,7 +138,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/link b/link index 22d076c9f13f9..b70c06dda7770 100755 --- a/link +++ b/link @@ -42,6 +42,8 @@ $directories = array_merge(...array_values(array_map(function ($part) { return glob(__DIR__.'/src/Symfony/'.$part.'/*', GLOB_ONLYDIR | GLOB_NOSORT); }, $braces))); +$directories[] = __DIR__.'/src/Symfony/Contracts'; + foreach ($directories as $dir) { if ($filesystem->exists($composer = "$dir/composer.json")) { $sfPackages[json_decode(file_get_contents($composer))->name] = $dir; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 59ec7725254f3..7b808fd674501 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -26,6 +26,7 @@ ./src/Symfony/Bridge/*/Tests/ ./src/Symfony/Component/*/Tests/ ./src/Symfony/Component/*/*/Tests/ + ./src/Symfony/Contract/*/Tests/ ./src/Symfony/Bundle/*/Tests/ @@ -44,6 +45,7 @@ ./src/Symfony/Bridge/*/Tests ./src/Symfony/Component/*/Tests ./src/Symfony/Component/*/*/Tests + ./src/Symfony/Contract/*/Tests ./src/Symfony/Bundle/*/Tests ./src/Symfony/Bundle/*/Resources ./src/Symfony/Component/*/Resources @@ -52,6 +54,7 @@ ./src/Symfony/Bundle/*/vendor ./src/Symfony/Component/*/vendor ./src/Symfony/Component/*/*/vendor + ./src/Symfony/Contract/*/vendor diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index a3865db662c2b..873d1e71f2397 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.2.0 +----- + + * deprecated injecting `ClassMetadataFactory` in `DoctrineExtractor`, + an instance of `EntityManagerInterface` should be injected instead + 4.1.0 ----- diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 92a7b673e27d0..c26a2f84ff249 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -80,10 +81,10 @@ private function addTaggedListeners(ContainerBuilder $container) { $listenerTag = $this->tagPrefix.'.event_listener'; $taggedListeners = $this->findAndSortTags($listenerTag, $container); + $listenerRefs = array(); foreach ($taggedListeners as $taggedListener) { list($id, $tag) = $taggedListener; - $taggedListenerDef = $container->getDefinition($id); if (!isset($tag['event'])) { throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); } @@ -93,15 +94,19 @@ private function addTaggedListeners(ContainerBuilder $container) if (!isset($this->connections[$con])) { throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections)))); } - - if ($lazy = !empty($tag['lazy'])) { - $taggedListenerDef->setPublic(true); - } + $listenerRefs[$con][$id] = new Reference($id); // we add one call per event per service so we have the correct order - $this->getEventManagerDef($container, $con)->addMethodCall('addEventListener', array(array($tag['event']), $lazy ? $id : new Reference($id))); + $this->getEventManagerDef($container, $con)->addMethodCall('addEventListener', array(array($tag['event']), $id)); } } + + // replace service container argument of event managers with smaller service locator + // so services can even remain private + foreach ($listenerRefs as $connection => $refs) { + $this->getEventManagerDef($container, $connection) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $refs)); + } } private function getEventManagerDef(ContainerBuilder $container, $name) diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 17b995297d3f9..2194e2d5e7166 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -13,7 +13,7 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\Mapping\MappingException; -use Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Persistence\Proxy; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as LegacyMappingException; @@ -162,7 +162,7 @@ public function guessPattern($class, $property) protected function getMetadata($class) { // normalize class name - $class = ClassUtils::getRealClass(ltrim($class, '\\')); + $class = self::getRealClass(ltrim($class, '\\')); if (array_key_exists($class, $this->cache)) { return $this->cache[$class]; @@ -179,4 +179,13 @@ protected function getMetadata($class) } } } + + private static function getRealClass(string $class): string + { + if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) { + return $class; + } + + return substr($class, $pos + Proxy::MARKER_LENGTH + 2); + } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 59cdc3b418eba..666ad25fc3ab1 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -24,8 +24,9 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Service\ResetInterface; -abstract class DoctrineType extends AbstractType +abstract class DoctrineType extends AbstractType implements ResetInterface { /** * @var ManagerRegistry diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index 2a41422e00bd0..030b09ecca6e5 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -14,6 +14,7 @@ use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory; use Doctrine\Common\Persistence\Mapping\MappingException; use Doctrine\DBAL\Types\Type as DBALType; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; @@ -27,11 +28,22 @@ */ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface { + private $entityManager; private $classMetadataFactory; - public function __construct(ClassMetadataFactory $classMetadataFactory) + /** + * @param EntityManagerInterface $entityManager + */ + public function __construct($entityManager) { - $this->classMetadataFactory = $classMetadataFactory; + if ($entityManager instanceof EntityManagerInterface) { + $this->entityManager = $entityManager; + } elseif ($entityManager instanceof ClassMetadataFactory) { + @trigger_error(sprintf('Injecting an instance of "%s" in "%s" is deprecated since Symfony 4.2, inject an instance of "%s" instead.', ClassMetadataFactory::class, __CLASS__, EntityManagerInterface::class), E_USER_DEPRECATED); + $this->classMetadataFactory = $entityManager; + } else { + throw new \InvalidArgumentException(sprintf('$entityManager must be an instance of "%s", "%s" given.', EntityManagerInterface::class, \is_object($entityManager) ? \get_class($entityManager) : \gettype($entityManager))); + } } /** @@ -40,7 +52,7 @@ public function __construct(ClassMetadataFactory $classMetadataFactory) public function getProperties($class, array $context = array()) { try { - $metadata = $this->classMetadataFactory->getMetadataFor($class); + $metadata = $this->entityManager ? $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class); } catch (MappingException $exception) { return; } catch (OrmMappingException $exception) { @@ -66,7 +78,7 @@ public function getProperties($class, array $context = array()) public function getTypes($class, $property, array $context = array()) { try { - $metadata = $this->classMetadataFactory->getMetadataFor($class); + $metadata = $this->entityManager ? $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class); } catch (MappingException $exception) { return; } catch (OrmMappingException $exception) { @@ -96,7 +108,7 @@ public function getTypes($class, $property, array $context = array()) if (isset($associationMapping['indexBy'])) { $indexProperty = $associationMapping['indexBy']; /** @var ClassMetadataInfo $subMetadata */ - $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); + $subMetadata = $this->entityManager ? $this->entityManager->getClassMetadata($associationMapping['targetEntity']) : $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); $typeOfField = $subMetadata->getTypeOfField($indexProperty); if (null === $typeOfField) { @@ -104,7 +116,7 @@ public function getTypes($class, $property, array $context = array()) /** @var ClassMetadataInfo $subMetadata */ $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($indexProperty); - $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); + $subMetadata = $this->entityManager ? $this->entityManager->getClassMetadata($associationMapping['targetEntity']) : $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); $typeOfField = $subMetadata->getTypeOfField($indexProperty); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php index 7e99a7d9356c2..dfdfecc094a0a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php @@ -13,9 +13,11 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; class RegisterEventListenersAndSubscribersPassTest extends TestCase { @@ -68,7 +70,6 @@ public function testProcessEventListenersWithPriorities() ->addTag('doctrine.event_listener', array( 'event' => 'foo_bar', 'priority' => 3, - 'lazy' => true, )) ; $container @@ -86,25 +87,30 @@ public function testProcessEventListenersWithPriorities() ; $this->process($container); - $methodCalls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); + $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); + $methodCalls = $eventManagerDef->getMethodCalls(); $this->assertEquals( array( - array('addEventListener', array(array('foo_bar'), new Reference('c'))), - array('addEventListener', array(array('foo_bar'), new Reference('a'))), - array('addEventListener', array(array('bar'), new Reference('a'))), - array('addEventListener', array(array('foo'), new Reference('b'))), - array('addEventListener', array(array('foo'), new Reference('a'))), + array('addEventListener', array(array('foo_bar'), 'c')), + array('addEventListener', array(array('foo_bar'), 'a')), + array('addEventListener', array(array('bar'), 'a')), + array('addEventListener', array(array('foo'), 'b')), + array('addEventListener', array(array('foo'), 'a')), ), $methodCalls ); - // not lazy so must be reference - $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $methodCalls[0][1][1]); - - // lazy so id instead of reference and must mark service public - $this->assertSame('a', $methodCalls[1][1][1]); - $this->assertTrue($container->getDefinition('a')->isPublic()); + $serviceLocatorDef = $container->getDefinition((string) $eventManagerDef->getArgument(0)); + $this->assertSame(ServiceLocator::class, $serviceLocatorDef->getClass()); + $this->assertEquals( + array( + 'c' => new ServiceClosureArgument(new Reference('c')), + 'a' => new ServiceClosureArgument(new Reference('a')), + 'b' => new ServiceClosureArgument(new Reference('b')), + ), + $serviceLocatorDef->getArgument(0) + ); } public function testProcessEventListenersWithMultipleConnections() @@ -136,20 +142,45 @@ public function testProcessEventListenersWithMultipleConnections() $this->process($container); + $eventManagerDef = $container->getDefinition('doctrine.dbal.default_connection.event_manager'); + + // first connection $this->assertEquals( array( - array('addEventListener', array(array('onFlush'), new Reference('a'))), - array('addEventListener', array(array('onFlush'), new Reference('b'))), + array('addEventListener', array(array('onFlush'), 'a')), + array('addEventListener', array(array('onFlush'), 'b')), ), - $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + $eventManagerDef->getMethodCalls() ); + $serviceLocatorDef = $container->getDefinition((string) $eventManagerDef->getArgument(0)); + $this->assertSame(ServiceLocator::class, $serviceLocatorDef->getClass()); $this->assertEquals( array( - array('addEventListener', array(array('onFlush'), new Reference('a'))), - array('addEventListener', array(array('onFlush'), new Reference('c'))), + 'a' => new ServiceClosureArgument(new Reference('a')), + 'b' => new ServiceClosureArgument(new Reference('b')), ), - $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls() + $serviceLocatorDef->getArgument(0) + ); + + // second connection + $secondEventManagerDef = $container->getDefinition('doctrine.dbal.second_connection.event_manager'); + $this->assertEquals( + array( + array('addEventListener', array(array('onFlush'), 'a')), + array('addEventListener', array(array('onFlush'), 'c')), + ), + $secondEventManagerDef->getMethodCalls() + ); + + $serviceLocatorDef = $container->getDefinition((string) $secondEventManagerDef->getArgument(0)); + $this->assertSame(ServiceLocator::class, $serviceLocatorDef->getClass()); + $this->assertEquals( + array( + 'a' => new ServiceClosureArgument(new Reference('a')), + 'c' => new ServiceClosureArgument(new Reference('c')), + ), + $serviceLocatorDef->getArgument(0) ); } @@ -269,11 +300,13 @@ private function createBuilder($multipleConnections = false) $connections = array('default' => 'doctrine.dbal.default_connection'); - $container->register('doctrine.dbal.default_connection.event_manager', 'stdClass'); + $container->register('doctrine.dbal.default_connection.event_manager', 'stdClass') + ->addArgument(new Reference('service_container')); $container->register('doctrine.dbal.default_connection', 'stdClass'); if ($multipleConnections) { - $container->register('doctrine.dbal.second_connection.event_manager', 'stdClass'); + $container->register('doctrine.dbal.second_connection.event_manager', 'stdClass') + ->addArgument(new Reference('service_container')); $container->register('doctrine.dbal.second_connection', 'stdClass'); $connections['second'] = 'doctrine.dbal.second_connection'; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index dcb52718b1176..498c5ecbc95cb 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -23,12 +23,7 @@ */ class DoctrineExtractorTest extends TestCase { - /** - * @var DoctrineExtractor - */ - private $extractor; - - protected function setUp() + private function createExtractor(bool $legacy = false) { $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'), true); $entityManager = EntityManager::create(array('driver' => 'pdo_sqlite'), $config); @@ -38,10 +33,20 @@ protected function setUp() $entityManager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('custom_foo', 'foo'); } - $this->extractor = new DoctrineExtractor($entityManager->getMetadataFactory()); + return new DoctrineExtractor($legacy ? $entityManager->getMetadataFactory() : $entityManager); } public function testGetProperties() + { + $this->doTestGetProperties(false); + } + + public function testLegacyGetProperties() + { + $this->doTestGetProperties(true); + } + + private function doTestGetProperties(bool $legacy) { $this->assertEquals( array( @@ -63,11 +68,21 @@ public function testGetProperties() 'indexedBar', 'indexedFoo', ), - $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy') + $this->createExtractor($legacy)->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy') ); } - public function testGetPropertiesWithEmbedded() + public function testTestGetPropertiesWithEmbedded() + { + $this->doTestGetPropertiesWithEmbedded(false); + } + + public function testLegacyTestGetPropertiesWithEmbedded() + { + $this->doTestGetPropertiesWithEmbedded(true); + } + + private function doTestGetPropertiesWithEmbedded(bool $legacy) { if (!class_exists('Doctrine\ORM\Mapping\Embedded')) { $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); @@ -78,7 +93,7 @@ public function testGetPropertiesWithEmbedded() 'id', 'embedded', ), - $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded') + $this->createExtractor($legacy)->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded') ); } @@ -87,10 +102,33 @@ public function testGetPropertiesWithEmbedded() */ public function testExtract($property, array $type = null) { - $this->assertEquals($type, $this->extractor->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, array())); + $this->doTestExtract(false, $property, $type); + } + + /** + * @dataProvider typesProvider + */ + public function testLegacyExtract($property, array $type = null) + { + $this->doTestExtract(true, $property, $type); + } + + private function doTestExtract(bool $legacy, $property, array $type = null) + { + $this->assertEquals($type, $this->createExtractor($legacy)->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, array())); } public function testExtractWithEmbedded() + { + $this->doTestExtractWithEmbedded(false); + } + + public function testLegacyExtractWithEmbedded() + { + $this->doTestExtractWithEmbedded(true); + } + + private function doTestExtractWithEmbedded(bool $legacy) { if (!class_exists('Doctrine\ORM\Mapping\Embedded')) { $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); @@ -102,7 +140,7 @@ public function testExtractWithEmbedded() 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable' )); - $actualTypes = $this->extractor->getTypes( + $actualTypes = $this->createExtractor($legacy)->getTypes( 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded', 'embedded', array() @@ -158,11 +196,31 @@ public function typesProvider() public function testGetPropertiesCatchException() { - $this->assertNull($this->extractor->getProperties('Not\Exist')); + $this->doTestGetPropertiesCatchException(false); + } + + public function testLegacyGetPropertiesCatchException() + { + $this->doTestGetPropertiesCatchException(true); + } + + private function doTestGetPropertiesCatchException(bool $legacy) + { + $this->assertNull($this->createExtractor($legacy)->getProperties('Not\Exist')); } public function testGetTypesCatchException() { - $this->assertNull($this->extractor->getTypes('Not\Exist', 'baz')); + return $this->doTestGetTypesCatchException(false); + } + + public function testLegacyGetTypesCatchException() + { + return $this->doTestGetTypesCatchException(true); + } + + private function doTestGetTypesCatchException(bool $legacy) + { + $this->assertNull($this->createExtractor($legacy)->getTypes('Not\Exist', 'baz')); } } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index f36c2d647c563..cc344b2039ed7 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -17,7 +17,10 @@ ], "require": { "php": "^7.1.3", - "doctrine/common": "~2.4", + "doctrine/collections": "~1.0", + "doctrine/event-manager": "~1.0", + "doctrine/persistence": "~1.0", + "symfony/contracts": "^1.0", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, @@ -33,9 +36,12 @@ "symfony/expression-language": "~3.4|~4.0", "symfony/validator": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.6", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", - "doctrine/orm": "^2.4.5" + "doctrine/orm": "^2.4.5", + "doctrine/reflection": "~1.0" }, "conflict": { "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", @@ -58,7 +64,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bridge/Monolog/CHANGELOG.md b/src/Symfony/Bridge/Monolog/CHANGELOG.md index b56fc6d70d864..6d984c218a4d6 100644 --- a/src/Symfony/Bridge/Monolog/CHANGELOG.md +++ b/src/Symfony/Bridge/Monolog/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + + * added `ProcessorInterface`: an optional interface to allow autoconfiguration of Monolog processors + 4.1.0 ----- diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php index 2f60299881719..ba5caf06ea115 100644 --- a/src/Symfony/Bridge/Monolog/Logger.php +++ b/src/Symfony/Bridge/Monolog/Logger.php @@ -14,13 +14,14 @@ use Monolog\Logger as BaseLogger; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Contracts\Service\ResetInterface; /** * Logger. * * @author Fabien Potencier */ -class Logger extends BaseLogger implements DebugLoggerInterface +class Logger extends BaseLogger implements DebugLoggerInterface, ResetInterface { /** * {@inheritdoc} @@ -56,6 +57,14 @@ public function clear() } } + /** + * {@inheritdoc} + */ + public function reset() + { + $this->clear(); + } + /** * Returns a DebugLoggerInterface instance if one is registered with this logger. * diff --git a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php index a6998517e7003..13f8a2de3e48a 100644 --- a/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/DebugProcessor.php @@ -15,8 +15,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Contracts\Service\ResetInterface; -class DebugProcessor implements DebugLoggerInterface +class DebugProcessor implements DebugLoggerInterface, ResetInterface { private $records = array(); private $errorCount = array(); @@ -91,4 +92,12 @@ public function clear() $this->records = array(); $this->errorCount = array(); } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->clear(); + } } diff --git a/src/Symfony/Bridge/Monolog/Processor/ProcessorInterface.php b/src/Symfony/Bridge/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 0000000000000..3b470992fc2f8 --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Processor/ProcessorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Monolog\Processor; + +/** + * An optional interface to allow autoconfiguration of Monolog processors. + * + * @author Nicolas Grekas + */ +interface ProcessorInterface +{ + /** + * @return array The processed records + */ + public function __invoke(array $records); +} diff --git a/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php b/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php index 11547be22b2ee..87c52c6a0d39f 100644 --- a/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php @@ -18,7 +18,7 @@ * * @author Dany Maillard */ -class TokenProcessor +class TokenProcessor implements ProcessorInterface { private $tokenStorage; diff --git a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php index 9c32e756c514e..671e7e57286af 100644 --- a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php @@ -21,7 +21,7 @@ * * @author Jordi Boggiano */ -class WebProcessor extends BaseWebProcessor implements EventSubscriberInterface +class WebProcessor extends BaseWebProcessor implements EventSubscriberInterface, ProcessorInterface { public function __construct(array $extraFields = null) { diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php index 92b319f13a989..b22299c11bf7c 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php @@ -180,19 +180,19 @@ public function testLogsFromListeners() $dispatcher = new EventDispatcher(); $dispatcher->addListener(ConsoleEvents::COMMAND, function () use ($logger) { - $logger->addInfo('Before command message.'); + $logger->info('Before command message.'); }); $dispatcher->addListener(ConsoleEvents::TERMINATE, function () use ($logger) { - $logger->addInfo('Before terminate message.'); + $logger->info('Before terminate message.'); }); $dispatcher->addSubscriber($handler); $dispatcher->addListener(ConsoleEvents::COMMAND, function () use ($logger) { - $logger->addInfo('After command message.'); + $logger->info('After command message.'); }); $dispatcher->addListener(ConsoleEvents::TERMINATE, function () use ($logger) { - $logger->addInfo('After terminate message.'); + $logger->info('After terminate message.'); }); $event = new ConsoleCommandEvent(new Command('foo'), $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(), $output); diff --git a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php index 193cde6728eca..46156adbd8b79 100644 --- a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php @@ -24,7 +24,7 @@ public function testGetLogsWithoutDebugProcessor() $handler = new TestHandler(); $logger = new Logger(__METHOD__, array($handler)); - $this->assertTrue($logger->error('error message')); + $logger->error('error message'); $this->assertSame(array(), $logger->getLogs()); } @@ -33,7 +33,7 @@ public function testCountErrorsWithoutDebugProcessor() $handler = new TestHandler(); $logger = new Logger(__METHOD__, array($handler)); - $this->assertTrue($logger->error('error message')); + $logger->error('error message'); $this->assertSame(0, $logger->countErrors()); } @@ -43,7 +43,7 @@ public function testGetLogsWithDebugProcessor() $processor = new DebugProcessor(); $logger = new Logger(__METHOD__, array($handler), array($processor)); - $this->assertTrue($logger->error('error message')); + $logger->error('error message'); $this->assertCount(1, $logger->getLogs()); } @@ -53,15 +53,15 @@ public function testCountErrorsWithDebugProcessor() $processor = new DebugProcessor(); $logger = new Logger(__METHOD__, array($handler), array($processor)); - $this->assertTrue($logger->debug('test message')); - $this->assertTrue($logger->info('test message')); - $this->assertTrue($logger->notice('test message')); - $this->assertTrue($logger->warning('test message')); + $logger->debug('test message'); + $logger->info('test message'); + $logger->notice('test message'); + $logger->warning('test message'); - $this->assertTrue($logger->error('test message')); - $this->assertTrue($logger->critical('test message')); - $this->assertTrue($logger->alert('test message')); - $this->assertTrue($logger->emergency('test message')); + $logger->error('test message'); + $logger->critical('test message'); + $logger->alert('test message'); + $logger->emergency('test message'); $this->assertSame(4, $logger->countErrors()); } @@ -72,7 +72,7 @@ public function testGetLogsWithDebugProcessor2() $logger = new Logger('test', array($handler)); $logger->pushProcessor(new DebugProcessor()); - $logger->addInfo('test'); + $logger->info('test'); $this->assertCount(1, $logger->getLogs()); list($record) = $logger->getLogs(); @@ -101,7 +101,7 @@ public function testClear() $logger = new Logger('test', array($handler)); $logger->pushProcessor(new DebugProcessor()); - $logger->addInfo('test'); + $logger->info('test'); $logger->clear(); $this->assertEmpty($logger->getLogs()); diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index a4d3e984100c9..535c2d23bcea3 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -18,6 +18,7 @@ "require": { "php": "^7.1.3", "monolog/monolog": "~1.19", + "symfony/contracts": "^1.0", "symfony/http-kernel": "~3.4|~4.0" }, "require-dev": { @@ -44,7 +45,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 7338fca00db74..4d72603eb452f 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -40,7 +40,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" }, "thanks": { "name": "phpunit/phpunit", diff --git a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md index 56c8b20e28272..3435a4a186494 100644 --- a/src/Symfony/Bridge/ProxyManager/CHANGELOG.md +++ b/src/Symfony/Bridge/ProxyManager/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + + * allowed creating lazy-proxies from interfaces + 3.3.0 ----- diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php similarity index 85% rename from src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php rename to src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php index a643f33710dd2..caea93ac5d9f4 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactory.php @@ -18,14 +18,14 @@ /** * @internal */ -class LazyLoadingValueHolderFactoryV2 extends BaseFactory +class LazyLoadingValueHolderFactory extends BaseFactory { private $generator; /** * {@inheritdoc} */ - protected function getGenerator(): ProxyGeneratorInterface + public function getGenerator(): ProxyGeneratorInterface { return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator(); } diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php deleted file mode 100644 index 3298b84d46278..0000000000000 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; - -use ProxyManager\Factory\LazyLoadingValueHolderFactory as BaseFactory; -use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator; - -/** - * @internal - */ -class LazyLoadingValueHolderFactoryV1 extends BaseFactory -{ - private $generatorV1; - - /** - * {@inheritdoc} - */ - protected function getGenerator() - { - return $this->generatorV1 ?: $this->generatorV1 = new LazyLoadingValueHolderGenerator(); - } -} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 7d083a6981e25..cc68d65058839 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -12,7 +12,6 @@ namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; use ProxyManager\Configuration; -use ProxyManager\Factory\LazyLoadingValueHolderFactory; use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; use ProxyManager\Proxy\LazyLoadingInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -26,9 +25,6 @@ */ class RuntimeInstantiator implements InstantiatorInterface { - /** - * @var LazyLoadingValueHolderFactory - */ private $factory; public function __construct() @@ -36,11 +32,7 @@ public function __construct() $config = new Configuration(); $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); - if (method_exists('ProxyManager\Version', 'getVersion')) { - $this->factory = new LazyLoadingValueHolderFactoryV2($config); - } else { - $this->factory = new LazyLoadingValueHolderFactoryV1($config); - } + $this->factory = new LazyLoadingValueHolderFactory($config); } /** @@ -49,7 +41,7 @@ public function __construct() public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator) { return $this->factory->createProxy( - $definition->getClass(), + $this->factory->getGenerator()->getProxifiedClass($definition), function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) { $wrappedInstance = \call_user_func($realInstantiator); diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php index 1d9432f622b41..356fd44cda454 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper; use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator; +use Symfony\Component\DependencyInjection\Definition; use Zend\Code\Generator\ClassGenerator; /** @@ -19,6 +20,13 @@ */ class LazyLoadingValueHolderGenerator extends BaseGenerator { + private $fluentSafe = false; + + public function setFluentSafe(bool $fluentSafe) + { + $this->fluentSafe = $fluentSafe; + } + /** * {@inheritdoc} */ @@ -26,6 +34,52 @@ public function generate(\ReflectionClass $originalClass, ClassGenerator $classG { parent::generate($originalClass, $classGenerator); + foreach ($classGenerator->getMethods() as $method) { + $body = preg_replace( + '/(\$this->initializer[0-9a-f]++) && \1->__invoke\(\$this->(valueHolder[0-9a-f]++), (.*?), \1\);/', + '$1 && ($1->__invoke(\$$2, $3, $1) || 1) && $this->$2 = \$$2;', + $method->getBody() + ); + $body = str_replace('(new \ReflectionClass(get_class()))', '$reflection', $body); + + if ($originalClass->isInterface()) { + $body = str_replace('get_parent_class($this)', var_export($originalClass->name, true), $body); + $body = preg_replace_callback('/\n\n\$realInstanceReflection = [^{]++\{([^}]++)\}\n\n.*/s', function ($m) { + $r = ''; + foreach (explode("\n", $m[1]) as $line) { + $r .= "\n".substr($line, 4); + if (0 === strpos($line, ' return ')) { + break; + } + } + + return $r; + }, $body); + } + + if ($this->fluentSafe) { + $indent = $method->getIndentation(); + $method->setIndentation(''); + $code = $method->generate(); + if (null !== $docBlock = $method->getDocBlock()) { + $code = substr($code, \strlen($docBlock->generate())); + } + $refAmp = (strpos($code, '&') ?: \PHP_INT_MAX) <= strpos($code, '(') ? '&' : ''; + $body = preg_replace( + '/\nreturn (\$this->valueHolder[0-9a-f]++)(->[^;]++);$/', + "\nif ($1 === \$returnValue = {$refAmp}$1$2) {\n \$returnValue = \$this;\n}\n\nreturn \$returnValue;", + $body + ); + $method->setIndentation($indent); + } + + if (0 === strpos($originalClass->getFilename(), __FILE__)) { + $body = str_replace(var_export($originalClass->name, true), '__CLASS__', $body); + } + + $method->setBody($body); + } + if ($classGenerator->hasMethod('__destruct')) { $destructor = $classGenerator->getMethod('__destruct'); $body = $destructor->getBody(); @@ -37,5 +91,53 @@ public function generate(\ReflectionClass $originalClass, ClassGenerator $classG $destructor->setBody($newBody); } + + if (0 === strpos($originalClass->getFilename(), __FILE__)) { + $interfaces = $classGenerator->getImplementedInterfaces(); + array_pop($interfaces); + $classGenerator->setImplementedInterfaces(array_merge($interfaces, $originalClass->getInterfaceNames())); + } + } + + public function getProxifiedClass(Definition $definition): ?string + { + if (!$definition->hasTag('proxy')) { + return \class_exists($class = $definition->getClass()) || \interface_exists($class) ? $class : null; + } + if (!$definition->isLazy()) { + throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": setting the "proxy" tag on a service requires it to be "lazy".', $definition->getClass())); + } + $tags = $definition->getTag('proxy'); + if (!isset($tags[0]['interface'])) { + throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": the "interface" attribute is missing on the "proxy" tag.', $definition->getClass())); + } + if (1 === \count($tags)) { + return \class_exists($tags[0]['interface']) || \interface_exists($tags[0]['interface']) ? $tags[0]['interface'] : null; + } + + $proxyInterface = 'LazyProxy'; + $interfaces = ''; + foreach ($tags as $tag) { + if (!isset($tag['interface'])) { + throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": the "interface" attribute is missing on a "proxy" tag.', $definition->getClass())); + } + if (!\interface_exists($tag['interface'])) { + throw new \InvalidArgumentException(sprintf('Invalid definition for service of class "%s": several "proxy" tags found but "%s" is not an interface.', $definition->getClass(), $tag['interface'])); + } + + $proxyInterface .= '\\'.$tag['interface']; + $interfaces .= ', \\'.$tag['interface']; + } + + if (!\interface_exists($proxyInterface)) { + $i = strrpos($proxyInterface, '\\'); + $namespace = substr($proxyInterface, 0, $i); + $interface = substr($proxyInterface, 1 + $i); + $interfaces = substr($interfaces, 2); + + eval("namespace {$namespace}; interface {$interface} extends {$interfaces} {}"); + } + + return $proxyInterface; } } diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index 18da9118a2226..208639a4b1ec3 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -14,7 +14,6 @@ use ProxyManager\Generator\ClassGenerator; use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; use ProxyManager\Version; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; @@ -43,7 +42,7 @@ public function __construct(string $salt = '') */ public function isProxyCandidate(Definition $definition) { - return $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class); + return ($definition->isLazy() || $definition->hasTag('proxy')) && $this->proxyGenerator->getProxifiedClass($definition); } /** @@ -54,7 +53,7 @@ public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = $instantiation = 'return'; if ($definition->isShared()) { - $instantiation .= sprintf(' $this->%s[\'%s\'] =', \method_exists(ContainerBuilder::class, 'addClassResource') || ($definition->isPublic() && !$definition->isPrivate()) ? 'services' : 'privates', $id); + $instantiation .= sprintf(' $this->%s[\'%s\'] =', $definition->isPublic() && !$definition->isPrivate() ? 'services' : 'privates', $id); } if (null === $factoryCode) { @@ -63,14 +62,10 @@ public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = $proxyClass = $this->getProxyClassName($definition); - $hasStaticConstructor = $this->generateProxyClass($definition)->hasMethod('staticProxyConstructor'); - - $constructorCall = sprintf($hasStaticConstructor ? '%s::staticProxyConstructor' : 'new %s', '\\'.$proxyClass); - return <<createProxy('$proxyClass', function () { - return $constructorCall(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) { + return \\$proxyClass::staticProxyConstructor(function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) { \$wrappedInstance = $factoryCode; \$proxy->setProxyInitializer(null); @@ -91,12 +86,6 @@ public function getProxyCode(Definition $definition) { $code = $this->classGenerator->generate($this->generateProxyClass($definition)); - $code = preg_replace( - '/(\$this->initializer[0-9a-f]++) && \1->__invoke\(\$this->(valueHolder[0-9a-f]++), (.*?), \1\);/', - '$1 && ($1->__invoke(\$$2, $3, $1) || 1) && $this->$2 = \$$2;', - $code - ); - if (version_compare(self::getProxyManagerVersion(), '2.2', '<')) { $code = preg_replace( '/((?:\$(?:this|initializer|instance)->)?(?:publicProperties|initializer|valueHolder))[0-9a-f]++/', @@ -122,20 +111,26 @@ private static function getProxyManagerVersion(): string */ private function getProxyClassName(Definition $definition): string { - return preg_replace('/^.*\\\\/', '', $definition->getClass()).'_'.$this->getIdentifierSuffix($definition); + $class = $this->proxyGenerator->getProxifiedClass($definition); + + return preg_replace('/^.*\\\\/', '', $class).'_'.$this->getIdentifierSuffix($definition); } private function generateProxyClass(Definition $definition): ClassGenerator { $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); + $class = $this->proxyGenerator->getProxifiedClass($definition); - $this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass); + $this->proxyGenerator->setFluentSafe($definition->hasTag('proxy')); + $this->proxyGenerator->generate(new \ReflectionClass($class), $generatedClass); return $generatedClass; } private function getIdentifierSuffix(Definition $definition): string { - return substr(hash('sha256', $definition->getClass().$this->salt), -7); + $class = $this->proxyGenerator->getProxifiedClass($definition); + + return substr(hash('sha256', $class.$this->salt), -7); } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php new file mode 100644 index 0000000000000..648eb36d4b598 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-factory.php @@ -0,0 +1,31 @@ +privates['foo'] = $this->createProxy('SunnyInterface_1eff735', function () { + return \SunnyInterface_1eff735::staticProxyConstructor(function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) { + $wrappedInstance = $this->getFooService(false); + + $proxy->setProxyInitializer(null); + + return true; + }); + }); + } + + return new Symfony\Bridge\ProxyManager\Tests\LazyProxy\PhpDumper\DummyClass(); + } + + protected function createProxy($class, \Closure $factory) + { + $this->proxyClass = $class; + + return $factory(); + } +}; diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php new file mode 100644 index 0000000000000..1b5ea69c907e7 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures/proxy-implem.php @@ -0,0 +1,165 @@ +initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, 'dummy', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + if ($this->valueHolder1eff735 === $returnValue = $this->valueHolder1eff735->dummy()) { + $returnValue = $this; + } + + return $returnValue; + } + + public function & dummyRef() + { + $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, 'dummyRef', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + if ($this->valueHolder1eff735 === $returnValue = &$this->valueHolder1eff735->dummyRef()) { + $returnValue = $this; + } + + return $returnValue; + } + + public function sunny() + { + $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, 'sunny', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + if ($this->valueHolder1eff735 === $returnValue = $this->valueHolder1eff735->sunny()) { + $returnValue = $this; + } + + return $returnValue; + } + + public static function staticProxyConstructor($initializer) + { + static $reflection; + + $reflection = $reflection ?: $reflection = new \ReflectionClass(__CLASS__); + $instance = $reflection->newInstanceWithoutConstructor(); + + $instance->initializer1eff735 = $initializer; + + return $instance; + } + + public function __construct() + { + static $reflection; + + if (! $this->valueHolder1eff735) { + $reflection = $reflection ?: new \ReflectionClass(__CLASS__); + $this->valueHolder1eff735 = $reflection->newInstanceWithoutConstructor(); + } + } + + public function & __get($name) + { + $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__get', ['name' => $name], $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + if (isset(self::$publicProperties1eff735[$name])) { + return $this->valueHolder1eff735->$name; + } + + $targetObject = $this->valueHolder1eff735; + + $backtrace = debug_backtrace(false); + trigger_error( + sprintf( + 'Undefined property: %s::$%s in %s on line %s', + __CLASS__, + $name, + $backtrace[0]['file'], + $backtrace[0]['line'] + ), + \E_USER_NOTICE + ); + return $targetObject->$name; + } + + public function __set($name, $value) + { + $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__set', array('name' => $name, 'value' => $value), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + $targetObject = $this->valueHolder1eff735; + + return $targetObject->$name = $value; + } + + public function __isset($name) + { + $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__isset', array('name' => $name), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + $targetObject = $this->valueHolder1eff735; + + return isset($targetObject->$name); + } + + public function __unset($name) + { + $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__unset', array('name' => $name), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + $targetObject = $this->valueHolder1eff735; + + unset($targetObject->$name); +return; + } + + public function __clone() + { + $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__clone', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + $this->valueHolder1eff735 = clone $this->valueHolder1eff735; + } + + public function __sleep() + { + $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, '__sleep', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + + return array('valueHolder1eff735'); + } + + public function __wakeup() + { + } + + public function setProxyInitializer(\Closure $initializer = null) + { + $this->initializer1eff735 = $initializer; + } + + public function getProxyInitializer() + { + return $this->initializer1eff735; + } + + public function initializeProxy() : bool + { + return $this->initializer1eff735 && ($this->initializer1eff735->__invoke($valueHolder1eff735, $this, 'initializeProxy', array(), $this->initializer1eff735) || 1) && $this->valueHolder1eff735 = $valueHolder1eff735; + } + + public function isProxyInitialized() : bool + { + return null !== $this->valueHolder1eff735; + } + + public function getWrappedValueHolderValue() + { + return $this->valueHolder1eff735; + } + + +} diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index e12b25fcb02f8..b1dc1cf66a2ea 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; /** @@ -99,7 +98,7 @@ public function getPrivatePublicDefinitions() array( (new Definition(__CLASS__)) ->setPublic(false), - \method_exists(ContainerBuilder::class, 'addClassResource') ? 'services' : 'privates', + 'privates', ), array( (new Definition(__CLASS__)) @@ -120,6 +119,61 @@ public function testGetProxyFactoryCodeWithoutCustomMethod() $this->dumper->getProxyFactoryCode($definition, 'foo'); } + public function testGetProxyFactoryCodeForInterface() + { + $class = DummyClass::class; + $definition = new Definition($class); + + $definition->setLazy(true); + $definition->addTag('proxy', array('interface' => DummyInterface::class)); + $definition->addTag('proxy', array('interface' => SunnyInterface::class)); + + $implem = "dumper->getProxyCode($definition); + $factory = $this->dumper->getProxyFactoryCode($definition, 'foo', '$this->getFooService(false)'); + $factory = <<proxyClass = \$class; + + return \$factory(); + } +}; + +EOPHP; + + $implem = preg_replace('#\n /\*\*.*?\*/#s', '', $implem); + $implem = str_replace('getWrappedValueHolderValue() : ?object', 'getWrappedValueHolderValue()', $implem); + $implem = str_replace("array(\n \n );", "[\n \n ];", $implem); + $this->assertStringEqualsFile(__DIR__.'/Fixtures/proxy-implem.php', $implem); + $this->assertStringEqualsFile(__DIR__.'/Fixtures/proxy-factory.php', $factory); + + require_once __DIR__.'/Fixtures/proxy-implem.php'; + $factory = require __DIR__.'/Fixtures/proxy-factory.php'; + + $foo = $factory->getFooService(); + + $this->assertInstanceof($factory->proxyClass, $foo); + $this->assertInstanceof(DummyInterface::class, $foo); + $this->assertInstanceof(SunnyInterface::class, $foo); + $this->assertNotInstanceof(DummyClass::class, $foo); + $this->assertSame($foo, $foo->dummy()); + + $foo->dynamicProp = 123; + $this->assertSame(123, @$foo->dynamicProp); + } + /** * @return array */ @@ -142,3 +196,34 @@ function ($definition) { return $definitions; } } + +final class DummyClass implements DummyInterface, SunnyInterface +{ + public function dummy() + { + return $this; + } + + public function sunny() + { + } + + public function &dummyRef() + { + return $this->ref; + } +} + +interface DummyInterface +{ + public function dummy(); + + public function &dummyRef(); +} + +interface SunnyInterface +{ + public function dummy(); + + public function sunny(); +} diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 90f000c828618..16b95e8318104 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -17,8 +17,8 @@ ], "require": { "php": "^7.1.3", - "symfony/dependency-injection": "~3.4|~4.0", - "ocramius/proxy-manager": "~0.4|~1.0|~2.0" + "symfony/dependency-injection": "~4.0", + "ocramius/proxy-manager": "~2.1" }, "require-dev": { "symfony/config": "~3.4|~4.0" @@ -32,7 +32,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index fcdb5e2756086..fd0a0fa54b4fc 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.2.0 +----- + +* add bundle name suggestion on wrongly overridden templates paths + 4.1.0 ----- diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index 6f40182405369..82d7b8523ab1e 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -31,13 +31,19 @@ class DebugCommand extends Command private $twig; private $projectDir; + private $bundlesMetadata; + private $twigDefaultPath; + private $rootDir; - public function __construct(Environment $twig, string $projectDir = null) + public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = array(), string $twigDefaultPath = null, string $rootDir = null) { parent::__construct(); $this->twig = $twig; $this->projectDir = $projectDir; + $this->bundlesMetadata = $bundlesMetadata; + $this->twigDefaultPath = $twigDefaultPath; + $this->rootDir = $rootDir; } protected function configure() @@ -82,6 +88,10 @@ protected function execute(InputInterface $input, OutputInterface $output) } $data['tests'] = array_keys($data['tests']); $data['loader_paths'] = $this->getLoaderPaths(); + if ($wrongBundles = $this->findWrongBundleOverrides()) { + $data['warnings'] = $this->buildWarningMessages($wrongBundles); + } + $io->writeln(json_encode($data)); return 0; @@ -131,6 +141,10 @@ protected function execute(InputInterface $input, OutputInterface $output) } $io->section('Loader Paths'); $io->table(array('Namespace', 'Paths'), $rows); + $messages = $this->buildWarningMessages($this->findWrongBundleOverrides()); + foreach ($messages as $message) { + $io->warning($message); + } return 0; } @@ -250,4 +264,85 @@ private function getPrettyMetadata($type, $entity) return $meta ? '('.implode(', ', $meta).')' : ''; } } + + private function findWrongBundleOverrides(): array + { + $alternatives = array(); + $bundleNames = array(); + + if ($this->rootDir && $this->projectDir) { + $folders = glob($this->rootDir.'/Resources/*/views', GLOB_ONLYDIR); + $relativePath = ltrim(substr($this->rootDir.'/Resources/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + $bundleNames = array_reduce( + $folders, + function ($carry, $absolutePath) use ($relativePath) { + if (0 === strpos($absolutePath, $this->projectDir)) { + $name = basename(\dirname($absolutePath)); + $path = $relativePath.$name; + $carry[$name] = $path; + } + + return $carry; + }, + $bundleNames + ); + } + + if ($this->twigDefaultPath && $this->projectDir) { + $folders = glob($this->twigDefaultPath.'/bundles/*', GLOB_ONLYDIR); + $relativePath = ltrim(substr($this->twigDefaultPath.'/bundles', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + $bundleNames = array_reduce( + $folders, + function ($carry, $absolutePath) use ($relativePath) { + if (0 === strpos($absolutePath, $this->projectDir)) { + $path = ltrim(substr($absolutePath, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + $name = ltrim(substr($path, \strlen($relativePath)), \DIRECTORY_SEPARATOR); + $carry[$name] = $path; + } + + return $carry; + }, + $bundleNames + ); + } + + if (\count($bundleNames)) { + $notFoundBundles = array_diff_key($bundleNames, $this->bundlesMetadata); + if (\count($notFoundBundles)) { + $alternatives = array(); + foreach ($notFoundBundles as $notFoundBundle => $path) { + $alternatives[$path] = array(); + foreach ($this->bundlesMetadata as $name => $bundle) { + $lev = levenshtein($notFoundBundle, $name); + if ($lev <= \strlen($notFoundBundle) / 3 || false !== strpos($name, $notFoundBundle)) { + $alternatives[$path][] = $name; + } + } + } + } + } + + return $alternatives; + } + + private function buildWarningMessages(array $wrongBundles): array + { + $messages = array(); + foreach ($wrongBundles as $path => $alternatives) { + $message = sprintf('Path "%s" not matching any bundle found', $path); + if ($alternatives) { + if (1 === \count($alternatives)) { + $message .= sprintf(", did you mean \"%s\"?\n", $alternatives[0]); + } else { + $message .= ", did you mean one of these:\n"; + foreach ($alternatives as $bundle) { + $message .= sprintf(" - %s\n", $bundle); + } + } + } + $messages[] = trim($message); + } + + return $messages; + } } diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index f84eaeb7e72e9..cd00f44d57b00 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -25,17 +25,19 @@ class CodeExtension extends AbstractExtension private $fileLinkFormat; private $rootDir; private $charset; + private $projectDir; /** * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files * @param string $rootDir The project root directory * @param string $charset The charset */ - public function __construct($fileLinkFormat, string $rootDir, string $charset) + public function __construct($fileLinkFormat, string $rootDir, string $charset, string $projectDir = null) { $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); $this->rootDir = str_replace('/', \DIRECTORY_SEPARATOR, \dirname($rootDir)).\DIRECTORY_SEPARATOR; $this->charset = $charset; + $this->projectDir = $projectDir; } /** @@ -53,6 +55,7 @@ public function getFilters() new TwigFilter('format_file_from_text', array($this, 'formatFileFromText'), array('is_safe' => array('html'))), new TwigFilter('format_log_message', array($this, 'formatLogMessage'), array('is_safe' => array('html'))), new TwigFilter('file_link', array($this, 'getFileLink')), + new TwigFilter('file_relative', array($this, 'getFileRelative')), ); } @@ -209,6 +212,15 @@ public function getFileLink($file, $line) return false; } + public function getFileRelative(string $file): ?string + { + if (null !== $this->projectDir && 0 === strpos($file, $this->projectDir)) { + return ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + + return null; + } + public function formatFileFromText($text) { return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 0094a1cde1261..02c86fddc2246 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -398,7 +398,7 @@ {# Support #} {%- block form_rows -%} - {% for child in form %} + {% for child in form if not child.rendered %} {{- form_row(child) -}} {% endfor %} {%- endblock form_rows -%} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php index 336991c6ca9f2..c152ba6d60137 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php @@ -23,6 +23,11 @@ public function testFormatFile() $this->assertEquals($expected, $this->getExtension()->formatFile(__FILE__, 25)); } + public function testFileRelative() + { + $this->assertEquals('CodeExtensionTest.php', $this->getExtension()->getFileRelative(__FILE__)); + } + /** * @dataProvider getClassNameProvider */ @@ -64,6 +69,6 @@ public function testGetName() protected function getExtension() { - return new CodeExtension(new FileLinkFormatter('proto://%f#&line=%l&'.substr(__FILE__, 0, 5).'>foobar'), '/root', 'UTF-8'); + return new CodeExtension(new FileLinkFormatter('proto://%f#&line=%l&'.substr(__FILE__, 0, 5).'>foobar'), '/root', 'UTF-8', __DIR__); } } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 839636282c78a..aaf5d775273f6 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -68,7 +68,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php index cbb27c479fd01..436a95c50b6f7 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php @@ -26,10 +26,9 @@ class Configuration implements ConfigurationInterface */ public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('debug'); + $treeBuilder = new TreeBuilder('debug'); - $rootNode + $treeBuilder->getRootNode() ->children() ->integerNode('max_items') ->info('Max number of displayed items past the first level, -1 means no limit') diff --git a/src/Symfony/Bundle/DebugBundle/composer.json b/src/Symfony/Bundle/DebugBundle/composer.json index e909b45814f7f..535d0edfa8050 100644 --- a/src/Symfony/Bundle/DebugBundle/composer.json +++ b/src/Symfony/Bundle/DebugBundle/composer.json @@ -23,11 +23,12 @@ "symfony/var-dumper": "^4.1.1" }, "require-dev": { - "symfony/config": "~3.4|~4.0", + "symfony/config": "~4.2", "symfony/dependency-injection": "~3.4|~4.0", "symfony/web-profiler-bundle": "~3.4|~4.0" }, "conflict": { + "symfony/config": "<4.2", "symfony/dependency-injection": "<3.4" }, "suggest": { @@ -43,7 +44,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index b03567d68e35a..e285ebf8a13cb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +4.2.0 +----- + + * Allowed configuring taggable cache pools via a new `framework.cache.pools.tags` option (bool|service-id) + * Allowed configuring PDO-based cache pools via a new `cache.adapter.pdo` abstract service + * Deprecated auto-injection of the container in AbstractController instances, register them as service subscribers instead + * Deprecated processing of services tagged `security.expression_language_provider` in favor of a new `AddExpressionLanguageProvidersPass` in SecurityBundle. + 4.1.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index f599a5ceef9f3..b2dcc5f238e63 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -334,6 +335,8 @@ protected function describeContainerDefinition(Definition $definition, array $op $argumentsInformation[] = sprintf('Service(%s)', (string) $argument); } elseif ($argument instanceof IteratorArgument) { $argumentsInformation[] = sprintf('Iterator (%d element(s))', \count($argument->getValues())); + } elseif ($argument instanceof ServiceLocatorArgument) { + $argumentsInformation[] = sprintf('Service locator (%d element(s))', \count($argument->getValues())); } elseif ($argument instanceof Definition) { $argumentsInformation[] = 'Inlined Service'; } else { @@ -352,7 +355,11 @@ protected function describeContainerDefinition(Definition $definition, array $op */ protected function describeContainerAlias(Alias $alias, array $options = array(), ContainerBuilder $builder = null) { - $options['output']->comment(sprintf('This service is an alias for the service %s', (string) $alias)); + if ($alias->isPublic()) { + $options['output']->comment(sprintf('This service is a public alias for the service %s', (string) $alias)); + } else { + $options['output']->comment(sprintf('This service is a private alias for the service %s', (string) $alias)); + } if (!$builder) { return; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index bdeb94cdd0d6b..385444d228ab0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -387,8 +388,8 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom) if ($argument instanceof Reference) { $argumentXML->setAttribute('type', 'service'); $argumentXML->setAttribute('id', (string) $argument); - } elseif ($argument instanceof IteratorArgument) { - $argumentXML->setAttribute('type', 'iterator'); + } elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) { + $argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator'); foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php index c782e2a718c46..552704f20d42d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php @@ -51,16 +51,22 @@ protected function createController($controller) */ protected function instantiateController($class) { - return $this->configureController(parent::instantiateController($class)); + return $this->configureController(parent::instantiateController($class), $class); } - private function configureController($controller) + private function configureController($controller, string $class) { if ($controller instanceof ContainerAwareInterface) { $controller->setContainer($this->container); } - if ($controller instanceof AbstractController && null !== $previousContainer = $controller->setContainer($this->container)) { - $controller->setContainer($previousContainer); + if ($controller instanceof AbstractController) { + if (null === $previousContainer = $controller->setContainer($this->container)) { + @trigger_error(sprintf('Auto-injection of the container for "%s" is deprecated since Symfony 4.2. Configure it as a service instead.', $class), E_USER_DEPRECATED); + // To be uncommented on Symfony 5: + //throw new \LogicException(sprintf('"%s" has no container set, did you forget to define it as a service subscriber?', $class)); + } else { + $controller->setContainer($previousContainer); + } } return $controller; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php index a5bdfd626faec..b1bc914f1819b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass as SecurityExpressionLanguageProvidersPass; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -22,6 +23,17 @@ */ class AddExpressionLanguageProvidersPass implements CompilerPassInterface { + private $handleSecurityLanguageProviders; + + public function __construct(bool $handleSecurityLanguageProviders = true) + { + if ($handleSecurityLanguageProviders) { + @trigger_error(sprintf('Registering services tagged "security.expression_language_provider" with "%s" is deprecated since Symfony 4.2, use the "%s" instead.', __CLASS__, SecurityExpressionLanguageProvidersPass::class), E_USER_DEPRECATED); + } + + $this->handleSecurityLanguageProviders = $handleSecurityLanguageProviders; + } + /** * {@inheritdoc} */ @@ -36,7 +48,7 @@ public function process(ContainerBuilder $container) } // security - if ($container->has('security.expression_language')) { + if ($this->handleSecurityLanguageProviders && $container->has('security.expression_language')) { $definition = $container->findDefinition('security.expression_language'); foreach ($container->findTaggedServiceIds('security.expression_language_provider', true) as $id => $attributes) { $definition->addMethodCall('registerProvider', array(new Reference($id))); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php index bd6908b9c4507..d0d3665a617d8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php @@ -30,9 +30,9 @@ public function process(ContainerBuilder $container) foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) { $clearer = $container->getDefinition($id); $pools = array(); - foreach ($clearer->getArgument(0) as $id => $ref) { - if ($container->hasDefinition($id)) { - $pools[$id] = new Reference($id); + foreach ($clearer->getArgument(0) as $name => $ref) { + if ($container->hasDefinition($ref)) { + $pools[$name] = new Reference($ref); } } $clearer->replaceArgument(0, $pools); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php index b515bcb69c1e4..583c3b80ea347 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php @@ -41,6 +41,7 @@ public function process(ContainerBuilder $container) $clearers = array(); $attributes = array( 'provider', + 'name', 'namespace', 'default_lifetime', 'reset', @@ -56,8 +57,9 @@ public function process(ContainerBuilder $container) $tags[0] += $t[0]; } } + $name = $tags[0]['name'] ?? $id; if (!isset($tags[0]['namespace'])) { - $tags[0]['namespace'] = $this->getNamespace($seed, $id); + $tags[0]['namespace'] = $this->getNamespace($seed, $name); } if (isset($tags[0]['clearer'])) { $clearer = $tags[0]['clearer']; @@ -67,7 +69,7 @@ public function process(ContainerBuilder $container) } else { $clearer = null; } - unset($tags[0]['clearer']); + unset($tags[0]['clearer'], $tags[0]['name']); if (isset($tags[0]['provider'])) { $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); @@ -86,14 +88,14 @@ public function process(ContainerBuilder $container) unset($tags[0][$attr]); } if (!empty($tags[0])) { - throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "namespace", "default_lifetime" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0])))); + throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0])))); } if (null !== $clearer) { - $clearers[$clearer][$id] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); } - $pools[$id] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); + $pools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); } $clearer = 'cache.global_clearer'; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php index 060d234d38772..a7d6986fa9d61 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php @@ -11,8 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -32,7 +32,7 @@ public function process(ContainerBuilder $container) foreach ($definitions as $id => $definition) { if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate()) && !$definition->getErrors() && !$definition->isAbstract()) { - $privateServices[$id] = new ServiceClosureArgument(new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE)); + $privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } @@ -44,13 +44,15 @@ public function process(ContainerBuilder $container) $alias = $aliases[$target]; } if (isset($definitions[$target]) && !$definitions[$target]->getErrors() && !$definitions[$target]->isAbstract()) { - $privateServices[$id] = new ServiceClosureArgument(new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE)); + $privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); } } } if ($privateServices) { - $definitions['test.private_services_locator']->replaceArgument(0, $privateServices); + $id = (string) ServiceLocatorTagPass::register($container, $privateServices); + $container->setDefinition('test.private_services_locator', $container->getDefinition($id))->setPublic(true); + $container->removeDefinition($id); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 14c97cfaef24a..00328b4b5fbee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -44,6 +44,7 @@ class UnusedTagsPass implements CompilerPassInterface 'messenger.receiver', 'messenger.message_handler', 'monolog.logger', + 'proxy', 'routing.expression_language_provider', 'routing.loader', 'security.expression_language_provider', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 327166cf650c1..1766366252147 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -22,6 +22,7 @@ use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; @@ -53,8 +54,8 @@ public function __construct(bool $debug) */ public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('framework'); + $treeBuilder = new TreeBuilder('framework'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->beforeNormalization() @@ -370,7 +371,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->scalarNode('guard') ->cannotBeEmpty() ->info('An expression to block the transition') - ->example('is_fully_authenticated() and has_role(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') + ->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') ->end() ->arrayNode('from') ->beforeNormalization() @@ -451,6 +452,7 @@ private function addRouterSection(ArrayNodeDefinition $rootNode) ) ->defaultTrue() ->end() + ->booleanNode('utf8')->defaultFalse()->end() ->end() ->end() ->end() @@ -833,7 +835,7 @@ private function addPropertyInfoSection(ArrayNodeDefinition $rootNode) ->children() ->arrayNode('property_info') ->info('Property info configuration') - ->canBeEnabled() + ->{!class_exists(FullStack::class) && interface_exists(PropertyInfoExtractorInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->end() ->end() ; @@ -865,11 +867,13 @@ private function addCacheSection(ArrayNodeDefinition $rootNode) ->scalarNode('default_psr6_provider')->end() ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() + ->scalarNode('default_pdo_provider')->defaultValue('doctrine.dbal.default_connection')->end() ->arrayNode('pools') ->useAttributeAsKey('name') ->prototype('array') ->children() ->scalarNode('adapter')->defaultValue('cache.app')->end() + ->scalarNode('tags')->defaultNull()->end() ->booleanNode('public')->defaultFalse()->end() ->integerNode('default_lifetime')->end() ->scalarNode('provider') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index b08f1bbde2068..736e98e306acb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -14,6 +14,7 @@ use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\Reader; use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Symfony\Bridge\Monolog\Processor\ProcessorInterface; use Symfony\Bridge\Twig\Extension\CsrfExtension; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -22,6 +23,9 @@ use Symfony\Component\Cache\Adapter\AbstractAdapter; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderInterface; @@ -92,6 +96,7 @@ use Symfony\Component\Workflow; use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; use Symfony\Component\Yaml\Yaml; +use Symfony\Contracts\Service\ResetInterface; /** * FrameworkExtension. @@ -321,8 +326,16 @@ public function load(array $configs, ContainerBuilder $container) ->addTag('kernel.cache_warmer'); $container->registerForAutoconfiguration(EventSubscriberInterface::class) ->addTag('kernel.event_subscriber'); - $container->registerForAutoconfiguration(ResettableInterface::class) + $container->registerForAutoconfiguration(ResetInterface::class) ->addTag('kernel.reset', array('method' => 'reset')); + + if (!interface_exists(MarshallerInterface::class)) { + $container->registerForAutoconfiguration(ResettableInterface::class) + ->addTag('kernel.reset', array('method' => 'reset')); + } + + $container->registerForAutoconfiguration(ProcessorInterface::class) + ->addTag('monolog.processor'); $container->registerForAutoconfiguration(PropertyListExtractorInterface::class) ->addTag('property_info.list_extractor'); $container->registerForAutoconfiguration(PropertyTypeExtractorInterface::class) @@ -685,6 +698,9 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co $loader->load('routing.xml'); + if ($config['utf8']) { + $container->getDefinition('routing.loader')->replaceArgument(2, array('utf8' => true)); + } if (!interface_exists(ContainerBagInterface::class)) { $container->getDefinition('router.default') ->replaceArgument(0, new Reference('service_container')) @@ -1542,9 +1558,12 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder private function registerCacheConfiguration(array $config, ContainerBuilder $container) { + if (!class_exists(DefaultMarshaller::class)) { + $container->removeDefinition('cache.default_marshaller'); + } + $version = new Parameter('container.build_id'); $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version); - $container->getDefinition('cache.adapter.system')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']); if (isset($config['prefix_seed'])) { @@ -1554,7 +1573,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con // Inline any env vars referenced in the parameter $container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true)); } - foreach (array('doctrine', 'psr6', 'redis', 'memcached') as $name) { + foreach (array('doctrine', 'psr6', 'redis', 'memcached', 'pdo') as $name) { if (isset($config[$name = 'default_'.$name.'_provider'])) { $container->setAlias('cache.'.$name, new Alias(Compiler\CachePoolPass::getServiceProvider($container, $config[$name]), false)); } @@ -1563,12 +1582,31 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con $config['pools']['cache.'.$name] = array( 'adapter' => $config[$name], 'public' => true, + 'tags' => false, ); } foreach ($config['pools'] as $name => $pool) { + if ($config['pools'][$pool['adapter']]['tags'] ?? false) { + $pool['adapter'] = '.'.$pool['adapter'].'.inner'; + } $definition = new ChildDefinition($pool['adapter']); + + if ($pool['tags']) { + if ($config['pools'][$pool['tags']]['tags'] ?? false) { + $pool['tags'] = '.'.$pool['tags'].'.inner'; + } + $container->register($name, TagAwareAdapter::class) + ->addArgument(new Reference('.'.$name.'.inner')) + ->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null) + ->setPublic($pool['public']) + ; + + $pool['name'] = $name; + $pool['public'] = false; + $name = '.'.$name.'.inner'; + } $definition->setPublic($pool['public']); - unset($pool['adapter'], $pool['public']); + unset($pool['adapter'], $pool['public'], $pool['tags']); $definition->addTag('cache.pool', $pool); $container->setDefinition($name, $definition); diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 506644d5fa320..96f09e8645acf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -101,7 +101,7 @@ public function build(ContainerBuilder $container) $this->addCompilerPassIfExists($container, AddConsoleCommandPass::class, PassConfig::TYPE_BEFORE_REMOVING); $this->addCompilerPassIfExists($container, TranslatorPass::class); $container->addCompilerPass(new LoggingTranslatorPass()); - $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); $this->addCompilerPassIfExists($container, TranslationExtractorPass::class); $this->addCompilerPassIfExists($container, TranslationDumperPass::class); $container->addCompilerPass(new FragmentRendererPass()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index f7162adb1c701..533b3780cf15f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -15,6 +15,10 @@ + + + + @@ -31,15 +35,16 @@ - - + 0 - %kernel.cache_dir%/pools - + true + + + @@ -70,6 +75,7 @@ 0 %kernel.cache_dir%/pools + @@ -88,6 +94,7 @@ 0 + @@ -99,6 +106,20 @@ 0 + + + + + + + + + + + + 0 + + @@ -113,6 +134,10 @@ + + null + + @@ -124,5 +149,6 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml index a893127276564..bcf2f33b10a3a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml @@ -13,7 +13,11 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index a2e24b00c8e3c..03bac811b2553 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -48,6 +48,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 2dbe54b5d938c..eeebffb1d3ad7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -265,6 +265,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 2c0072d85d9a1..ec6128553ff73 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -32,6 +32,8 @@ + %kernel.debug% + %kernel.cache_dir%/%kernel.container_class%Deprecations.log diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index 97d3b094b0d3c..7f474bd132893 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -58,14 +58,9 @@ - - - - - - - - + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml index 09df99cfc086f..a3c2281c87831 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml @@ -24,13 +24,8 @@ - - - - - - - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 0622c4196c104..b97125375a57b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -67,6 +67,16 @@ + + + + null + null + %kernel.debug% + %kernel.charset% + %debug.file_link_format% + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rows.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rows.html.php index 8c3ba86f7a3a5..4de226f2b8b2a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rows.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/form_rows.html.php @@ -1,3 +1,5 @@ - row($child) ?> + isRendered()): ?> + row($child) ?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php index 0b77b553ffa80..f7cb2f18da4ae 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php @@ -28,14 +28,16 @@ class DelegatingLoader extends BaseDelegatingLoader { protected $parser; private $loading = false; + private $defaultOptions; /** * @param ControllerNameParser $parser A ControllerNameParser instance * @param LoaderResolverInterface $resolver A LoaderResolverInterface instance */ - public function __construct(ControllerNameParser $parser, LoaderResolverInterface $resolver) + public function __construct(ControllerNameParser $parser, LoaderResolverInterface $resolver, array $defaultOptions = array()) { $this->parser = $parser; + $this->defaultOptions = $defaultOptions; parent::__construct($resolver); } @@ -73,6 +75,9 @@ public function load($resource, $type = null) } foreach ($collection->all() as $route) { + if ($this->defaultOptions) { + $route->setOptions($route->getOptions() + $this->defaultOptions); + } if (!\is_string($controller = $route->getDefault('_controller'))) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php index a4529a657c7f2..d5d35cf4e2855 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php @@ -92,6 +92,10 @@ class_exists(AbstractControllerTest::class); $this->assertSame($container, $controller->getContainer()); } + /** + * @group legacy + * @expectedDeprecation Auto-injection of the container for "Symfony\Bundle\FrameworkBundle\Tests\Controller\TestAbstractController" is deprecated since Symfony 4.2. Configure it as a service instead. + */ public function testAbstractControllerGetsContainerWhenNotSet() { class_exists(AbstractControllerTest::class); @@ -110,6 +114,10 @@ class_exists(AbstractControllerTest::class); $this->assertSame($container, $controller->setContainer($container)); } + /** + * @group legacy + * @expectedDeprecation Auto-injection of the container for "Symfony\Bundle\FrameworkBundle\Tests\Controller\DummyController" is deprecated since Symfony 4.2. Configure it as a service instead. + */ public function testAbstractControllerServiceWithFcqnIdGetsContainerWhenNotSet() { class_exists(AbstractControllerTest::class); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php index 7f9724bddbecf..73a40128dbdd9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php @@ -22,7 +22,7 @@ class AddExpressionLanguageProvidersPassTest extends TestCase public function testProcessForRouter() { $container = new ContainerBuilder(); - $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); $definition = new Definition('\stdClass'); $definition->addTag('routing.expression_language_provider'); @@ -41,7 +41,7 @@ public function testProcessForRouter() public function testProcessForRouterAlias() { $container = new ContainerBuilder(); - $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); $definition = new Definition('\stdClass'); $definition->addTag('routing.expression_language_provider'); @@ -58,6 +58,10 @@ public function testProcessForRouterAlias() $this->assertEquals(new Reference('some_routing_provider'), $calls[0][1][0]); } + /** + * @group legacy + * @expectedDeprecation Registering services tagged "security.expression_language_provider" with "Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass" is deprecated since Symfony 4.2, use the "Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass" instead. + */ public function testProcessForSecurity() { $container = new ContainerBuilder(); @@ -76,6 +80,10 @@ public function testProcessForSecurity() $this->assertEquals(new Reference('some_security_provider'), $calls[0][1][0]); } + /** + * @group legacy + * @expectedDeprecation Registering services tagged "security.expression_language_provider" with "Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass" is deprecated since Symfony 4.2, use the "Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass" instead. + */ public function testProcessForSecurityAlias() { $container = new ContainerBuilder(); @@ -94,4 +102,43 @@ public function testProcessForSecurityAlias() $this->assertEquals('registerProvider', $calls[0][0]); $this->assertEquals(new Reference('some_security_provider'), $calls[0][1][0]); } + + /** + * @group legacy + */ + public function testProcessIgnoreSecurity() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); + + $definition = new Definition('\stdClass'); + $definition->addTag('security.expression_language_provider'); + $container->setDefinition('some_security_provider', $definition->setPublic(true)); + + $container->register('security.expression_language', '\stdClass')->setPublic(true); + $container->compile(); + + $calls = $container->getDefinition('security.expression_language')->getMethodCalls(); + $this->assertCount(0, $calls); + } + + /** + * @group legacy + */ + public function testProcessIgnoreSecurityAlias() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); + + $definition = new Definition('\stdClass'); + $definition->addTag('security.expression_language_provider'); + $container->setDefinition('some_security_provider', $definition->setPublic(true)); + + $container->register('my_security.expression_language', '\stdClass')->setPublic(true); + $container->setAlias('security.expression_language', 'my_security.expression_language'); + $container->compile(); + + $calls = $container->getDefinition('my_security.expression_language')->getMethodCalls(); + $this->assertCount(0, $calls); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php index 9230405d7560e..3de867203f1e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php @@ -39,6 +39,11 @@ public function testPoolRefsAreWeak() $publicPool->addTag('cache.pool', array('clearer' => 'clearer_alias')); $container->setDefinition('public.pool', $publicPool); + $publicPool = new Definition(); + $publicPool->addArgument('namespace'); + $publicPool->addTag('cache.pool', array('clearer' => 'clearer_alias', 'name' => 'pool2')); + $container->setDefinition('public.pool2', $publicPool); + $privatePool = new Definition(); $privatePool->setPublic(false); $privatePool->addArgument('namespace'); @@ -50,12 +55,21 @@ public function testPoolRefsAreWeak() $container->setAlias('clearer_alias', 'clearer'); $pass = new RemoveUnusedDefinitionsPass(); - $pass->setRepeatedPass(new RepeatedPass(array($pass))); + foreach ($container->getCompiler()->getPassConfig()->getRemovingPasses() as $removingPass) { + if ($removingPass instanceof RepeatedPass) { + $pass->setRepeatedPass(new RepeatedPass(array($pass))); + break; + } + } foreach (array(new CachePoolPass(), $pass, new CachePoolClearerPass()) as $pass) { $pass->process($container); } - $this->assertEquals(array(array('public.pool' => new Reference('public.pool'))), $clearer->getArguments()); - $this->assertEquals(array(array('public.pool' => new Reference('public.pool'))), $globalClearer->getArguments()); + $expected = array(array( + 'public.pool' => new Reference('public.pool'), + 'pool2' => new Reference('public.pool2'), + )); + $this->assertEquals($expected, $clearer->getArguments()); + $this->assertEquals($expected, $globalClearer->getArguments()); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php index 4619301b6e997..44443a52a50e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolPassTest.php @@ -93,6 +93,28 @@ public function testArgsAreReplaced() $this->assertSame(3, $cachePool->getArgument(2)); } + public function testWithNameAttribute() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('kernel.name', 'app'); + $container->setParameter('kernel.environment', 'prod'); + $container->setParameter('cache.prefix.seed', 'foo'); + $cachePool = new Definition(); + $cachePool->addTag('cache.pool', array( + 'name' => 'foobar', + 'provider' => 'foobar', + )); + $cachePool->addArgument(null); + $cachePool->addArgument(null); + $cachePool->addArgument(null); + $container->setDefinition('app.cache_pool', $cachePool); + + $this->cachePoolPass->process($container); + + $this->assertSame('9HvPgAayyh', $cachePool->getArgument(1)); + } + /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage Invalid "cache.pool" tag for service "app.cache_pool": accepted attributes are diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 9d3639b62140f..8dbda3e78deed 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -218,13 +218,14 @@ protected static function getBundleDefaultConfig() 'throw_exception_on_invalid_index' => false, ), 'property_info' => array( - 'enabled' => false, + 'enabled' => !class_exists(FullStack::class), ), 'router' => array( 'enabled' => false, 'http_port' => 80, 'https_port' => 443, 'strict_requirements' => true, + 'utf8' => false, ), 'session' => array( 'enabled' => false, @@ -265,6 +266,7 @@ protected static function getBundleDefaultConfig() 'directory' => '%kernel.cache_dir%/pools', 'default_redis_provider' => 'redis://localhost', 'default_memcached_provider' => 'memcached://localhost', + 'default_pdo_provider' => 'doctrine.dbal.default_connection', ), 'workflows' => array( 'enabled' => false, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 47a496ad247b6..69d5647e2fec4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1154,12 +1154,6 @@ public function testSerializerServiceIsNotRegisteredWhenDisabled() $this->assertFalse($container->hasDefinition('serializer')); } - public function testPropertyInfoDisabled() - { - $container = $this->createContainerFromFile('default_config'); - $this->assertFalse($container->has('property_info')); - } - public function testPropertyInfoEnabled() { $container = $this->createContainerFromFile('property_info'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt index 4be149bffbe07..f42d0a4ebab9d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_1.txt @@ -1,3 +1,3 @@ - // This service is an alias for the service service_1 + // This service is a public alias for the service service_1 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt index d4d8a41cbfea0..018dc0f43451a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_2.txt @@ -1,3 +1,3 @@ - // This service is an alias for the service .service_2 + // This service is a private alias for the service .service_2 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_1.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_1.txt index 011ae42095b72..f4e559e214249 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_1.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_1.txt @@ -1,4 +1,4 @@ - // This service is an alias for the service service_1 + // This service is a public alias for the service service_1 Information for Service "service_1" =================================== diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.txt b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.txt index aaa6d658bc75b..12e90d48ae40b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.txt +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Descriptor/alias_with_definition_2.txt @@ -1,4 +1,4 @@ - // This service is an alias for the service .service_2 + // This service is a private alias for the service .service_2 Information for Service ".service_2" ==================================== diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php index 2f45cd98534e2..9022bc24c26de 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/DependencyInjection/Configuration.php @@ -25,11 +25,10 @@ public function __construct($customConfig = null) public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('test'); + $treeBuilder = new TreeBuilder('test'); if ($this->customConfig) { - $this->customConfig->addConfiguration($rootNode); + $this->customConfig->addConfiguration($treeBuilder->getRootNode()); } return $treeBuilder; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php index 9cdb93a493f20..d152f5cd873af 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolsTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\Exception\InvalidArgumentException; class CachePoolsTest extends WebTestCase @@ -94,6 +95,25 @@ private function doTestCachePools($options, $adapterClass) $item = $pool2->getItem($key); $this->assertTrue($item->isHit()); + + $prefix = "\0".TagAwareAdapter::class."\0"; + $pool4 = $container->get('cache.pool4'); + $this->assertInstanceof(TagAwareAdapter::class, $pool4); + $pool4 = (array) $pool4; + $this->assertSame($pool4[$prefix.'pool'], $pool4[$prefix.'tags'] ?? $pool4['tags']); + + $pool5 = $container->get('cache.pool5'); + $this->assertInstanceof(TagAwareAdapter::class, $pool5); + $pool5 = (array) $pool5; + $this->assertSame($pool2, $pool5[$prefix.'tags'] ?? $pool5['tags']); + + $pool6 = $container->get('cache.pool6'); + $this->assertInstanceof(TagAwareAdapter::class, $pool6); + $pool6 = (array) $pool6; + $this->assertSame($pool4[$prefix.'pool'], $pool6[$prefix.'tags'] ?? $pool6['tags']); + + $pool7 = $container->get('cache.pool7'); + $this->assertNotInstanceof(TagAwareAdapter::class, $pool7); } protected static function createKernel(array $options = array()) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml index de1e144dad062..8c7bcb4eb1fac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/config.yml @@ -12,3 +12,15 @@ framework: adapter: cache.pool3 cache.pool3: clearer: ~ + cache.pool4: + tags: true + public: true + cache.pool5: + tags: cache.pool2 + public: true + cache.pool6: + tags: cache.pool4 + public: true + cache.pool7: + adapter: cache.pool4 + public: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml index 3bf10f448f9c2..30c69163d4f2f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_config.yml @@ -15,3 +15,17 @@ framework: cache.pool2: public: true clearer: ~ + cache.pool3: + clearer: ~ + cache.pool4: + tags: true + public: true + cache.pool5: + tags: cache.pool2 + public: true + cache.pool6: + tags: cache.pool4 + public: true + cache.pool7: + adapter: cache.pool4 + public: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml index d0a219753eb8e..df20c5357f7a4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePools/redis_custom_config.yml @@ -26,3 +26,17 @@ framework: cache.pool2: public: true clearer: ~ + cache.pool3: + clearer: ~ + cache.pool4: + tags: true + public: true + cache.pool5: + tags: cache.pool2 + public: true + cache.pool6: + tags: cache.pool4 + public: true + cache.pool7: + adapter: cache.pool4 + public: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php index a0ad94b33e02e..2cef381f7272c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/DelegatingLoaderTest.php @@ -22,6 +22,49 @@ public function testConstructorApi() $this->assertTrue(true, '__construct() takes a ControllerNameParser and LoaderResolverInterface respectively as its first and second argument.'); } + public function testLoadDefaultOptions() + { + $controllerNameParser = $this->getMockBuilder(ControllerNameParser::class) + ->disableOriginalConstructor() + ->getMock(); + + $loaderResolver = $this->getMockBuilder(LoaderResolverInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $loader = $this->getMockBuilder(LoaderInterface::class)->getMock(); + + $loaderResolver->expects($this->once()) + ->method('resolve') + ->willReturn($loader); + + $routeCollection = new RouteCollection(); + $routeCollection->add('foo', new Route('/', array(), array(), array('utf8' => false))); + $routeCollection->add('bar', new Route('/', array(), array(), array('foo' => 123))); + + $loader->expects($this->once()) + ->method('load') + ->willReturn($routeCollection); + + $delegatingLoader = new DelegatingLoader($controllerNameParser, $loaderResolver, array('utf8' => true)); + + $loadedRouteCollection = $delegatingLoader->load('foo'); + $this->assertCount(2, $loadedRouteCollection); + + $expected = array( + 'compiler_class' => 'Symfony\Component\Routing\RouteCompiler', + 'utf8' => false, + ); + $this->assertSame($expected, $routeCollection->get('foo')->getOptions()); + + $expected = array( + 'compiler_class' => 'Symfony\Component\Routing\RouteCompiler', + 'foo' => 123, + 'utf8' => true, + ); + $this->assertSame($expected, $routeCollection->get('bar')->getOptions()); + } + /** * @group legacy * @expectedDeprecation Referencing controllers with foo:bar:baz is deprecated since Symfony 4.1, use "some_parsed::controller" instead. diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index de0fe10f91224..76d949b9670dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -19,11 +19,11 @@ "php": "^7.1.3", "ext-xml": "*", "symfony/cache": "~3.4|~4.0", - "symfony/dependency-injection": "^4.1.1", - "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "^4.2", + "symfony/config": "~4.2", "symfony/event-dispatcher": "^4.1", - "symfony/http-foundation": "^4.1", - "symfony/http-kernel": "^4.1", + "symfony/http-foundation": "^4.1.2", + "symfony/http-kernel": "^4.2", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -94,7 +94,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 648189bb15a68..69806c3ced4d3 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,17 @@ CHANGELOG ========= +4.2.0 +----- + + * Using the `security.authentication.trust_resolver.anonymous_class` and + `security.authentication.trust_resolver.rememberme_class` parameters to define + the token classes is deprecated. To use + custom tokens extend the existing `Symfony\Component\Security\Core\Authentication\Token\AnonymousToken` + or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`. + * Added `Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass` + * Added `json_login_ldap` authentication provider to use LDAP authentication with a REST API. + 4.1.0 ----- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php new file mode 100644 index 0000000000000..8a4bf0c6e618f --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Registers the expression language providers. + * + * @author Fabien Potencier + */ +class AddExpressionLanguageProvidersPass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if ($container->has('security.expression_language')) { + $definition = $container->findDefinition('security.expression_language'); + foreach ($container->findTaggedServiceIds('security.expression_language_provider', true) as $id => $attributes) { + $definition->addMethodCall('registerProvider', array(new Reference($id))); + } + } + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php index af2c517744e14..f4e06e848eb90 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php @@ -41,8 +41,8 @@ public function __construct(array $factories, array $userProviderFactories) */ public function getConfigTreeBuilder() { - $tb = new TreeBuilder(); - $rootNode = $tb->root('security'); + $tb = new TreeBuilder('security'); + $rootNode = $tb->getRootNode(); $rootNode ->beforeNormalization() diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php new file mode 100644 index 0000000000000..df6d4fc2c9e06 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginLdapFactory.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * JsonLoginLdapFactory creates services for json login ldap authentication. + */ +class JsonLoginLdapFactory extends JsonLoginFactory +{ + public function getKey() + { + return 'json-login-ldap'; + } + + protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) + { + $provider = 'security.authentication.provider.ldap_bind.'.$id; + $definition = $container + ->setDefinition($provider, new ChildDefinition('security.authentication.provider.ldap_bind')) + ->replaceArgument(0, new Reference($userProviderId)) + ->replaceArgument(1, new Reference('security.user_checker.'.$id)) + ->replaceArgument(2, $id) + ->replaceArgument(3, new Reference($config['service'])) + ->replaceArgument(4, $config['dn_string']) + ; + + if (!empty($config['query_string'])) { + $definition->addMethodCall('setQueryString', array($config['query_string'])); + } + + return $provider; + } + + public function addConfiguration(NodeDefinition $node) + { + parent::addConfiguration($node); + + $node + ->children() + ->scalarNode('service')->defaultValue('ldap')->end() + ->scalarNode('dn_string')->defaultValue('{username}')->end() + ->scalarNode('query_string')->end() + ->end() + ; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index 89b428a27d404..5f9eb85011e4b 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -25,6 +25,7 @@ class RememberMeFactory implements SecurityFactoryInterface 'domain' => null, 'secure' => false, 'httponly' => true, + 'samesite' => null, 'always_remember_me' => false, 'remember_me_parameter' => '_remember_me', ); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 57ab081538ef7..9fbdae0bdd46f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -5,8 +5,8 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Symfony\Component\Security\Core\Authentication\Token\AnonymousToken - Symfony\Component\Security\Core\Authentication\Token\RememberMeToken + null + null @@ -27,14 +27,9 @@ - - - - - - - - + + + @@ -123,6 +118,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index d87e2287fda02..4f598d86ef52f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -132,6 +132,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index 901d914c31137..a83cf69b8bd50 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -36,6 +36,7 @@ public function __construct(iterable $listeners, ExceptionListener $exceptionLis $this->exceptionListener = $exceptionListener; if ($logoutListener instanceof FirewallConfig) { $this->config = $logoutListener; + @trigger_error(sprintf('Passing an instance of %s as the 3rd argument to "%s()" is deprecated since Symfony 4.2. Pass a %s instance instead.', FirewallConfig::class, __METHOD__, LogoutListener::class), E_USER_DEPRECATED); } elseif (null === $logoutListener || $logoutListener instanceof LogoutListener) { $this->logoutListener = $logoutListener; $this->config = $config; diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 1a3a1e3ab1d37..d7cbf2e08433d 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass; @@ -20,6 +21,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasicLdapFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginLdapFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory; @@ -46,6 +48,7 @@ public function build(ContainerBuilder $container) $extension->addSecurityListenerFactory(new FormLoginFactory()); $extension->addSecurityListenerFactory(new FormLoginLdapFactory()); $extension->addSecurityListenerFactory(new JsonLoginFactory()); + $extension->addSecurityListenerFactory(new JsonLoginLdapFactory()); $extension->addSecurityListenerFactory(new HttpBasicFactory()); $extension->addSecurityListenerFactory(new HttpBasicLdapFactory()); $extension->addSecurityListenerFactory(new RememberMeFactory()); @@ -57,6 +60,7 @@ public function build(ContainerBuilder $container) $extension->addUserProviderFactory(new InMemoryFactory()); $extension->addUserProviderFactory(new LdapFactory()); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); $container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index 5e71ac6d8ae2a..fc07f22bd6989 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -208,7 +208,7 @@ public function testGetListeners() ->expects($this->once()) ->method('getListeners') ->with($request) - ->willReturn(array(array($listener), null)); + ->willReturn(array(array($listener), null, null)); $firewall = new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator()); $firewall->onKernelRequest($event); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php index 854391c235ccb..c77ed14dc6e45 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php @@ -51,7 +51,7 @@ public function testOnKernelRequestRecordsListeners() ->expects($this->once()) ->method('getListeners') ->with($request) - ->willReturn(array(array($listener), null)); + ->willReturn(array(array($listener), null, null)); $firewall = new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator()); $firewall->onKernelRequest($event); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php new file mode 100644 index 0000000000000..6e79f8fc644d1 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddExpressionLanguageProvidersPassTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Compiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +class AddExpressionLanguageProvidersPassTest extends TestCase +{ + public function testProcessForSecurity() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); + + $definition = new Definition('\stdClass'); + $definition->addTag('security.expression_language_provider'); + $container->setDefinition('some_security_provider', $definition->setPublic(true)); + + $container->register('security.expression_language', '\stdClass')->setPublic(true); + $container->compile(); + + $calls = $container->getDefinition('security.expression_language')->getMethodCalls(); + $this->assertCount(1, $calls); + $this->assertEquals('registerProvider', $calls[0][0]); + $this->assertEquals(new Reference('some_security_provider'), $calls[0][1][0]); + } + + public function testProcessForSecurityAlias() + { + $container = new ContainerBuilder(); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); + + $definition = new Definition('\stdClass'); + $definition->addTag('security.expression_language_provider'); + $container->setDefinition('some_security_provider', $definition->setPublic(true)); + + $container->register('my_security.expression_language', '\stdClass')->setPublic(true); + $container->setAlias('security.expression_language', 'my_security.expression_language'); + $container->compile(); + + $calls = $container->getDefinition('my_security.expression_language')->getMethodCalls(); + $this->assertCount(1, $calls); + $this->assertEquals('registerProvider', $calls[0][0]); + $this->assertEquals(new Reference('some_security_provider'), $calls[0][1][0]); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php new file mode 100644 index 0000000000000..09b22e2ffae74 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginLdapTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Functional; + +use Symfony\Component\HttpKernel\Kernel; + +class JsonLoginLdapTest extends WebTestCase +{ + public function testKernelBoot() + { + $kernel = self::createKernel(array('test_case' => 'JsonLoginLdap', 'root_config' => 'config.yml')); + $kernel->boot(); + + $this->assertInstanceOf(Kernel::class, $kernel); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/bundles.php new file mode 100644 index 0000000000000..336bd003a2925 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/bundles.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return array( + new Symfony\Bundle\SecurityBundle\SecurityBundle(), + new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new Symfony\Bundle\TwigBundle\TwigBundle(), +); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml new file mode 100644 index 0000000000000..d608f309f85d4 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLoginLdap/config.yml @@ -0,0 +1,39 @@ +imports: + - { resource: ./../config/default.yml } +services: + Symfony\Component\Ldap\Ldap: + arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter'] + + Symfony\Component\Ldap\Adapter\ExtLdap\Adapter: + arguments: + - host: 'localhost' + port: 389 + options: + protocol_version: 3 + referrals: false +security: + providers: + ldap: + ldap: + service: Symfony\Component\Ldap\Ldap + base_dn: 'dc=onfroy,dc=net' + search_dn: '' + search_password: '' + default_roles: ROLE_USER + uid_key: uid + + firewalls: + main: + pattern: ^/login + stateless: true + anonymous: true + json_login_ldap: + check_path: /login + require_previous_session: false + service: Symfony\Component\Ldap\Ldap + dn_string: '' + username_path: user.login + password_path: user.password + + access_control: + - { path: ^/, roles: ROLE_USER } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml index 19b9d8952ec5e..d7c73aa0b6dc0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/StandardFormLogin/config.yml @@ -40,5 +40,5 @@ security: - { path: ^/secured-by-one-ip$, ip: 10.10.10.10, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/secured-by-two-ips$, ips: [1.1.1.1, 2.2.2.2], roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/highly_protected_resource$, roles: IS_ADMIN } - - { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or has_role('ROLE_USER')" } + - { path: ^/protected-via-expression$, allow_if: "(is_anonymous() and request.headers.get('user-agent') matches '/Firefox/i') or is_granted('ROLE_USER')" } - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php index 520a129716f4f..fc2fbcc023cab 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/FirewallContextTest.php @@ -40,6 +40,15 @@ public function testGetters() $this->assertEquals($config, $context->getConfig()); } + /** + * @group legacy + * @expectedDeprecation Passing an instance of Symfony\Bundle\SecurityBundle\Security\FirewallConfig as the 3rd argument to "Symfony\Bundle\SecurityBundle\Security\FirewallContext::__construct()" is deprecated since Symfony 4.2. Pass a Symfony\Component\Security\Http\Firewall\LogoutListener instance instead. + */ + public function testFirewallConfigAs3rdConstructorArgument() + { + new FirewallContext(array(), $this->getExceptionListenerMock(), new FirewallConfig('main', 'user_checker', 'request_matcher')); + } + private function getExceptionListenerMock() { return $this diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 77d778a9de616..ed648eb4004cf 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -18,8 +18,9 @@ "require": { "php": "^7.1.3", "ext-xml": "*", - "symfony/security": "^4.1.1", - "symfony/dependency-injection": "^3.4.3|^4.0.3", + "symfony/config": "^4.2", + "symfony/security": "~4.2", + "symfony/dependency-injection": "^4.2", "symfony/http-kernel": "^4.1" }, "require-dev": { @@ -30,7 +31,7 @@ "symfony/dom-crawler": "~3.4|~4.0", "symfony/event-dispatcher": "~3.4|~4.0", "symfony/form": "~3.4|~4.0", - "symfony/framework-bundle": "~4.1", + "symfony/framework-bundle": "~4.2", "symfony/http-foundation": "~3.4|~4.0", "symfony/translation": "~3.4|~4.0", "symfony/twig-bundle": "~3.4|~4.0", @@ -46,7 +47,7 @@ "conflict": { "symfony/var-dumper": "<3.4", "symfony/event-dispatcher": "<3.4", - "symfony/framework-bundle": "<4.1.1", + "symfony/framework-bundle": "<4.2", "symfony/console": "<3.4" }, "autoload": { @@ -58,7 +59,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index 748f3b7d29e3b..d924353771a35 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -29,8 +29,8 @@ class Configuration implements ConfigurationInterface */ public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('twig'); + $treeBuilder = new TreeBuilder('twig'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index d3a24dfa479a3..074be35a46d42 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; +use Symfony\Bundle\TwigBundle\Loader\NativeFilesystemLoader; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\Console\Application; @@ -83,6 +84,10 @@ public function load(array $configs, ContainerBuilder $container) $twigFilesystemLoaderDefinition = $container->getDefinition('twig.loader.native_filesystem'); + if ($container->getParameter('kernel.debug')) { + $twigFilesystemLoaderDefinition->setClass(NativeFilesystemLoader::class); + } + // register user-configured paths foreach ($config['paths'] as $path => $namespace) { if (!$namespace) { diff --git a/src/Symfony/Bundle/TwigBundle/Loader/NativeFilesystemLoader.php b/src/Symfony/Bundle/TwigBundle/Loader/NativeFilesystemLoader.php new file mode 100644 index 0000000000000..9ef58d7bdbbe6 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Loader/NativeFilesystemLoader.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Loader; + +use Twig\Error\LoaderError; +use Twig\Loader\FilesystemLoader; + +/** + * @author Behnoush Norouzali + * + * @internal + */ +class NativeFilesystemLoader extends FilesystemLoader +{ + /** + * {@inheritdoc} + */ + protected function findTemplate($template, $throw = true) + { + try { + return parent::findTemplate($template, $throw); + } catch (LoaderError $e) { + if ('' === $template || '@' === $template[0] || !preg_match('/^(?P[^:]*?)(?:Bundle)?:(?P[^:]*+):(?P