Call Symfony console command within another command and suppress output - php

I have a simple console app using the Symfony console component.
I have two commands (say, cmdOne and cmdTwo) which can be called independently easily.
$ myApp.php cmdOne
$ myApp.php cmdTwo
Both commands have a considerable amount of output, which I can easily mute by issuing the -q option.
Now, I'd like cmdOne to call cmdTwo however I'd like cmdTwo to be quiet. I'm not trying to do anything crazy, but I'm struggling to get anywhere, despite reading through the docs.
Here's my sample code so far (this snippet would be contained within cmdOne->execute()):
$command = $this->getApplication()->find('cmdTwo');
$input = new ArrayInput(array(
'command' => 'cmdTwo',
'-q' => true
));
$returnCode = $command->run($input, $output);
This runs fine, as in the code command executes, but there's output on the console (generated by cmdTwo) which I'd like to not show.
Is specifying the -q option not possible because it's "reserved" (i.e not created by the dev), or am I missing something obvious?

Instead of passing the same $output instance (the one that outputs to your current console) create an instance of NullOutput
$returnCode = $command->run($input, new \Symfony\Component\Console\Output\NullOutput);
It basically is a blackhole - it accepts output and silently drops it.

Related

Better way to run python & display python errors from PHP

I am running a webserver for my laboratory that basically has a bunch of scripts I wrote in python for processing and analyzing tabular data.
I have a DigitalOcean droplet with a Laravel application deployed on it. When I want to run a script, I have the user upload some data file, and then from the PHP controller run:
shell_exec(python my_script.py arg1 arg2 etc);
The problem is, there are differences in dependencies and libraries between my development environment and 'production' environment. As such, when I try to run the script from the webserver and there is a python error, the object returned by shell_exec is just null. When the PHP blade template tries to parse/get data from this object, I get an error like so:
In this case, 'matchCount' is just a variable stored within a python list like this:
#Label peptides we found experimentally but do not have an in silico match for... as to predict contaminants
output = {
'sequence': protSeq,
'peptides': pepList,
'observablePeptideCount': str(len(pepList)),
'possibleObserved': possibleObserved,
'matchCount': matchCount,
'coverage': matchSumAA/protSeqAALength*100,
'massList': massList,
'tolerances': tolerances,
}
output = json.dumps(output)
The problem is, I understand the python script failed somewhere, but the error log does not give any indication of why or where. Is there some way I can have the webpage output the python error so I can correct it in the production environment?
Is there a better way to be doing all of this? Thank you for any help.
I won't recommend you print things out in production, but if it's a last resort you can try this:
<?php
$output = '';
$result = '';
exec('python my_script.py arg1 arg2 etc 2>&1', $output, $result);
var_dump($output); // all the output is here
var_dump($result); // gives an exit code, might be 1 if it's error

How can I get MonoLog's ErrorHandler to output to stdout?

I am a bit confused about using MongoLog to send logs to stdout. I am using this code:
# console/proxy-server.php
require_once '../vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\ErrorLogHandler;
// This is for test - it *definitely* goes to stdout!
echo "Hello";
// Here is the logger
$logger = new Logger('stdout');
$logger->pushHandler(new ErrorLogHandler());
$logger->info('Logging');
The log line does indeed output to the console where I run the program, but it seems it is not going to a real stdout. However, the "Hello" from the echo works fine.
I am launching it from a shell script thus:
#!/bin/sh
# Write to both stdout and a log file
php console/proxy-server.php | tee --append proxy.log
If I use tail -f proxy.log in another console then the echo message pops in real-time, but the logs I am interested in do not (even if I exit the script cleanly, though I would expect them to append in real-time too). However, both sets of outputs are output to the console in which I have run my script.
This implementation approach was sourced from this blog article.
Update
In the blog article comments there is an alternative solution using StreamHandler, which is thus. It's the same as above except for a changed pushHandler() line:
use Monolog\Handler\StreamHandler;
...
$logger->pushHandler(new StreamHandler("php://stdout"));
This is recorded by tee, so it looks like some stdouts are more equal than others! I wonder if there are effectively many stdouts, and tee only records the "real" one? What is the difference between the two approaches, in terms of shell pipes?
I haven't tested this, as my alternative solution works fine with tee. However, of late I suspect that ErrorLogHandler in fact sends output to stderr, not stdout, which would be why tee does not render it.
If anyone else bumps into this, I should be interested in their observations. In some ways it's an edge-case, since in most cases error output is rendered to the console just in the same way as non-error output, and one cannot normally tell the difference.

Open Linux terminal command in PHP

I have a server running on Linux that execute commands to 12 nodes (12 computers with Linux running in them). I recently downloaded PHP on the server to create web pages that can execute commands by opening a specific PHP file.
I used exec(), passthru(), shell_​exec(), and system(). system() is the only one that returns a part of my code. I would like PHP to act like open termainal command in linux and I cannot figure out how to do it!
Here is an example of what is happening now (Linux directly vs PHP):
When using linux open terminal command directly:
user#wizard:/home/hyperwall/Desktop> /usr/local/bin/chbg -mt
I get an output:
The following settings will be used:
option = mtsu COLOR = IMAGE = imagehereyouknow!
NODES = LOCAL
and additional code to send it to 12 nodes.
Now with PHP:
switch($_REQUEST['do'])
{ case 'test':
echo system('/usr/local/bin/chbg -mt');
break;
}
Output:
The following settings will be used:
option = mtsu COLOR = IMAGE = imagehereyouknow!
NODES = LOCAL
And stops! Anyone has an explanation of what is happening? And how to fix it? Only system displays part of the code the other functions display nothing!
My First thought is it can be something about std and output error. Some softwares dump some informations on std out and some in std error. When you are not redirecting std error to std out, most of the system calls only returns the stdout part. It sounds thats why you see the whole output in terminal and can't in the system calls.
So try with
/usr/local/bin/chbg -mt 2>&1
Edit:
Also for a temporary work through, you can try some other things. For example redirect the output to file next to the script and read its contents after executing the command, This way you can use the exec:
exec("usr/local/bin/chbg -mt 2>&1 > chbg_out");
//Then start reading chbg_out and see is it work
Edit2
Also it does not make sense why others not working for you.
For example this piece of code written in c, dumps a string in stderr and there is other in stdout.
#include <stdio.h>
#include<stdlib.h>
int main()
{
fputs("\nerr\nrro\nrrr\n",stderr);
fputs("\nou\nuu\nuttt\n",stdout);
return 0;
}
and this php script, tries to run that via exec:
<?php
exec("/tmp/ctest",&$result);
foreach ( $result as $v )
{
echo $v;
}
#output ouuuuttt
?>
See it still dumps out the stdout. But it did not receive the stderr.
Now consider this:
<?php
exec("/tmp/ctest 2>&1",&$result);
foreach ( $result as $v )
{
echo $v;
}
//output: errrrorrrouuuuttt
?>
See, this time we got the whole outputs.
This time the system:
<?php
echo system("/tmp/ctest 2>&1");
//output: err rro rrr ou uu uttt uttt
?>
and so on ...
Maybe your chbg -mt writes additional code to stderr instead of stdout? Try to execute your script inside php like this:
/usr/local/bin/chbg -mt 2>&1
The other responses are good for generic advice. But in this specific case, it appears you are trying to change your background on your desktop. This requires many special considerations because of 'user context':
First, your web server is probably running as a different user, and therefore would not have permissions to change your desktop.
Second, the program probably requires some environmental variables from your user context. For example, X programs need a DISPLAY variable, ssh-agent needs SSH_AGENT_PID and SSH_AUTH_SOCK, etc. I don't know much about changing backgrounds, but I'm guessing it involves D-Bus, which probably requires things like DBUS_SESSION_BUS_ADDRESS, KONSOLE_DBUS_SERVICE, KONSOLE_DBUS_SESSION, and KONSOLE_DBUS_WINDOW. There may be many others. Note that some of these vars change every time you log in, so you can't hard-code them on the PHP side.
For testing, it might be simpler to start your own webserver right from your user session. (i.e. Don't use the system one, it has to run as you. You will need to run it on an alternate port, like 8080). The web server you start manually will have all the 'context' it needs. I'll mention websocketd because it just came out and looks neat.
For "production", you may need to run a daemon in your user context all the time, and have the web server talk to that daemon to 'get stuff done' inside your user context.
PHP's system only returns the last line of execution:
Return Value: Returns the last line of the command output on success, and FALSE on failure.
You will most likely want to use either exec or passthru. exec has an optional parameter to put the output into an array. You could implode the output and use that to echo it.
switch($_REQUEST['do'])
{ case 'test':
exec('/usr/local/bin/chbg -mt', $output);
echo implode('\n', $output); // Could use <br /> if HTML output is desired
break;
}
I think that the result of execution, can changes between users.
First, try to run your PHP script directly into your terminal php yourScript.php
If it runs as expected, go to your Apache service and update it to run with your own credentials
You are trying to change the backgrounds for currently logged in users... While they are using the desktop. Like while I'm typing this message. I minimize my browser and 'ooh my desktop background is different'. Hopefully this is for something important like it turns red when the reactor or overheating.
Anyway to my answer:
Instead of trying to remotely connect and run items as the individual users. Setup each user to run a bash script (in their own account, in their own shell) on a repeating timer. Say every 10 minutes. Have it select the SAME file.. from a network location
/somenetworkshare/backgrounds/images/current.png
Then you can update ALL nodes (1 to a million) just by changing the image itself in /somenetworkshare/backgrounds/images/current.png
I wrote something a while ago that does just this -- you can run a command interpreter (/bin/sh), send it commands, read back responses, send more commands, etc. It uses proc_open() to open a child process and talk to it.
It's at http://github.com/andrasq/quicklib, Quick/Proc/Process.php
Using it would look something like (easier if you have a flexible autoloader; I wrote one of those too in Quicklib):
include 'lib/Quick/Proc/Exception.php';
include 'lib/Quick/Proc/Exists.php';
include 'lib/Quick/Proc/Process.php';
$proc = new Quick_Proc_Process("/bin/sh");
$proc->putInput("pwd\n");
$lines = $proc->getOutputLines($nlines = 10, $timeoutSec = 0.2);
echo $lines[0];
$proc->putInput("date\n");
$lines = $proc->getOutputLines(1, 0.2);
echo $lines[0];
Outputs
/home/andras/quicklib
Sat Feb 21 01:50:39 EST 2015
The unit of communication between php and the process is newline terminated lines. All commands must be newline terminated, and all responses are retrieved in units of lines. Don't forget the newlines, they're hard to identify afterward.
I am working on a project that uses Terminal A on machine A to output to Terminal B on Machine B, both using linux for now. I didnt see it mentioned, but perhaps you can use redirection, something like this in your webserver:
switch($_REQUEST['do'])
{ case 'test':
#process ID on the target (12345, 12346 etc)
echo system('/usr/local/bin/chbg -mt > /proc/<processID>/fd/1');
#OR
#device file on the target (pts/0,tty0, etc)
echo system('/usr/local/bin/chbg -mt > /dev/<TTY-TYPE>/<TTYNUM>');
break;
}
Definitely the permissions need to be set correctly for this to work. The command "mesg y" in a terminal may also assist...Hope that helps.

How to run a linux command to compile a c program, from php script

I am trying to make an online judge for c programming. When user enters the c code and submits it, my form redirects to judge.php which is the action file for the form.
Here is what I have written in judge.php
<?php
$text=$_POST['code'];
//echo $text;
$var_str = var_export($text, true);
file_put_contents('code.c', $text);
$ans=exec('pwd');
$ans= exec('gcc code.c');
echo $ans;
?>
I have captured user input in $text and wrote it to a c file(code.c). Till now, it is fine.
But exec(gcc code.c) is not working and not giving any output. I tried other linux commnads like pwd, date, etc. They are working fine. What may be the reason for this and how to fix it?
It is not a directory issue i tried exec(pwd) and it gave the output as the same directory in which code is present.
I tried to run same code.c file from terminal and it is running fine. So, it is also not a 'permission' problem.
One more thing, how to echo the error message generated if any exec() command is not working properly?
After getting suggestion from the answer below, i tried
$cmd="gcc -std=c99 code.c -g -Wall mysql_config --libs --cflags -o db_obj.o --pedantic";
exec($cmd,$out,$status);
But it is also not working. The status returned is 1
Most probably it is permission issue. "whoami" says nobody. Please tell how to change the owner from nobody to root or how to assign the permission to execute gcc from nobody
Three main aspects to my answer
improper use of the exec function.
Look at the man pages. First, the exec function's signature is:
string exec ( string $command [, array &$output [, int &$return_var ]] )
So exec can take up to 3 arguments. It returns the last line of the command's output, like the docs state quite clearly:
The last line from the result of the command. If you need to execute a command and have all the data from the command passed directly back without any interference, use the passthru() function.
To get the output of the executed command, be sure to set and use the output parameter.
So in your case:
$lastLine = exec($command, $fullOutput, $status);
Is what you're looking for. If $status is anything else than 0, your command was unsuccessful. That's what you should check to react accordingly.
The full output of any command can be found in $fullOutput as a line-per-line array.
Output like:
all went well
except for this
Will look like this in the $fullOutput array:
array('all went well', 'except for this');
permissions can be an issue, still.
You say permissions aren't likely to be the cause of the problem, because you can run gcc from the command-line. All fine and dandy, but what user is running the PHP script on the server?
In the case of web-servers, that user is often called nobody, apache or something, and that user is very likely not permitted to run gcc. It's PHP that runs a new instance of whatever default shell it has set up (bash, probably), and it's PHP's user that logs in to that shell, and it's that user that is calling gcc...
Know who you are, and what groups you belong to. Try adding this to your script:
echo 'Script is running under user: ', exec('whoami'), '<br>', PHP_EOL;
echo 'member of the following groups: ', exec('groups'), '<br>', PHP_EOL;
And before you ask: yes, those are comma's... no need to concatenate, you can pass multiple variables/values to echo, separated by a comma. It's actually faster (think of it as C++'s std::cout << some_var << another_var;)
general issues + security
This all said and done: compiling C code from a php script isn't as simple as you seem to think it is. Suppose I were to write this:
#include <stdio.h>
#include <time.h>
int main ( void )
{
time_t t = time(NULL);
if (t%2)
{
float val = (float) t/2.0;
//do stuff with float...
}
else
{
unsigned long long val = t/2;
//do stuff with unsigned long long...
}
}
Your gcc test.c command would fail, because you failed to pass the argument -std=c99, for example.
If I wanted a script to compile a given file, I'd also expect that script to allow me to choose which arguments I compiled my code with, too -g, -Wall and, not to mention: cflags and libs (the output of pkg-config or mysql_config --cflags --libs, to name a specific example I recently used).
Basically, your script simply cannot deal with my wanting to compile something with a commind like
gcc -std=c99 code.c -g -Wall `mysql_config --libs --cflags` -o db_obj.o --pedantic
Which still is a simplified version of what many compilation commands look like, especially when debugging code under development. For stable releases, you'd probably drop -g and --pedantic, but you get my point...
Just think of what it means, allowing the user to pass a set of cli arguments, along with the code. They might pass an argument like -DSOME_MACRO or -O0, which means they might also pass -O0 && rm -Rf *. That means you'll have to call escapeshellcmd or escapeshellarg. Both of which will prohibit me from passing a valid argument, being:
`mysql_config --libs --cflags`
Which contains back-ticks, and thus will be escaped.
To be frank, I struggle to see the point of this exercise... and I'm leaving a lot out, still: the dangers of compiling (let alone running) user-provided code on your machine, for example, are not to be overlooked. You can't just compile code, and run it on your server: memory leaks, segfaults... heck, pure evil code is all getting compiled on your server unchecked if this is the code you have. Really, save yourself a lot of tears, and include an iframe that loads codepad or some similar service...
Recap:
always check the man for a function, see if you're getting all information it returns
check the permissions and runtime for the user that is actually executing the commands
Never trust the network, don't blindly assume people will submit valid, harmless code for you to compile.
Don't reinvent the wheel: compilation services exist, just forward those (but ask for permission first)
Try this code to execute c program from PHP file
<?php
// used to compile the c file using exec() in php
exec('gcc helloworld.c -o helloworld', $out, $status);
if (0 === $status) {
var_dump($out);
// used to execute the c file using exec() in php
exec('./helloworld', $out, $status);
if (0 === $status) {
var_dump($out);
} else {
echo "Command failed with status: $status";
}
} else {
echo "Command failed with status: $status";
}
?>

a "blank" command for symfony2 console

I've created a little command line tool to help me launch sites. It uses Symfony2 console to help create the commands and add some structure. What I'm trying to figure out is if there is a way, I can crate a "blank" or a default command so if you don't put in a command it just defaults to this. An example might help explain:
A "normal" console command would look like this:
php launch site foo
I want to make this do the exact same thing as above:
php launch foo
The only thing I can think of is to sort-of short circuit the application->run process and check if "foo" is in my own command list, if it's not then force the console to run site foo.
The crappy thing about that is if you just typo'ed a different command, the system would just try and run as a site and instead of an error message you'd get an error saying it can't launch that site (which is an error, but the wrong error and not a helpful one to a user).
Maybe I missed something in the console docs, but is there a way to do what I'm trying here?
So what I ended up doing was just attempt my own match, if I can find the command, then I run the application as normal, if not, I try to launch the site:
if (!array_key_exists($argv[1], $list))
{
$cmd = $this->application->find('launch');
$args = array(
'command' => 'launch',
'alias' => $argv[1]
);
$input = new ArrayInput($args);
$output = new ConsoleOutput();
$cmd->run($input, $output);
}
else
{
$this->application->run();
}
It works fine, it just feels a little meh, I'm open to other suggestions if anyone has them.

Categories