How to get the cli command that was run, from PHP?
I have this logging class
class Logger {
public function log($logMessage)
{
$this->writeLog($this->getCommand() . ' : ' . $logMessage);
}
private function getCommand()
{
//How to get the command?
//here var_dump($argv); returns NULL
}
}
This logger is being used by many classes that are run from several commands.
Tow examples:
process_users.php
$logger = new Logger();
$users = new Users();
foreach ($users->getUnprocessed() as $user) {
$logger->log('processing '.$user->getId());
if ($user->process()){
$logger->log('processed ' . $user->getId());
} else {
$logger->log('failed ' . $user->getId());
}
}
upload_consumer.php
$logger = new Logger();
$consumer = new UploadConsumer();
while ($message = $consumer->getNextMessage()){
$logger->log('Uploading ' . $message['name']);
$this->upload($message);
$logger->log('Finished uploading '. $message['name']);
}
So when I run php process_users.php corporate --limit 10
I want to have logs like:
php process_users.php corporate --limit 10 : processing 2554
php process_users.php corporate --limit 10 : processed 2554
php process_users.php corporate --limit 10 : processing 2555
php process_users.php corporate --limit 10 : failed 2555
And when I run php upload_consumer.php -m 100 -w
I want to have logs like:
php upload_consumer.php -m 100 -w : Uploading 564sdf564sdf56sd4f.png
php upload_consumer.php -m 100 -w : Finished uploading 564sdf564sdf56sd4f.png
php upload_consumer.php -m 100 -w : Uploading sd56f4sd54f6sd6f54.png
php upload_consumer.php -m 100 -w : Finished uploading sd56f4sd54f6sd6f54.png
Is this possible?
use $argv which will give you the arguments passed to the script and the script name try var_dump($argv); so you can see what data you're working with.
More info here http://php.net/manual/en/reserved.variables.argv.php
You can also use getopt() if you just want the cli arguments and not the script name
Remember when using within a class or a function then you will have to do global $argv to bring it within the scope of the class or the function. Alternatively you can pass it in as a parameter rather than using global.
Related
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'm trying to write a bash script that adds a line into a php function like this:
public function register()
{
$this->app->bind('App\Repositories\Corporate\CorporateContract',
'App\Repositories\Corporate\EloquentCorporateRepository');
}
Here's my code:
function bindContractToRepository {
sed -i -e '/register()\n/a \ \n\t\t'${repoBinding}' \n\t\t\t\t ' ./app/Providers/${repoName}${provider}.php 2> /dev/null
}
bindContractToRepository
I actually want my code to come inside the function itself like the example at the top.
NB. I can't specify a particular line because the line number varies with different version
In general it is very difficult to detect PHP function boundaries without a parser.
We can do the parser's job in Bash: iterate the code line by line, solve the opening and closing braces etc. until we get the range of lines covered by this function and, finally, replace it with new content. We can do the same using the sed commands (in this case will likely look less readable than an assembly code).
But we already have a PHP parser! Why not use it?
Example
The following PHP CLI script accepts:
PHP input file (where we are going to replace a method/function);
PHP Method/function name
PHP code string for the replacement as a string, or dash(the standard input)
replace-method.php
<?php
function usage($error = false) {
global $argv;
$str = <<<EOS
USAGE: php {$argv[0]} OPTIONS
OPTIONS:
-h, --help Print help message
-i, --input-file Input PHP file
-m, --method PHP function/method name, e.g.:
my_func, MyClass::myMethod
-c, --code The new PHP code including the function/method declaration.
String of code, or dash ('-') meaning the standard input.
EXAMPLE:
php {$argv[0]} -i source/file.php -m 'MyClass::register' -c - <<ENDOFPHP
public function register()
{
echo time();
}
ENDOFPHP
Replaces the code of 'MyClass::register' method with new code from the standard input
in source/file.php.
EOS;
fprintf($error ? STDERR : STDOUT, $str);
exit((int)!$error);
}
if (false === ($opt = getopt('hi:m:c:', ['help', 'input-file:', 'method:', 'code:']))) {
fprintf(STDERR, "Failed to parse options\n");
usage(true);
}
if (isset($opt['h']) || isset($opt['help']))
usage();
// Using PHP7 Null coalescing operator
$file = $opt['i'] ?? $opt['input-file'] ?? null;
if (!file_exists($file)) {
fprintf(STDERR, "File '$file' does not exist\n");
usage(true);
}
$new_code = $opt['c'] ?? $opt['code'] ?? null;
if (!$new_code) {
fprintf(STDERR, "Code option expected\n");
usage(true);
}
if ($new_code == '-')
$new_code = file_get_contents('php://stdin');
$method = $opt['m'] ?? $opt['method'] ?? null;
if (!$method) {
fprintf(STDERR, "Method option expected\n");
usage(true);
}
// You most likely want to include project's autoloading file instead.
// (You might accept it as a CLI argument, too.)
require_once $file;
$rf = strpos($method, '::') ?
new ReflectionMethod($method) :
new ReflectionFunction($method);
$start_line = $rf->getStartLine();
$end_line = $rf->getEndLine();
$lines = file($file);
$code = implode(array_slice($lines, 0, $start_line - 1)) .
$new_code . implode(array_slice($lines, $end_line));
file_put_contents($file, $code);
Suppose we have path/to/file.php with A class and its register method:
<?php
class A
{
public function register()
{
echo 'aaaa';
}
}
We can replace the code of A::register method in a Bash as follows:
php replace-method.php -i path/to/file.php -m 'A::register' -c - <<ENDOFPHP
public function register()
{
echo 'something new';
}
ENDOFPHP
I've used Bash here document for input. You can use any kind of the shell redirection, for instance:
generate_php_code | php 1.php -i file.php -m 'A::register' -c -
I'm testing a web crawler script. I'm using the php builtin webserver to test against pages locally.
I can start the server but I cannot kill the process because it is already killed (I get the exception that I set Could not kill the testing web server).
Here is my attempt:
<?php
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
/**
* Defines application features from the specific context.
*/
class FeatureContext implements Context, SnippetAcceptingContext
{
const TESTING_BASE_URL = 'http://127.0.0.1:6666';
const TESTING_DIR = '/tmp/testDirectory';
private $pid;
/**
* Initializes context.
*
* Every scenario gets its own context instance.
* You can also pass arbitrary arguments to the
* context constructor through behat.yml.
*/
public function __construct()
{
}
/**
* #BeforeScenario
*/
public function before(BeforeScenarioScope $scope)
{
// Create testing directory holding our pages
if (!is_dir(self::TESTING_DIR)) {
if (!mkdir(self::TESTING_DIR)) {
throw new \Exception('Cannot create the directory for testing');
}
}
// Start the testing server
$command = sprintf(
'php -S %s -t $%s >/dev/null 2>&1 & echo $!',
escapeshellarg(self::TESTING_BASE_URL),
escapeshellarg(self::TESTING_DIR)
);
$output = [];
exec($command, $output, $return_var);
if ($return_var !== 0) {
throw new \Exception('Cannot start the testing web server');
}
$this->pid = (int)$output[0];
echo sprintf(
'Testing web server started on %s with PID %s %s %s',
self::TESTING_BASE_URL,
(string)$this->pid,
PHP_EOL,
PHP_EOL
);
}
/**
* #AfterScenario
*/
public function after(AfterScenarioScope $scope)
{
// ... kill the web server
$output = [];
exec('kill ' . (string) $this->pid, $return_var);
if ($return_var !== 0) {
throw new \Exception('Could not kill the testing web server (PID ' . (string) $this->pid . ')');
}
echo 'Testing web server killed (PID ', (string) $this->pid, ')', PHP_EOL, PHP_EOL;
// ... remove the test directory
$o = [];
exec('rm -rf ' . escapeshellarg(self::TESTING_DIR), $o, $returnVar);
if ($returnVar !== 0) {
throw new \Exception('Cannot remove the testing directory');
}
}
// ...
}
I also tried various things like putting it all in the constructor, using register_shutdown_function, without any success.
What am I missing? Any idea on how I can solve this?
Instead of just "not caring about killing the server process" (because to me it looks like it's gone when I try to kill the process, hence the error, I can't find it when I issue ps aux | grep php on the command line after running behat), isn't it "cleaner" to kill it as I attend to?
The exec call is missing the output parameter:
exec('kill ' . (string) $this->pid, $output, $return_var);
Unless this is set, the exception will always be thrown, because $return_var is actually the output of the command (which is an array not an integer).
I am experiencing an interesting strange issue with shell_exec...
Description
I develop PHP web app that uses C++ backend app for calculations. Server is running on linux and I use shell_exec for C++ program execution. I updated version of my C++ app and since then shell_exec doesn't work, but
I've checked both versions of C++ app have 777 rights
Both versions run flawlessly from a console
Both versions were tested for same data
Both versions were tested on two different PCs/webservers with same results
For both versions the webapp PHP frontend is exactly the same
Second version (that can't be launched) runs faster then previous one
Questions
Have you ever experienced a similar problem?
Is it possible that in shell_exec a problem could occur, that during standard execution from console doesn't?
Piece of PHP code
class LauncherManager extends Nette\Object {
private $wwwDir;
private $db;
private $f;
public function __construct($wwwDir, \DibiConnection $db) {
$this->wwwDir = $wwwDir;
$this->db = $db;
$this->f = (new Dao\DaoFactory())->setDb($db);
}
public function execMeasurement($measurementId) {
$this->execGenetrac(" -m $measurementId");
}
public function execSamples($analysisId) {
$this->execGenetrac(" -s $analysisId");
}
public function execAnalysis($analysisId) {
$this->execGenetrac(" -a $analysisId");
}
public function execGenetrac($params) {
// Check path to genetrac can be set
$path = $this->wwwDir . "/genetrac";
$this->checkPathExist($path);
// Check library path
$lib = './lib';
$expl = 'export LD_LIBRARY_PATH="' . $lib . '"';
$this->checkPathExist($path . "/" . $lib);
// Check genetrac executable exist
$this->checkPathExist($path . "/genetrac");
// Launch genetrac with parameters
$this->exec("cd $path; $expl; ./genetrac $params");
}
public function exec($command) {
return shell_exec($command);
}
...
shell_exec returns NULL in two situations:
an error occurs
executed program returns no output
In order to distinguish these situations use exec() instead:
public function exec($command) {
exec($command, $arrOutputLines, $intReturnStatus);
return join("", $arrOutputLines);
}
You can debug this code by var_dump'ing $arrOutputLines and $intReturnStatus (these are the array of lines that your program printed out and the numeric exit status of your program (0 usually means OK, non-zero means error)).
I am trying to use Behat for BDD testing. When running a build on Jenkins, I would like Behat to open PHP's build in web server and then close it after running the tests. How to do that?
Basically I need to run:
php -S localhost:8000
In my BDD tests I tried:
/**
* #Given /^I call "([^"]*)" with email and password$/
*/
public function iCallWithPostData($uri)
{
echo exec('php -S localhost:8000');
$client = new Guzzle\Service\Client();
$request = $client->post('http://localhost:8000' . $uri, array(), '{"email":"a","password":"a"}')->send();
$this->response = $request->getBody(true);
}
But then when running Behat it gets stuck without any message.
Just start the server as a part of your build process. Create an ant tasks which would start the server before behat is run and would kill it once behat is finished.
I've been successfully using this approach to start and stop the selenium server.
Solved this myself. I have create two methods. I call the first one before running my BDD tests and the second one after I ran the tests:
private function _startDevelopmentServer($pidfile)
{
$cmd = 'cd ../../public && php -S 127.0.0.1:8027 index.php';
$outputfile = '/dev/null';
shell_exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));
sleep(1);
}
private function _killDevelopmentServer($pidfile)
{
if (file_exists($pidfile)) {
$pids = file($pidfile);
foreach ($pids as $pid) {
shell_exec('kill -9 ' . $pid);
}
unlink($pidfile);
}
}