php exécuter un processus d'arrière-plan


259

J'ai besoin d'exécuter une copie du répertoire sur une action de l'utilisateur, mais les répertoires sont assez volumineux, donc je voudrais pouvoir effectuer une telle action sans que l'utilisateur ne soit conscient du temps qu'il faut pour que la copie se termine.

Toute suggestion serait très appréciée.


3
Ce stackoverflow.com/questions/5367261/… explique comment faire cela sous Windows
shealtiel

Réponses:


365

En supposant que cela fonctionne sur une machine Linux, je l'ai toujours géré comme ceci:

exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));

Cela lance la commande $cmd, redirige la sortie de la commande vers $outputfileet écrit l'ID du processus $pidfile.

Cela vous permet de surveiller facilement ce que fait le processus et s'il est toujours en cours d'exécution.

function isRunning($pid){
    try{
        $result = shell_exec(sprintf("ps %d", $pid));
        if( count(preg_split("/\n/", $result)) > 2){
            return true;
        }
    }catch(Exception $e){}

    return false;
}

2
L'installation sudo doit-elle s'exécuter sans demander de mot de passe? Toutes les commandes qui nécessitent une entrée utilisateur ne fonctionneront pas.
Mark Biek

17
Ce stackoverflow.com/questions/5367261/… explique comment faire la même chose sous Windows
shealtiel

13
Je l'ai utilisé, mais je l'ai légèrement mis à jour pour ne pas avoir à écrire le PID dans un fichier. J'utilise donc ce format: <code> exec (sprintf ("$ s> $ s 2> & 1 & echo $ 1", $ cmd, $ outputfile), $ pidArr); </code> Le processus PID résultant est dans $ pidArr [0]
Kaiesh

3
@Kaiesh: ce devrait être "echo $!"
brunobg

8
Kaiesh a fait une faute de frappe, '$' au lieu de '%'. Version corrigée: exec (sprintf ("% s>% s 2> & 1 & echo $!", $ Cmd, $ outputfile), $ pidArr)
Yuri Gor

23

Écrivez le processus en tant que script côté serveur dans n'importe quelle langue (php / bash / perl / etc), puis appelez-le à partir des fonctions de contrôle de processus de votre script php.

La fonction détecte probablement si la norme io est utilisée comme flux de sortie et si c'est le cas, cela définira la valeur de retour ... sinon elle se termine

proc_close( proc_open( "./command --foo=1 &", array(), $foo ) );

J'ai testé cela rapidement à partir de la ligne de commande en utilisant "sleep 25s" comme commande et cela a fonctionné comme un charme.

( Réponse trouvée ici )


3
C'est la seule façon de le faire fonctionner sur les centos. Je vous remercie!
Deac Karns

Merci ! Il y a une incompatibilité de PHP et BASH. Si vous exécutez un système plus récent, il ne pourra pas être lancé en arrière-plan à l'aide de system () ou exec (). Les descripteurs de fichiers semblent rester bloqués même si vous redirigez. Votre méthode a fonctionné. une autre alternative consiste à utiliser un ancien binaire bash pour PHP, mais c'est fou
John

22

Vous voudrez peut-être essayer de l'ajouter à votre commande

>/dev/null 2>/dev/null &

par exemple.

shell_exec('service named reload >/dev/null 2>/dev/null &');

Je voulais dire> / dev / null 2> / dev / null &
wlf

Cela fonctionne pour moi, et j'utiliserai le makefile pour faire quelque chose sur le site du serveur, merci! (ex. make, make restart)
Chu-Siang Lai

C'est beaucoup plus simple que la réponse acceptée! (Tant que vous n'avez besoin de rien de plus complexe, comme vérifier si le processus est toujours en cours d'exécution.)
Sean the Bean

18

Je voudrais juste ajouter un exemple très simple pour tester cette fonctionnalité sur Windows:

Créez les deux fichiers suivants et enregistrez-les dans un répertoire Web:

foreground.php:

<?php

ini_set("display_errors",1);
error_reporting(E_ALL);

echo "<pre>loading page</pre>";

function run_background_process()
{
    file_put_contents("testprocesses.php","foreground start time = " . time() . "\n");
    echo "<pre>  foreground start time = " . time() . "</pre>";

    // output from the command must be redirected to a file or another output stream 
    // http://ca.php.net/manual/en/function.exec.php

    exec("php background.php > testoutput.php 2>&1 & echo $!", $output);

    echo "<pre>  foreground end time = " . time() . "</pre>";
    file_put_contents("testprocesses.php","foreground end time = " . time() . "\n", FILE_APPEND);
    return $output;
}

echo "<pre>calling run_background_process</pre>";

$output = run_background_process();

echo "<pre>output = "; print_r($output); echo "</pre>";
echo "<pre>end of page</pre>";
?>

background.php:

<?
file_put_contents("testprocesses.php","background start time = " . time() . "\n", FILE_APPEND);
sleep(10);
file_put_contents("testprocesses.php","background end time = " . time() . "\n", FILE_APPEND);
?>

Accordez à IUSR l'autorisation d'écrire dans le répertoire dans lequel vous avez créé les fichiers ci-dessus

Accordez à IUSR l'autorisation de LIRE et EXÉCUTER C: \ Windows \ System32 \ cmd.exe

Cliquez sur foreground.php à partir d'un navigateur Web

Les éléments suivants doivent être rendus au navigateur avec les horodatages actuels et la ressource locale # dans le tableau de sortie:

loading page
calling run_background_process
  foreground start time = 1266003600
  foreground end time = 1266003600
output = Array
(
    [0] => 15010
)
end of page

Vous devriez voir testoutput.php dans le même répertoire que les fichiers ci-dessus ont été enregistrés, et il doit être vide

Vous devriez voir testprocesses.php dans le même répertoire que les fichiers ci-dessus ont été enregistrés, et il doit contenir le texte suivant avec les horodatages actuels:

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610

10

Si vous avez juste besoin de faire quelque chose en arrière-plan sans attendre que la page PHP soit terminée, vous pouvez utiliser un autre script PHP (en arrière-plan) qui est "invoqué" avec la commande wget. Ce script PHP d'arrière-plan sera exécuté avec des privilèges, bien sûr, comme tout autre script PHP sur votre système.

Voici un exemple sous Windows utilisant wget à partir de packages gnuwin32.

Le code d'arrière-plan (fichier test-proc-bg.php) à titre d'exemple ...

sleep(5);   // some delay
file_put_contents('test.txt', date('Y-m-d/H:i:s.u')); // writes time in a file

Le script de premier plan, celui invoquant ...

$proc_command = "wget.exe http://localhost/test-proc-bg.php -q -O - -b";
$proc = popen($proc_command, "r");
pclose($proc);

Vous devez utiliser le popen / pclose pour que cela fonctionne correctement.

Les options wget:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background

7

Voici une fonction pour lancer un processus d'arrière-plan en PHP. Enfin créé celui qui fonctionne réellement sur Windows aussi, après beaucoup de lecture et de test de différentes approches et paramètres.

function LaunchBackgroundProcess($command){
  // Run command Asynchroniously (in a separate thread)
  if(PHP_OS=='WINNT' || PHP_OS=='WIN32' || PHP_OS=='Windows'){
    // Windows
    $command = 'start "" '. $command;
  } else {
    // Linux/UNIX
    $command = $command .' /dev/null &';
  }
  $handle = popen($command, 'r');
  if($handle!==false){
    pclose($handle);
    return true;
  } else {
    return false;
  }
}

Remarque 1: sous Windows, n'utilisez pas le /Bparamètre comme suggéré ailleurs. Il force le processus à exécuter la même fenêtre de console que la startcommande elle-même, ce qui entraîne un traitement synchrone du processus. Pour exécuter le processus dans un thread séparé (de manière asynchrone), n'utilisez pas /B.

Remarque 2: Les guillemets vides après start ""sont obligatoires si la commande est un chemin entre guillemets. startLa commande interprète le premier paramètre cité comme le titre de la fenêtre.


6

Eh bien, j'ai trouvé une version un peu plus rapide et plus facile à utiliser

shell_exec('screen -dmS $name_of_screen $command'); 

et il fonctionne.


@ Alpha.Dev Je l'ai testé sur Linux uniquement, je ne sais pas si ça va fonctionner sur un autre système.
Morfidon

4

Pouvez-vous arranger un processus distinct, puis exécuter votre copie en arrière-plan? Cela fait un moment que je n'ai fait aucun PHP, mais la fonction pcntl-fork semble prometteuse.


Cela ne fournit pas de réponse à la question. Pour critiquer ou demander des éclaircissements à un auteur, laissez un commentaire sous son article.
Rizier123

13
Merci - Les commentaires n'existaient pas en 2008, mais je garderai cela à l'esprit :)
Matt Sheppard

4

Utilisez cette fonction pour exécuter votre programme en arrière-plan. Il est multiplateforme et entièrement personnalisable.

<?php
function startBackgroundProcess(
    $command,
    $stdin = null,
    $redirectStdout = null,
    $redirectStderr = null,
    $cwd = null,
    $env = null,
    $other_options = null
) {
    $descriptorspec = array(
        1 => is_string($redirectStdout) ? array('file', $redirectStdout, 'w') : array('pipe', 'w'),
        2 => is_string($redirectStderr) ? array('file', $redirectStderr, 'w') : array('pipe', 'w'),
    );
    if (is_string($stdin)) {
        $descriptorspec[0] = array('pipe', 'r');
    }
    $proc = proc_open($command, $descriptorspec, $pipes, $cwd, $env, $other_options);
    if (!is_resource($proc)) {
        throw new \Exception("Failed to start background process by command: $command");
    }
    if (is_string($stdin)) {
        fwrite($pipes[0], $stdin);
        fclose($pipes[0]);
    }
    if (!is_string($redirectStdout)) {
        fclose($pipes[1]);
    }
    if (!is_string($redirectStderr)) {
        fclose($pipes[2]);
    }
    return $proc;
}

Notez qu'après le démarrage de la commande, par défaut, cette fonction ferme les stdin et stdout du processus en cours d'exécution. Vous pouvez rediriger la sortie du processus dans un fichier via les arguments $ redirectStdout et $ redirectStderr.

Remarque pour les utilisateurs de Windows:
vous ne pouvez pas rediriger stdout / stderr vers nulde la manière suivante:

startBackgroundProcess('ping yandex.com', null, 'nul', 'nul');

Cependant, vous pouvez le faire:

startBackgroundProcess('ping yandex.com >nul 2>&1');

Notes pour les utilisateurs de * nix:

1) Utilisez la commande shell exécutable si vous voulez obtenir le PID réel:

$proc = startBackgroundProcess('exec ping yandex.com -c 15', null, '/dev/null', '/dev/null');
print_r(proc_get_status($proc));

2) Utilisez l'argument $ stdin si vous souhaitez passer des données à l'entrée de votre programme:

startBackgroundProcess('cat > input.txt', "Hello world!\n");

3

Vous pouvez essayer un système de mise en file d'attente comme Resque . Vous pouvez ensuite générer un travail, qui traite les informations et retourne assez rapidement avec l'image "de traitement". Avec cette approche, vous ne saurez cependant pas quand elle sera terminée.

Cette solution est destinée aux applications à plus grande échelle, où vous ne voulez pas que vos machines frontales fassent le gros du travail, afin qu'elles puissent traiter les demandes des utilisateurs. Par conséquent, il peut ou non fonctionner avec des données physiques telles que des fichiers et des dossiers, mais pour le traitement d'une logique plus compliquée ou d'autres tâches asynchrones (c'est-à-dire de nouveaux courriers d'inscription), il est agréable d'avoir et très évolutif.


2

Une solution de travail pour Windows et Linux. En savoir plus sur ma page github .

function run_process($cmd,$outputFile = '/dev/null', $append = false){
                    $pid=0;
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        $cmd = 'wmic process call create "'.$cmd.'" | find "ProcessId"';
                        $handle = popen("start /B ". $cmd, "r");
                        $read = fread($handle, 200); //Read the output 
                        $pid=substr($read,strpos($read,'=')+1);
                        $pid=substr($pid,0,strpos($pid,';') );
                        $pid = (int)$pid;
                        pclose($handle); //Close
                }else{
                    $pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $cmd, ($append) ? '>>' : '>', $outputFile));
                }
                    return $pid;
            }
            function is_process_running($pid){
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        //tasklist /FI "PID eq 6480"
                    $result = shell_exec('tasklist /FI "PID eq '.$pid.'"' );
                    if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                        return true;
                    }
                }else{
                    $result = shell_exec(sprintf('ps %d 2>&1', $pid));
                    if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) {
                        return true;
                    }
                }
                return false;
            }
            function stop_process($pid){
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                            $result = shell_exec('taskkill /PID '.$pid );
                        if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                            return true;
                        }
                    }else{
                            $result = shell_exec(sprintf('kill %d 2>&1', $pid));
                        if (!preg_match('/No such process/', $result)) {
                            return true;
                        }
                    }
            }


1

Au lieu de lancer un processus d'arrière-plan, qu'en est-il de la création d'un fichier déclencheur et d'un ordonnanceur comme cron ou autosys qui exécute périodiquement un script qui recherche et agit sur les fichiers déclencheurs? Les déclencheurs pourraient contenir des instructions ou même des commandes brutes (mieux encore, faites-en simplement un script shell).



1

J'utilise beaucoup fast_cgi_finish_request()

En combinaison avec une fermeture et une fonction register_shutdown_function ()

$message ='job executed';
$backgroundJob = function() use ($message) {
     //do some work here
    echo $message;
}

Enregistrez ensuite cette fermeture pour qu'elle soit exécutée avant l'arrêt.

register_shutdown_function($backgroundJob);

Enfin, lorsque la réponse a été envoyée au client, vous pouvez fermer la connexion au client et continuer à travailler avec le processus PHP:

fast_cgi_finish_request();

La fermeture sera exécutée après fast_cgi_finish_request.

Le message $ ne sera visible à aucun moment. Et vous pouvez enregistrer autant de fermetures que vous le souhaitez, mais faites attention au temps d'exécution du script. Cela ne fonctionnera que si PHP fonctionne en tant que module Fast CGI (était-ce vrai?!)


0

Les scripts PHP ne sont pas comme les autres langages de développement d'applications de bureau. Dans les langages d'application de bureau, nous pouvons définir des threads démon pour exécuter un processus d'arrière-plan, mais en PHP, un processus se produit lorsque l'utilisateur demande une page. Cependant, il est possible de définir un travail en arrière-plan à l'aide de la fonctionnalité de travail cron du serveur exécutée par le script php.


0

Pour ceux d'entre nous qui utilisent Windows , regardez ceci:

Référence: http://php.net/manual/en/function.exec.php#43917

Moi aussi, je me suis battu pour obtenir un programme à exécuter en arrière-plan dans Windows pendant que le script continue de s'exécuter. Cette méthode, contrairement aux autres solutions, vous permet de démarrer n'importe quel programme minimisé, maximisé ou sans fenêtre du tout. La solution de llbra @ phpbrasil fonctionne, mais elle produit parfois une fenêtre indésirable sur le bureau lorsque vous voulez vraiment que la tâche s'exécute cachée.

démarrez Notepad.exe minimisé en arrière-plan:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("notepad.exe", 7, false); 
?> 

lancer une commande shell invisible en arrière-plan:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false); 
?> 

lancez MSPaint maximized et attendez que vous le fermiez avant de continuer le script:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("mspaint.exe", 3, true); 
?> 

Pour plus d'informations sur la méthode Run (), accédez à: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

URL modifiée:

Accédez plutôt à https://technet.microsoft.com/en-us/library/ee156605.aspx car le lien ci-dessus n'existe plus.


1
Que faut-il faire pour exécuter ce code? Je reçoisFatal error: Class 'COM' not found
Alph.Dev

Ps Lien vers MSDN est rompu
Alph.Dev

@ Alph.Dev: Le doc msdn semble avoir été temporairement supprimé. Il contenait plus d'exemples et d'explications sur l'utilisation du WScript.Shell.
Jimmy Ilenloa

@ Alph.Dev: Pour la classe COM introuvable, c'est une tout autre chose. Vérifiez cela sur google. Vous pouvez également consulter ce php.net/manual/en/com.installation.php
Jimmy Ilenloa

@ Alph.Dev veuillez vérifier technet.microsoft.com/en-us/library/ee156605.aspx pour des informations mises à jour sur la fonction Run ()
Jimmy Ilenloa

-12

Je sais que c'est un poste vieux de 100 ans, mais de toute façon, j'ai pensé qu'il pourrait être utile à quelqu'un. Vous pouvez mettre une image invisible quelque part sur la page pointant vers l'URL qui doit s'exécuter en arrière-plan, comme ceci:

<img src="run-in-background.php" border="0" alt="" width="1" height="1" />


12
Vraiment mauvaise solution. Si l'utilisateur quitte la page, le processus sera interrompu.
Sergey P. aka azur

3
"l'image invisible" et son lien sont exposés dans le code source de la page.
tony gil
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.