diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php index 6db6da85e0..3b9130a8e4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -23,6 +23,8 @@ use Symfony\Component\HttpKernel\KernelInterface; * A console command to display information about the current installation. * * @author Roland Franssen + * + * @final since version 3.4 */ class AboutCommand extends ContainerAwareCommand { @@ -45,7 +47,7 @@ class AboutCommand extends ContainerAwareCommand $io = new SymfonyStyle($input, $output); /** @var $kernel KernelInterface */ - $kernel = $this->getContainer()->get('kernel'); + $kernel = $this->getApplication()->getKernel(); $io->table(array(), array( array('Symfony'), diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php index a0c43cac90..5244b7ef33 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php @@ -31,7 +31,7 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand $headers = array('Bundle name', 'Extension alias'); $rows = array(); - $bundles = $this->getContainer()->get('kernel')->getBundles(); + $bundles = $this->getApplication()->getKernel()->getBundles(); usort($bundles, function ($bundleA, $bundleB) { return strcmp($bundleA->getName(), $bundleB->getName()); }); @@ -117,7 +117,7 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand // Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method // as this method is not called when the container is loaded from the cache. $container = $this->getContainerBuilder(); - $bundles = $this->getContainer()->get('kernel')->getBundles(); + $bundles = $this->getApplication()->getKernel()->getBundles(); foreach ($bundles as $bundle) { if ($extension = $bundle->getContainerExtension()) { $container->registerExtension($extension); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index 06ed6a4a05..0af2c55010 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -26,6 +26,8 @@ use Symfony\Component\HttpKernel\Bundle\BundleInterface; * * @author Fabien Potencier * @author Gábor Egyed + * + * @final since version 3.4 */ class AssetsInstallCommand extends ContainerAwareCommand { @@ -33,11 +35,26 @@ class AssetsInstallCommand extends ContainerAwareCommand const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; const METHOD_RELATIVE_SYMLINK = 'relative symlink'; - /** - * @var Filesystem - */ private $filesystem; + /** + * @param Filesystem $filesystem + */ + public function __construct($filesystem = null) + { + parent::__construct(); + + if (!$filesystem instanceof Filesystem) { + @trigger_error(sprintf('Passing a command name as the first argument of "%s" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED); + + $this->setName(null === $filesystem ? 'assets:install' : $filesystem); + + return; + } + + $this->filesystem = $filesystem; + } + /** * {@inheritdoc} */ @@ -79,18 +96,23 @@ EOT */ protected function execute(InputInterface $input, OutputInterface $output) { + // BC to be removed in 4.0 + if (null === $this->filesystem) { + $this->filesystem = $this->getContainer()->get('filesystem'); + $baseDir = $this->getContainer()->getParameter('kernel.project_dir'); + } + + $kernel = $this->getApplication()->getKernel(); $targetArg = rtrim($input->getArgument('target'), '/'); if (!is_dir($targetArg)) { - $targetArg = $this->getContainer()->getParameter('kernel.project_dir').'/'.$targetArg; + $targetArg = (isset($baseDir) ? $baseDir : $kernel->getContainer()->getParameter('kernel.project_dir')).'/'.$targetArg; if (!is_dir($targetArg)) { throw new \InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $input->getArgument('target'))); } } - $this->filesystem = $this->getContainer()->get('filesystem'); - // Create the bundles directory otherwise symlink will fail. $bundlesDir = $targetArg.'/bundles/'; $this->filesystem->mkdir($bundlesDir, 0777); @@ -116,7 +138,7 @@ EOT $exitCode = 0; $validAssetDirs = array(); /** @var BundleInterface $bundle */ - foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { + foreach ($kernel->getBundles() as $bundle) { if (!is_dir($originDir = $bundle->getPath().'/Resources/public')) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index 0a70d9a859..9d90c594ec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -15,15 +15,34 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; /** * Clear and Warmup the cache. * * @author Francis Besset * @author Fabien Potencier + * + * @final since version 3.4 */ class CacheClearCommand extends ContainerAwareCommand { + private $cacheClearer; + private $filesystem; + + /** + * @param CacheClearerInterface $cacheClearer + * @param Filesystem|null $filesystem + */ + public function __construct(CacheClearerInterface $cacheClearer, Filesystem $filesystem = null) + { + parent::__construct(); + + $this->cacheClearer = $cacheClearer; + $this->filesystem = $filesystem ?: new Filesystem(); + } + /** * {@inheritdoc} */ @@ -53,22 +72,21 @@ EOF { $io = new SymfonyStyle($input, $output); - $cacheDir = $this->getContainer()->getParameter('kernel.cache_dir'); - $filesystem = $this->getContainer()->get('filesystem'); + $kernel = $this->getApplication()->getKernel(); + $cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'); if (!is_writable($cacheDir)) { throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $cacheDir)); } - $kernel = $this->getContainer()->get('kernel'); $io->comment(sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); - $this->getContainer()->get('cache_clearer')->clear($cacheDir); + $this->cacheClearer->clear($cacheDir); if ($output->isVerbose()) { $io->comment('Removing old cache directory...'); } - $filesystem->remove($cacheDir); + $this->filesystem->remove($cacheDir); if ($output->isVerbose()) { $io->comment('Finished'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php index 3ee9f086ae..60c42e8729 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php @@ -25,6 +25,26 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; */ final class CachePoolClearCommand extends ContainerAwareCommand { + private $poolClearer; + + /** + * @param Psr6CacheClearer $poolClearer + */ + public function __construct($poolClearer = null) + { + parent::__construct(); + + if (!$poolClearer instanceof Psr6CacheClearer) { + @trigger_error(sprintf('Passing a command name as the first argument of "%s" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED); + + $this->setName(null === $poolClearer ? 'cache:pool:clear' : $poolClearer); + + return; + } + + $this->poolClearer = $poolClearer; + } + /** * {@inheritdoc} */ @@ -50,18 +70,22 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { + // BC to be removed in 4.0 + if (null === $this->poolClearer) { + $this->poolClearer = $this->getContainer()->get('cache.global_clearer'); + $cacheDir = $this->getContainer()->getParameter('kernel.cache_dir'); + } + $io = new SymfonyStyle($input, $output); + $kernel = $this->getApplication()->getKernel(); $pools = array(); $clearers = array(); - $container = $this->getContainer(); - $cacheDir = $container->getParameter('kernel.cache_dir'); - $globalClearer = $container->get('cache.global_clearer'); foreach ($input->getArgument('pools') as $id) { - if ($globalClearer->hasPool($id)) { + if ($this->poolClearer->hasPool($id)) { $pools[$id] = $id; } else { - $pool = $container->get($id); + $pool = $kernel->getContainer()->get($id); if ($pool instanceof CacheItemPoolInterface) { $pools[$id] = $pool; @@ -75,7 +99,7 @@ EOF foreach ($clearers as $id => $clearer) { $io->comment(sprintf('Calling cache clearer: %s', $id)); - $clearer->clear($cacheDir); + $clearer->clear(isset($cacheDir) ? $cacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir')); } foreach ($pools as $id => $pool) { @@ -84,7 +108,7 @@ EOF if ($pool instanceof CacheItemPoolInterface) { $pool->clear(); } else { - $globalClearer->clearPool($id); + $this->poolClearer->clearPool($id); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php index 1e0c4b6b3a..0f72db8305 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheWarmupCommand.php @@ -15,14 +15,37 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; /** * Warmup the cache. * * @author Fabien Potencier + * + * @final since version 3.4 */ class CacheWarmupCommand extends ContainerAwareCommand { + private $cacheWarmer; + + /** + * @param CacheWarmerAggregate $cacheWarmer + */ + public function __construct($cacheWarmer = null) + { + parent::__construct(); + + if (!$cacheWarmer instanceof CacheWarmerAggregate) { + @trigger_error(sprintf('Passing a command name as the first argument of "%s" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED); + + $this->setName(null === $cacheWarmer ? 'cache:warmup' : $cacheWarmer); + + return; + } + + $this->cacheWarmer = $cacheWarmer; + } + /** * {@inheritdoc} */ @@ -54,18 +77,22 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { - $io = new SymfonyStyle($input, $output); - - $kernel = $this->getContainer()->get('kernel'); - $io->comment(sprintf('Warming up the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); - - $warmer = $this->getContainer()->get('cache_warmer'); - - if (!$input->getOption('no-optional-warmers')) { - $warmer->enableOptionalWarmers(); + // BC to be removed in 4.0 + if (null === $this->cacheWarmer) { + $this->cacheWarmer = $this->getContainer()->get('cache_warmer'); + $cacheDir = $this->getContainer()->getParameter('kernel.cache_dir'); } - $warmer->warmUp($this->getContainer()->getParameter('kernel.cache_dir')); + $io = new SymfonyStyle($input, $output); + + $kernel = $this->getApplication()->getKernel(); + $io->comment(sprintf('Warming up the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + + if (!$input->getOption('no-optional-warmers')) { + $this->cacheWarmer->enableOptionalWarmers(); + } + + $this->cacheWarmer->warmUp(isset($cacheDir) ? $cacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir')); $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index 924bd8e92f..f460fd5f09 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -23,6 +23,8 @@ use Symfony\Component\Yaml\Yaml; * A console command for dumping available configuration reference. * * @author Grégoire Pineau + * + * @final since version 3.4 */ class ConfigDebugCommand extends AbstractConfigCommand { @@ -111,7 +113,7 @@ EOF private function compileContainer() { - $kernel = clone $this->getContainer()->get('kernel'); + $kernel = clone $this->getApplication()->getKernel(); $kernel->boot(); $method = new \ReflectionMethod($kernel, 'buildContainer'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 476813fc36..fb51f8c3fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -25,6 +25,8 @@ use Symfony\Component\Console\Style\SymfonyStyle; * @author Kevin Bond * @author Wouter J * @author Grégoire Pineau + * + * @final since version 3.4 */ class ConfigDumpReferenceCommand extends AbstractConfigCommand { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 891ca9279e..51c0496987 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -27,6 +27,8 @@ use Symfony\Component\Config\FileLocator; * A console command for retrieving information about services. * * @author Ryan Weaver + * + * @internal since version 3.4 */ class ContainerDebugCommand extends ContainerAwareCommand { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php index ef1c3017fc..d2f8bd7775 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/EventDispatcherDebugCommand.php @@ -23,9 +23,31 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; * A console command for retrieving information about event dispatcher. * * @author Matthieu Auger + * + * @final since version 3.4 */ class EventDispatcherDebugCommand extends ContainerAwareCommand { + private $dispatcher; + + /** + * @param EventDispatcherInterface $dispatcher + */ + public function __construct($dispatcher = null) + { + parent::__construct(); + + if (!$dispatcher instanceof EventDispatcherInterface) { + @trigger_error(sprintf('Passing a command name as the first argument of "%s" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED); + + $this->setName(null === $dispatcher ? 'debug:event-dispatcher' : $dispatcher); + + return; + } + + $this->dispatcher = $dispatcher; + } + /** * {@inheritdoc} */ @@ -59,12 +81,16 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { + // BC to be removed in 4.0 + if (null === $this->dispatcher) { + $this->dispatcher = $this->getEventDispatcher(); + } + $io = new SymfonyStyle($input, $output); - $dispatcher = $this->getEventDispatcher(); $options = array(); if ($event = $input->getArgument('event')) { - if (!$dispatcher->hasListeners($event)) { + if (!$this->dispatcher->hasListeners($event)) { $io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event)); return; @@ -77,12 +103,14 @@ EOF $options['format'] = $input->getOption('format'); $options['raw_text'] = $input->getOption('raw'); $options['output'] = $io; - $helper->describe($io, $dispatcher, $options); + $helper->describe($io, $this->dispatcher, $options); } /** * Loads the Event Dispatcher from the container. * + * BC to removed in 4.0 + * * @return EventDispatcherInterface */ protected function getEventDispatcher() diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php index f26fddabb0..253f9a13f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php @@ -27,14 +27,41 @@ use Symfony\Component\Routing\Route; * * @author Fabien Potencier * @author Tobias Schultze + * + * @final since version 3.4 */ class RouterDebugCommand extends ContainerAwareCommand { + private $router; + + /** + * @param RouterInterface $router + */ + public function __construct($router = null) + { + parent::__construct(); + + if (!$router instanceof RouterInterface) { + @trigger_error(sprintf('Passing a command name as the first argument of "%s" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED); + + $this->setName(null === $router ? 'debug:router' : $router); + + return; + } + + $this->router = $router; + } + /** * {@inheritdoc} + * + * BC to be removed in 4.0 */ public function isEnabled() { + if (null !== $this->router) { + return parent::isEnabled(); + } if (!$this->getContainer()->has('router')) { return false; } @@ -77,10 +104,15 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { + // BC to be removed in 4.0 + if (null === $this->router) { + $this->router = $this->getContainer()->get('router'); + } + $io = new SymfonyStyle($input, $output); $name = $input->getArgument('name'); $helper = new DescriptorHelper(); - $routes = $this->getContainer()->get('router')->getRouteCollection(); + $routes = $this->router->getRouteCollection(); if ($name) { if (!$route = $routes->get($name)) { @@ -132,7 +164,7 @@ EOF if (1 === substr_count($controller, ':')) { list($service, $method) = explode(':', $controller); try { - return sprintf('%s::%s', get_class($this->getContainer()->get($service)), $method); + return sprintf('%s::%s', get_class($this->getApplication()->getKernel()->getContainer()->get($service)), $method); } catch (ServiceNotFoundException $e) { } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php index 2e23ad72c2..c6d2bdbf99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php @@ -24,14 +24,41 @@ use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; * A console command to test route matching. * * @author Fabien Potencier + * + * @final since version 3.4 */ class RouterMatchCommand extends ContainerAwareCommand { + private $router; + + /** + * @param RouterInterface $router + */ + public function __construct($router = null) + { + parent::__construct(); + + if (!$router instanceof RouterInterface) { + @trigger_error(sprintf('Passing a command name as the first argument of "%s" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED); + + $this->setName(null === $router ? 'router:match' : $router); + + return; + } + + $this->router = $router; + } + /** * {@inheritdoc} + * + * BC to be removed in 4.0 */ public function isEnabled() { + if (null !== $this->router) { + return parent::isEnabled(); + } if (!$this->getContainer()->has('router')) { return false; } @@ -76,10 +103,14 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { + // BC to be removed in 4.0 + if (null === $this->router) { + $this->router = $this->getContainer()->get('router'); + } + $io = new SymfonyStyle($input, $output); - $router = $this->getContainer()->get('router'); - $context = $router->getContext(); + $context = $this->router->getContext(); if (null !== $method = $input->getOption('method')) { $context->setMethod($method); } @@ -90,7 +121,7 @@ EOF $context->setHost($host); } - $matcher = new TraceableUrlMatcher($router->getRouteCollection(), $context); + $matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context); $traces = $matcher->getTraces($input->getArgument('path_info')); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 79b4234d81..44730737bc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -17,25 +17,55 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Translation\Catalogue\MergeOperation; +use Symfony\Component\Translation\Extractor\ExtractorInterface; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\DataCollectorTranslator; use Symfony\Component\Translation\LoggingTranslator; +use Symfony\Component\Translation\TranslatorInterface; /** * Helps finding unused or missing translation messages in a given locale * and comparing them with the fallback ones. * * @author Florian Voutzinos + * + * @final since version 3.4 */ class TranslationDebugCommand extends ContainerAwareCommand { + private $translator; + private $loader; + private $extractor; + const MESSAGE_MISSING = 0; const MESSAGE_UNUSED = 1; const MESSAGE_EQUALS_FALLBACK = 2; + /** + * @param TranslatorInterface $translator + * @param TranslationLoader $loader + * @param ExtractorInterface $extractor + */ + public function __construct($translator = null, TranslationLoader $loader = null, ExtractorInterface $extractor = null) + { + parent::__construct(); + + if (!$translator instanceof TranslatorInterface) { + @trigger_error(sprintf('Passing a command name as the first argument of "%s" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED); + + $this->setName(null === $translator ? 'debug:translation' : $translator); + + return; + } + + $this->translator = $translator; + $this->loader = $loader; + $this->extractor = $extractor; + } + /** * {@inheritdoc} */ @@ -88,9 +118,14 @@ EOF /** * {@inheritdoc} + * + * BC to be removed in 4.0 */ public function isEnabled() { + if (null !== $this->translator) { + return parent::isEnabled(); + } if (!class_exists('Symfony\Component\Translation\Translator')) { return false; } @@ -103,14 +138,19 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { + // BC to be removed in 4.0 + if (null === $this->translator) { + $this->translator = $this->getContainer()->get('translator'); + $this->loader = $this->getContainer()->get('translation.loader'); + $this->extractor = $this->getContainer()->get('translation.extractor'); + } + $io = new SymfonyStyle($input, $output); $locale = $input->getArgument('locale'); $domain = $input->getOption('domain'); - /** @var TranslationLoader $loader */ - $loader = $this->getContainer()->get('translation.loader'); - /** @var Kernel $kernel */ - $kernel = $this->getContainer()->get('kernel'); + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); // Define Root Path to App folder $transPaths = array($kernel->getRootDir().'/Resources/'); @@ -142,7 +182,7 @@ EOF $extractedCatalogue = $this->extractMessages($locale, $transPaths); // Load defined messages - $currentCatalogue = $this->loadCurrentMessages($locale, $transPaths, $loader); + $currentCatalogue = $this->loadCurrentMessages($locale, $transPaths); // Merge defined and extracted messages to get all message ids $mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue); @@ -165,7 +205,7 @@ EOF } // Load the fallback catalogues - $fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths, $loader); + $fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths); // Display header line $headers = array('State', 'Domain', 'Id', sprintf('Message Preview (%s)', $locale)); @@ -271,7 +311,7 @@ EOF foreach ($transPaths as $path) { $path = $path.'views'; if (is_dir($path)) { - $this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue); + $this->extractor->extract($path, $extractedCatalogue); } } @@ -279,19 +319,18 @@ EOF } /** - * @param string $locale - * @param array $transPaths - * @param TranslationLoader $loader + * @param string $locale + * @param array $transPaths * * @return MessageCatalogue */ - private function loadCurrentMessages($locale, $transPaths, TranslationLoader $loader) + private function loadCurrentMessages($locale, $transPaths) { $currentCatalogue = new MessageCatalogue($locale); foreach ($transPaths as $path) { $path = $path.'translations'; if (is_dir($path)) { - $loader->loadMessages($path, $currentCatalogue); + $this->loader->loadMessages($path, $currentCatalogue); } } @@ -299,18 +338,16 @@ EOF } /** - * @param string $locale - * @param array $transPaths - * @param TranslationLoader $loader + * @param string $locale + * @param array $transPaths * * @return MessageCatalogue[] */ - private function loadFallbackCatalogues($locale, $transPaths, TranslationLoader $loader) + private function loadFallbackCatalogues($locale, $transPaths) { $fallbackCatalogues = array(); - $translator = $this->getContainer()->get('translator'); - if ($translator instanceof Translator || $translator instanceof DataCollectorTranslator || $translator instanceof LoggingTranslator) { - foreach ($translator->getFallbackLocales() as $fallbackLocale) { + if ($this->translator instanceof Translator || $this->translator instanceof DataCollectorTranslator || $this->translator instanceof LoggingTranslator) { + foreach ($this->translator->getFallbackLocales() as $fallbackLocale) { if ($fallbackLocale === $locale) { continue; } @@ -319,7 +356,7 @@ EOF foreach ($transPaths as $path) { $path = $path.'translations'; if (is_dir($path)) { - $loader->loadMessages($path, $fallbackCatalogue); + $this->loader->loadMessages($path, $fallbackCatalogue); } } $fallbackCatalogues[] = $fallbackCatalogue; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index aa5d4f0c34..d0928b9131 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Translation\Catalogue\TargetOperation; use Symfony\Component\Translation\Catalogue\MergeOperation; @@ -18,16 +19,49 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Translation\Extractor\ExtractorInterface; use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Writer\TranslationWriter; /** * A command that parses templates to extract translation messages and adds them * into the translation files. * * @author Michel Salib + * + * @final since version 3.4 */ class TranslationUpdateCommand extends ContainerAwareCommand { + private $writer; + private $loader; + private $extractor; + private $defaultLocale; + + /** + * @param TranslationWriter $writer + * @param TranslationLoader $loader + * @param ExtractorInterface $extractor + * @param string $defaultLocale + */ + public function __construct($writer = null, TranslationLoader $loader = null, ExtractorInterface $extractor = null, $defaultLocale = null) + { + parent::__construct(); + + if (!$writer instanceof TranslationWriter) { + @trigger_error(sprintf('Passing a command name as the first argument of "%s" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED); + + $this->setName(null === $writer ? 'translation:update' : $writer); + + return; + } + + $this->writer = $writer; + $this->loader = $loader; + $this->extractor = $extractor; + $this->defaultLocale = $defaultLocale; + } + /** * {@inheritdoc} */ @@ -68,9 +102,14 @@ EOF /** * {@inheritdoc} + * + * BC to be removed in 4.0 */ public function isEnabled() { + if (null !== $this->writer) { + return parent::isEnabled(); + } if (!class_exists('Symfony\Component\Translation\Translator')) { return false; } @@ -83,6 +122,14 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { + // BC to be removed in 4.0 + if (null === $this->writer) { + $this->writer = $this->getContainer()->get('translation.writer'); + $this->loader = $this->getContainer()->get('translation.loader'); + $this->extractor = $this->getContainer()->get('translation.extractor'); + $this->defaultLocale = $this->getContainer()->getParameter('kernel.default_locale'); + } + $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); @@ -94,14 +141,13 @@ EOF } // check format - $writer = $this->getContainer()->get('translation.writer'); - $supportedFormats = $writer->getFormats(); + $supportedFormats = $this->writer->getFormats(); if (!in_array($input->getOption('output-format'), $supportedFormats)) { $errorIo->error(array('Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).'.')); return 1; } - $kernel = $this->getContainer()->get('kernel'); + $kernel = $this->getApplication()->getKernel(); // Define Root Path to App folder $transPaths = array($kernel->getRootDir().'/Resources/'); @@ -133,23 +179,21 @@ EOF // load any messages from templates $extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); $errorIo->comment('Parsing templates...'); - $extractor = $this->getContainer()->get('translation.extractor'); - $extractor->setPrefix($input->getOption('prefix')); + $this->extractor->setPrefix($input->getOption('prefix')); foreach ($transPaths as $path) { $path .= 'views'; if (is_dir($path)) { - $extractor->extract($path, $extractedCatalogue); + $this->extractor->extract($path, $extractedCatalogue); } } // load any existing messages from the translation files $currentCatalogue = new MessageCatalogue($input->getArgument('locale')); $errorIo->comment('Loading translation files...'); - $loader = $this->getContainer()->get('translation.loader'); foreach ($transPaths as $path) { $path .= 'translations'; if (is_dir($path)) { - $loader->loadMessages($path, $currentCatalogue); + $this->loader->loadMessages($path, $currentCatalogue); } } @@ -206,7 +250,7 @@ EOF } if ($input->getOption('no-backup') === true) { - $writer->disableBackup(); + $this->writer->disableBackup(); } // save the files @@ -225,7 +269,7 @@ EOF $bundleTransPath = end($transPaths).'translations'; } - $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale'))); + $this->writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->defaultLocale)); if (true === $input->getOption('dump-messages')) { $resultMessage .= ' and translation files were updated'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php index 0287f42a8e..27e0f645d8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @@ -20,9 +20,16 @@ use Symfony\Component\Workflow\Marking; /** * @author Grégoire Pineau + * + * @final since version 3.4 */ class WorkflowDumpCommand extends ContainerAwareCommand { + /** + * {@inheritdoc} + * + * BC to be removed in 4.0 + */ public function isEnabled() { return $this->getContainer()->has('workflow.registry'); @@ -56,7 +63,7 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { - $container = $this->getContainer(); + $container = $this->getApplication()->getKernel()->getContainer(); $serviceId = $input->getArgument('name'); if ($container->has('workflow.'.$serviceId)) { $workflow = $container->get('workflow.'.$serviceId); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php index dcc3eb3abe..8f4739a9f8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/XliffLintCommand.php @@ -22,6 +22,8 @@ use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand; * @author Grégoire Pineau * @author Robin Chalas * @author Javier Eguiluz + * + * @final since version 3.4 */ class XliffLintCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php index 41551acc3d..6e8f7dbfae 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php @@ -21,6 +21,8 @@ use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand; * * @author Grégoire Pineau * @author Robin Chalas + * + * @final since version 3.4 */ class YamlLintCommand extends Command { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 85524ffe2b..f2169a9184 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -13,6 +13,11 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Doctrine\Common\Annotations\Reader; use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand; +use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand; +use Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; @@ -213,11 +218,7 @@ class FrameworkExtension extends Extension $this->registerCacheConfiguration($config['cache'], $container); $this->registerWorkflowConfiguration($config['workflows'], $container, $loader); $this->registerDebugConfiguration($config['php_errors'], $container, $loader); - - if ($this->isConfigEnabled($container, $config['router'])) { - $this->registerRouterConfiguration($config['router'], $container, $loader); - } - + $this->registerRouterConfiguration($config['router'], $container, $loader); $this->registerAnnotationsConfiguration($config['annotations'], $container, $loader); $this->registerPropertyAccessConfiguration($config['property_access'], $container); @@ -462,6 +463,10 @@ class FrameworkExtension extends Extension private function registerWorkflowConfiguration(array $workflows, ContainerBuilder $container, XmlFileLoader $loader) { if (!$workflows) { + if (!class_exists(Workflow\Workflow::class)) { + $container->removeDefinition(WorkflowDumpCommand::class); + } + return; } @@ -639,6 +644,13 @@ class FrameworkExtension extends Extension */ private function registerRouterConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { + if (!$this->isConfigEnabled($container, $config)) { + $container->removeDefinition(RouterDebugCommand::class); + $container->removeDefinition(RouterMatchCommand::class); + + return; + } + $loader->load('routing.xml'); $container->setParameter('router.resource', $config['resource']); @@ -927,6 +939,9 @@ class FrameworkExtension extends Extension private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader) { if (!$this->isConfigEnabled($container, $config)) { + $container->removeDefinition(TranslationDebugCommand::class); + $container->removeDefinition(TranslationUpdateCommand::class); + return; } diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 5b4e4d4946..b12d34ba68 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -26,6 +26,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilder use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass; use Symfony\Component\Config\DependencyInjection\ConfigCachePass; +use Symfony\Component\Console\Application; use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; use Symfony\Component\HttpKernel\DependencyInjection\AddCacheClearerPass; use Symfony\Component\HttpKernel\DependencyInjection\AddCacheWarmerPass; @@ -123,4 +124,9 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new $class(), $type, $priority); } } + + public function registerCommands(Application $application) + { + // noop + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index 182d07e6b3..e0d5788bc4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -100,11 +100,6 @@ - - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 3d9bd1b248..4bdaa85fee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -12,5 +12,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %kernel.default_locale% + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php index ab96721edd..c201fe9db7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterDebugCommandTest.php @@ -47,20 +47,34 @@ class RouterDebugCommandTest extends TestCase $this->createCommandTester()->execute(array('name' => 'test')); } + /** + * @group legacy + * @expectedDeprecation Passing a command name as the first argument of "Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand::__construct" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead. + */ + public function testLegacyDebugCommand() + { + $application = new Application($this->getKernel()); + $application->add(new RouterDebugCommand()); + + $tester = new CommandTester($application->find('debug:router')); + + $tester->execute(array()); + + $this->assertRegExp('/foo\s+ANY\s+ANY\s+ANY\s+\\/foo/', $tester->getDisplay()); + } + /** * @return CommandTester */ private function createCommandTester() { $application = new Application($this->getKernel()); - - $command = new RouterDebugCommand(); - $application->add($command); + $application->add(new RouterDebugCommand($this->getRouter())); return new CommandTester($application->find('debug:router')); } - private function getKernel() + private function getRouter() { $routeCollection = new RouteCollection(); $routeCollection->add('foo', new Route('foo')); @@ -68,9 +82,13 @@ class RouterDebugCommandTest extends TestCase $router ->expects($this->any()) ->method('getRouteCollection') - ->will($this->returnValue($routeCollection)) - ; + ->will($this->returnValue($routeCollection)); + return $router; + } + + private function getKernel() + { $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); $container ->expects($this->atLeastOnce()) @@ -87,7 +105,7 @@ class RouterDebugCommandTest extends TestCase ->expects($this->any()) ->method('get') ->with('router') - ->willReturn($router) + ->willReturn($this->getRouter()) ; $kernel = $this->getMockBuilder(KernelInterface::class)->getMock(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php index 4e3bcefc58..2570d41d78 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/RouterMatchCommandTest.php @@ -42,18 +42,36 @@ class RouterMatchCommandTest extends TestCase } /** - * @return CommandTester + * @group legacy + * @expectedDeprecation Passing a command name as the first argument of "Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand::__construct" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead. + * @expectedDeprecation Passing a command name as the first argument of "Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand::__construct" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead. */ - private function createCommandTester() + public function testLegacyMatchCommand() { $application = new Application($this->getKernel()); $application->add(new RouterMatchCommand()); $application->add(new RouterDebugCommand()); + $tester = new CommandTester($application->find('router:match')); + + $tester->execute(array('path_info' => '/')); + + $this->assertContains('None of the routes match the path "/"', $tester->getDisplay()); + } + + /** + * @return CommandTester + */ + private function createCommandTester() + { + $application = new Application($this->getKernel()); + $application->add(new RouterMatchCommand($this->getRouter())); + $application->add(new RouterDebugCommand($this->getRouter())); + return new CommandTester($application->find('router:match')); } - private function getKernel() + private function getRouter() { $routeCollection = new RouteCollection(); $routeCollection->add('foo', new Route('foo')); @@ -62,14 +80,17 @@ class RouterMatchCommandTest extends TestCase $router ->expects($this->any()) ->method('getRouteCollection') - ->will($this->returnValue($routeCollection)) - ; + ->will($this->returnValue($routeCollection)); $router ->expects($this->any()) ->method('getContext') - ->will($this->returnValue($requestContext)) - ; + ->will($this->returnValue($requestContext)); + return $router; + } + + private function getKernel() + { $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); $container ->expects($this->atLeastOnce()) @@ -85,7 +106,9 @@ class RouterMatchCommandTest extends TestCase $container ->expects($this->any()) ->method('get') - ->willReturn($router); + ->with('router') + ->willReturn($this->getRouter()) + ; $kernel = $this->getMockBuilder(KernelInterface::class)->getMock(); $kernel diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php index 19c6d70156..edf006c868 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php @@ -12,7 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; use PHPUnit\Framework\TestCase; -use Symfony\Component\Console\Application; +use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; use Symfony\Component\Filesystem\Filesystem; @@ -24,7 +24,7 @@ class TranslationDebugCommandTest extends TestCase public function testDebugMissingMessages() { - $tester = $this->createCommandTester($this->getContainer(array('foo' => 'foo'))); + $tester = $this->createCommandTester(array('foo' => 'foo')); $tester->execute(array('locale' => 'en', 'bundle' => 'foo')); $this->assertRegExp('/missing/', $tester->getDisplay()); @@ -32,7 +32,7 @@ class TranslationDebugCommandTest extends TestCase public function testDebugUnusedMessages() { - $tester = $this->createCommandTester($this->getContainer(array(), array('foo' => 'foo'))); + $tester = $this->createCommandTester(array(), array('foo' => 'foo')); $tester->execute(array('locale' => 'en', 'bundle' => 'foo')); $this->assertRegExp('/unused/', $tester->getDisplay()); @@ -40,7 +40,7 @@ class TranslationDebugCommandTest extends TestCase public function testDebugFallbackMessages() { - $tester = $this->createCommandTester($this->getContainer(array(), array('foo' => 'foo'))); + $tester = $this->createCommandTester(array(), array('foo' => 'foo')); $tester->execute(array('locale' => 'fr', 'bundle' => 'foo')); $this->assertRegExp('/fallback/', $tester->getDisplay()); @@ -48,7 +48,7 @@ class TranslationDebugCommandTest extends TestCase public function testNoDefinedMessages() { - $tester = $this->createCommandTester($this->getContainer()); + $tester = $this->createCommandTester(); $tester->execute(array('locale' => 'fr', 'bundle' => 'test')); $this->assertRegExp('/No defined or extracted messages for locale "fr"/', $tester->getDisplay()); @@ -56,7 +56,7 @@ class TranslationDebugCommandTest extends TestCase public function testDebugDefaultDirectory() { - $tester = $this->createCommandTester($this->getContainer(array('foo' => 'foo'), array('bar' => 'bar'))); + $tester = $this->createCommandTester(array('foo' => 'foo'), array('bar' => 'bar')); $tester->execute(array('locale' => 'en')); $this->assertRegExp('/missing/', $tester->getDisplay()); @@ -71,7 +71,7 @@ class TranslationDebugCommandTest extends TestCase ->with($this->equalTo($this->translationDir)) ->willThrowException(new \InvalidArgumentException()); - $tester = $this->createCommandTester($this->getContainer(array('foo' => 'foo'), array('bar' => 'bar'), $kernel)); + $tester = $this->createCommandTester(array('foo' => 'foo'), array('bar' => 'bar'), $kernel); $tester->execute(array('locale' => 'en', 'bundle' => $this->translationDir)); $this->assertRegExp('/missing/', $tester->getDisplay()); @@ -89,7 +89,7 @@ class TranslationDebugCommandTest extends TestCase ->with($this->equalTo('dir')) ->will($this->throwException(new \InvalidArgumentException())); - $tester = $this->createCommandTester($this->getContainer(array(), array(), $kernel)); + $tester = $this->createCommandTester(array(), array(), $kernel); $tester->execute(array('locale' => 'en', 'bundle' => 'dir')); } @@ -109,18 +109,7 @@ class TranslationDebugCommandTest extends TestCase /** * @return CommandTester */ - private function createCommandTester($container) - { - $command = new TranslationDebugCommand(); - $command->setContainer($container); - - $application = new Application(); - $application->add($command); - - return new CommandTester($application->find('debug:translation')); - } - - private function getContainer($extractedMessages = array(), $loadedMessages = array(), $kernel = null) + private function createCommandTester($extractedMessages = array(), $loadedMessages = array(), $kernel = null) { $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator') ->disableOriginalConstructor() @@ -167,6 +156,41 @@ class TranslationDebugCommandTest extends TestCase ->method('getRootDir') ->will($this->returnValue($this->translationDir)); + $kernel + ->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel + ->expects($this->any()) + ->method('getContainer') + ->will($this->returnValue($this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock())); + + $command = new TranslationDebugCommand($translator, $loader, $extractor); + + $application = new Application($kernel); + $application->add($command); + + return new CommandTester($application->find('debug:translation')); + } + + /** + * @group legacy + * @expectedDeprecation Passing a command name as the first argument of "Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand::__construct" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead. + */ + public function testLegacyDebugCommand() + { + $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator') + ->disableOriginalConstructor() + ->getMock(); + $extractor = $this->getMockBuilder('Symfony\Component\Translation\Extractor\ExtractorInterface')->getMock(); + $loader = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader')->getMock(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel + ->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array())); + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); $container ->expects($this->any()) @@ -178,7 +202,21 @@ class TranslationDebugCommandTest extends TestCase array('kernel', 1, $kernel), ))); - return $container; + $kernel + ->expects($this->any()) + ->method('getContainer') + ->will($this->returnValue($container)); + + $command = new TranslationDebugCommand(); + $command->setContainer($container); + + $application = new Application($kernel); + $application->add($command); + + $tester = new CommandTester($application->find('debug:translation')); + $tester->execute(array('locale' => 'en')); + + $this->assertContains('No defined or extracted', $tester->getDisplay()); } private function getBundle($path) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php index e845619d9a..db61398b26 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php @@ -12,11 +12,10 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Command; use PHPUnit\Framework\TestCase; -use Symfony\Component\Console\Application; +use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand; use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\DependencyInjection; use Symfony\Component\HttpKernel; class TranslationUpdateCommandTest extends TestCase @@ -26,7 +25,7 @@ class TranslationUpdateCommandTest extends TestCase public function testDumpMessagesAndClean() { - $tester = $this->createCommandTester($this->getContainer(array('messages' => array('foo' => 'foo')))); + $tester = $this->createCommandTester(array('messages' => array('foo' => 'foo'))); $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--dump-messages' => true, '--clean' => true)); $this->assertRegExp('/foo/', $tester->getDisplay()); $this->assertRegExp('/1 message was successfully extracted/', $tester->getDisplay()); @@ -34,7 +33,7 @@ class TranslationUpdateCommandTest extends TestCase public function testDumpTwoMessagesAndClean() { - $tester = $this->createCommandTester($this->getContainer(array('messages' => array('foo' => 'foo', 'bar' => 'bar')))); + $tester = $this->createCommandTester(array('messages' => array('foo' => 'foo', 'bar' => 'bar'))); $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--dump-messages' => true, '--clean' => true)); $this->assertRegExp('/foo/', $tester->getDisplay()); $this->assertRegExp('/bar/', $tester->getDisplay()); @@ -43,7 +42,7 @@ class TranslationUpdateCommandTest extends TestCase public function testDumpMessagesForSpecificDomain() { - $tester = $this->createCommandTester($this->getContainer(array('messages' => array('foo' => 'foo'), 'mydomain' => array('bar' => 'bar')))); + $tester = $this->createCommandTester(array('messages' => array('foo' => 'foo'), 'mydomain' => array('bar' => 'bar'))); $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--dump-messages' => true, '--clean' => true, '--domain' => 'mydomain')); $this->assertRegExp('/bar/', $tester->getDisplay()); $this->assertRegExp('/1 message was successfully extracted/', $tester->getDisplay()); @@ -51,14 +50,14 @@ class TranslationUpdateCommandTest extends TestCase public function testWriteMessages() { - $tester = $this->createCommandTester($this->getContainer(array('messages' => array('foo' => 'foo')))); + $tester = $this->createCommandTester(array('messages' => array('foo' => 'foo'))); $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--force' => true)); $this->assertRegExp('/Translation files were successfully updated./', $tester->getDisplay()); } public function testWriteMessagesForSpecificDomain() { - $tester = $this->createCommandTester($this->getContainer(array('messages' => array('foo' => 'foo'), 'mydomain' => array('bar' => 'bar')))); + $tester = $this->createCommandTester(array('messages' => array('foo' => 'foo'), 'mydomain' => array('bar' => 'bar'))); $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--force' => true, '--domain' => 'mydomain')); $this->assertRegExp('/Translation files were successfully updated./', $tester->getDisplay()); } @@ -79,18 +78,7 @@ class TranslationUpdateCommandTest extends TestCase /** * @return CommandTester */ - private function createCommandTester(DependencyInjection\ContainerInterface $container) - { - $command = new TranslationUpdateCommand(); - $command->setContainer($container); - - $application = new Application(); - $application->add($command); - - return new CommandTester($application->find('translation:update')); - } - - private function getContainer($extractedMessages = array(), $loadedMessages = array(), HttpKernel\KernelInterface $kernel = null) + private function createCommandTester($extractedMessages = array(), $loadedMessages = array(), HttpKernel\KernelInterface $kernel = null) { $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator') ->disableOriginalConstructor() @@ -147,6 +135,42 @@ class TranslationUpdateCommandTest extends TestCase ->method('getRootDir') ->will($this->returnValue($this->translationDir)); + $kernel + ->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel + ->expects($this->any()) + ->method('getContainer') + ->will($this->returnValue($this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock())); + + $command = new TranslationUpdateCommand($writer, $loader, $extractor, 'en'); + + $application = new Application($kernel); + $application->add($command); + + return new CommandTester($application->find('translation:update')); + } + + /** + * @group legacy + * @expectedDeprecation Passing a command name as the first argument of "Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand::__construct" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead. + */ + public function testLegacyUpdateCommand() + { + $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator') + ->disableOriginalConstructor() + ->getMock(); + $extractor = $this->getMockBuilder('Symfony\Component\Translation\Extractor\ExtractorInterface')->getMock(); + $loader = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader')->getMock(); + $writer = $this->getMockBuilder('Symfony\Component\Translation\Writer\TranslationWriter')->getMock(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel + ->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array())); + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); $container ->expects($this->any()) @@ -159,7 +183,21 @@ class TranslationUpdateCommandTest extends TestCase array('kernel', 1, $kernel), ))); - return $container; + $kernel + ->expects($this->any()) + ->method('getContainer') + ->will($this->returnValue($container)); + + $command = new TranslationUpdateCommand(); + $command->setContainer($container); + + $application = new Application($kernel); + $application->add($command); + + $tester = new CommandTester($application->find('translation:update')); + $tester->execute(array('locale' => 'en')); + + $this->assertContains('You must choose one of --force or --dump-messages', $tester->getDisplay()); } private function getBundle($path) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 2f46cefa4f..79fcf04da8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -955,6 +955,7 @@ abstract class FrameworkExtensionTest extends TestCase 'kernel.bundles' => array('FrameworkBundle' => 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle'), 'kernel.bundles_metadata' => array('FrameworkBundle' => array('namespace' => 'Symfony\\Bundle\\FrameworkBundle', 'path' => __DIR__.'/../..', 'parent' => null)), 'kernel.cache_dir' => __DIR__, + 'kernel.project_dir' => __DIR__, 'kernel.debug' => false, 'kernel.environment' => 'test', 'kernel.name' => 'kernel', diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php index c4ea8e3974..9a2d198a72 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; +use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; /** @@ -74,11 +75,28 @@ class CachePoolClearCommandTest extends WebTestCase ->execute(array('pools' => array('unknown_pool')), array('decorated' => false)); } + /** + * @group legacy + * @expectedDeprecation Passing a command name as the first argument of "Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand::__construct" is deprecated since version 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead. + */ + public function testLegacyClearCommand() + { + $application = new Application(static::$kernel); + $application->add(new CachePoolClearCommand()); + + $tester = new CommandTester($application->find('cache:pool:clear')); + + $tester->execute(array('pools' => array())); + + $this->assertContains('Cache was successfully cleared', $tester->getDisplay()); + } + private function createCommandTester() { - $command = new CachePoolClearCommand(); - $command->setContainer(static::$kernel->getContainer()); + $container = static::$kernel->getContainer(); + $application = new Application(static::$kernel); + $application->add(new CachePoolClearCommand($container->get('cache.global_clearer'))); - return new CommandTester($command); + return new CommandTester($application->find('cache:pool:clear')); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php index e128596612..1512e910c9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/InitAclCommand.php @@ -20,6 +20,8 @@ use Doctrine\DBAL\Schema\SchemaException; * Installs the tables required by the ACL system. * * @author Johannes M. Schmitt + * + * @final since version 3.4 */ class InitAclCommand extends ContainerAwareCommand { diff --git a/src/Symfony/Bundle/SecurityBundle/Command/SetAclCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/SetAclCommand.php index ba34782346..7d0146fdc6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/SetAclCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/SetAclCommand.php @@ -27,6 +27,8 @@ use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface; * Sets ACL for objects. * * @author Kévin Dunglas + * + * @final since version 3.4 */ class SetAclCommand extends ContainerAwareCommand { diff --git a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php index c307dca1f7..af6c866551 100644 --- a/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php +++ b/src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php @@ -26,6 +26,8 @@ use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; * Encode a user's password. * * @author Sarah Khalil + * + * @final since version 3.4 */ class UserPasswordEncoderCommand extends Command { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 7dd91ca7b0..c530a38717 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection; +use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; @@ -107,7 +108,7 @@ class SecurityExtension extends Extension if (class_exists(Application::class)) { $loader->load('console.xml'); - $container->getDefinition('security.console.user_password_encoder_command')->replaceArgument(1, array_keys($config['encoders'])); + $container->getDefinition(UserPasswordEncoderCommand::class)->replaceArgument(1, array_keys($config['encoders'])); } // load ACL diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/console.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/console.xml index c94c00f754..f14b4a5a35 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/console.xml @@ -7,7 +7,15 @@ - + + + + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index c50aab24e1..3b8f6dda85 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory; +use Symfony\Component\Console\Application; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; @@ -58,4 +59,9 @@ class SecurityBundle extends Bundle $extension->addUserProviderFactory(new LdapFactory()); $container->addCompilerPass(new AddSecurityVotersPass()); } + + public function registerCommands(Application $application) + { + // noop + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 9ccc1762e8..87b8e30cbd 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Reference; use Symfony\Bundle\SecurityBundle\SecurityBundle; @@ -355,7 +356,7 @@ abstract class CompleteConfigurationTest extends TestCase public function testUserPasswordEncoderCommandIsRegistered() { - $this->assertTrue($this->getContainer('remember_me_options')->has('security.console.user_password_encoder_command')); + $this->assertTrue($this->getContainer('remember_me_options')->has(UserPasswordEncoderCommand::class)); } public function testDefaultAccessDecisionManagerStrategyIsAffirmative() diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/console.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/console.xml index 8a1b157489..a899782374 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/console.xml @@ -7,12 +7,12 @@ - + - + diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php index 76d0676409..21a39a1237 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php +++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\TwigBundle; +use Symfony\Component\Console\Application; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -37,4 +38,9 @@ class TwigBundle extends Bundle $container->addCompilerPass(new ExceptionListenerPass()); $container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING); } + + public function registerCommands(Application $application) + { + // noop + } } diff --git a/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php b/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php index 9db0b36767..cac53044a9 100644 --- a/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php +++ b/src/Symfony/Component/Config/Definition/Builder/ArrayNodeDefinition.php @@ -428,7 +428,7 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition $node->setKeyAttribute($this->key, $this->removeKeyItem); } - if (true === $this->atLeastOne) { + if (true === $this->atLeastOne || false === $this->allowEmptyValue) { $node->setMinNumberOfElements(1); } @@ -490,6 +490,12 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition ); } + if (false === $this->allowEmptyValue) { + throw new InvalidDefinitionException( + sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s"', $path) + ); + } + if (true === $this->atLeastOne) { throw new InvalidDefinitionException( sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path) diff --git a/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php b/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php index f2a32351a8..4c2a397cff 100644 --- a/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/Builder/ArrayNodeDefinitionTest.php @@ -54,6 +54,7 @@ class ArrayNodeDefinitionTest extends TestCase array('defaultValue', array(array())), array('addDefaultChildrenIfNoneSet', array()), array('requiresAtLeastOneElement', array()), + array('cannotBeEmpty', array()), array('useAttributeAsKey', array('foo')), ); } @@ -285,6 +286,32 @@ class ArrayNodeDefinitionTest extends TestCase ); } + public function testRequiresAtLeastOneElement() + { + $node = new ArrayNodeDefinition('root'); + $node + ->requiresAtLeastOneElement() + ->integerPrototype(); + + $node->getNode()->finalize(array(1)); + + $this->addToAssertionCount(1); + } + + /** + * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException + * @expectedExceptionMessage The path "root" should have at least 1 element(s) defined. + */ + public function testCannotBeEmpty() + { + $node = new ArrayNodeDefinition('root'); + $node + ->cannotBeEmpty() + ->integerPrototype(); + + $node->getNode()->finalize(array()); + } + protected function getField($object, $field) { $reflection = new \ReflectionProperty($object, $field); diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 1f03f0ef51..47365a0310 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG 3.4.0 ----- + * added support for validation groups to the `Valid` constraint * not setting the `strict` option of the `Choice` constraint to `true` is deprecated and will throw an exception in Symfony 4.0 * setting the `checkDNS` option of the `Url` constraint to `true` is deprecated in favor of diff --git a/src/Symfony/Component/Validator/Constraints/Valid.php b/src/Symfony/Component/Validator/Constraints/Valid.php index 439da3ae00..8939423778 100644 --- a/src/Symfony/Component/Validator/Constraints/Valid.php +++ b/src/Symfony/Component/Validator/Constraints/Valid.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Validator\Constraints; use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Exception\ConstraintDefinitionException; /** * @Annotation @@ -24,15 +23,23 @@ class Valid extends Constraint { public $traverse = true; - public function __construct($options = null) + public function __get($option) { - if (is_array($options) && array_key_exists('groups', $options)) { - throw new ConstraintDefinitionException(sprintf( - 'The option "groups" is not supported by the constraint %s', - __CLASS__ - )); + if ('groups' === $option) { + // when this is reached, no groups have been configured + return null; } - parent::__construct($options); + return parent::__get($option); + } + + /** + * {@inheritdoc} + */ + public function addImplicitGroupName($group) + { + if (null !== $this->groups) { + parent::addImplicitGroupName($group); + } } } diff --git a/src/Symfony/Component/Validator/Constraints/ValidValidator.php b/src/Symfony/Component/Validator/Constraints/ValidValidator.php new file mode 100644 index 0000000000..45ac69d91d --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/ValidValidator.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\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Christian Flothmann + */ +class ValidValidator extends ConstraintValidator +{ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Valid) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Valid'); + } + + $violations = $this->context->getValidator()->validate($value, null, array($this->context->getGroup())); + + foreach ($violations as $violation) { + $this->context->buildViolation($violation->getMessage(), $violation->getParameters()) + ->atPath($violation->getPropertyPath()) + ->addViolation(); + } + } +} diff --git a/src/Symfony/Component/Validator/Mapping/GenericMetadata.php b/src/Symfony/Component/Validator/Mapping/GenericMetadata.php index ff1cb6d37f..a14b6578e3 100644 --- a/src/Symfony/Component/Validator/Mapping/GenericMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/GenericMetadata.php @@ -131,7 +131,7 @@ class GenericMetadata implements MetadataInterface )); } - if ($constraint instanceof Valid) { + if ($constraint instanceof Valid && null === $constraint->groups) { $this->cascadingStrategy = CascadingStrategy::CASCADE; if ($constraint->traverse) { diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ValidTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ValidTest.php index 83722fd2df..9928bd82d0 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ValidTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ValidTest.php @@ -19,11 +19,17 @@ use Symfony\Component\Validator\Constraints\Valid; */ class ValidTest extends TestCase { - /** - * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException - */ - public function testRejectGroupsOption() + public function testGroupsCanBeSet() { - new Valid(array('groups' => 'foo')); + $constraint = new Valid(array('groups' => 'foo')); + + $this->assertSame(array('foo'), $constraint->groups); + } + + public function testGroupsAreNullByDefault() + { + $constraint = new Valid(); + + $this->assertNull($constraint->groups); } } diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php index 77a7cc6c2b..90b384a311 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Validator\Tests\Validator; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\Traverse; use Symfony\Component\Validator\Constraints\Valid; @@ -670,4 +671,38 @@ abstract class AbstractTest extends AbstractValidatorTest $this->assertCount(1, $violations); $this->assertSame($constraint, $violations[0]->getConstraint()); } + + public function testNestedObjectIsNotValidatedIfGroupInValidConstraintIsNotValidated() + { + $entity = new Entity(); + $entity->firstName = ''; + $reference = new Reference(); + $reference->value = ''; + $entity->childA = $reference; + + $this->metadata->addPropertyConstraint('firstName', new NotBlank(array('groups' => 'group1'))); + $this->metadata->addPropertyConstraint('childA', new Valid(array('groups' => 'group1'))); + $this->referenceMetadata->addPropertyConstraint('value', new NotBlank()); + + $violations = $this->validator->validate($entity, null, array()); + + $this->assertCount(0, $violations); + } + + public function testNestedObjectIsValidatedIfGroupInValidConstraintIsValidated() + { + $entity = new Entity(); + $entity->firstName = ''; + $reference = new Reference(); + $reference->value = ''; + $entity->childA = $reference; + + $this->metadata->addPropertyConstraint('firstName', new NotBlank(array('groups' => 'group1'))); + $this->metadata->addPropertyConstraint('childA', new Valid(array('groups' => 'group1'))); + $this->referenceMetadata->addPropertyConstraint('value', new NotBlank(array('groups' => 'group1'))); + + $violations = $this->validator->validate($entity, null, array('Default', 'group1')); + + $this->assertCount(2, $violations); + } } diff --git a/src/Symfony/Component/VarDumper/Caster/DateCaster.php b/src/Symfony/Component/VarDumper/Caster/DateCaster.php index bc0f8d6cf8..c967bedb64 100644 --- a/src/Symfony/Component/VarDumper/Caster/DateCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DateCaster.php @@ -32,7 +32,7 @@ class DateCaster ; $a = array(); - $a[$prefix.'date'] = new ConstStub($d->format('Y-m-d H:i:s.u '.($location ? 'e (P)' : 'P')), $title); + $a[$prefix.'date'] = new ConstStub($d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).($location ? ' e (P)' : ' P')), $title); $stub->class .= $d->format(' @U'); @@ -56,7 +56,7 @@ class DateCaster .($i->y ? '%yy ' : '') .($i->m ? '%mm ' : '') .($i->d ? '%dd ' : '') - .($i->h || $i->i || $i->s || $i->f ? '%H:%I:%S.%F' : '') + .($i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, $i->f) : '') ; $format = '%R ' === $format ? '0s' : $format; @@ -74,4 +74,9 @@ class DateCaster return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; } + + private static function formatSeconds($s, $us) + { + return sprintf('%02d.%s', $s, 0 === ($len = strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); + } } diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php index 8e2a4f4509..ec85040839 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/DateCasterTest.php @@ -48,7 +48,7 @@ EODUMP; $xDump = <<<'EODUMP' array:1 [ - "\x00~\x00date" => 2017-08-30 00:00:00.000000 Europe/Zurich (+02:00) + "\x00~\x00date" => 2017-08-30 00:00:00.0 Europe/Zurich (+02:00) ] EODUMP; @@ -57,7 +57,7 @@ EODUMP; $xDump = <<<'EODUMP' Symfony\Component\VarDumper\Caster\ConstStub { +type: 1 - +class: "2017-08-30 00:00:00.000000 Europe/Zurich (+02:00)" + +class: "2017-08-30 00:00:00.0 Europe/Zurich (+02:00)" +value: """ Wednesday, August 30, 2017\n +%a from now\n @@ -77,8 +77,15 @@ EODUMP; public function provideDateTimes() { return array( - array('2017-04-30 00:00:00.000000', 'Europe/Zurich', '2017-04-30 00:00:00.000000 Europe/Zurich (+02:00)'), - array('2017-04-30 00:00:00.000000', '+02:00', '2017-04-30 00:00:00.000000 +02:00'), + array('2017-04-30 00:00:00.000000', 'Europe/Zurich', '2017-04-30 00:00:00.0 Europe/Zurich (+02:00)'), + array('2017-04-30 00:00:00.000000', '+02:00', '2017-04-30 00:00:00.0 +02:00'), + + array('2017-04-30 00:00:00.100000', '+02:00', '2017-04-30 00:00:00.100 +02:00'), + array('2017-04-30 00:00:00.120000', '+02:00', '2017-04-30 00:00:00.120 +02:00'), + array('2017-04-30 00:00:00.123000', '+02:00', '2017-04-30 00:00:00.123 +02:00'), + array('2017-04-30 00:00:00.123400', '+02:00', '2017-04-30 00:00:00.123400 +02:00'), + array('2017-04-30 00:00:00.123450', '+02:00', '2017-04-30 00:00:00.123450 +02:00'), + array('2017-04-30 00:00:00.123456', '+02:00', '2017-04-30 00:00:00.123456 +02:00'), ); } @@ -159,22 +166,22 @@ EODUMP; { return array( array('PT0S', 0, '0s', '0s'), - array('PT1S', 0, '+ 00:00:01.000000', '1s'), - array('PT2M', 0, '+ 00:02:00.000000', '120s'), - array('PT3H', 0, '+ 03:00:00.000000', '10 800s'), + array('PT1S', 0, '+ 00:00:01.0', '1s'), + array('PT2M', 0, '+ 00:02:00.0', '120s'), + array('PT3H', 0, '+ 03:00:00.0', '10 800s'), array('P4D', 0, '+ 4d', '345 600s'), array('P5M', 0, '+ 5m', null), array('P6Y', 0, '+ 6y', null), - array('P1Y2M3DT4H5M6S', 0, '+ 1y 2m 3d 04:05:06.000000', null), + array('P1Y2M3DT4H5M6S', 0, '+ 1y 2m 3d 04:05:06.0', null), array('PT0S', 1, '0s', '0s'), - array('PT1S', 1, '- 00:00:01.000000', '-1s'), - array('PT2M', 1, '- 00:02:00.000000', '-120s'), - array('PT3H', 1, '- 03:00:00.000000', '-10 800s'), + array('PT1S', 1, '- 00:00:01.0', '-1s'), + array('PT2M', 1, '- 00:02:00.0', '-120s'), + array('PT3H', 1, '- 03:00:00.0', '-10 800s'), array('P4D', 1, '- 4d', '-345 600s'), array('P5M', 1, '- 5m', null), array('P6Y', 1, '- 6y', null), - array('P1Y2M3DT4H5M6S', 1, '- 1y 2m 3d 04:05:06.000000', null), + array('P1Y2M3DT4H5M6S', 1, '- 1y 2m 3d 04:05:06.0', null), ); } diff --git a/src/Symfony/Component/Workflow/MarkingStore/MultipleStateMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/MultipleStateMarkingStore.php index 874d1fb513..6d2d088489 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/MultipleStateMarkingStore.php +++ b/src/Symfony/Component/Workflow/MarkingStore/MultipleStateMarkingStore.php @@ -54,4 +54,12 @@ class MultipleStateMarkingStore implements MarkingStoreInterface { $this->propertyAccessor->setValue($subject, $this->property, $marking->getPlaces()); } + + /** + * @return string + */ + public function getProperty() + { + return $this->property; + } } diff --git a/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php index 99adf1671b..d6afc6aeeb 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php +++ b/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php @@ -59,4 +59,12 @@ class SingleStateMarkingStore implements MarkingStoreInterface { $this->propertyAccessor->setValue($subject, $this->property, key($marking->getPlaces())); } + + /** + * @return string + */ + public function getProperty() + { + return $this->property; + } } diff --git a/src/Symfony/Component/Workflow/SupportStrategy/ClassInstanceSupportStrategy.php b/src/Symfony/Component/Workflow/SupportStrategy/ClassInstanceSupportStrategy.php index c2aaa27212..8828946dbf 100644 --- a/src/Symfony/Component/Workflow/SupportStrategy/ClassInstanceSupportStrategy.php +++ b/src/Symfony/Component/Workflow/SupportStrategy/ClassInstanceSupportStrategy.php @@ -26,4 +26,12 @@ final class ClassInstanceSupportStrategy implements SupportStrategyInterface { return $subject instanceof $this->className; } + + /** + * @return string + */ + public function getClassName() + { + return $this->className; + } } diff --git a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php index 3ca61914aa..45dcbc73cc 100644 --- a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php +++ b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php @@ -137,6 +137,31 @@ class WorkflowTest extends TestCase $this->assertFalse($workflow->can($subject, 't1')); } + public function testCanDoesNotTriggerGuardEventsForNotEnabledTransitions() + { + $definition = $this->createComplexWorkflowDefinition(); + $subject = new \stdClass(); + $subject->marking = null; + + $dispatchedEvents = array(); + $eventDispatcher = new EventDispatcher(); + + $workflow = new Workflow($definition, new MultipleStateMarkingStore(), $eventDispatcher, 'workflow_name'); + $workflow->apply($subject, 't1'); + $workflow->apply($subject, 't2'); + + $eventDispatcher->addListener('workflow.workflow_name.guard.t3', function () use (&$dispatchedEvents) { + $dispatchedEvents[] = 'workflow_name.guard.t3'; + }); + $eventDispatcher->addListener('workflow.workflow_name.guard.t4', function () use (&$dispatchedEvents) { + $dispatchedEvents[] = 'workflow_name.guard.t4'; + }); + + $workflow->can($subject, 't3'); + + $this->assertSame(array('workflow_name.guard.t3'), $dispatchedEvents); + } + /** * @expectedException \Symfony\Component\Workflow\Exception\LogicException * @expectedExceptionMessage Unable to apply transition "t2" for workflow "unnamed". diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index 617c05b381..31c830fcee 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -92,10 +92,19 @@ class Workflow */ public function can($subject, $transitionName) { - $transitions = $this->getEnabledTransitions($subject); + $transitions = $this->definition->getTransitions(); + $marking = $this->getMarking($subject); foreach ($transitions as $transition) { - if ($transitionName === $transition->getName()) { + foreach ($transition->getFroms() as $place) { + if (!$marking->has($place)) { + // do not emit guard events for transitions where the marking does not contain + // all "from places" (thus the transition couldn't be applied anyway) + continue 2; + } + } + + if ($transitionName === $transition->getName() && $this->doCan($subject, $marking, $transition)) { return true; } } @@ -186,6 +195,14 @@ class Workflow return $this->definition; } + /** + * @return MarkingStoreInterface + */ + public function getMarkingStore() + { + return $this->markingStore; + } + private function doCan($subject, Marking $marking, Transition $transition) { foreach ($transition->getFroms() as $place) {