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 are a few simple alternatives.

To run PHP scripts in the background, we can either use the popen() or exec() function to run another PHP script in the command line.

  • For Windows servers – pclose(popen("start /B php PATH/SCRIPT.PHP", "r"));
  • For Linux servers – exec("php PATH/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!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

QUICK SLIDES

 

TABLE OF CONTENTS

Download & Notes Background PHP Process Control
Useful Bits & Links The End

 

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

EXAMPLE CODE DOWNLOAD

Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

QUICK NOTES

  • If you only want to run a simple background script, stick with 1a-background.php and 1b-run.php.
  • For “advanced task management”, go through all the 2- scripts.

If you spot a bug, please feel free to comment below. I try to answer questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

BACKGROUND PHP SCRIPT

All right, let us now begin with the simple version of “how to run PHP in the background”.

 

1A) CREATE A COMMAND-LINE SCRIPT

1a-background.php
<?php
// (A) COMMAND LINE ONLY!
if (isset($_SERVER["REMOTE_ADDR"]) || isset($_SERVER["HTTP_USER_AGENT"]) || !isset($_SERVER["argv"])) {
  exit("Please run this script from command line");
}
 
// (B) THIS DUMMY SCRIPT WILL CREATE A DUMMY TEXT FILE
// BUT YOU DO WHATEVER IS REQUIRED IN YOUR OWN PROJECT...
file_put_contents("dummy.txt", "Background script ran at " . date("Y-m-d H:i:s"));

This should be self-explanatory, start by creating your “background process script”. This can be anything massive that you want to do – Crunch some numbers, lengthy scans, or generating massive reports. For this simple example, we will just create a dummy.txt file on the server.

 

 

1B) RUN THE BACKGROUND SCRIPT

1b-run.php
<?php
// (A) ABSOLUTE PATH TO BACKGROUND SCRIPT
$script = __DIR__ . DIRECTORY_SEPARATOR ."1a-background.php";

// (B) RUN BACKGROUND SCRIPT
// 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":
    echo "Windows";
    pclose(popen("start /B php $script", "r"));
    break;
 
  // (B3) LINUX
  case "linux":
    echo "Linux";
    exec("php $script > /dev/null &");
    break;
}

Just how do we run 1a-background.php “in the background”? We create a PHP script to run a PHP script in the command line. Yes, simply access 1b-run.php and check if the dummy.txt file is generated on the server.

P.S. Make sure that PHP has permission to run the commands or this will fail.

 

THE POTENTIAL PROBLEM & PREVENTION

This method works, but there is a potential problem when impatient users spam the reload button many times. That will spawn multiple instances of the background script and may crash the server. It is better to put some “checks and locks” in place, a simple trick that I use:

  • Generate a dummy lock.txt when the background processing starts.
  • Delete lock.txt at the end.
  • Do not allow the background script to run if the lock exists – if (file_exists("lock.txt")) { exit("Already Running"); }

 

 

PROCESS CONTROL

The above trick will work, but here’s an addition should you need some “process control”. But take note, this will only work on Windows servers. Linux users, you are going to need some tweaks on your own.

 

2A) BACKGROUND TASKS DATABASE TABLE

2a-database.sql
CREATE TABLE `tasks` (
  `user_id` int(11) NOT NULL,
  `process_id` varchar(24) NOT NULL,
  `task_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

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 (Windows) 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 a COUNT(*) FROM `tasks` and set a “global limit” on the number of background tasks.

 

 

2B) PHP BACKGROUND TASKS LIBRARY

2b-bg-core.php
<?php
class Background {
  // (A) CONSTRUCTOR - CONNECT TO DATABASE
  private $pdo = null;
  private $stmt = null;
  public $error = "";
  function __construct () {
    try {
      $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
        ]
      );
    } catch (Exception $ex) { exit($ex->getMessage()); }
  }

  // (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
  function __destruct () {
    if ($this->stmt!==null) { $this->stmt = null; }
    if ($this->pdo!==null) { $this->pdo = null; }
  }

  // (C) START A BACKGROUND SCRIPT
  function start ($uid, $script) {
    // (C1) CHECK IF USER ALREADY HAS A RUNNING PROCESS
    $this->stmt = $this->pdo->prepare("SELECT * FROM `tasks` WHERE `user_id`=?");
    $this->stmt->execute([$uid]);
    $check = $this->stmt->fetch();
    if (is_array($check)) {
      $this->error = "User already has a running process.";
      return false;
    }
    
    // (C2) RUN SCRIPT IN BACKGROUND
    // start /B wmic process call create "PATH/PHP.EXE -f PATH/SCRIPT.PHP USER-ID | find ProcessId"
    $cmd = sprintf('start /B wmic process call create "%s -f %s %u | find ProcessId"',
      PHP_PATH, $script, $uid
    );
    $fp = popen($cmd, "r");

    // (C3) GET PROCESS ID - WORKS ON WINDOWS 10
    // BUT MAY DIFFER ON OTHER VERSIONS, YOU MAY NEED TO TWEAK THIS PART ACCORDINGLY
    $processID = 0;
    while (!feof($fp)) {
      $line = fread($fp, 1024);
      if (strpos($line, "ProcessId") !== false) {
        preg_match('/\d+/',$line, $processID);
        $processID = $processID[0];
      }
    }
    pclose($fp);

    // (C4) REGISTER ENTRY
    $this->stmt = $this->pdo->prepare("INSERT INTO `tasks` (`user_id`, `process_id`) VALUES (?,?)");
    $this->stmt->execute([$uid, $processID]);

    // (C5) DONE
    return true;
  }

  // (D) BACKGROUND SCRIPT HAS ENDED
  function end ($uid) {
    $this->stmt = $this->pdo->prepare("DELETE FROM `tasks` WHERE `user_id`=?");
    $this->stmt->execute([$uid]);
    return true;
  }
  
  // (E) KILL TASK
  function kill ($uid) {
    // (E1) CHECK IF USER HAS PENDING TASK
    $this->stmt = $this->pdo->prepare("SELECT * FROM `tasks` WHERE `user_id`=?");
    $this->stmt->execute([$uid]);
    $task = $this->stmt->fetch();
    if (!is_array($task)) {
      $this->error = "User does not have any pending tasks.";
      return false;
    }

    // (E2) WINDOWS KILL TASK
    pclose(popen("taskkill /PID ".$task['process_id']." /F", "r"));

    // (E3) CLOSE ENTRY
    $this->end($uid);

    // (E4) DONE
    return true;
  }
}

// (F) SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8");
define("DB_USER", "root");
define("DB_PASSWORD", "");
define("PHP_PATH", "c:/xampp/php/php.exe");

// (G) NEW BACKGROUND OBJECT
$BG = new Background();

This looks intimidating at first, but keep calm and look carefully.

  • On creating a $BG = new Background() object, the constructor will automatically connect to the database; The destructor will close the connection when done.
  • There are only 3 functions in this library!
    • function start() The so-called “step 1”. To run a script in the background, register the user ID and process ID in the database. Also stops the user from running multiple background tasks.
    • function end() Call this when the task is complete, deletes the database entry.
    • function kill() Manual intervention. Calls Windows taskkill to end a script that may have hung.

That’s all, but a quick reminder to change the settings to your own.

 

2C) RUN SCRIPT IN BACKGROUND

2c-run.php
<?php
// (A) DEMO - USER ID 999 WANTS TO EXPORT A DUMMY REPORT
// NOTE: USE ABSOLUTE PATH TO SCRIPT
$uid = 999;
$script = __DIR__ . DIRECTORY_SEPARATOR . "2d-background.php";
 
// (B) RUN!
require "2b-bg-core.php";
echo $BG->start($uid, $script)
  ? "RUNNING!" : $BG->error ;

This works the same as 1b-run.php, with the exception that we now make a database entry to keep track.

 

 

2D) BACKGROUND SCRIPT

2d-background.php
<?php
// (A) COMMAND LINE ONLY!
if (isset($_SERVER["REMOTE_ADDR"]) || isset($_SERVER["HTTP_USER_AGENT"]) || !isset($_SERVER["argv"])) {
  exit("Please run this script from command line");
}

// (B) THIS DUMMY SCRIPT WILL CREATE A DUMMY TEXT FILE
// BUT YOU DO WHATEVER IS REQUIRED IN YOUR OWN PROJECT...
file_put_contents("dummy.txt", "Background script ran at " . date("Y-m-d H:i:s"));

/// FOR TESTING $BG->KILL(), IF YOU WANT THIS SCRIPT TO "HANG"
// sleep(99999);

// (C) REMEMBER TO "END" THE TASK IN THE DATABASE
require __DIR__ . DIRECTORY_SEPARATOR . "2b-bg-core.php";
$BG->end($argv[1]);

This is the same 1a-background.php, with a small addition to delete the database entry when it is complete.

 

2E) KILL TASK SCRIPT

2e-kill.php
<?php
// DEMO - KILL USER ID 999 TASK
require "2b-bg-core.php";
echo $BG->kill(999)
  ? "KILLED" : $BG->error ;

Bad things do happen… Here is how to clean things up, or to cancel a task halfway.

 

USEFUL BITS & LINKS

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

 

INFOGRAPHIC CHEAT SHEET

Run PHP Scripts In The Background (Click To Enlarge)

 

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.

Leave a Comment

Your email address will not be published. Required fields are marked *