Solved

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

Posted on 2011-09-14
21
279 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
ID: 36539735
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 83

Expert Comment

by:Dave Baldwin
ID: 36539737
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
ID: 36539743
Link is giving me a 404
0
 

Author Comment

by:skione
ID: 36539749
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 83

Expert Comment

by:Dave Baldwin
ID: 36539783
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
ID: 36539823
The firs script is being run from the command line via a cron
0
 
LVL 83

Expert Comment

by:Dave Baldwin
ID: 36539882
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
ID: 36540488
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
ID: 36542579
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
ID: 36543288
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
Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

 
LVL 108

Expert Comment

by:Ray Paseur
ID: 36543332
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
ID: 36543356
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
ID: 36543366
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
ID: 36543399
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
ID: 36544152
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
ID: 36544182
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
ID: 36545236
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
ID: 36545632
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
ID: 36573587
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
ID: 36573740
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
ID: 36579358
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

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Entering a date in Microsoft Access can be tricky. A typo can cause month and day to be shuffled, entering the day only causes an error, as does entering, say, day 31 in June. This article shows how an inputmask supported by code can help the user a…
Whether you've completed a degree in computer sciences or you're a self-taught programmer, writing your first lines of code in the real world is always a challenge. Here are some of the most common pitfalls for new programmers.
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 …
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…

911 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

19 Experts available now in Live!

Get 1:1 Help Now