Merge branch '3.4'

* 3.4:
  [FrameworkBundle] Commands as a service
  [Config] Enable cannotBeEmpty along with requiresAtLeastOneElement
  Remove leading 0 in ms of date caster
  [Workflow] feature: add getter in workflow
  [Workflow] do not emit not needed guard events
  add groups support to the Valid constraint
This commit is contained in:
Nicolas Grekas 2017-08-06 12:41:54 +02:00
commit c377f04b03
51 changed files with 921 additions and 178 deletions

View File

@ -23,6 +23,8 @@ use Symfony\Component\HttpKernel\KernelInterface;
* A console command to display information about the current installation.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*
* @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('<info>Symfony</>'),

View File

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

View File

@ -26,6 +26,8 @@ use Symfony\Component\HttpKernel\Bundle\BundleInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Gábor Egyed <gabor.egyed@gmail.com>
*
* @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;
}

View File

@ -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 <francis.besset@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*
* @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 <info>%s</info> environment with debug <info>%s</info>', $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');

View File

@ -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: <info>%s</info>', $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);
}
}

View File

@ -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 <fabien@symfony.com>
*
* @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 <info>%s</info> environment with debug <info>%s</info>', $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 <info>%s</info> environment with debug <info>%s</info>', $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)));
}

View File

@ -23,6 +23,8 @@ use Symfony\Component\Yaml\Yaml;
* A console command for dumping available configuration reference.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @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');

View File

@ -25,6 +25,8 @@ use Symfony\Component\Console\Style\SymfonyStyle;
* @author Kevin Bond <kevinbond@gmail.com>
* @author Wouter J <waldio.webdesign@gmail.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final since version 3.4
*/
class ConfigDumpReferenceCommand extends AbstractConfigCommand
{

View File

@ -27,6 +27,8 @@ use Symfony\Component\Config\FileLocator;
* A console command for retrieving information about services.
*
* @author Ryan Weaver <ryan@thatsquality.com>
*
* @internal since version 3.4
*/
class ContainerDebugCommand extends ContainerAwareCommand
{

View File

@ -23,9 +23,31 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
* A console command for retrieving information about event dispatcher.
*
* @author Matthieu Auger <mail@matthieuauger.com>
*
* @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()

View File

@ -27,14 +27,41 @@ use Symfony\Component\Routing\Route;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*
* @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) {
}
}

View File

@ -24,14 +24,41 @@ use Symfony\Component\Routing\Matcher\TraceableUrlMatcher;
* A console command to test route matching.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @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'));

View File

@ -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 <florian@voutzinos.com>
*
* @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;

View File

@ -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 <michelsalib@hotmail.com>
*
* @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';

View File

@ -20,9 +20,16 @@ use Symfony\Component\Workflow\Marking;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @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);

View File

@ -22,6 +22,8 @@ use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand;
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*
* @final since version 3.4
*/
class XliffLintCommand extends Command
{

View File

@ -21,6 +21,8 @@ use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand;
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @final since version 3.4
*/
class YamlLintCommand extends Command
{

View File

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

View File

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

View File

@ -100,11 +100,6 @@
</call>
</service>
<service id="cache.command.pool_pruner" class="Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand">
<argument type="iterator" />
<tag name="console.command" command="cache:pool:prune" />
</service>
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer" public="true">
<tag name="kernel.cache_clearer" />
</service>

View File

@ -12,5 +12,89 @@
<tag name="kernel.event_subscriber" />
<tag name="monolog.logger" channel="console" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\AboutCommand">
<tag name="console.command" command="about" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand">
<argument type="service" id="filesystem" />
<tag name="console.command" command="assets:install" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand">
<argument type="service" id="cache_clearer" />
<argument type="service" id="filesystem" />
<tag name="console.command" command="cache:clear" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand">
<argument type="service" id="cache.global_clearer" />
<tag name="console.command" command="cache:pool:clear" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand">
<argument type="iterator" />
<tag name="console.command" command="cache:pool:prune" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand">
<argument type="service" id="cache_warmer" />
<tag name="console.command" command="cache:warmup" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand">
<tag name="console.command" command="debug:config" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand">
<tag name="console.command" command="config:dump-reference" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\ContainerDebugCommand">
<tag name="console.command" command="debug:container" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand">
<argument type="service" id="event_dispatcher" />
<tag name="console.command" command="debug:event-dispatcher" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand">
<argument type="service" id="router" />
<tag name="console.command" command="debug:router" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand">
<argument type="service" id="router" />
<tag name="console.command" command="router:match" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand">
<argument type="service" id="translator" />
<argument type="service" id="translation.loader" />
<argument type="service" id="translation.extractor" />
<tag name="console.command" command="debug:translation" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand">
<argument type="service" id="translation.writer" />
<argument type="service" id="translation.loader" />
<argument type="service" id="translation.extractor" />
<argument>%kernel.default_locale%</argument>
<tag name="console.command" command="translation:update" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand">
<tag name="console.command" command="workflow:dump" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\XliffLintCommand">
<tag name="console.command" command="lint:xliff" />
</service>
<service id="Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand">
<tag name="console.command" command="lint:yaml" />
</service>
</services>
</container>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,8 @@ use Doctrine\DBAL\Schema\SchemaException;
* Installs the tables required by the ACL system.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @final since version 3.4
*/
class InitAclCommand extends ContainerAwareCommand
{

View File

@ -27,6 +27,8 @@ use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;
* Sets ACL for objects.
*
* @author Kévin Dunglas <kevin@les-tilleuls.coop>
*
* @final since version 3.4
*/
class SetAclCommand extends ContainerAwareCommand
{

View File

@ -26,6 +26,8 @@ use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
* Encode a user's password.
*
* @author Sarah Khalil <mkhalil.sarah@gmail.com>
*
* @final since version 3.4
*/
class UserPasswordEncoderCommand extends Command
{

View File

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

View File

@ -7,7 +7,15 @@
<services>
<defaults public="false" />
<service id="security.console.user_password_encoder_command" class="Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand">
<service id="Symfony\Bundle\SecurityBundle\Command\InitAclCommand">
<tag name="console.command" command="init:acl" />
</service>
<service id="Symfony\Bundle\SecurityBundle\Command\SetAclCommand">
<tag name="console.command" command="acl:set" />
</service>
<service id="Symfony\Bundle\SecurityBundle\Command\UserPasswordEncoderCommand">
<argument type="service" id="security.encoder_factory"/>
<argument type="collection" /> <!-- encoders' user classes -->
<tag name="console.command" command="security:encode-password" />

View File

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

View File

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

View File

@ -7,12 +7,12 @@
<services>
<defaults public="false" />
<service id="twig.command.debug" class="Symfony\Bridge\Twig\Command\DebugCommand">
<service id="Symfony\Bridge\Twig\Command\DebugCommand">
<argument type="service" id="twig" />
<tag name="console.command" command="debug:twig" />
</service>
<service id="twig.command.lint" class="Symfony\Bundle\TwigBundle\Command\LintCommand">
<service id="Symfony\Bundle\TwigBundle\Command\LintCommand">
<argument type="service" id="twig" />
<tag name="console.command" command="lint:twig" />
</service>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
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();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -54,4 +54,12 @@ class MultipleStateMarkingStore implements MarkingStoreInterface
{
$this->propertyAccessor->setValue($subject, $this->property, $marking->getPlaces());
}
/**
* @return string
*/
public function getProperty()
{
return $this->property;
}
}

View File

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

View File

@ -26,4 +26,12 @@ final class ClassInstanceSupportStrategy implements SupportStrategyInterface
{
return $subject instanceof $this->className;
}
/**
* @return string
*/
public function getClassName()
{
return $this->className;
}
}

View File

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

View File

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