0

I want to run some vendor-scripts of Composer from my PHP-script.

Calling each command takes long although the command itself finishes quick. I assume creating a new shell by shell_exec() takes some time.

I wanted to call the PHP-scripts directly via the require keyword but changing the global $argv to contain the parameters for a script does not apply to the called script. Is $argv implicitly immutable across script-files or do I have another error in my way of thinking?

Here is some sample code (should be executed via CLI, not tested):

namespace Foo;
class Bar
{
    public static function call_cs_fixer()
    {
        $GLOBALS['argv'] = [
            '/path/to/vendor/bin/php-cs-fixer',
            'fix',
            '--config',
            '"/path/to/.php-cs-fixer.php"',
            '"/path/to/project"',
        ];
        return require $GLOBALS['argv'][0];
    }
}
echo \Foo\Bar::call_cs_fixer();

1 Answer 1

1

Is $argv implicitly immutable across script-files or do I have another error in my way of thinking?

That depends on how the script/utility works that you try to invoke. Which means you can't expect it to work stable and I would refrain from it unless you know it has this interface. As you don't know it - otherwise you would not ask the question that way - throw that idea into the bin in this case.

I assume creating a new shell by shell_exec() takes some time.

This may be (we can't look into your system configuration), but if it is a linux system this is very likely not the case.

In practice, the use of a new shell sub-process to invoke the tooling is the much, much better way to do things here. This is also how composer(1) invokes scripts (see Scripts) - unless they are bound as (static) methods - and is always true for the composer exec command.

The reason is that you can control not only the command line arguments much better but also the working directory and the environment parameters (a.k.a. environment variables or environment in short), compare proc_open(php). The standard streams are available as well.

As you're running in context of composer, and if you have access to the sources of it (e.g. you bind a composer script or hook in your composer.json configuration), you can use the process components that ship with composer itself (its all PHP), it has quite some utility in there.

If you just want to start lightly, I found the passthru(php) function a good fit for quickly getting started:

// the command you'd like to execute
$command = '/path/to/vendor/bin/php-cs-fixer';
$args = [
    'fix',
    '--config',
    '/path/to/.php-cs-fixer.php',
    '/path/to/project'
];


// build the command-line
$commandLine = sprintf(
   '%s %s',
   $command,
   implode(' ', array_map('escapeshellarg', $args))
);

// execute
$result = passthru($commandLine, $exitStatus);

// be verbose and give some debug info
fprintf(
    STDERR, 
    "debug: command %s exited with status %d\n", 
    $commandLine, 
    $exitStatus
);

// throw on exit status != 0, a convention only but you often want this
if (false === $result || $existStatus !== 0) {
    throw new \RuntimeException(sprintf(
        'command "%s" exited with non-zero status %d (result=%s).\n',
        addcslashes($commandLine, "\0..\37\"\\\177..\377"),
        $exitStatus,
        var_export($result, true)
    ), (int)$exitStatus);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.