In my composer.json I have a script called tests that is just an array of several commands for testing tools.
One of those commands is pretty slow and somewhat unimportant. So I would like to run it only occasionally and not always (whole composer tests is run as pre-commit hook)
I had an idea of doing something like
"scripts": {
"slowScript": "rand(0,10) != 5 || actualSlowScript"
}
but I can't get it to work. The script needs to be platform independent as we use different OS.
Do you have an idea on how to get it working?
This is a solution:
Create a PHP class with static method as in https://getcomposer.org/doc/articles/scripts.md#defining-scripts
<?php
namespace ComposerScripts;
use Composer\Console\Application;
use Composer\Script\Event;
use Symfony\Component\Console\Input\ArrayInput;
final class RandomLint
{
public static function runLintAtRandom(Event $event): void
{
$random = random_int(1, 5);
if ($random === 1) {
$event->getIO()->write('Lint skipped');
return;
}
$input = new ArrayInput(array('command' => 'lint'));
$application = new Application();
$application->setAutoExit(false);
$application->run($input);
}
}
then define the script as
"scripts": {
"test": [
"ComposerScripts\\RandomLint::runLintAtRandom",
"secondScript",
"#thirdOne"
]
}
and you can control scripts from PHP Class. I can see quite some potential in this solution.
I would not build it into the test script because then you can never force the test to run when you want, like in a build pipeline. You can do it via the composer scripts feature by using the exit status of a shell command:
"scripts": {
"slowScript": "[ $(rand -M 5) -eq 0 ] && actualSlowScript"
}
Assuming you have the rand command installed, this picks a random number between 0 and 4, then only runs the test if it picked 0.
Note this will throw a composer warning 80% of the time. If that's problematic, you could do something like this:
"scripts": {
"slowScript": "if [ $(rand -M 5) -eq 0 ]; then actualSlowScript; else echo skipping slow test; fi"
}
Related
I followed this guide: https://symfony.com/doc/current/console/lockable_trait.html and implemented the command lock feature for my one of my commands to see how it works. It worked as described and then I was going to implement it for all of my commands. But the issue is that I have about 50 commands and:
I do not want spent time adding the necessary code to each command
I want to have the centralized management of commands locking. I mean, adding extra option to regular commands so that they will be used by my future management center. For now I will need a pretty simple option protected function isLocked() for a regular command which will help me to manage if a command should have lockable feature.
So, I went to the source of \Symfony\Component\Console\Command\LockableTrait and after some time created the following listener to the event console.command:
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
class LockCommandsListener
{
/**
* #var array<string, Lock>
*/
private $commandLocks = [];
private static function init()
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
}
}
public function onConsoleCommand(ConsoleCommandEvent $event)
{
static::init();
$name = $event->getCommand()->getName();
$this->ensureLockNotPlaced($name);
$lock = $this->createLock($name);
$this->commandLocks[$name] = $lock;
if (!$lock->acquire()) {
$this->disableCommand($event, $name);
}
}
private function disableCommand(ConsoleCommandEvent $event, string $name)
{
unset($this->commandLocks[$name]);
$event->getOutput()->writeln('The command ' . $name . ' is already running');
$event->disableCommand();
$event->getCommand()->setCode()
}
private function createLock(string $name): LockInterface
{
if (SemaphoreStore::isSupported()) {
$store = new SemaphoreStore();
} else {
$store = new FlockStore();
}
return (new LockFactory($store))->createLock($name);
}
private function ensureLockNotPlaced(string $name)
{
if (isset($this->commandLocks[$name])) {
throw new LogicException('A lock is already in place.');
}
}
}
I made some tests and it kind of worked. But I am not sure this is the right way of doing things.
Another problem is that I can not find the proper exit code when I disabled a command. Should I just disable it? But it seems that exit code would be a great feature here. Specially when it comes to this listener testing (PHPUnit testing).
And I also have with testing itself. How can I run commands in parallel in my test class. For now I have this:
class LockCommandTest extends CommandTest
{
public function testOneCommandCanBeRun()
{
$commandTester = new ApplicationTester($this->application);
$commandTester->run([
'command' => 'app:dummy-command'
]);
$output = $commandTester->getDisplay();
dd($output);
}
}
It will allow only to run my commands one by one. But I would like to run them both so after running the first one, the second will fail (with some exit code).
As for me the best way to make background task is doing it via supervisor, create config file, like:
[program:your_service]
command=/usr/local/bin/php /srv/www/bin/console <your:app:command>
priority=1
numprocs=1
# Each 5 min.
startsecs=300
autostart=true
autorestart=true
process_name=%(program_name)s_%(process_num)02d
user=root
this is the best way to be sure that your command will be ran only in one process
Am trying to run php Interactive shell from a php script. To be more specific, I want to be able to call my classes from interactive shell.
I manage to find this
# custom_interactive_shell.php
function proc($command, &$return_var = null, array &$stderr_output = null)
{
$return_var = null;
$stderr_output = [];
$descriptorspec = [
// Must use php://stdin(out) in order to allow display of command output
// and the user to interact with the process.
0 => ['file', 'php://stdin', 'r'],
1 => ['file', 'php://stdout', 'w'],
2 => ['pipe', 'w'],
];
$pipes = [];
$process = #proc_open($command, $descriptorspec, $pipes);
if (is_resource($process)) {
// Loop on process until it exits normally.
do {
$status = proc_get_status($process);
// If our stderr pipe has data, grab it for use later.
if (!feof($pipes[2])) {
// We're acting like passthru would and displaying errors as they come in.
$error_line = fgets($pipes[2]);
echo $error_line;
$stderr_output[] = $error_line;
}
} while ($status['running']);
// According to documentation, the exit code is only valid the first call
// after a process is finished. We can't rely on the return value of
// proc_close because proc_get_status will read the exit code first.
$return_var = $status['exitcode'];
proc_close($process);
}
}
proc('php -a -d auto_prepend_file=./vendor/autoload.php');
But its just not working, it tries to be interactive but freezes up a lot, and even after the lag it doesn't really execute commands properly.
Example:
> php custom_interactive_shell.php
Interactive shell
php > echo 1;
Warning: Use of undefined constant eo - assumed 'eo' (this will throw an Error in a future version of PHP) in php shell code on line 1
If you want to be able to run your PHP classes from an interactive shell then you can use the default one that ships with Terminal. From the terminal just type:
php -a
Then, in the below example I had created a file called Agency.php that had class Agency in it. I was able to require_once() it into the active shell and then call the class and its methods:
Interactive shell
php > require_once('Agency.php');
php > $a = new Agency();
php > $a->setname("some random name");
php > echo $a->getname();
some random name
You can also use the following in the interactive shell to autoload the files / classes in the current directory:
spl_autoload_register(function ($class_name) {
include $class_name . '.php';
});
I am attempting to call one Artisan (Laravel) command from another command. However, I need to be able to retrieve an array from the command that is called from the "main" command...
i.e
// Command 1
public function handle() {
$returnedValue = $this->call( 'test:command' );
dump( $returnedValue ); // <-- is 5
}
// Command 2
public function handle() {
return $this->returnValue();
}
private function returnValue() {
$val = 5;
return $val;
}
I have looked through the documentation and can't find a way to do this, so I was wondering if there was a way or if I need to re-think my approach.
Thanks!
Artisan Commands don't behave the same way as, for example, Controller functions. They return an exitCode, which in my testing was always 0 (couldn't get anything to return if an error is thrown).
Your approach won't work if you try to get a return value, but you can access \Artisan::output(); to see what exactly is sent by the first artisan command you call.
// FirstCommand.php
public function handle(){
\Artisan::call("second:command");
if(\Artisan::output() == 1){
$this->info("This Worked");
} else {
$this->error("This Didn't Work");
}
}
Note: I used \Artisan::call(); there's some apparent differences between the two where using $this->call() didn't work as expected, but \Artisan::call() did. $this->call() sent both 0 and 1 back, regardless of the actual code being executed; not sure what's up there. Tested on Laravel 5.0, which is quite behind the current, so maybe that's it.
// SecondCommand.php
public function handle(){
try {
$test = 1 / 1;
} catch (\Exception $ex){
$this->error("0");
}
$this->info("1");
}
Running php artisan first:command in my console returns:
$ php artisan first:command
This Worked
Now, if switch the code in $test to
$test = 1 / 0;
I get this in my console:
$ php artisan first:command
This Didn't Work
So, the rule here I guess is to avoid outputting anything in the second command prior to the result you want to check with \Artisan::output().
Symfony2 enables developers to create their own command-line commands. They can be executed from command line, but also from the controller. According to official Symfony2 documentation, it can be done like that:
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find('demo:greet');
$arguments = array(
...
);
$input = new ArrayInput($arguments);
$returnCode = $command->run($input, $output);
}
But in this situation we wait for the command to finish it's execution and return the return code.
How can I, from controller, execute command forking it to background without waiting for it to finish execution?
In other words what would be equivalent of
$ nohup php app/console demo:greet &
From the documentation is better use start() instead run() if you want to create a background process. The process_max_time could kill your process if you create it with run()
"Instead of using run() to execute a process, you can start() it: run() is blocking and waits for the process to finish, start() creates a background process."
According to the documentation I don't think there is such an option: http://api.symfony.com/2.1/Symfony/Component/Console/Application.html
But regarding what you are trying to achieve, I think you should use the process component instead:
use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
$process->run(function ($type, $buffer) {
if ('err' === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
And as mentioned in the documentation "if you want to be able to get some feedback in real-time, just pass an anonymous function to the run() method".
http://symfony.com/doc/master/components/process.html
Is there a way to run a console command from a Symfony 2 test case? I want to run the doctrine commands for creating and dropping schemas.
This documentation chapter explains how to run commands from different places. Mind, that using exec() for your needs is quite dirty solution...
The right way of executing console command in Symfony2 is as below:
Option one
use Symfony\Bundle\FrameworkBundle\Console\Application as App;
use Symfony\Component\Console\Tester\CommandTester;
class YourTest extends WebTestCase
{
public function setUp()
{
$kernel = $this->createKernel();
$kernel->boot();
$application = new App($kernel);
$application->add(new YourCommand());
$command = $application->find('your:command:name');
$commandTester = new CommandTester($command);
$commandTester->execute(array('command' => $command->getName()));
}
}
Option two
use Symfony\Component\Console\Input\StringInput;
use Symfony\Bundle\FrameworkBundle\Console\Application;
class YourClass extends WebTestCase
{
protected static $application;
public function setUp()
{
self::runCommand('your:command:name');
// you can also specify an environment:
// self::runCommand('your:command:name --env=test');
}
protected static function runCommand($command)
{
$command = sprintf('%s --quiet', $command);
return self::getApplication()->run(new StringInput($command));
}
protected static function getApplication()
{
if (null === self::$application) {
$client = static::createClient();
self::$application = new Application($client->getKernel());
self::$application->setAutoExit(false);
}
return self::$application;
}
}
P.S. Guys, don't shame Symfony2 with calling exec()...
The docs tell you the suggested way to do it. The example code is pasted below:
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find('demo:greet');
$arguments = array(
'command' => 'demo:greet',
'name' => 'Fabien',
'--yell' => true,
);
$input = new ArrayInput($arguments);
$returnCode = $command->run($input, $output);
// ...
}
Yes, if your directory structure looks like
/symfony
/app
/src
then you would run
phpunit -c app/phpunit.xml.dist
from your unit tests you can run php commands either by using
passthru("php app/console [...]") (http://php.net/manual/en/function.passthru.php)
exec("php app/console [...]") (http://www.php.net/manual/en/function.exec.php)
or by putting the command in back ticks
php app/consode [...]
If you are running the unit tests from a directory other than symofny, you'll have to adjust the relative path to the app directory for it to work.
To run it from the app:
// the document root should be the web folder
$root = $_SERVER['DOCUMENT_ROOT'];
passthru("php $root/../app/console [...]");
The documentation has been updated since my last answer to reflect the proper Symfony 2 way of calling an existing command:
http://symfony.com/doc/current/components/console/introduction.html#calling-an-existing-command