How to Run PHP Scripts In The Background (Simple Examples)

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

Source code on GitHub Gist

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

1a-background.php
<?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:

  1. To make sure that the script is launched in the command line only.
  2. 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

1b-run.php
<?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

2a-database.sql
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

2b-lib-background.php
<?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. Calls taskkill 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

2c-run-kill.php
<?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

2d-background.php
<?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

 

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!

2 thoughts on “How to Run PHP Scripts In The Background (Simple Examples)”

  1. 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.

Comments are closed.