So I'm running a Symfony Process for some commands, and I noticed that when I write those to Symfony's OutputInterface it is not showing colors or progress bars. I think that the commands (npm, artisan, ls, etc) are using terminal control codes, and one of those classes is eating the non-standard ASCII characters.
EDIT:
I've done some digging and I belive Symfony uses its StreamOutput class by default. It appears to be able to output in color, and I've tried telling it to OUTPUT_RAW. No beans there. Perhaps the problem is somewhere else...
Is there a built-in way to tell these classes not to do that? How can I get my pretty output back?
Colors availability depends on the program you are calling.
You may try to set the tty/pty:
protected function execute(InputInterface $input, OutputInterface $output)
{
$process = new Process('ls -l --color="always"');
$process->setTty(true); // or $process->setPty(true);
$process->run();
$output->write($process->getOutput());
}
See related issue.
I don't think that a command output removes the escape codes. Next example works well (for me):
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->write(shell_exec('ls -l --color="always"')); // ok, output is colored
}
Hope this helps.
Related
Lets say I have the following Symfony 4 command:
class Command1 extends Command {
protected static $defaultName = 'app:command1';
protected function execute(InputInterface $input, OutputInterface $output){
$process = new Process('bin/console list', getcwd());
$process->start(); // or even $process->run() does not matter if its async or not
// ... where we handle if its finished, etc...
}
}
If I simply call bin/console app:command1 it will return the expected command list. Basically works as I expect.
But if I have a phpunit test which uses the Symfony\Component\Console\Application::run() to start this command, I end up in an "infinite loop" (well, actually not, it times out after 60 sec) in the Symfony\Component\Process::wait() in the
do {
$this->checkTimeout();
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
} while ($running);
where the $this->processPipes->areOpen() will be always open.
It seems to me, if I use any Symfony console command in a Process through phpunit, there will be always two pipes open like these:
1 = {resource} resource id='x' type='stream'
2 = {resource} resource id='z' type='stream'
but I don't know what are these actually. I also saw in htop, that the start()'s proc_open actually starts up a new process, but it just hangs (does absolutely nothing, cant even debug it), until times out. Nothing in error logs (other than the timeout).
I have a custom command in my symfony project to populate the database with the default data that the application need to work in both dev and prod environments.
For the dev environment I have a fixture script that depends on these default common data.
I'm trying to call my custom Symfony command in the fixture script so that I'm sure to have the required data to properly load my fixtures.
This is my custom command app:db:populate in "pseudo script", just creating a bunch of entities, persit & flush. My custom command works fine when I call it through php bin/console app:db:populate
protected function execute(InputInterface $input, OutputInterface $output)
{
// Creating a bunch of default entities, persist them and flush
$data = new MyDefaultEntity();
// ...
$this->manager->persist($data);
// ...
$this->manager->flush();
}
Then, in my fixture script, I want to call app:db:populate first, because fixtures depends on these data. So I tried to use the Process class to execute my script this way :
public function load(ObjectManager $manager)
{
// Execute the custom command
$cmd = 'php bin/console app:db:populate';
$process = new Process($cmd);
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
// Then load the fixtures !
// ...
}
The custom command seems to execute well until the $this->manager->flush();
I have the following error in my console (Data is obfuscated for the post):
In AbstractMySQLDriver.php line 36:
An exception occurred while executing 'INSERT INTO ....(..., ..., ...) VALUES (?, ?, ?)' with params ["...", "...", "..."]:
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction
I don't know what to do regarding this error ... Why the command is working normally when used through a classic console call and why it is not working in a Process?
So, the short answer is
Quoting Symfony documentation :
You may have the need to execute some function that is only available in a console command. Usually, you should refactor the command and move some logic into a service that can be reused in the controller.
I ended up making a service class that handles all the app:db:populate logic (read a json file and insert basic app entities in the database). Then I call this service in both app:db:populate execute methods and AppFixtures load methods.
Hope this will help someone.
I use Symfony\Component\Cache\Simple\FilesystemCache;
It works when I $cache->set $cache->get $cache->clear() etc
I don't want to use a custom ttl. I want to clear the cache setted only with console.
But when I do php bin/console cache:clear, it doesn't clear cache I have set before with FilesystemCache.
I have tried to clear every pools with console but it doesn't clear $cache either.
1. Why it happens
Symfony's bin/console cache:clear command clears the cache only from kernel cache dir, which is var/cache/{env} by default.
When you create instance of FilesystemCache, you can provide a path where you want to store your cache as a 3rd parameter. Here's a signature of FilesystemCache constructor
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
If you don't provide 3rd parameter it will end up as sys_get_temp_dir().'/symfony-cache', which is /tmp/symfony-cache on Linux.
As you can see it's a different directory and it won't be cleared by cache:clear command.
2. How to fix it
The proper way
You need to create your own data-cache:clear command. It's very simple https://symfony.com/doc/current/console.html
In execute() method of your command you should instantiate your FilesystemCache and call clear() on it. Example:
protected function execute(InputInterface $input, OutputInterface $output)
{
$cache = new FilesystemCache();
$cache->clear();
}
Then you can call php bin/console data-cache:clear from console.
If you decide to switch to some other caching engine in future (Redis, Memcached etc.) you can simply adjust that command to clear that cache.
The wrong way
It will only work if you keep using FilesystemCache and does not
provide fine-grained control of which cache you actually clear.
You can store your cache in kernel.cache_dir by passing a 3rd parameter to FilesystemCache when you instantiate it.
Example:
$cache = new FilesystemCache('', 0, $container->getParameter('kernel.cache_dir').'/data-cache');
or when configured as a service
Symfony\Component\Cache\Simple\FilesystemCache:
arguments:
- ''
- 0
- '%kernel.cache_dir%/data-cache'
This way Symfony's cache:clear command will work for you, but it's not a good idea to store these 2 types of cache in the same place.
If you change some of your project files, you may want to clear only
kernel cache in /var/cache while keeping your data cache intact and
vice versa. That's why I recommend not to use this solution!
It finally works, using AdapterInterface
<?php
namespace Gh\GhBundle\Manager;
use Symfony\Component\Cache\Adapter\AdapterInterface;
class AppManager
{
protected $_rootDir;
protected $_cache;
public function __construct($rootDir, AdapterInterface $cache)
{
$this->_rootDir = $rootDir;
$this->_cache = $cache;
}
/**
*
* Get version of this app
* #return string
*/
public function getVersion()
{
$cache = $this->_cache;
$numVersion = $cache->getItem('stats.num_version');
if (!$numVersion->isHit()) {
$version = !file_exists($this->_rootDir . '/RELEASE.TXT') ? 'dev' : file_get_contents($this->_rootDir . '/RELEASE.TXT');
$numVersion->set($version);
$cache->save($numVersion);
}
return $numVersion->get();
}
/**
*
* Get name of this app
* #return string
*/
public function getName()
{
return 'GH';
}
}
I use symfony and I have installed whiteoctober TCPDF with success.
It works if I use it in a controller, my PDF is generated.
However I want to use it in a command.
So I have a command called "CronInvoicesCommand" in a command folder and of course I have this error:
Attempted to call method "get" on class "OandP\boBundle\Command\CronInvoicesCommand" in C:\wamp\www\OandPlocal\src\OandP\boBundle\Command\CronInvoicesCommand.php line 187. Did you mean to call: "getAliases", "getApplication", "getDefinition", "getDescription", "getHelp", "getHelper", "getHelperSet", "getName", "getNativeDefinition", "getProcessedHelp", "getSynopsis"?
So my question is how can I load all those method in a command.
Thank you se much for your help
you can use a container aware commmand :
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
class MyCommand extends ContainerAwareCommand
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$em = $this->getContainer()->get('doctrine')->getEntityManager();
// ...
Probably you are trying to get a service from a container similar to the controller method get. In a Command you can access to the container with the getContainer() method, so try to substitute something like:
$this->get('service_name');
with
$this->getContainer()->get('service_name');
More info here in the doc.
Hope this help
I am writing a console command which generates data files to be used by external services (for example, a Google feed, inventory feed, etc). Should the location of the generated data files be within the Symfony app? I know they can actually be anywhere, I'm just wondering if there is a standard way to do it.
It's up to you, but it is better to have this path in a parameter. For example you can you have a parameter group related to your command. This allows you to have different configurations depending on the current environment:
parameters:
# /app/config.yml
# #see MyExportCommand.php
my_export_command:
base_path: '/data/ftp/export'
other_command_related_param: true
In your command, get and store those parameters in the initialize function:
// MyExportCommand.php
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->parameters = $this->getContainer()->getParameter('my_export_command');
}
Finally in your execute function, you can use something like this: ($this->fs is an instance of the Symfony2 Filesystem component)
// execute()
// Write the file
$filePath = $this->parameters['base_path']. '/'. $this->fileName;
$this->fs->dumpFile($filePath, $myContent);