This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Style\StyleInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
/**
* A console command for dumping available configuration reference.
*
* @author Kevin Bond <kevinbond@gmail.com>
* @author Wouter J <waldio.webdesign@gmail.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
abstract class AbstractConfigCommand extends ContainerDebugCommand
{
protected function listBundles($output)
{
$headers = array('Bundle name', 'Extension alias');
$rows = array();
$bundles = $this->getContainer()->get('kernel')->getBundles();
usort($bundles, function ($bundleA, $bundleB) {
return strcmp($bundleA->getName(), $bundleB->getName());
});
foreach ($bundles as $bundle) {
$extension = $bundle->getContainerExtension();
$rows[] = array($bundle->getName(), $extension ? $extension->getAlias() : '');
}
if ($output instanceof StyleInterface) {
$output->table($headers, $rows);
} else {
$output->writeln('Available registered bundles with their extension alias if available:');
$table = new Table($output);
$table->setHeaders($headers)->setRows($rows)->render();
}
}
protected function findExtension($name)
{
$bundles = $this->initializeBundles();
foreach ($bundles as $bundle) {
if ($name === $bundle->getName()) {
if (!$bundle->getContainerExtension()) {
throw new \LogicException(sprintf('Bundle "%s" does not have a container extension.', $name));
}
return $bundle->getContainerExtension();
}
$extension = $bundle->getContainerExtension();
if ($extension && $name === $extension->getAlias()) {
return $extension;
}
}
if ('Bundle' !== substr($name, -6)) {
$message = sprintf('No extensions with configuration available for "%s"', $name);
} else {
$message = sprintf('No extension with alias "%s" is enabled', $name);
}
throw new \LogicException($message);
}
public function validateConfiguration(ExtensionInterface $extension, $configuration)
{
if (!$configuration) {
throw new \LogicException(sprintf('The extension with alias "%s" does not have its getConfiguration() method setup', $extension->getAlias()));
}
if (!$configuration instanceof ConfigurationInterface) {
throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable', get_class($configuration)));
}
}
private function initializeBundles()
{
// 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')->registerBundles();
foreach ($bundles as $bundle) {
if ($extension = $bundle->getContainerExtension()) {
$container->registerExtension($extension);
}
}
foreach ($bundles as $bundle) {
$bundle->build($container);
}
return $bundles;
}
}

View File

@@ -0,0 +1,248 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
/**
* Command that places bundle web assets into a given directory.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Gábor Egyed <gabor.egyed@gmail.com>
*/
class AssetsInstallCommand extends ContainerAwareCommand
{
const METHOD_COPY = 'copy';
const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink';
const METHOD_RELATIVE_SYMLINK = 'relative symlink';
/**
* @var Filesystem
*/
private $filesystem;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('assets:install')
->setDefinition(array(
new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', 'web'),
))
->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlinks the assets instead of copying it')
->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks')
->setDescription('Installs bundles web assets under a public web directory')
->setHelp(<<<'EOT'
The <info>%command.name%</info> command installs bundle assets into a given
directory (e.g. the <comment>web</comment> directory).
<info>php %command.full_name% web</info>
A "bundles" directory will be created inside the target directory and the
"Resources/public" directory of each bundle will be copied into it.
To create a symlink to each bundle instead of copying its assets, use the
<info>--symlink</info> option (will fall back to hard copies when symbolic links aren't possible:
<info>php %command.full_name% web --symlink</info>
To make symlink relative, add the <info>--relative</info> option:
<info>php %command.full_name% web --symlink --relative</info>
EOT
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$targetArg = rtrim($input->getArgument('target'), '/');
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);
$io = new SymfonyStyle($input, $output);
$io->newLine();
if ($input->getOption('relative')) {
$expectedMethod = self::METHOD_RELATIVE_SYMLINK;
$io->text('Trying to install assets as <info>relative symbolic links</info>.');
} elseif ($input->getOption('symlink')) {
$expectedMethod = self::METHOD_ABSOLUTE_SYMLINK;
$io->text('Trying to install assets as <info>absolute symbolic links</info>.');
} else {
$expectedMethod = self::METHOD_COPY;
$io->text('Installing assets as <info>hard copies</info>.');
}
$io->newLine();
$rows = array();
$copyUsed = false;
$exitCode = 0;
/** @var BundleInterface $bundle */
foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) {
if (!is_dir($originDir = $bundle->getPath().'/Resources/public')) {
continue;
}
$targetDir = $bundlesDir.preg_replace('/bundle$/', '', strtolower($bundle->getName()));
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$message = sprintf("%s\n-> %s", $bundle->getName(), $targetDir);
} else {
$message = $bundle->getName();
}
try {
$this->filesystem->remove($targetDir);
if (self::METHOD_RELATIVE_SYMLINK === $expectedMethod) {
$method = $this->relativeSymlinkWithFallback($originDir, $targetDir);
} elseif (self::METHOD_ABSOLUTE_SYMLINK === $expectedMethod) {
$method = $this->absoluteSymlinkWithFallback($originDir, $targetDir);
} else {
$method = $this->hardCopy($originDir, $targetDir);
}
if (self::METHOD_COPY === $method) {
$copyUsed = true;
}
if ($method === $expectedMethod) {
$rows[] = array(sprintf('<fg=green;options=bold>%s</>', '\\' === DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */), $message, $method);
} else {
$rows[] = array(sprintf('<fg=yellow;options=bold>%s</>', '\\' === DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method);
}
} catch (\Exception $e) {
$exitCode = 1;
$rows[] = array(sprintf('<fg=red;options=bold>%s</>', '\\' === DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage());
}
}
$io->table(array('', 'Bundle', 'Method / Error'), $rows);
if (0 !== $exitCode) {
$io->error('Some errors occurred while installing assets.');
} else {
if ($copyUsed) {
$io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.');
}
$io->success('All assets were successfully installed.');
}
return $exitCode;
}
/**
* Try to create relative symlink.
*
* Falling back to absolute symlink and finally hard copy.
*
* @param string $originDir
* @param string $targetDir
*
* @return string
*/
private function relativeSymlinkWithFallback($originDir, $targetDir)
{
try {
$this->symlink($originDir, $targetDir, true);
$method = self::METHOD_RELATIVE_SYMLINK;
} catch (IOException $e) {
$method = $this->absoluteSymlinkWithFallback($originDir, $targetDir);
}
return $method;
}
/**
* Try to create absolute symlink.
*
* Falling back to hard copy.
*
* @param string $originDir
* @param string $targetDir
*
* @return string
*/
private function absoluteSymlinkWithFallback($originDir, $targetDir)
{
try {
$this->symlink($originDir, $targetDir);
$method = self::METHOD_ABSOLUTE_SYMLINK;
} catch (IOException $e) {
// fall back to copy
$method = $this->hardCopy($originDir, $targetDir);
}
return $method;
}
/**
* Creates symbolic link.
*
* @param string $originDir
* @param string $targetDir
* @param bool $relative
*
* @throws IOException If link can not be created.
*/
private function symlink($originDir, $targetDir, $relative = false)
{
if ($relative) {
$originDir = $this->filesystem->makePathRelative($originDir, realpath(dirname($targetDir)));
}
$this->filesystem->symlink($originDir, $targetDir);
if (!file_exists($targetDir)) {
throw new IOException(sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), 0, null, $targetDir);
}
}
/**
* Copies origin to target.
*
* @param string $originDir
* @param string $targetDir
*
* @return string
*/
private function hardCopy($originDir, $targetDir)
{
$this->filesystem->mkdir($targetDir, 0777);
// We use a custom iterator to ignore VCS files
$this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir));
return self::METHOD_COPY;
}
}

View File

@@ -0,0 +1,260 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Finder\Finder;
/**
* Clear and Warmup the cache.
*
* @author Francis Besset <francis.besset@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class CacheClearCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('cache:clear')
->setDefinition(array(
new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'),
new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
))
->setDescription('Clears the cache')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command clears the application cache for a given environment
and debug mode:
<info>php %command.full_name% --env=dev</info>
<info>php %command.full_name% --env=prod --no-debug</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$outputIsVerbose = $output->isVerbose();
$io = new SymfonyStyle($input, $output);
$realCacheDir = $this->getContainer()->getParameter('kernel.cache_dir');
// the old cache dir name must not be longer than the real one to avoid exceeding
// the maximum length of a directory or file path within it (esp. Windows MAX_PATH)
$oldCacheDir = substr($realCacheDir, 0, -1).('~' === substr($realCacheDir, -1) ? '+' : '~');
$filesystem = $this->getContainer()->get('filesystem');
if (!is_writable($realCacheDir)) {
throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $realCacheDir));
}
if ($filesystem->exists($oldCacheDir)) {
$filesystem->remove($oldCacheDir);
}
$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($realCacheDir);
if ($input->getOption('no-warmup')) {
$filesystem->rename($realCacheDir, $oldCacheDir);
} else {
// the warmup cache dir name must have the same length than the real one
// to avoid the many problems in serialized resources files
$realCacheDir = realpath($realCacheDir);
$warmupDir = substr($realCacheDir, 0, -1).('_' === substr($realCacheDir, -1) ? '-' : '_');
if ($filesystem->exists($warmupDir)) {
if ($outputIsVerbose) {
$io->comment('Clearing outdated warmup directory...');
}
$filesystem->remove($warmupDir);
}
if ($outputIsVerbose) {
$io->comment('Warming up cache...');
}
$this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers'));
$filesystem->rename($realCacheDir, $oldCacheDir);
if ('\\' === DIRECTORY_SEPARATOR) {
sleep(1); // workaround for Windows PHP rename bug
}
$filesystem->rename($warmupDir, $realCacheDir);
}
if ($outputIsVerbose) {
$io->comment('Removing old cache directory...');
}
$filesystem->remove($oldCacheDir);
if ($outputIsVerbose) {
$io->comment('Finished');
}
$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
}
/**
* @param string $warmupDir
* @param string $realCacheDir
* @param bool $enableOptionalWarmers
*/
protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = true)
{
// create a temporary kernel
$realKernel = $this->getContainer()->get('kernel');
$realKernelClass = get_class($realKernel);
$namespace = '';
if (false !== $pos = strrpos($realKernelClass, '\\')) {
$namespace = substr($realKernelClass, 0, $pos);
$realKernelClass = substr($realKernelClass, $pos + 1);
}
$tempKernel = $this->getTempKernel($realKernel, $namespace, $realKernelClass, $warmupDir);
$tempKernel->boot();
$tempKernelReflection = new \ReflectionObject($tempKernel);
$tempKernelFile = $tempKernelReflection->getFileName();
// warmup temporary dir
$warmer = $tempKernel->getContainer()->get('cache_warmer');
if ($enableOptionalWarmers) {
$warmer->enableOptionalWarmers();
}
$warmer->warmUp($warmupDir);
// fix references to the Kernel in .meta files
$safeTempKernel = str_replace('\\', '\\\\', get_class($tempKernel));
$realKernelFQN = get_class($realKernel);
foreach (Finder::create()->files()->name('*.meta')->in($warmupDir) as $file) {
file_put_contents($file, preg_replace(
'/(C\:\d+\:)"'.$safeTempKernel.'"/',
sprintf('$1"%s"', $realKernelFQN),
file_get_contents($file)
));
}
// fix references to cached files with the real cache directory name
$search = array($warmupDir, str_replace('\\', '\\\\', $warmupDir));
$replace = str_replace('\\', '/', $realCacheDir);
foreach (Finder::create()->files()->in($warmupDir) as $file) {
$content = str_replace($search, $replace, file_get_contents($file));
file_put_contents($file, $content);
}
// fix references to kernel/container related classes
$fileSearch = $tempKernel->getName().ucfirst($tempKernel->getEnvironment()).'*';
$search = array(
$tempKernel->getName().ucfirst($tempKernel->getEnvironment()),
sprintf('\'kernel.name\' => \'%s\'', $tempKernel->getName()),
sprintf('key="kernel.name">%s<', $tempKernel->getName()),
);
$replace = array(
$realKernel->getName().ucfirst($realKernel->getEnvironment()),
sprintf('\'kernel.name\' => \'%s\'', $realKernel->getName()),
sprintf('key="kernel.name">%s<', $realKernel->getName()),
);
foreach (Finder::create()->files()->name($fileSearch)->in($warmupDir) as $file) {
$content = str_replace($search, $replace, file_get_contents($file));
file_put_contents(str_replace($search, $replace, $file), $content);
unlink($file);
}
// remove temp kernel file after cache warmed up
@unlink($tempKernelFile);
}
/**
* @param KernelInterface $parent
* @param string $namespace
* @param string $parentClass
* @param string $warmupDir
*
* @return KernelInterface
*/
protected function getTempKernel(KernelInterface $parent, $namespace, $parentClass, $warmupDir)
{
$cacheDir = var_export($warmupDir, true);
$rootDir = var_export(realpath($parent->getRootDir()), true);
$logDir = var_export(realpath($parent->getLogDir()), true);
// the temp kernel class name must have the same length than the real one
// to avoid the many problems in serialized resources files
$class = substr($parentClass, 0, -1).'_';
// the temp kernel name must be changed too
$name = var_export(substr($parent->getName(), 0, -1).'_', true);
$code = <<<EOF
<?php
namespace $namespace
{
class $class extends $parentClass
{
public function getCacheDir()
{
return $cacheDir;
}
public function getName()
{
return $name;
}
public function getRootDir()
{
return $rootDir;
}
public function getLogDir()
{
return $logDir;
}
protected function buildContainer()
{
\$container = parent::buildContainer();
// filter container's resources, removing reference to temp kernel file
\$resources = \$container->getResources();
\$filteredResources = array();
foreach (\$resources as \$resource) {
if ((string) \$resource !== __FILE__) {
\$filteredResources[] = \$resource;
}
}
\$container->setResources(\$filteredResources);
return \$container;
}
}
}
EOF;
$this->getContainer()->get('filesystem')->mkdir($warmupDir);
file_put_contents($file = $warmupDir.'/kernel.tmp', $code);
require_once $file;
$class = "$namespace\\$class";
return new $class($parent->getEnvironment(), $parent->isDebug());
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Warmup the cache.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CacheWarmupCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('cache:warmup')
->setDefinition(array(
new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
))
->setDescription('Warms up an empty cache')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command warms up the cache.
Before running this command, the cache must be empty.
This command does not generate the classes cache (as when executing this
command, too many classes that should be part of the cache are already loaded
in memory). Use <comment>curl</comment> or any other similar tool to warm up
the classes cache if you want.
EOF
)
;
}
/**
* {@inheritdoc}
*/
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();
}
$warmer->warmUp($this->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

@@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Yaml;
/**
* A console command for dumping available configuration reference.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ConfigDebugCommand extends AbstractConfigCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('debug:config')
->setDefinition(array(
new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'),
))
->setDescription('Dumps the current configuration for an extension')
->setHelp(<<<EOF
The <info>%command.name%</info> command dumps the current configuration for an
extension/bundle.
Either the extension alias or bundle name can be used:
<info>php %command.full_name% framework</info>
<info>php %command.full_name% FrameworkBundle</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
if (empty($name)) {
$io->comment('Provide the name of a bundle as the first argument of this command to dump its configuration.');
$io->newLine();
$this->listBundles($output);
return;
}
$extension = $this->findExtension($name);
$container = $this->compileContainer();
$configs = $container->getExtensionConfig($extension->getAlias());
$configuration = $extension->getConfiguration($configs, $container);
$this->validateConfiguration($extension, $configuration);
$configs = $container->getParameterBag()->resolveValue($configs);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, $configs);
if ($name === $extension->getAlias()) {
$io->title(sprintf('Current configuration for extension with alias "%s"', $name));
} else {
$io->title(sprintf('Current configuration for "%s"', $name));
}
$io->writeln(Yaml::dump(array($extension->getAlias() => $config), 10));
}
private function compileContainer()
{
$kernel = clone $this->getContainer()->get('kernel');
$kernel->boot();
$method = new \ReflectionMethod($kernel, 'buildContainer');
$method->setAccessible(true);
$container = $method->invoke($kernel);
$container->getCompiler()->compile($container);
return $container;
}
}

View File

@@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper;
use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* A console command for dumping available configuration reference.
*
* @author Kevin Bond <kevinbond@gmail.com>
* @author Wouter J <waldio.webdesign@gmail.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ConfigDumpReferenceCommand extends AbstractConfigCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('config:dump-reference')
->setDefinition(array(
new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml'),
))
->setDescription('Dumps the default configuration for an extension')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the default configuration for an
extension/bundle.
Either the extension alias or bundle name can be used:
<info>php %command.full_name% framework</info>
<info>php %command.full_name% FrameworkBundle</info>
With the <info>--format</info> option specifies the format of the configuration,
this is either <comment>yaml</comment> or <comment>xml</comment>.
When the option is not provided, <comment>yaml</comment> is used.
<info>php %command.full_name% FrameworkBundle --format=xml</info>
EOF
)
;
}
/**
* {@inheritdoc}
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
if (empty($name)) {
$io->comment('Provide the name of a bundle as the first argument of this command to dump its default configuration.');
$io->newLine();
$this->listBundles($output);
return;
}
$extension = $this->findExtension($name);
$configuration = $extension->getConfiguration(array(), $this->getContainerBuilder());
$this->validateConfiguration($extension, $configuration);
if ($name === $extension->getAlias()) {
$message = sprintf('Default configuration for extension with alias: "%s"', $name);
} else {
$message = sprintf('Default configuration for "%s"', $name);
}
switch ($input->getOption('format')) {
case 'yaml':
$io->writeln(sprintf('# %s', $message));
$dumper = new YamlReferenceDumper();
break;
case 'xml':
$io->writeln(sprintf('<!-- %s -->', $message));
$dumper = new XmlReferenceDumper();
break;
default:
$io->writeln($message);
throw new \InvalidArgumentException('Only the yaml and xml formats are supported.');
}
$io->writeln($dumper->dump($configuration, $extension->getNamespace()));
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
/**
* Command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class ContainerAwareCommand extends Command implements ContainerAwareInterface
{
/**
* @var ContainerInterface|null
*/
private $container;
/**
* @return ContainerInterface
*
* @throws \LogicException
*/
protected function getContainer()
{
if (null === $this->container) {
$application = $this->getApplication();
if (null === $application) {
throw new \LogicException('The container cannot be retrieved as the application instance is not yet set.');
}
$this->container = $application->getKernel()->getContainer();
}
return $this->container;
}
/**
* {@inheritdoc}
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}

View File

@@ -0,0 +1,214 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
/**
* A console command for retrieving information about services.
*
* @author Ryan Weaver <ryan@thatsquality.com>
*/
class ContainerDebugCommand extends ContainerAwareCommand
{
/**
* @var ContainerBuilder|null
*/
protected $containerBuilder;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('debug:container')
->setDefinition(array(
new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'),
new InputOption('show-private', null, InputOption::VALUE_NONE, 'Used to show public *and* private services'),
new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Shows all services with a specific tag'),
new InputOption('tags', null, InputOption::VALUE_NONE, 'Displays tagged services for an application'),
new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Displays a specific parameter for an application'),
new InputOption('parameters', null, InputOption::VALUE_NONE, 'Displays parameters for an application'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
))
->setDescription('Displays current services for an application')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all configured <comment>public</comment> services:
<info>php %command.full_name%</info>
To get specific information about a service, specify its name:
<info>php %command.full_name% validator</info>
By default, private services are hidden. You can display all services by
using the <info>--show-private</info> flag:
<info>php %command.full_name% --show-private</info>
Use the --tags option to display tagged <comment>public</comment> services grouped by tag:
<info>php %command.full_name% --tags</info>
Find all services with a specific tag by specifying the tag name with the <info>--tag</info> option:
<info>php %command.full_name% --tag=form.type</info>
Use the <info>--parameters</info> option to display all parameters:
<info>php %command.full_name% --parameters</info>
Display a specific parameter by specifying its name with the <info>--parameter</info> option:
<info>php %command.full_name% --parameter=kernel.debug</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$this->validateInput($input);
$object = $this->getContainerBuilder();
if ($input->getOption('parameters')) {
$object = $object->getParameterBag();
$options = array();
} elseif ($parameter = $input->getOption('parameter')) {
$options = array('parameter' => $parameter);
} elseif ($input->getOption('tags')) {
$options = array('group_by' => 'tags', 'show_private' => $input->getOption('show-private'));
} elseif ($tag = $input->getOption('tag')) {
$options = array('tag' => $tag, 'show_private' => $input->getOption('show-private'));
} elseif ($name = $input->getArgument('name')) {
$name = $this->findProperServiceName($input, $io, $object, $name);
$options = array('id' => $name);
} else {
$options = array('show_private' => $input->getOption('show-private'));
}
$helper = new DescriptorHelper();
$options['format'] = $input->getOption('format');
$options['raw_text'] = $input->getOption('raw');
$options['output'] = $io;
$helper->describe($output, $object, $options);
if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && $input->isInteractive()) {
if ($input->getOption('tags')) {
$io->comment('To search for a specific tag, re-run this command with a search term. (e.g. <comment>debug:container --tag=form.type</comment>)');
} elseif ($input->getOption('parameters')) {
$io->comment('To search for a specific parameter, re-run this command with a search term. (e.g. <comment>debug:container --parameter=kernel.debug</comment>)');
} else {
$io->comment('To search for a specific service, re-run this command with a search term. (e.g. <comment>debug:container log</comment>)');
}
}
}
/**
* Validates input arguments and options.
*
* @param InputInterface $input
*
* @throws \InvalidArgumentException
*/
protected function validateInput(InputInterface $input)
{
$options = array('tags', 'tag', 'parameters', 'parameter');
$optionsCount = 0;
foreach ($options as $option) {
if ($input->getOption($option)) {
++$optionsCount;
}
}
$name = $input->getArgument('name');
if ((null !== $name) && ($optionsCount > 0)) {
throw new \InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined with the service name argument.');
} elseif ((null === $name) && $optionsCount > 1) {
throw new \InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined together.');
}
}
/**
* Loads the ContainerBuilder from the cache.
*
* @return ContainerBuilder
*
* @throws \LogicException
*/
protected function getContainerBuilder()
{
if ($this->containerBuilder) {
return $this->containerBuilder;
}
if (!$this->getApplication()->getKernel()->isDebug()) {
throw new \LogicException(sprintf('Debug information about the container is only available in debug mode.'));
}
if (!is_file($cachedFile = $this->getContainer()->getParameter('debug.container.dump'))) {
throw new \LogicException(sprintf('Debug information about the container could not be found. Please clear the cache and try again.'));
}
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator());
$loader->load($cachedFile);
return $this->containerBuilder = $container;
}
private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, $name)
{
if ($builder->has($name) || !$input->isInteractive()) {
return $name;
}
$matchingServices = $this->findServiceIdsContaining($builder, $name);
if (empty($matchingServices)) {
throw new \InvalidArgumentException(sprintf('No services found that match "%s".', $name));
}
return $io->choice('Select one of the following services to display its information', $matchingServices);
}
private function findServiceIdsContaining(ContainerBuilder $builder, $name)
{
$serviceIds = $builder->getServiceIds();
$foundServiceIds = array();
$name = strtolower($name);
foreach ($serviceIds as $serviceId) {
if (false === strpos($serviceId, $name)) {
continue;
}
$foundServiceIds[] = $serviceId;
}
return $foundServiceIds;
}
}

View File

@@ -0,0 +1,92 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* A console command for retrieving information about event dispatcher.
*
* @author Matthieu Auger <mail@matthieuauger.com>
*/
class EventDispatcherDebugCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('debug:event-dispatcher')
->setDefinition(array(
new InputArgument('event', InputArgument::OPTIONAL, 'An event name'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
))
->setDescription('Displays configured listeners for an application')
->setHelp(<<<EOF
The <info>%command.name%</info> command displays all configured listeners:
<info>php %command.full_name%</info>
To get specific listeners for an event, specify its name:
<info>php %command.full_name% kernel.request</info>
EOF
)
;
}
/**
* {@inheritdoc}
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$dispatcher = $this->getEventDispatcher();
$options = array();
if ($event = $input->getArgument('event')) {
if (!$dispatcher->hasListeners($event)) {
$io->warning(sprintf('The event "%s" does not have any registered listeners.', $event));
return;
}
$options = array('event' => $event);
}
$helper = new DescriptorHelper();
$options['format'] = $input->getOption('format');
$options['raw_text'] = $input->getOption('raw');
$options['output'] = $io;
$helper->describe($io, $dispatcher, $options);
}
/**
* Loads the Event Dispatcher from the container.
*
* @return EventDispatcherInterface
*/
protected function getEventDispatcher()
{
return $this->getContainer()->get('event_dispatcher');
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
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\Routing\RouterInterface;
use Symfony\Component\Routing\Route;
/**
* A console command for retrieving information about routes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*/
class RouterDebugCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
public function isEnabled()
{
if (!$this->getContainer()->has('router')) {
return false;
}
$router = $this->getContainer()->get('router');
if (!$router instanceof RouterInterface) {
return false;
}
return parent::isEnabled();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('debug:router')
->setDefinition(array(
new InputArgument('name', InputArgument::OPTIONAL, 'A route name'),
new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'),
))
->setDescription('Displays current routes for an application')
->setHelp(<<<'EOF'
The <info>%command.name%</info> displays the configured routes:
<info>php %command.full_name%</info>
EOF
)
;
}
/**
* {@inheritdoc}
*
* @throws \InvalidArgumentException When route does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
$helper = new DescriptorHelper();
if ($name) {
$route = $this->getContainer()->get('router')->getRouteCollection()->get($name);
if (!$route) {
throw new \InvalidArgumentException(sprintf('The route "%s" does not exist.', $name));
}
$this->convertController($route);
$helper->describe($io, $route, array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'name' => $name,
'output' => $io,
));
} else {
$routes = $this->getContainer()->get('router')->getRouteCollection();
foreach ($routes as $route) {
$this->convertController($route);
}
$helper->describe($io, $routes, array(
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'show_controllers' => $input->getOption('show-controllers'),
'output' => $io,
));
}
}
private function convertController(Route $route)
{
$nameParser = $this->getContainer()->get('controller_name_converter');
if ($route->hasDefault('_controller')) {
try {
$route->setDefault('_controller', $nameParser->build($route->getDefault('_controller')));
} catch (\InvalidArgumentException $e) {
}
}
}
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
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\Routing\RouterInterface;
use Symfony\Component\Routing\Matcher\TraceableUrlMatcher;
/**
* A console command to test route matching.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RouterMatchCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
public function isEnabled()
{
if (!$this->getContainer()->has('router')) {
return false;
}
$router = $this->getContainer()->get('router');
if (!$router instanceof RouterInterface) {
return false;
}
return parent::isEnabled();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('router:match')
->setDefinition(array(
new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'),
new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Sets the HTTP method'),
new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Sets the URI scheme (usually http or https)'),
new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Sets the URI host'),
))
->setDescription('Helps debug routes by simulating a path info match')
->setHelp(<<<'EOF'
The <info>%command.name%</info> shows which routes match a given request and which don't and for what reason:
<info>php %command.full_name% /foo</info>
or
<info>php %command.full_name% /foo --method POST --scheme https --host symfony.com --verbose</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$router = $this->getContainer()->get('router');
$context = $router->getContext();
if (null !== $method = $input->getOption('method')) {
$context->setMethod($method);
}
if (null !== $scheme = $input->getOption('scheme')) {
$context->setScheme($scheme);
}
if (null !== $host = $input->getOption('host')) {
$context->setHost($host);
}
$matcher = new TraceableUrlMatcher($router->getRouteCollection(), $context);
$traces = $matcher->getTraces($input->getArgument('path_info'));
$io->newLine();
$matches = false;
foreach ($traces as $trace) {
if (TraceableUrlMatcher::ROUTE_ALMOST_MATCHES == $trace['level']) {
$io->text(sprintf('Route <info>"%s"</> almost matches but %s', $trace['name'], lcfirst($trace['log'])));
} elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) {
$io->success(sprintf('Route "%s" matches', $trace['name']));
$routerDebugCommand = $this->getApplication()->find('debug:router');
$routerDebugCommand->run(new ArrayInput(array('name' => $trace['name'])), $output);
$matches = true;
} elseif ($input->getOption('verbose')) {
$io->text(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log']));
}
}
if (!$matches) {
$io->error(sprintf('None of the routes match the path "%s"', $input->getArgument('path_info')));
return 1;
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
/**
* Base methods for commands related to PHP's built-in web server.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
abstract class ServerCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
public function isEnabled()
{
if (defined('HHVM_VERSION')) {
return false;
}
if (!class_exists('Symfony\Component\Process\Process')) {
return false;
}
return parent::isEnabled();
}
/**
* Determines the name of the lock file for a particular PHP web server process.
*
* @param string $address An address/port tuple
*
* @return string The filename
*/
protected function getLockFile($address)
{
return sys_get_temp_dir().'/'.strtr($address, '.:', '--').'.pid';
}
protected function isOtherServerProcessRunning($address)
{
$lockFile = $this->getLockFile($address);
if (file_exists($lockFile)) {
return true;
}
list($hostname, $port) = explode(':', $address);
$fp = @fsockopen($hostname, $port, $errno, $errstr, 5);
if (false !== $fp) {
fclose($fp);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,163 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\ProcessBuilder;
/**
* Runs Symfony application using PHP built-in web server.
*
* @author Michał Pipa <michal.pipa.xsolve@gmail.com>
*/
class ServerRunCommand extends ServerCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'),
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null),
new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
))
->setName('server:run')
->setDescription('Runs PHP built-in web server')
->setHelp(<<<'EOF'
The <info>%command.name%</info> runs PHP built-in web server:
<info>%command.full_name%</info>
To change default bind address and port use the <info>address</info> argument:
<info>%command.full_name% 127.0.0.1:8080</info>
To change default docroot directory use the <info>--docroot</info> option:
<info>%command.full_name% --docroot=htdocs/</info>
If you have custom docroot directory layout, you can specify your own
router script using <info>--router</info> option:
<info>%command.full_name% --router=app/config/router.php</info>
Specifing a router script is required when the used environment is not "dev",
"prod", or "test".
See also: http://www.php.net/manual/en/features.commandline.webserver.php
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$documentRoot = $input->getOption('docroot');
if (null === $documentRoot) {
$documentRoot = $this->getContainer()->getParameter('kernel.root_dir').'/../web';
}
if (!is_dir($documentRoot)) {
$io->error(sprintf('The given document root directory "%s" does not exist', $documentRoot));
return 1;
}
$env = $this->getContainer()->getParameter('kernel.environment');
$address = $input->getArgument('address');
if (false === strpos($address, ':')) {
$address = $address.':'.$input->getOption('port');
}
if ($this->isOtherServerProcessRunning($address)) {
$io->error(sprintf('A process is already listening on http://%s.', $address));
return 1;
}
if ('prod' === $env) {
$io->error('Running PHP built-in server in production environment is NOT recommended!');
}
$io->success(sprintf('Server running on http://%s', $address));
$io->comment('Quit the server with CONTROL-C.');
if (null === $builder = $this->createPhpProcessBuilder($io, $address, $input->getOption('router'), $env)) {
return 1;
}
$builder->setWorkingDirectory($documentRoot);
$builder->setTimeout(null);
$process = $builder->getProcess();
if (OutputInterface::VERBOSITY_VERBOSE > $output->getVerbosity()) {
$process->disableOutput();
}
$this
->getHelper('process')
->run($output, $process, null, null, OutputInterface::VERBOSITY_VERBOSE);
if (!$process->isSuccessful()) {
$errorMessages = array('Built-in server terminated unexpectedly.');
if ($process->isOutputDisabled()) {
$errorMessages[] = 'Run the command again with -v option for more details.';
}
$io->error($errorMessages);
}
return $process->getExitCode();
}
private function createPhpProcessBuilder(SymfonyStyle $io, $address, $router, $env)
{
$router = $router ?: $this
->getContainer()
->get('kernel')
->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env))
;
if (!file_exists($router)) {
$io->error(sprintf('The given router script "%s" does not exist.', $router));
return;
}
$router = realpath($router);
$finder = new PhpExecutableFinder();
if (false === $binary = $finder->find()) {
$io->error('Unable to find PHP binary to run server.');
return;
}
return new ProcessBuilder(array($binary, '-S', $address, $router));
}
}

View File

@@ -0,0 +1,234 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;
/**
* Runs PHP's built-in web server in a background process.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class ServerStartCommand extends ServerCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'),
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null),
new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
new InputOption('force', 'f', InputOption::VALUE_NONE, 'Force web server startup'),
))
->setName('server:start')
->setDescription('Starts PHP built-in web server in the background')
->setHelp(<<<EOF
The <info>%command.name%</info> runs PHP's built-in web server:
<info>php %command.full_name%</info>
To change the default bind address and the default port use the <info>address</info> argument:
<info>php %command.full_name% 127.0.0.1:8080</info>
To change the default document root directory use the <info>--docroot</info> option:
<info>php %command.full_name% --docroot=htdocs/</info>
If you have a custom document root directory layout, you can specify your own
router script using the <info>--router</info> option:
<info>php %command.full_name% --router=app/config/router.php</info>
Specifying a router script is required when the used environment is not <comment>"dev"</comment> or
<comment>"prod"</comment>.
See also: http://www.php.net/manual/en/features.commandline.webserver.php
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $cliOutput = $output);
if (!extension_loaded('pcntl')) {
$io->error(array(
'This command needs the pcntl extension to run.',
'You can either install it or use the "server:run" command instead to run the built-in web server.',
));
if ($io->ask('Do you want to execute <info>server:run</info> immediately? [Yn] ', true)) {
$command = $this->getApplication()->find('server:run');
return $command->run($input, $cliOutput);
}
return 1;
}
$documentRoot = $input->getOption('docroot');
if (null === $documentRoot) {
$documentRoot = $this->getContainer()->getParameter('kernel.root_dir').'/../web';
}
if (!is_dir($documentRoot)) {
$io->error(sprintf('The given document root directory "%s" does not exist.', $documentRoot));
return 1;
}
$env = $this->getContainer()->getParameter('kernel.environment');
if (false === $router = $this->determineRouterScript($input->getOption('router'), $env, $io)) {
return 1;
}
$address = $input->getArgument('address');
if (false === strpos($address, ':')) {
$address = $address.':'.$input->getOption('port');
}
if (!$input->getOption('force') && $this->isOtherServerProcessRunning($address)) {
$io->error(array(
sprintf('A process is already listening on http://%s.', $address),
'Use the --force option if the server process terminated unexpectedly to start a new web server process.',
));
return 1;
}
if ('prod' === $env) {
$io->error('Running PHP built-in server in production environment is NOT recommended!');
}
$pid = pcntl_fork();
if ($pid < 0) {
$io->error('Unable to start the server process.');
return 1;
}
if ($pid > 0) {
$io->success(sprintf('Web server listening on http://%s', $address));
return;
}
if (posix_setsid() < 0) {
$io->error('Unable to set the child process as session leader');
return 1;
}
if (null === $process = $this->createServerProcess($io, $address, $documentRoot, $router)) {
return 1;
}
$process->disableOutput();
$process->start();
$lockFile = $this->getLockFile($address);
touch($lockFile);
if (!$process->isRunning()) {
$io->error('Unable to start the server process');
unlink($lockFile);
return 1;
}
// stop the web server when the lock file is removed
while ($process->isRunning()) {
if (!file_exists($lockFile)) {
$process->stop();
}
sleep(1);
}
}
/**
* Determine the absolute file path for the router script, using the environment to choose a standard script
* if no custom router script is specified.
*
* @param string|null $router File path of the custom router script, if set by the user; otherwise null
* @param string $env The application environment
* @param SymfonyStyle $io An SymfonyStyle instance
*
* @return string|bool The absolute file path of the router script, or false on failure
*/
private function determineRouterScript($router, $env, SymfonyStyle $io)
{
if (null === $router) {
$router = $this
->getContainer()
->get('kernel')
->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env))
;
}
if (false === $path = realpath($router)) {
$io->error(sprintf('The given router script "%s" does not exist.', $router));
return false;
}
return $path;
}
/**
* Creates a process to start PHP's built-in web server.
*
* @param SymfonyStyle $io A SymfonyStyle instance
* @param string $address IP address and port to listen to
* @param string $documentRoot The application's document root
* @param string $router The router filename
*
* @return Process The process
*/
private function createServerProcess(SymfonyStyle $io, $address, $documentRoot, $router)
{
$finder = new PhpExecutableFinder();
if (false === $binary = $finder->find()) {
$io->error('Unable to find PHP binary to start server.');
return;
}
$script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), array(
$binary,
'-S',
$address,
$router,
)));
return new Process('exec '.$script, $documentRoot, null, null, null);
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Shows the status of a process that is running PHP's built-in web server in
* the background.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class ServerStatusCommand extends ServerCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'),
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
))
->setName('server:status')
->setDescription('Outputs the status of the built-in web server for the given address')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$address = $input->getArgument('address');
if (false === strpos($address, ':')) {
$address = $address.':'.$input->getOption('port');
}
// remove an orphaned lock file
if (file_exists($this->getLockFile($address)) && !$this->isServerRunning($address)) {
unlink($this->getLockFile($address));
}
if (file_exists($this->getLockFile($address))) {
$io->success(sprintf('Web server still listening on http://%s', $address));
} else {
$io->warning(sprintf('No web server is listening on http://%s', $address));
}
}
private function isServerRunning($address)
{
list($hostname, $port) = explode(':', $address);
if (false !== $fp = @fsockopen($hostname, $port, $errno, $errstr, 1)) {
fclose($fp);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Stops a background process running PHP's built-in web server.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class ServerStopCommand extends ServerCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'),
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
))
->setName('server:stop')
->setDescription('Stops PHP\'s built-in web server that was started with the server:start command')
->setHelp(<<<EOF
The <info>%command.name%</info> stops PHP's built-in web server:
<info>php %command.full_name%</info>
To change the default bind address and the default port use the <info>address</info> argument:
<info>php %command.full_name% 127.0.0.1:8080</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$address = $input->getArgument('address');
if (false === strpos($address, ':')) {
$address = $address.':'.$input->getOption('port');
}
$lockFile = $this->getLockFile($address);
if (!file_exists($lockFile)) {
$io->error(sprintf('No web server is listening on http://%s', $address));
return 1;
}
unlink($lockFile);
$io->success(sprintf('Stopped the web server listening on http://%s', $address));
}
}

View File

@@ -0,0 +1,317 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader;
use Symfony\Component\Console\Style\SymfonyStyle;
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\Translation\Catalogue\MergeOperation;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Translator;
/**
* Helps finding unused or missing translation messages in a given locale
* and comparing them with the fallback ones.
*
* @author Florian Voutzinos <florian@voutzinos.com>
*/
class TranslationDebugCommand extends ContainerAwareCommand
{
const MESSAGE_MISSING = 0;
const MESSAGE_UNUSED = 1;
const MESSAGE_EQUALS_FALLBACK = 2;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('debug:translation')
->setDefinition(array(
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages, defaults to app/Resources folder'),
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'),
new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Displays only missing messages'),
new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Displays only unused messages'),
new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'),
))
->setDescription('Displays translation messages information')
->setHelp(<<<EOF
The <info>%command.name%</info> command helps finding unused or missing translation
messages and comparing them with the fallback ones by inspecting the
templates and translation files of a given bundle or the app folder.
You can display information about bundle translations in a specific locale:
<info>php %command.full_name% en AcmeDemoBundle</info>
You can also specify a translation domain for the search:
<info>php %command.full_name% --domain=messages en AcmeDemoBundle</info>
You can only display missing messages:
<info>php %command.full_name% --only-missing en AcmeDemoBundle</info>
You can only display unused messages:
<info>php %command.full_name% --only-unused en AcmeDemoBundle</info>
You can display information about app translations in a specific locale:
<info>php %command.full_name% en</info>
You can display information about translations in all registered bundles in a specific locale:
<info>php %command.full_name% --all en</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$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');
// Define Root Path to App folder
$transPaths = array($kernel->getRootDir().'/Resources/');
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
try {
$bundle = $kernel->getBundle($input->getArgument('bundle'));
$transPaths = array(
$bundle->getPath().'/Resources/',
sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName()),
);
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$transPaths = array($input->getArgument('bundle').'/Resources/');
if (!is_dir($transPaths[0])) {
throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
}
}
} elseif ($input->getOption('all')) {
foreach ($kernel->getBundles() as $bundle) {
$transPaths[] = $bundle->getPath().'/Resources/';
$transPaths[] = sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName());
}
}
// Extract used messages
$extractedCatalogue = $this->extractMessages($locale, $transPaths);
// Load defined messages
$currentCatalogue = $this->loadCurrentMessages($locale, $transPaths, $loader);
// Merge defined and extracted messages to get all message ids
$mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue);
$allMessages = $mergeOperation->getResult()->all($domain);
if (null !== $domain) {
$allMessages = array($domain => $allMessages);
}
// No defined or extracted messages
if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) {
$outputMessage = sprintf('No defined or extracted messages for locale "%s"', $locale);
if (null !== $domain) {
$outputMessage .= sprintf(' and domain "%s"', $domain);
}
$io->warning($outputMessage);
return;
}
// Load the fallback catalogues
$fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths, $loader);
// Display header line
$headers = array('State', 'Domain', 'Id', sprintf('Message Preview (%s)', $locale));
foreach ($fallbackCatalogues as $fallbackCatalogue) {
$headers[] = sprintf('Fallback Message Preview (%s)', $fallbackCatalogue->getLocale());
}
$rows = array();
// Iterate all message ids and determine their state
foreach ($allMessages as $domain => $messages) {
foreach (array_keys($messages) as $messageId) {
$value = $currentCatalogue->get($messageId, $domain);
$states = array();
if ($extractedCatalogue->defines($messageId, $domain)) {
if (!$currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_MISSING;
}
} elseif ($currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_UNUSED;
}
if (!in_array(self::MESSAGE_UNUSED, $states) && true === $input->getOption('only-unused')
|| !in_array(self::MESSAGE_MISSING, $states) && true === $input->getOption('only-missing')) {
continue;
}
foreach ($fallbackCatalogues as $fallbackCatalogue) {
if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) {
$states[] = self::MESSAGE_EQUALS_FALLBACK;
break;
}
}
$row = array($this->formatStates($states), $domain, $this->formatId($messageId), $this->sanitizeString($value));
foreach ($fallbackCatalogues as $fallbackCatalogue) {
$row[] = $this->sanitizeString($fallbackCatalogue->get($messageId, $domain));
}
$rows[] = $row;
}
}
$io->table($headers, $rows);
}
private function formatState($state)
{
if (self::MESSAGE_MISSING === $state) {
return '<error> missing </error>';
}
if (self::MESSAGE_UNUSED === $state) {
return '<comment> unused </comment>';
}
if (self::MESSAGE_EQUALS_FALLBACK === $state) {
return '<info> fallback </info>';
}
return $state;
}
private function formatStates(array $states)
{
$result = array();
foreach ($states as $state) {
$result[] = $this->formatState($state);
}
return implode(' ', $result);
}
private function formatId($id)
{
return sprintf('<fg=cyan;options=bold>%s</>', $id);
}
private function sanitizeString($string, $length = 40)
{
$string = trim(preg_replace('/\s+/', ' ', $string));
if (false !== $encoding = mb_detect_encoding($string, null, true)) {
if (mb_strlen($string, $encoding) > $length) {
return mb_substr($string, 0, $length - 3, $encoding).'...';
}
} elseif (strlen($string) > $length) {
return substr($string, 0, $length - 3).'...';
}
return $string;
}
/**
* @param string $locale
* @param array $transPaths
*
* @return MessageCatalogue
*/
private function extractMessages($locale, $transPaths)
{
$extractedCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
$path = $path.'views';
if (is_dir($path)) {
$this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue);
}
}
return $extractedCatalogue;
}
/**
* @param string $locale
* @param array $transPaths
* @param TranslationLoader $loader
*
* @return MessageCatalogue
*/
private function loadCurrentMessages($locale, $transPaths, TranslationLoader $loader)
{
$currentCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
$path = $path.'translations';
if (is_dir($path)) {
$loader->loadMessages($path, $currentCatalogue);
}
}
return $currentCatalogue;
}
/**
* @param string $locale
* @param array $transPaths
* @param TranslationLoader $loader
*
* @return MessageCatalogue[]
*/
private function loadFallbackCatalogues($locale, $transPaths, TranslationLoader $loader)
{
$fallbackCatalogues = array();
$translator = $this->getContainer()->get('translator');
if ($translator instanceof Translator) {
foreach ($translator->getFallbackLocales() as $fallbackLocale) {
if ($fallbackLocale === $locale) {
continue;
}
$fallbackCatalogue = new MessageCatalogue($fallbackLocale);
foreach ($transPaths as $path) {
$path = $path.'translations';
if (is_dir($path)) {
$loader->loadMessages($path, $fallbackCatalogue);
}
}
$fallbackCatalogues[] = $fallbackCatalogue;
}
}
return $fallbackCatalogues;
}
}

View File

@@ -0,0 +1,216 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Translation\Catalogue\TargetOperation;
use Symfony\Component\Translation\Catalogue\MergeOperation;
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\MessageCatalogue;
/**
* A command that parses templates to extract translation messages and adds them
* into the translation files.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class TranslationUpdateCommand extends ContainerAwareCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('translation:update')
->setDefinition(array(
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages, defaults to app/Resources folder'),
new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'),
new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'yml'),
new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'),
new InputOption('force', null, InputOption::VALUE_NONE, 'Should the update be done'),
new InputOption('no-backup', null, InputOption::VALUE_NONE, 'Should backup be disabled'),
new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'),
))
->setDescription('Updates the translation file')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command extracts translation strings from templates
of a given bundle or the app folder. It can display them or merge the new ones into the translation files.
When new translation strings are found it can automatically add a prefix to the translation
message.
Example running against a Bundle (AcmeBundle)
<info>php %command.full_name% --dump-messages en AcmeBundle</info>
<info>php %command.full_name% --force --prefix="new_" fr AcmeBundle</info>
Example running against app messages (app/Resources folder)
<info>php %command.full_name% --dump-messages en</info>
<info>php %command.full_name% --force --prefix="new_" fr</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
// check presence of force or dump-message
if ($input->getOption('force') !== true && $input->getOption('dump-messages') !== true) {
$io->error('You must choose one of --force or --dump-messages');
return 1;
}
// check format
$writer = $this->getContainer()->get('translation.writer');
$supportedFormats = $writer->getFormats();
if (!in_array($input->getOption('output-format'), $supportedFormats)) {
$io->error(array('Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).'.'));
return 1;
}
$kernel = $this->getContainer()->get('kernel');
// Define Root Path to App folder
$transPaths = array($kernel->getRootDir().'/Resources/');
$currentName = 'app folder';
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
try {
$foundBundle = $kernel->getBundle($input->getArgument('bundle'));
$transPaths = array(
$foundBundle->getPath().'/Resources/',
sprintf('%s/Resources/%s/', $kernel->getRootDir(), $foundBundle->getName()),
);
$currentName = $foundBundle->getName();
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$transPaths = array($input->getArgument('bundle').'/Resources/');
$currentName = $transPaths[0];
if (!is_dir($transPaths[0])) {
throw new \InvalidArgumentException(sprintf('<error>"%s" is neither an enabled bundle nor a directory.</error>', $transPaths[0]));
}
}
}
$io->title('Translation Messages Extractor and Dumper');
$io->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
// load any messages from templates
$extractedCatalogue = new MessageCatalogue($input->getArgument('locale'));
$io->comment('Parsing templates...');
$extractor = $this->getContainer()->get('translation.extractor');
$extractor->setPrefix($input->getOption('prefix'));
foreach ($transPaths as $path) {
$path .= 'views';
if (is_dir($path)) {
$extractor->extract($path, $extractedCatalogue);
}
}
// load any existing messages from the translation files
$currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
$io->comment('Loading translation files...');
$loader = $this->getContainer()->get('translation.loader');
foreach ($transPaths as $path) {
$path .= 'translations';
if (is_dir($path)) {
$loader->loadMessages($path, $currentCatalogue);
}
}
// process catalogues
$operation = $input->getOption('clean')
? new TargetOperation($currentCatalogue, $extractedCatalogue)
: new MergeOperation($currentCatalogue, $extractedCatalogue);
// Exit if no messages found.
if (!count($operation->getDomains())) {
$io->warning('No translation messages were found.');
return;
}
$resultMessage = 'Translation files were successfully updated';
// show compiled list of messages
if (true === $input->getOption('dump-messages')) {
$extractedMessagesCount = 0;
$io->newLine();
foreach ($operation->getDomains() as $domain) {
$newKeys = array_keys($operation->getNewMessages($domain));
$allKeys = array_keys($operation->getMessages($domain));
$domainMessagesCount = count($newKeys) + count($allKeys);
$io->section(sprintf('Messages extracted for domain "<info>%s</info>" (%d messages)', $domain, $domainMessagesCount));
$io->listing(array_merge(
array_diff($allKeys, $newKeys),
array_map(function ($id) {
return sprintf('<fg=green>%s</>', $id);
}, $newKeys),
array_map(function ($id) {
return sprintf('<fg=red>%s</>', $id);
}, array_keys($operation->getObsoleteMessages($domain)))
));
$extractedMessagesCount += $domainMessagesCount;
}
if ($input->getOption('output-format') == 'xlf') {
$io->comment('Xliff output version is <info>1.2</info>');
}
$resultMessage = sprintf('%d messages were successfully extracted', $extractedMessagesCount);
}
if ($input->getOption('no-backup') === true) {
$writer->disableBackup();
}
// save the files
if ($input->getOption('force') === true) {
$io->comment('Writing files...');
$bundleTransPath = false;
foreach ($transPaths as $path) {
$path .= 'translations';
if (is_dir($path)) {
$bundleTransPath = $path;
}
}
if (!$bundleTransPath) {
$bundleTransPath = end($transPaths).'translations';
}
$writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale')));
if (true === $input->getOption('dump-messages')) {
$resultMessage .= ' and translation files were updated';
}
}
$io->success($resultMessage.'.');
}
}

View File

@@ -0,0 +1,165 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
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\Finder\Finder;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
/**
* Validates YAML files syntax and outputs encountered errors.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class YamlLintCommand extends Command
{
protected function configure()
{
$this
->setName('lint:yaml')
->setDescription('Lints a file and outputs encountered errors')
->addArgument('filename', null, 'A file or a directory or STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
->setHelp(<<<EOF
The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT
the first encountered syntax error.
You can validate the syntax of a file:
<info>php %command.full_name% filename</info>
Or of a whole directory:
<info>php %command.full_name% dirname</info>
<info>php %command.full_name% dirname --format=json</info>
Or all YAML files in a bundle:
<info>php %command.full_name% @AcmeDemoBundle</info>
You can also pass the YAML contents from STDIN:
<info>cat filename | php %command.full_name%</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$filename = $input->getArgument('filename');
if (!$filename) {
if (0 !== ftell(STDIN)) {
throw new \RuntimeException('Please provide a filename or pipe file content to STDIN.');
}
$content = '';
while (!feof(STDIN)) {
$content .= fread(STDIN, 1024);
}
return $this->display($input, $output, $io, array($this->validate($content)));
}
if (0 !== strpos($filename, '@') && !is_readable($filename)) {
throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename));
}
$files = array();
if (is_file($filename)) {
$files = array($filename);
} elseif (is_dir($filename)) {
$files = Finder::create()->files()->in($filename)->name('*.yml');
} else {
$dir = $this->getApplication()->getKernel()->locateResource($filename);
$files = Finder::create()->files()->in($dir)->name('*.yml');
}
$filesInfo = array();
foreach ($files as $file) {
$filesInfo[] = $this->validate(file_get_contents($file), $file);
}
return $this->display($input, $output, $io, $filesInfo);
}
private function validate($content, $file = null)
{
$parser = new Parser();
try {
$parser->parse($content);
} catch (ParseException $e) {
return array('file' => $file, 'valid' => false, 'message' => $e->getMessage());
}
return array('file' => $file, 'valid' => true);
}
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, $files)
{
switch ($input->getOption('format')) {
case 'txt':
return $this->displayTxt($output, $io, $files);
case 'json':
return $this->displayJson($io, $files);
default:
throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
}
}
private function displayTxt(OutputInterface $output, SymfonyStyle $io, $filesInfo)
{
$errors = 0;
foreach ($filesInfo as $info) {
if ($info['valid'] && $output->isVerbose()) {
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
} elseif (!$info['valid']) {
++$errors;
$io->text(sprintf('<error> ERROR </error> in %s', $info['file']));
$io->text(sprintf('<error> >> %s</error>', $info['message']));
}
}
if ($errors === 0) {
$io->success(sprintf('All %d YAML files contain valid syntax.', count($filesInfo)));
} else {
$io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.', count($filesInfo) - $errors, $errors));
}
return min($errors, 1);
}
private function displayJson(OutputInterface $output, $filesInfo)
{
$errors = 0;
array_walk($filesInfo, function (&$v) use (&$errors) {
$v['file'] = (string) $v['file'];
if (!$v['valid']) {
++$errors;
}
});
$output->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT));
return min($errors, 1);
}
}