Welcome to a tutorial on how to run PHP scripts in the background. So you are looking for a way to “silently run” a massive PHP script? The bad news is, PHP is not quite made to run in the background “by default”. But thankfully, there is a simple alternative.
We can use popen()
or exec()
to run a PHP script in the background.
- Windows servers –
pclose(popen("start /B php SCRIPT.PHP", "r"));
- Linux servers –
exec("php SCRIPT.PHP > /dev/null &");
Of course, this is a simplification of what can be done. Let us walk through some actual examples in this guide – Read on!
TABLE OF CONTENTS
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
EXAMPLE CODE DOWNLOAD
Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
BACKGROUND PHP
All right, let us now let into the examples of running PHP scripts in the background.
EXAMPLE 1) SIMPLE BACKGROUND PHP
1A) COMMAND-LINE BACKGROUND SCRIPT
<?php
// (A) COMMAND LINE ONLY
// CREDITS : https://stackoverflow.com/questions/933367/php-how-to-best-determine-if-the-current-invocation-is-from-cli-or-web-server
function is_cli () {
if (php_sapi_name()==="cli") { return true; }
if (defined("STDIN")) { return true; }
if (array_key_exists("SHELL", $_ENV)) { return true; }
if (!array_key_exists("REQUEST_METHOD", $_SERVER)) { return true; }
if (empty($_SERVER["REMOTE_ADDR"]) && !isset($_SERVER["HTTP_USER_AGENT"]) && count($_SERVER["argv"])>0) { return true; }
return false;
}
if (!is_cli()) { exit("Please run this in the command line."); }
// (B) CREATE A DUMMY TEXT FILE
// YOU DO WHATEVER IS REQUIRED IN YOUR PROJECT...
file_put_contents(
__DIR__ . DIRECTORY_SEPARATOR . "dummy.txt",
"Background script ran at " . date("Y-m-d H:i:s")
);
First, create the PHP script that you want to run in the background. For this example:
- To make sure that the script is launched in the command line only.
- Do whatever is required in your project. But for this one, we will just create a
dummy.txt
to indicate “it works”.
1B) RUN THE BACKGROUND SCRIPT
<?php
// (A) SCRIPT TO RUN IN BACKGROUND
$script = __DIR__ . DIRECTORY_SEPARATOR ."1a-background.php";
// (B) RUN
// NOTE: PHP_OS_FAMILY IS AVAILABLE IN PHP 7.2+ ONLY
switch (strtolower(PHP_OS_FAMILY)) {
// (B1) UNSUPPORTED OS
default: echo "Unsupported OS"; break;
// (B2) WINDOWS
case "windows":
pclose(popen("start /B php $script", "r"));
break;
// (B3) LINUX
case "linux":
exec("php $script > /dev/null &");
break;
}
Next, we create a PHP script that will run another PHP script in the background… Yes, simply access 1b-run.php
in the browser or run in the command line. Check the timestamp in the dummy.txt
file.
P.S. Make sure that PHP has permission to run the commands or this will fail.
1C) POTENTIAL PROBLEM & PREVENTION
- Take note that the “background process script” is silent. It will not output any text nor will you see any error messages.
- Impatient people may also spam run or reload (if run from the browser). This spawns multiple instances of the background script and may crash the server.
- So it is best to keep a log file, so you can track and fix errors. Also implement some sort of “lock”, which brings us to the next example – Process control.
EXAMPLE 2) BACKGROUND PHP WITH PROCESS CONTROL
2A) BACKGROUND TASKS DATABASE TABLE
CREATE TABLE `tasks` (
`user_id` bigint(20) NOT NULL,
`process_id` varchar(32) NOT NULL,
`task_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `tasks`
ADD PRIMARY KEY (`user_id`),
ADD KEY `task_date` (`task_date`);
Field | Description |
user_id | Primary and foreign key, the user running the task. |
process_id | System process ID. |
task_date | The time when the task is started. |
The idea of this table is to keep track of the number of background processes.
- We register the system process ID when a user runs a background task; Delete the entry when the task is complete.
- A user can only run one background task at any given time.
- If desired, we can also do
COUNT(*) FROM `tasks`
and set a “global limit” on the number of background tasks.
2B) PHP BACKGROUND TASKS LIBRARY
<?php
class Background {
// (A) CONSTRUCTOR - CONNECT TO DATABASE
private $pdo = null;
private $stmt = null;
public $error = "";
function __construct () {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
}
// (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
function __destruct () {
if ($this->stmt!==null) { $this->stmt = null; }
if ($this->pdo!==null) { $this->pdo = null; }
}
// (C) HELPER FUNCTION - EXECUTE SQL QUERY
function query ($sql, $data=null) : void {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// (D) RUN A SCRIPT IN BACKGROUND - TESTED ON WINDOWS ONLY
function start ($uid, $script) {
// (D1) CHECK IF USER ALREADY HAS A RUNNING PROCESS
$this->query("SELECT * FROM `tasks` WHERE `user_id`=?", [$uid]);
if (is_array($this->stmt->fetch())) {
$this->error = "User already has a running process.";
return false;
}
// (D2) RUN SCRIPT & GET PROCESS ID
// start /B wmic process call create "PATH/PHP.EXE -f PATH/SCRIPT.PHP USER-ID | find ProcessId"
$processID = 0;
$fp = popen(sprintf('start /B wmic process call create "%s -f %s %u | find ProcessId"', PHP_PATH, $script, $uid), "r");
while (!feof($fp)) {
$line = fread($fp, 1024);
if (strpos($line, "ProcessId") !== false) {
preg_match('/\d+/', $line, $processID);
$processID = $processID[0];
}
}
pclose($fp);
// (D3) REGISTER ENTRY & DONE
$this->query("INSERT INTO `tasks` (`user_id`, `process_id`) VALUES (?,?)", [$uid, $processID]);
return true;
}
// (E) BACKGROUND SCRIPT HAS ENDED
function end ($uid) {
$this->query("DELETE FROM `tasks` WHERE `user_id`=?", [$uid]);
return true;
}
// (F) KILL TASK
function kill ($uid) {
// (F1) CHECK IF USER HAS PENDING TASK
$this->query("SELECT * FROM `tasks` WHERE `user_id`=?", [$uid]);
$task = $this->stmt->fetch();
if (!is_array($task)) {
$this->error = "User does not have any pending tasks.";
return false;
}
// (F2) WINDOWS KILL TASK & CLOSE ENTRY
pclose(popen("taskkill /PID ".$task['process_id']." /F", "r"));
$this->end($uid);
return true;
}
}
// (G) SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
define("PHP_PATH", "c:/xampp/php/php.exe");
// (H) NEW BACKGROUND OBJECT
$_BG = new Background();
This looks intimidating at first, but keep calm and look carefully.
- (A, B, H) On creating
$_BG = new Background()
, the constructor automatically connects to the database. The destructor closes the connection. - (C)
query()
A helper function to run an SQL query. - (D to F) There are only 3 “actual functions” in this library.
start()
Use this to run a script in the background. This creates a database entry, lock and prevents the user from running multiple tasks.end()
The background script should call this when the task is complete. Deletes the database entry, and allows the user to start another background task.kill()
Manual intervention. Callstaskkill
to end a script that may have hung.
- (G) Change the database settings and path to
php.exe
to your own.
P.S. This library has only been tested on Windows. Linux/Mac users – You will need to do some of your own research and changes… Too lazy and too much of a hassle to install and set up a Linux VM.
2C) RUNNING & STOPPING BACKGROUND SCRIPTS
<?php
// (A) LOAD LIBRARY
require "2b-lib-background.php";
// (B) TO RUN
echo $_BG->start(999, __DIR__ . DIRECTORY_SEPARATOR . "2d-background.php") ? "RUNNING!" : $_BG->error ;
// (C) TO "FORCE STOP"
// echo $_BG->kill(999) ? "KILLED" : $_BG->error ;
As above:
- We use
$_BG->start(USER ID, SCRIPT)
to run a script in the background. - Use
$_BG->kill(USER ID)
to manually stop the script if something goes wrong.
2D) BACKGROUND SCRIPT
<?php
// (A) COMMAND LINE ONLY
// CREDITS : https://stackoverflow.com/questions/933367/php-how-to-best-determine-if-the-current-invocation-is-from-cli-or-web-server
function is_cli () {
if (php_sapi_name()==="cli") { return true; }
if (defined("STDIN")) { return true; }
if (array_key_exists("SHELL", $_ENV)) { return true; }
if (!array_key_exists("REQUEST_METHOD", $_SERVER)) { return true; }
if (empty($_SERVER["REMOTE_ADDR"]) && !isset($_SERVER["HTTP_USER_AGENT"]) && count($_SERVER["argv"])>0) { return true; }
return false;
}
if (!is_cli()) { exit("Please run this in the command line."); }
// (B) CREATE A DUMMY TEXT FILE
// YOU DO WHATEVER IS REQUIRED IN YOUR PROJECT...
file_put_contents(
__DIR__ . DIRECTORY_SEPARATOR . "dummy.txt",
"Background script ran at " . date("Y-m-d H:i:s")
);
// (C) REMEMBER TO "END" THE TASK IN THE DATABASE
require __DIR__ . DIRECTORY_SEPARATOR . "2b-lib-background.php";
$_BG->end($argv[1]);
Lastly, this is the script that you want to run in the background… It’s the same 1a-background.php
, but with a small addition to deleting the database entry when it is complete.
EXTRAS
That’s all for the tutorial, and here are some small extras that may be useful to you.
THREADING ALTERNATIVES
Of course, the shell script is only one of the many ways to do parallel processing. There are many other packages that you can check out, and here are a few that I find to be pretty interesting:
LINKS & REFERENCES
- popen, pclose, exec – Official PHP Manual
- Error Log – Official PHP Manual
- wmic command – Microsoft
- Simple PHP Daemon – Code Boxx
THE END
Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
Hi, I am grateful for your background api. I have sms api which I would want to embed in your code but I am finding it very difficult. I have placed it on the 1a-background.php file but it’s not working for me. I am performing sql query to get over 3000+ phone numbers from my client in a table and then send them sms. Kindly help.
https://code-boxx.com/faq/#help
https://code-boxx.com/faq/#notwork
Sorry mate, can’t help with personal projects, I ain’t the one getting paid to do work here. 😛
P.S. Debug your script first, manually run in the command line and find out what is “not working”. My gut feeling is probably ran out of memory or a database timeout. Check out how I deal with massive sets here – https://code-boxx.com/bulk-email-php-mysql/