What is the solution for asynchronous processes in PHP?

11

In PHP, often in the middle of an operation, I need to send an email, make a webservice call, or log a log, and these can sometimes take a long time to process, but I do not have to wait for them end a request.

I've been looking around, and I've seen that some languages like C #, NodeJS and even Python3.6 have been implementing asynchronous tasks in the language.

I searched for something in PHP, but found nothing satisfactory. I have seen a lot of gambiarras like using a Message Queue , fsockopen , calls on the command line and the like to be able to perform an asynchronous task.

I needed something that could be done within the scope of a particular code, without having to wait for something built in the hand.

  • Is there anything better to implement asynchronous tasks in PHP?

  • Is there any extension or any library that does this work (or at least that simulates in a more coherent way than the gambiarras that I usually see around)?

Note : I've been reading some things about PThreads, but I do not know if it's the solution. If you can explain this extension, I'll be grateful.

    
asked by anonymous 26.05.2017 / 16:04

3 answers

9

One thing is a function to run asynchronously, another thing is to make a call to something external without waiting for a response (or pick up the answer later), I will not go into detail because to be Sincerely, I do not know Guzzle.

PHP only really supports Threads with PThread , yet it is possible to simulate something. I personally do not see a need to run a function asynchronously in a PHP script that processes a web page , maybe I do not fully understand the need for this, I think maybe in a CLI (Command-line interface / command-line interface) using PHP would be more interesting, since it could run multiple tasks and be running endlessly (or until all "events" end).

Calling a PHP script without waiting for it to respond

Speaking independently of need, an example which would be something similar to "asynchronous" (in fact, it is a totally separate process) would call a PHP script through another script in CLI mode, it would look something like:

/**
 * Iniciar script em outro processo
 *
 * @param string $script  Define a localização do script
 * @param string $php_exe Define a localização do interpretador (opcional) 
 * @param string $php_ini Define a localização do php.ini (opcional)
 * @return void
 */
function processPhpScript($script, $php_exe = 'php', $php_ini = null) {
    $script = realpath($script);

    $php_ini = $php_ini ? $php_ini : php_ini_loaded_file();

    if (stripos(PHP_OS, 'WIN') !== false) {
        /* Windows OS */
        $exec = 'start /B cmd /S /C ' . escapeshellarg($php_exe . ' -c ' . $php_ini . ' ' . $script) . ' > NUL';
    } else {
        /* nix OS */
        $exec = escapeshellarg($php_exe) . ' -c ' . escapeshellarg($php_ini) . ' ' . escapeshellarg($script . ' >/dev/null 2>&1');
    }

    $handle = popen($exec, 'r');

    if ($handle) {
        pclose($handle);
    }
}

processPhpScript('pasta/script1.php');
processPhpScript('pasta/script2.php');

If it's in windows, the scripts will run as if they were in the CMD with the command start so you do not have to wait:

start /B cmd /S /C "c:\php\php.exe -c c:\php\php.ini c:\documents\user\pasta\script1.php" > NUL
start /B cmd /S /C "c:\php\php.exe -c c:\php\php.ini c:\documents\user\pasta\script2.php" > NUL

If in a unix-like environment they will run with to write output to /dev/null instead of returning to output

php -c /etc/php/php.ini /home/user/pasta/script1.php >/dev/null 2>&1
php -c /etc/php/php.ini /home/user/pasta/script2.php >/dev/null 2>&1

This will cause your script that called the processPhpScript function to wait for the response.

Curl and fsockopen

I believe that when we speak of asynchronous in Guzzle we are actually talking about external requests from which you do not have to wait for the answer because you do not want it or multiple requests that work concurrently concurrently and are delivered as they end, Curl himself can do something like this:

<?php
// Inicia dois CURLs
$ch1 = curl_init("https://pt.stackoverflow.com/");
$ch2 = curl_init("https://meta.pt.stackoverflow.com/");

//Esta parte é apenas devido ao SSL, é tudo apenas um exemplo
curl_setopt($ch1, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, 0);

curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, 1);

//Inicia o manipulador e adiciona o curls
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch1);
curl_multi_add_handle($mh, $ch2);

//Executa as requisições simultaneamente
$running = null;

do {
    curl_multi_exec($mh, $running);
} while ($running);

//Finaliza o manipulador
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_close($mh);

//Pega o conteúdo
$response1 = curl_multi_getcontent($ch1);
$response2 = curl_multi_getcontent($ch2);

//Exibe as respostas
echo $response1, PHP_EOL;
echo $response2, PHP_EOL;

Now, if we observe correctly, we request two requests at the same time, but in order to obtain the answer we had to wait for everything. I believe that in Guzzle requestAsync should work with something like Promisse , that what it is going to get it will send for a response in a callback , I do not know I'm not sure how to do this, but I'm not sure how to do this.

<?php

function createRequest($url, &$failno, &$failstr) {
    $parsed = parse_url($url);

    $isHttps = $parsed['scheme'] == 'https';
    $host = ($isHttps ? 'ssl://' : '') . $parsed['host'];
    $port = isset($parsed['port']) ? $parsed['port'] : ($isHttps ? 443 : 80);

    $socket = fsockopen($host, $port, $errorno, $errorstr);

    echo $host, $port;

    if ($socket) {
        $out = "GET " . $parsed['path'] . " HTTP/1.1\r\n";
        $out .= "Host: " . $parsed['host'] . "\r\n";
        $out .= "Connection: close\r\n\r\n";
        fwrite($socket, $out);
        return $socket;
    }

    return false;
}

function checkStatus(&$promisses, \Closure &$done) {
    if (empty($promisses)) {
        return false;
    }

    $nocomplete = false;

    foreach ($promisses as &$promisse) {
        if (feof($promisse['socket']) === false) {
            $nocomplete = true;
            $promisse['response'] .= fgets($promisse['socket'], 1024);
        } else if ($promisse['complete'] === false) {
            $promisse['complete'] = true;
            $done($promisse['url'], $promisse['response']);
        }
    }

    return $nocomplete;
}

function promisseRequests(array $urls, \Closure $done, \Closure $fail)
{
    $promisses = array();

    foreach ($urls as $url) {
        $current = createRequest($url, $errorno, $errorstr);

        if ($current) {
            $promisses[] = array(
                'complete' => false,
                'response' => '',
                'socket' => $current,
                'url' => $url
            );
        } else {
            $fail($url, $errorno, $errorstr);
        }
    }

    $processing = true;

    while ($processing) {
        $processing = checkStatus($promisses, $done);
    }
}

// Inicia dois CURLs
$urls = array(
    'http://localhost/',
    'http://localhost/inphinit/'
);

promisseRequests($urls, function ($url, $response) {
    var_dump('Sucesso:', $url, $response);
}, function ($url, $errorno, $errorstr) {
    var_dump('Falhou:', $url, $errorno, $errorstr);
});

In general, what I did was make the requests work at the same time, what I find interesting is that you can get the result that you finish first and manipulate as you wish, but I think it is not always useful. >

Where Thread would be interesting

Both of the examples I mentioned above are not threads , one is a separate process that avoids having to wait (since the call is entirely the same) and the other is multiple HTTP requests and (or next to it) the only place I see that maybe it would be interesting is with a PHP script that keeps running continuously, for example a CLI script as I've already mentioned or a Socket to work with WebSocket (which by the way is also a CLI).

I'm going to use WebSocket as an example, a socket server handles multiple calls and it responds to the websocket only when it wishes, imagining that 5 people connect to the socket through WebSocket making requests would be interesting to move the requests to Threds and deliver them only when it is finished, otherwise it would have to process them as it was requested and then the user who made a simple request ends up having to wait for the users who made more time consuming requests to process, with the Thread this could improve a bit, work the competition (of course if the script is well written).

  

As soon as possible I'll post an example with WebSocket

Installing PThreads

It can be installed via PECL , using the command:

 pecl install pthreads

But if you do not have Pecl you can try and using Windows you can download the binaries here link , but if you use Mac OSX, Linux or do not have the binary for your version of PHP and do not have PECL, then you will have to compile after downloading link , of course the PHP executable has to have been compiled on your machine too and by the same compiler (I will not go into detail as this is not the focus of the question)     

27.05.2017 / 02:50
2

Well, everything goes by the approach you are going to take. If you wait as async and await of C # you will not find it in PHP itself.

I will not go into too much detail about what is asynchronous and lower level implementations because I have never studied this in depth.

  

Is there anything better to implement asynchronous tasks in PHP?

     

Is there any extension or any library that does this work (or at least that simulates in a more coherent way than the gambiarras that I usually see there)?

For these types of issues you reported, we can use ReactPHP . It is a collection of libraries that helps you perform tasks asynchronously. It suggests that you install some extensions to improve event-loop performance, but nothing complicated to install.

It includes an HTTP Server implementation that somewhat resembles the nodeJS

require 'vendor/autoload.php';

$app = function ($request, $response) {
    $response->writeHead(200, array('Content-Type' => 'text/plain'));
    $response->end("Hello World\n");
};

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$http = new React\Http\Server($socket, $loop);

$http->on('request', $app);
echo "Server running at http://127.0.0.1:1337\n";

$socket->listen(1337);
$loop->run();
  

In PHP many times, in the middle of an operation, I need to send an email,   make a webservice call or log a log, and those times   processing, but without the need for   wait for them to complete a request.

Here the approach comes in and how you will work and implement your architecture can help you.

Calling webservices will usually have to wait, unless there are multiple calls to several different services. These requests can asynchronous with guzzle .

use GuzzleHttp\Client;
use GuzzleHttp\Promise;

$client = new Client(['base_uri' => 'http://httpbin.org/']);

// Initiate each request but do not block
$promises = [
    'image' => $client->getAsync('/image'),
    'png'   => $client->getAsync('/image/png'),
    'jpeg'  => $client->getAsync('/image/jpeg'),
    'webp'  => $client->getAsync('/image/webp')
];

// Wait on all of the requests to complete. Throws a ConnectException
// if any of the requests fail
$results = Promise\unwrap($promises);

// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();

// You can access each result using the key provided to the unwrap
// function.
echo $results['image']['value']->getHeader('Content-Length')[0]
echo $results['png']['value']->getHeader('Content-Length')[0]

Heavy operations like sending e-mail, in my opinion are much better taken if you forward to a queue off of the server used to be your web server. Several famous frameworks (Zend, Symfony, Laravel) already come with some queue implementation to solve these problems, but doing something from scratch is also possible. Here's an example using a library to work with queues managed by Beanstalkd:

<?php

// Hopefully you're using Composer autoloading.

use Pheanstalk\Pheanstalk;

$pheanstalk = new Pheanstalk('127.0.0.1');

// ----------------------------------------
// producer (queues jobs)

$pheanstalk
  ->useTube('testtube')
  ->put("job payload goes here\n");

// ----------------------------------------
// worker (performs jobs)

$job = $pheanstalk
  ->watch('testtube')
  ->ignore('default')
  ->reserve();

echo $job->getData();

$pheanstalk->delete($job);

// ----------------------------------------
// check server availability

$pheanstalk->getConnection()->isServiceListening(); // true or false

In conclusion, we have therefore a few options. All languages have their advantages and disadvantages and their resources are more a reflection of how those who use language usually solve problems than what we want.

Sometimes before deciding how the implementation will be, we can reflect on what my real problems are and how others have resolved.

    
27.05.2017 / 03:35
1

The pthreads library is a great option for using parallel processing.

How to install:

  

Before you begin, you must have Thread Safety in PHP enabled. Thread Safety means that the binary can work in a multithreaded web server context, such as Apache 2 in Windows. Thread Safety works by creating a local copy on each thread, so data will not collide with another thread.

In Windows , look for the version that matches your php in windows.php. net , unzip the file and look for the php_pthreads.dll file and place it in the ext folder inside the php folder in the directory you installed. And place the file pthreadVC2.dll in the php folder. If you have any errors, please also put this dll in C: \ Windows \ System32 .

In linux , type the command pecl install pthreads . You can check the versions available at pecl.php.net

After everything is working, let's go to the basic example:

/** THREAD DE ENVIO DE E-MAIL *******************/
class tEmail extends Thread
{
     private $id;

     public function __construct($id)
     {
           $this->id = $id;
     }

     public function run()
     {
         sleep(rand (1, 50));
         echo "Thread: ".$this->id.", Email enviado; ".date("d/m/Y H:i:s")."\r\n";
     }

     public function getId(){
          return $this->id;
     }
}

//Criar 50 thread para rodar
$tEmail = array();
for($i=0;$i<50;$i++){
    $tEmail[] = (new tEmail($i));
}

foreach ($tEmail as $t) {
   echo ("Thread " . $t->getId() . " iniciada! \r\n");
   $t->start(); //Manda rodar em paralelo
}

//Você pode trabalhar com as instancias delas, mesmo estando em paralelo
foreach ($tEmail as $t) {
     while ($t->isRunning()) { //Pode ser verificado se ainda está rodando
        usleep(100);
     }
     echo ("Thread " . $t->getId() . " finalizada! \r\n");
}

I hope I have helped!

    
05.06.2017 / 14:40