Solved

the best way to EXECUTE a PHP script from another PHP script

Posted on 2011-09-14
21
278 Views
Last Modified: 2012-05-12
I want to be able to execute a PHP script, send it to the background and then exit the script cleaning.

I do not want to "include" the file for other reasons.
0
Comment
Question by:skione
  • 11
  • 4
  • 3
  • +1
21 Comments
 

Author Comment

by:skione
Comment Utility
I am trying something like this:

$pid = pcntl_fork();
                        if($pid) {
                          // parent process runs what is here
                              exec('/usr/bin/php ' . $svcValue['service_path'].'&> /dev/null');
                        } else {
                              $log->error("[".$dbuser."] Unable to spawn processes");
                        }
0
 
LVL 82

Expert Comment

by:Dave Baldwin
Comment Utility
There is no 'background' in PHP as such.  However, if you access another script with a URL like "http://www.mysite.com/thisphp.php", it will execute the PHP page instead of loading it into the current script.  The only catch to that is that all the functions I can think of expect a value to be returned and you don't want to wait for that.
0
 

Author Comment

by:skione
Comment Utility
Link is giving me a 404
0
 

Author Comment

by:skione
Comment Utility
NVM, sorry I didn't see what you were writing at first. I believe you exec a PHP script just like you would from a command line. I don't want to open an HTTP connect as I want to execute the script from CLI
0
 
LVL 82

Expert Comment

by:Dave Baldwin
Comment Utility
I think you're right.  Is the first script being run from the command line or from the web server?  I ask because there are two different sets of permissions there.  Also, read the notes here: http://us3.php.net/manual/en/function.exec.php
0
 

Author Comment

by:skione
Comment Utility
The firs script is being run from the command line via a cron
0
 
LVL 82

Expert Comment

by:Dave Baldwin
Comment Utility
I'd try it then.  I don't know if there are any permission issues associated with using cron.
0
 
LVL 108

Expert Comment

by:Ray Paseur
Comment Utility
Can you please step back from the technical details and just tell us in plain language what you want these scripts to do?  Once we know that, we may have a good design pattern for you.  One possible pattern would be to use CURL POST or FSockOpen to start an asynchronous script.  But we really need to know the business purpose of the script to offer the best suggestions.  Thanks, ~Ray
0
 
LVL 26

Expert Comment

by:skullnobrains
Comment Utility
fork + exec is not in the php way (at least exec is not)
you probably can manage to use fork + include but it is likeley to produse a mess with your existing file descriptors
fork+system('php your_script') should work but i would not recommend it for similar reasons

the way to go, is popen('php your_script 2>&1','r') or something similar, but you'll need to read the output
you may of course redirect the output to a file or /dev/null so your callee runs by itself
in this case, just poll the resource popen gave you to determine when your callee script finishes

if you need more complex IO handling, you may also use proc_open() which will let you map STDIN, STDOUT, and STDERR, and possibly any FD to files or resources in the caller script

beware these functions should not be used in many web server contexts, and may be forbidden for security reasons.
on a webserver, you can also do things like system('nohup php your_script 2>/var/log/script.stderr >/var/log/script.stdout &');
0
 

Author Comment

by:skione
Comment Utility
This is closer to what I need skullnobrains (nice handle).

The script has no output and has a nice logging class, can you provide me with the exact syntax that will execute the script as if it were being executed at the command line and then send all output to /dev/null?

PS Its my pizza box so I can set PHP however i need and exec() and the like is not disabled.

The script that is called is smart enough to do the right thing. I just need the main script to just launch all the other processes and then finish.

Actually what I would like to do is have the script wait for all the processes to finish and then move on. So in pseudo code I would like it to do this:

1. start script
2. loop through processes
3. exec(processes) in the back ground
4. wait for all processes to finish
5. update time stamps

I only need 3 & 4, if 4 is not possible then just 3

thanks
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 108

Expert Comment

by:Ray Paseur
Comment Utility
Just a thought... If your main script is going to wait for all the background processes to finish, why not just run them inline?
0
 

Author Comment

by:skione
Comment Utility
because I want all the scripts to run at the same time (in parallel) otherwise the script would take 6 times longer to finish. It gets called every minute so I need to make sure it finishes within the 60 second time frame.
0
 
LVL 108

Expert Comment

by:Ray Paseur
Comment Utility
OK, I understand.  I would probably use CURL POST or FSockopen to start the scripts.  But there are lots of ways to skin a cat...
0
 

Author Comment

by:skione
Comment Utility
CURL post would not execute the script via CLI but FCGI and that won't work but basically using passthru or exec or whatnot is esentially the same thing. I think skullnobrains has the syntax. I understand what he is saying but I am in a firedrill and don't have the time to figure out the syntax exactly and test. That is why I was looking for help
0
 
LVL 26

Accepted Solution

by:
skullnobrains earned 500 total points
Comment Utility
i have no time for a full php script today, but i may tomorrow

something like

$torun=array('cmd1','cmd2',...);
$running=array();
$num_running=0;
$max_running=6;

while(true){
  // launch as many processes as allowed
  while($num_running<=$max_running){
    // start a new one with redirection : $res=popen($cmd.' 2>&1 >/dev/null','r');
}
  $r=$running;
  select($r,null,null,1) or die ();
  foreach($r as $fd)
   if(feof($fd)); // remove from the running array and decrement the number of running processes
}


--------

but if your script does not do anything more, you should just use a simple xargs command
something like

cat LIST_OF_COMMANDS_TO_RUN | xargs -I % -p 5 sh %
0
 

Author Comment

by:skione
Comment Utility
The script after launching each child script updates a timestamp that the child scripts use when they launch next.

I do not understand your while(true) part, can you explain that?

I doubt you need to write a full script at this point I think I am just about there.
0
 

Author Comment

by:skione
Comment Utility
Here is the code snippet, that I believe illustrates what I am doing. Thanks for you help:
            $services                         = $Service->GetServices();

            if (count($services) > 0) {            
                  foreach ($services as $svcKey => $svcValue) {
                        $currTime = $Service->current_time();
                        $log->trace( "[" . $dbuser . "] PROCESSING ".$svcValue['service_path']);
                        $handle[] = popen('/usr/bin/php ' . $svcValue['service_path']."> /dev/null 2>/dev/null &","r");
                        $log->trace( "[" . $dbuser . "] COMPLETED " .$svcValue['service_path'] );
                        $svcUpdate['id'] = $svcValue['service_id'];
                        $svcUpdate['id']['service_freq'] = $svcValue['service_freq'];
                        $svcUpdate['id']['next_run'] = $svcValue['service_next_run'];
                        $svcUpdate['id']['currTime'] = $currTime;
                  }      
                  
                  while (true) {
                        $end = true;
                        foreach ($handle as $h) {
                              if (!feof($handle)) {
                                    $end = false;
                              }
                        }
                        if ($end) {
                              break;
                        }
                  }
                  
                  foreach ($svcUpdate as $s) {
                        if (isset($s['next_run'])) {
                              $Service->UpdateNextRunTime($s['service_id'], $s['service_freq'],$s['next_run'], $s['currTime']);
                        }
                  }
            }      
0
 

Author Comment

by:skione
Comment Utility
OK so the problem I am running into is that the test feof() is never reporting that the file has ended even though it has. I think its because I am redirecting so it never knows when the file is over
0
 
LVL 26

Expert Comment

by:skullnobrains
Comment Utility
the while true part was intended to build a script that will run many queries but only a specific number of parallel ones
it spawns up to max_running processes when iterating the first time, and then only spawns new ones when old ones died after that

----

if you do not select or read the file handles, feof will almost never be set properly

performing a select over the file handles prevents your master script from using up the CPU and forces the script to set feof properly
an other way to force feof detection is to use fseek(0,SEEK_CUR) which moves the pointer from a location to the same one

using select will probably solve both the feof issue and the CPU one
if you want to keep your script, you need a sleep to prevent eating up CPU and use any of the above methods to make feof work properly

in this case, you can forget about feof and use a simple fread. just ensure you set the stream timeout to zero and properly detect that fread returns false and not an empty string as will be returned while the script is running. this method is about the same as performing fseek+feof but you need to make sure the timeout works as expected which may not be the case on some OSes

----

if it still does not work as expected :
if you redirect stdout, you cannot detect feof on stdout on some (rare) OSes

many hacks can prevent this behaviour
- you can use a dummy hack like "CMD 2>&1 >/dev/null ; echo ended ;" so you can read the "ended" string
- you can use a better hack by explicitely running your command through sh -c "CMD" and let sh close the file descriptors cleanly
- you can use the parent process as a logger to make feof available
- you can open the script in 'w' mode and perform a select on the fd so the parent process will detect when the STDIN of the script is closed
... but this is quite an ugly hack and it will not work the same on all platforms if at all
- you can switch to proc_open() and use proc_get_status() to determine which process died (and the return codes and status) easily

---

happy coding
0
 

Author Comment

by:skione
Comment Utility
Actually I ended up using a Pear package called System_Daemon.

Here are 2 useful links:
http://kevin.vanzonneveld.net/techblog/article/create_daemons_in_php/
http://pear.php.net/package/System_Daemon

I have to say, while I didn't use your exact solution, it did help me to get the right search semantics that lead me to the actual solution.

For anyone who wants to do what I was trying to do, right a daemon in PHP you should use this package. It is simple, stable and just freaking works! I have pounded on it with many MANY tests and I have to say it has stayed up. It is not perfect but neither is trying to write a daemon in php but it takes much of the pain out of things.

I still need to get my head wrap around using process forking in PHP as I will further want to refine my daemons so that I can spawn child processes that handle less important tasks.

For instance, the group of Daemons are largely centered around messaging functions. So one though would be the send message in one process but I also need to update the status of the message so I could spawn that into a child process. This way the messages could be sent very quickly and then the updating of the status of those messages could happen slower. Now everything is linear and that limits my speed to about 3-4 messages per second. I eventually want to get it to about 10-20. If I can remove all transactions from the main thread except sending of the message it should be possible.

Again, thank you very much for your help. It still astounds me what people will do for points (which I awarded you of course). In addition you have my gratitude plus the knowledge you help me learn some stuff.

I have achieved my goals for this effort and need to move on but once I am reading to further refine my scripts I will post a new question and post a response here with the url so hopefully you can help me there as well!
0
 
LVL 26

Expert Comment

by:skullnobrains
Comment Utility
thanks for the information about the package which i did not know about
maybe this lets you understand that many people are not here for points
as a matter of fact, i just want to have enough of them to be able to view other threads

since you are interested in forking
don't be afraid, daemonising and forking is perfectly supported in php and works fine
i had production servers running php powered forking deamons for years without trouble

you can demonize easily using the old doublefork method or more cleanly by forking+closing all fds + using setsid

forking is a little trickier :
most php libs are not fork-happy so you should close any fds other than stdin, stdout, and stderr just to make sure
or more likely in real life, do not open these connections before you fork
ex: mysql connections will mess up, ldap connections will not, but may behave weird performace-wise
obviously master tcp sockets can be used in forked processes so writing a forking daemon will work fine
basically anything that claims to be thread-safe will work, the rest should not be used before the forking is performed

you probably also will stumble on the select limit
in C you cannot select more than a number (usually 1024) fds at once
in php you cannot use select with a resource number greater than that number
so you need to make sure your master process does not use up fds over time if you need to use select

happy coding
0

Featured Post

Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

Join & Write a Comment

Suggested Solutions

Whether you’re a college noob or a soon-to-be pro, these tips are sure to help you in your journey to becoming a programming ninja and stand out from the crowd.
Password hashing is better than message digests or encryption, and you should be using it instead of message digests or encryption.  Find out why and how in this article, which supplements the original article on PHP Client Registration, Login, Logo…
An introduction to basic programming syntax in Java by creating a simple program. Viewers can follow the tutorial as they create their first class in Java. Definitions and explanations about each element are given to help prepare viewers for future …
In this fifth video of the Xpdf series, we discuss and demonstrate the PDFdetach utility, which is able to list and, more importantly, extract attachments that are embedded in PDF files. It does this via a command line interface, making it suitable …

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now