A cross-platform PHP utility library that harmonizes OS differences and executes external programs in a safer way
Command is a small lightweight utility library that enables you to run external programs or commands in a safer way. It also harmonizes the differences between Windows and Unix environments, removing the need to create specific code for each platform.
- Platform independent: run the same code in Unix and Windows
- Fixes issues with
proc_open
in windows environment - Object Oriented command builder with fluent interface
- Argument escaping, for safer command calls
- Command chaining with support for conditional calls and piping in both Windows and Unix environment
Run commands in background(coming soon)
You can install it by cloning the git repository or using composer.
git clone https://github.com/tivie/command.git
Add these lines to your composer.json:
{
"require": {
"tivie/command": "*"
}
}
or run the following command:
php composer.phar require tivie/command
Let's say we want to 'ping' google.com 3 times with 32bytes packets. With PHP we could do something like this:
exec("ping -c 3 -s 24 www.google.com", $otp, $ec);
We want, however, to make our command a little bit safer and escape our arguments.
$host = 'www.google.com';
$c = 3;
$s = 24; //Linux adds 8 bytes of ICMP header data
$cmd = sprintf("ping -c %d -s %d %s", escapeshellarg($c), escapeshellarg($s), escapeshellarg($host));
exec($cmd, $otp, $ec);
This will work as expected in a GNU/Linux environment but will fail on Windows, since '-c' is an unrecognized flag and '-s' means something entirely different. The windows version would be :
$host = 'www.google.com';
$c = 3;
$s = 32;
$cmd = sprintf("ping -n %d -l %d %s", escapeshellarg($c), escapeshellarg($s), escapeshellarg($host));
exec($cmd, $otp, $ec);
If we want to ensure cross platform compatibility we will need to perform some kind of OS check and run the appropriate command based on that check:
$host = 'www.google.com';
$c = 3;
if (PHP_OS === 'WINDOWS' || PHP_OS === 'WIN32' || PHP_OS === 'WINNT' /* And a few more*/ ) {
$s = 32;
$cmd = sprintf("ping -n %d -l %d %s", escapeshellarg($c), escapeshellarg($s), escapeshellarg($host));
} else {
$s = 24; //Linux adds 8 bytes of ICMP header data
$cmd = sprintf("ping -c %d -s %d %s", escapeshellarg($c), escapeshellarg($s), escapeshellarg($host));
}
exec($cmd, $otp, $ec);
While this works in most cases, with more complex commands (or command chains) you would be forced to repeat yourself a lot, with a lot of conditional checks.
With command library, you don't need to: it will do this work for you.
$cmd = new \Tivie\Command\Command(\Tivie\Command\ESCAPE);
$cmd->setCommand('ping')
->addArgument(
new Argument('-n', 3, \Tivie\OS\WINDOWS_FAMILY)
)
->addArgument(
new Argument('-l', 32, \Tivie\OS\WINDOWS_FAMILY)
)
->addArgument(
new Argument('-c', 3, \Tivie\OS\UNIX_FAMILY)
)
->addArgument(
new Argument('-s', 24, \Tivie\OS\UNIX_FAMILY)
)
->addArgument(
new Argument('www.google.com')
);
$result = $cmd->run();
Command::run()
returns a Result object that you can access to retrieve the result of the command.
echo $result->getStdOut(); // The Standard Output of the command
echo $result->getLastLine(); // The last line of the Standard Output
echo $result->getStdIn(); // The passed standard input
echo $result->getStdErr(); // The standard error
echo $result->getExitCode(); // The command's exit code
Command library supports command chaining
$cmd1 = new \Tivie\Command\Command();
$cmd1->setCommand('php')
->addArgument(new Argument('-v'));
$cmd2 = new \Tivie\Command\Command();
$cmd2->setCommand('echo')
->addArgument(new Argument('foo'));
$results = $cmd1->chain()
->add($cmd2)
/* any number of commands here */
->run();
$results
will be an array of Result objects.
You can also specify chaining conditions, similar to Linux's Chaining Operators.
$cmd1->chain()->add($cmd2, \Tivie\Command\RUN_REGARDLESS)->run();
$cmd2
will be run regardless of the exitcode of $cmd1
. Mimics the ';' chaining operator and is the default action.
$cmd1->chain()->add($cmd2, \Tivie\Command\RUN_IF_PREVIOUS_SUCCEEDS)->run();
$cmd2
will only be run if $cmd1
is successful, that is, if it exits with exitcode 0. Mimics the '&&' chaining operator.
$cmd1->chain()->add($cmd2, \Tivie\Command\RUN_IF_PREVIOUS_FAILS)->run();
$cmd2
will only be run if $cmd1
is not successful, that is, if it exits with exitcode different than 0.
Mimics the '||' chaining operator.
That being said, you can create complex command chains. For instance:
$cmd1->chain()
->add($cmd2, \Tivie\Command\RUN_IF_PREVIOUS_SUCCEEDS)
->add($cmd3, \Tivie\Command\RUN_IF_PREVIOUS_FAILS)
->add($cmd4, \Tivie\Command\RUN_REGARDLESS)
->run();
This will:
- Run
$cmd1
- If
$cmd1
is successful then runs$cmd2
- If
$cmd1
or$cmd2
fails it will run$cmd3
- Finally will run
$cmd4
Command library supports 2 types of piping:
- STDOUT->STDIN
- STDOUT->Argument
Piping the standard output of one command to the next's standard input is easy. You just need to set the third argument
of Chain::add()
to true
.
$cmd1->chain()->add($cmd2, \Tivie\Command\RUN_REGARDLESS, true)
You can also pass the STDOUT of previous command as an argument of the next command. The library will look for the special
keyword (placeholder) '!PIPE!' in the command's argument key and values and replace them with the previous command's STDOUT.
You will then need to pass true as the third argument in Chain::add()
function, same as the above case.
$cmd2->addArgument(new Argument('foo'), \Tivie\Command\PIPE_PH); // PIPE_PH = '!PIPE!'
$cmd1->chain()->add($cmd2, \Tivie\Command\RUN_REGARDLESS, true);
IF you need to more specific OS checks, you can extend Detector class or create a new class that implements DetectorInterface. For further information, please read the php-os-detector documentation.
Example:
const OS_2_WARP = 65540; //65536 + 4
class MyOSDetector extends \Tivie\OS\Detector
{
public function detect()
{
$os = parent::detect();
switch($os->name) {
case "OS/2":
case "OS/2 WARP":
$os->family = \Tivie\Command\OS\OTHER_FAMILY;
$os->def = OS_2_WARP;
break;
}
return $os;
}
}
You don't need to create a new constant pertaining the new OS (you can use one of the pre existing families). If, however, you choose to do so, the new OS const value should be a unique number in the 2^n sequence plus the family the OS belongs to. In the example we chose 16th term (65536) plus the OS family (in this case, FAMILY_OTHER) which is 4.
Feel free to contribute by forking or making suggestions.
Issue tracker: https://github.com/tivie/command/issues
Source code: https://github.com/tivie/command
Command Library is released under Apache 2.0 license. For more information, please consult the LICENSE file in this repository or http://www.apache.org/licenses/LICENSE-2.0.txt.