We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

How to run php function after redirecting user to another page?

I am building a form which the user fills and then I save it into the db. Then I retrieve users who match the criteria and for each one of them I store into another table and also send them an email.

   $userModel = new User();
   $currentUser = $userModel->findUserById($user->id);
   $requestModel = new Requests();
   $success = $requestModel->saveRequest($currentUser->usr_id, $tagId, $title, $task, $fixed, $price, $hour, $quality, $multiple, $datetime, $postal, $cityId, $travel);
                if($success){
                    $request = $requestModel->getUserLatestRequest($currentUser->usr_id);
                    if($request){
                        $user = new User();
                        $alluserids= $user->getAllSkillCityUserIds($cityId, $tagId);
                        $targetId = (array_column($alluserids, 'usr_id'));
                        //error_log("<pre>targetId".print_r($targetId,true)."</pre>"); 
                        foreach($targetId as $target) {
                            if($target == $currentUser->usr_id){
                                continue;
                            }
                            $lead = new RequestsLead();
                            $lead->addRequest($request->req_id, $request->req_userid, $target);

                            $contractor = $userModel->findUserbyId($target);
                            $nemail = new NotificationsEmail();
                            $nemail->sendGotRequest($contractor->usr_email, $contractor->usr_firstname);
                        }
                    }
                    $this->flash->success('<div data-toggle="notify" data-onload data-message="Thanks for using our service!." data-options="{"status":"success"}" class="hidden-xs"></div>');
                    $this->response->redirect($this->url->get(""));
                }else{
                    $this->flash->error('<div data-toggle="notify" data-onload data-message="Sorry! Please try again." data-options="{"status":"danger"}" class="hidden-xs"></div>');
                    $this->response->redirect($this->url->get("request"));
                }   

The problem comes when there are alot of users and this function will need to finish running before the user is redirected back to the page with the flash message. How can I modify this so I redirect the user back to the page with the flash message first then run the php foreach functions storing into the db and sending emails?

I tried switching the order of the functions but once the user is redirected the php functions stopped proceeding.



16.0k
Accepted
answer

give it a try forwarding the dispatcher. But it makes sense once you redirect to another page to cancel the current one. Why you don't use ajax for that?

https://docs.phalcon.io/en/latest/reference/dispatching.html#forwarding-to-other-actions

edited Oct '15

Thanks for pointing me to Ajax, it did cross my mind but assuming I returned a successful ajax response to the user after successful POST, and the user thinks the form submission is done and started doing other stuff, do the php functions still runs for a continuous amount of time?

Edit: I think I will use Ajax with ignore_user_abort(true); for this. Thanks for the help.

give it a try forwarding the dispatcher. But it makes sense once you redirect to another page to cancel the current one. Why you don't use ajax for that?

https://docs.phalcon.io/en/latest/reference/dispatching.html#forwarding-to-other-actions

edited Oct '15

For sending emails you should use queue and benstalkd i have implemented it and can share with it:

cli bootstrap file

<?php
use Phalcon\CLI\Console as ConsoleApp;
use Phalcon\Config\Adapter\Ini;
use Phalcon\Di\FactoryDefault\Cli;
use Phalcon\Logger;
use Phalcon\Mvc\Model\Manager;
use Phalcon\Queue\Beanstalk;

define('VERSION', '1.0.0');
// Using the CLI factory default services container
$di = new Cli();
// Define path to application directory
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__)));
/**
 * Register the autoloader and tell it to register the tasks directory
 */
$loader = new \Phalcon\Loader();
$loader->registerDirs(
    array(
        APPLICATION_PATH . '/tasks'
    )
);
// Loading your classes
$loader->register();
$config = new Ini(APPLICATION_PATH."/../app/config/config.ini");
// Create a console application
$console = new ConsoleApp();
$console->setDI($di);
$di->set('queue', function () {
    $queue = new Beanstalk(
        array(
            'host' => '127.0.0.1',
            'port' => '11300'
        )
    );
    return $queue;
});
$di->set('mailLogger', function () {
    $mailLogger = new Phalcon\Logger\Adapter\File(APPLICATION_PATH."/logs/mail-log-" . date('Y-m-d') . ".log");
    return $mailLogger;
});
$di->set('mailer', function () use ($config) {
    include APPLICATION_PATH.'/../lib/swift/swift_required.php';
    $transport = Swift_SmtpTransport::newInstance($config->smtp->address, $config->smtp->port)
        ->setUsername($config->smtp->username)
        ->setPassword($config->smtp->password);
    $mailer = Swift_Mailer::newInstance($transport);
    return $mailer;
});
/**
 * Process the console arguments
 */
$arguments = array();
foreach($argv as $k => $arg) {
    if($k == 1) {
        $arguments['task'] = $arg;
    } elseif($k == 2) {
        $arguments['action'] = $arg;
    } elseif($k >= 3) {
        $arguments['params'][] = $arg;
    }
}
// Define global constants for the current task and action
define('CURRENT_TASK', (isset($argv[1]) ? $argv[1] : null));
define('CURRENT_ACTION', (isset($argv[2]) ? $argv[2] : null));
try {
    // Handle incoming arguments
    $console->handle($arguments);
} catch(\Phalcon\Exception $e) {
    echo $e->getMessage();
    exit(255);
}

Here are MailTask:

<?php
use Phalcon\Cli\Task;
use Phalcon\Logger\Adapter\File;
use Phalcon\Queue\Beanstalk;

/**
 * Created by PhpStorm.
 * User: User
 * Date: 30.09.15
 * Time: 13:04
 */
class MailTask extends Task
{
    /**
     * @var Beanstalk
     */
    protected $queue;
    /**
     * @var Swift_Mailer
     */
    protected $mailer;
    /**
     * @var File
     */
    protected $mailLogger;

    public function initialize()
    {
        $this->queue = $this->di->get('queue');
        $this->mailer = $this->di->get('mailer');
        $this->mailLogger = $this->di->get('mailLogger');
    }

    public function sendAction()
    {
        $this->queue->watch('mail');
        while ($this->queue->stats()["current-jobs-ready"] > 0 && ($job = $this->queue->reserve())) {

            $message=$job->getBody()['mail'];
            if($this->mailer->send($message)){
                $this->mailLogger->info(PHP_EOL."Mail Sended: ".PHP_EOL.
                "Subject: ".$message->getSubject().PHP_EOL.
                "To: ".json_encode($message->getTo()).PHP_EOL.
                "Reply To: ".json_encode($message->getReplyTo()).PHP_EOL.
                "From: ".json_encode($message->getFrom()).PHP_EOL.
                "=======================================================");
            }
            else{
                $this->mailLogger->error(PHP_EOL."Mail Failed: ".PHP_EOL.
                    "Subject: ".$message->getSubject().PHP_EOL.
                    "To: ".json_encode($message->getTo()).PHP_EOL.
                    "Reply To: ".json_encode($message->getReplyTo()).PHP_EOL.
                    "From: ".json_encode($message->getFrom()).PHP_EOL.
                    "=======================================================");
            };

            $job->delete();
        }
    }
}

How its working ?

  1. Its watching for tube 'mail'
  2. If it contains any ready job to do it reserve it
  3. message is getted with getBody
  4. And then it is sending with swiftMailer.

How to use it ?

  1. Add queue service to your application.
$di->set('queue',function(){
    $queue = new Beanstalk(
        array(
            'host' => '127.0.0.1',
            'port' => '11300'
        )
    );
    return $queue;
});

And somewhere in your application:

 $this->queue->choose('mail');
      $this->queue->put(array(
          'mail'=>$mail
));

Where $mail is prepared Swift_Message for sent. And queue is service i retrieve earlier from di.

To make it working you need add something like this to crontab:

* * * * * root (cd / && sudo /usr/bin/php5 && sudo {PATH TO BOOTSTRAP CLI} mail send)

Its running mail send every minute, if there are more messages(which cant be send fast enough) then multiple instanced are runned and it runned pretty fast.

edited Nov '15

Thank you for sharing this! But I think this is too heavyweight for me. I manage to finish the task with ajax giving response first then another ajax processing sending email.

Well i think my solution is better cuz you dont have another request. If there gonna be many requests then you gonna have a problem cuz it will take more cpu than cli application.