PHP Асинхронный метод вызова в Yii Framework

Вопрос

Я хочу знать, можно ли асинхронно вызывать метод контроллера Yii из одного из своих действий, в то время как действие отображает представление, оставляя метод для завершения длительной операции. Я хотел бы сделать что-то вроде кода ниже, и мне не нужно возвращать результат из my_long_running_func.

public function actionCreate() {
    $model = new Vacancies;
    if (isset($_POST['Vacancies'])) {
        $model->setAttributes($_POST['Vacancies']);
        $model->save();
        //I wish :)
        call_user_func_async('my_long_running_func',$model);
    }
    $this->render('create', array( 'model' => $model));
}

Проблема

Я пытаюсь написать действие контроллера в Yii, которое размещает вакансию и уведомляет заинтересованных подписчиков о почте. Проблема заключается в том, что для выполнения запроса уведомления требуется много времени.

Теперь я ищу способ асинхронного запуска запроса, чтобы плакат увидел его ответ за минимальное время, пока запрос выполняется в фоновом режиме способом, подобным делегатам или событиям С#.

Решения, которые я искал, выполняли асинхронные запросы (запросы) во время действия контроллера, но все, что я хочу сделать, - это запустить асинхронный метод контроллера, и действие должно было дождаться завершения запроса (ов).

Покушение

Я пробовал следующие методы, но запрос по-прежнему медленный для моих тестовых данных около 1500 пользователей.

  • Yii ActiveRecord

    if ($vacancy->save()) {                
        if($vacancy->is_active == 1) {
            $url = Yii::app()->createUrl('vacancies/view',array('id'=>$model->id));
            $trainees = YumUser::getUsersByRole('Trainees');
            if($trainees!=null) {
                foreach($trainees as $trainee){
                    $message = new YumMessage;
                    $message->from_user_id = Yii::app()->user->id;
                    $message->title = 'Vacancy Notification: '.date('M j, Y');
                    $message->message = "A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
                    $message->to_user_id = $trainee->id;
                    $message->save();                
                }
            }
        }    
    }
    
  • Объекты доступа Yii

    if ($vacancy->save()) {        
        if($vacancy->is_active == 1) {
            $url = Yii::app()->createAbsoluteUrl('vacancies/view',array('id'=>$model->id));
            $trainee_ids=Yii::app()->db->createCommand()->select('user_id')->from('trainee')->queryColumn();
            $fid=Yii::app()->user->id;
            $msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
            $ts = time();
            $tt = 'Vacancy Notification: '.date('M j, Y');
            if($trainee_ids!=null) {
                foreach($trainee_ids as $trainee_id){
                    Yii::app()->db->createCommand()
                      ->insert('message',array('timestamp'=>$ts,'from_user_id'=>$fid,'to_user_id'=>$tid,'title'=>$tt,'message'=>$msg));
                }
            }
        }
    }
    
  • Подготовленные утверждения

    if ($vacancy->save()) {                
        if($vacancy->is_active == 1) {
            $url = Yii::app()->createUrl('vacancies/view',array('id'=>$model->id));                    
            $trainee_ids=Yii::app()->db->createCommand()->select('user_id')->from('trainee')->queryColumn();
            $fu=Yii::app()->user->id;
            $msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
            $ts = time();
            $tt = 'Vacancy Notification: '.date('M j, Y');
            $sql="INSERT INTO message (timestamp,from_user_id,title,message,to_user_id) VALUES (:ts,:fu,:tt,:msg,:tu)";
            if($trainee_ids!=null) {
                foreach($trainee_ids as $trainee_id){
    
                    $command=Yii::app()->db->createCommand($sql);
                    $command->bindParam(":ts",$ts,PDO::PARAM_INT);
                    $command->bindParam(":fu",$fu,PDO::PARAM_INT);
                    $command->bindParam(":tt",$tt,PDO::PARAM_STR);
                    $command->bindParam(":msg",$msg,PDO::PARAM_STR);
                    $command->bindParam(":tu",$trainee_id,PDO::PARAM_INT);
    
                    $command->execute();
    
                }
            }
        }
    }
    

Исследование

Я также проверил следующие веб-сайты (мне разрешено размещать только две ссылки), но они либо требуют, чтобы действие дождалось завершения запроса или потребовалось завиток (к которому у меня нет доступа к развертыванию сервер) или нужна внешняя библиотека. Я надеялся на собственную реализацию PHP.

Изменить

Мне удалось значительно уменьшить время отклика, переписав мой запрос таким образом (перемещение петли пользователя на уровень базы данных):

public function actionCreate() {
    $user=YumUser::model()->findByPk(Yii::app()->user->id);
    $model = new Vacancies;
    $model->corporate_id=$user->professional->institution->corporate->id;
    $model->date_posted=date('Y-m-d');
    $model->last_modified=date('Y-m-d H:i:s');

    if (isset($_POST['Vacancies'])) {
        $model->setAttributes($_POST['Vacancies']);
        if ($model->save()) {                
            if($model->is_active == 1) {
                $url = Yii::app()->createAbsoluteUrl('vacancies/view',array('id'=>$model->id));                    
                $fu=Yii::app()->user->id;
                $msg="A new vacancy has been posted at <a href='{$url}'>{$url}</a>.";
                $ts = time();
                $tt = 'New Vacancy: '.$model->title;
                $sql='INSERT INTO message (timestamp,from_user_id,title,message,to_user_id) SELECT :ts,:fu,:tt,:msg,t.user_id FROM trainee t';
                Yii::app()->db->createCommand($sql)->execute(array(':ts'=>$ts,':fu'=>$fu,':tt'=>$tt,':msg'=>$msg));
            }                
            if (Yii::app()->getRequest()->getIsAjaxRequest())
                Yii::app()->end();
            else
                $this->redirect(array('view', 'id' => $model->id));
        }
    }
    $this->render('create', array( 'model' => $model));
}

Несмотря на это, было бы неплохо, если бы кто-то мог написать способ для вызова функций асинхронно.

Ответ 1

Как правило, решением таких проблем было бы объединение шины сообщений в вашу систему. Вы можете рассмотреть такой продукт, как Beanstalkd. Это требует установки программного обеспечения на вашем сервере. Я предполагаю, что это предложение будет называться "с использованием внешней библиотеки".

Если вы можете получить доступ к серверу развертывания, и вы можете добавить cronjob (или, может быть, sysadmin can), вы можете рассмотреть cronjob, который выполняет вызов php-cli для script, который читает задания из очереди заданий в вашей базе данных, заполняется методом контроллера.

Если вы не можете установить программное обеспечение на сервер, на котором вы работаете, вы можете рассмотреть использование решения SAAS, например Iron.io, чтобы разместить для вас. Iron.io использует так называемую push-очередь. С помощью push-очереди шина сообщения активно выполняет запрос (push) к зарегистрированным слушателям с содержимым сообщения. Это может сработать, так как это не требует от вас запроса на завивание.

Если ни одно из указанных выше не возможно, ваши руки связаны. Другая статья, которая весьма актуальна по этому вопросу: Масштабируемая, отложенная обработка PHP

Ответ 2

Я бы попробовал это, хотя я не на 100% уверен, что Yii будет работать правильно, но его относительно просто и стоит:

public function actionCreate() {
    $model = new Vacancies;
    if (isset($_POST['Vacancies'])) {
        $model->setAttributes($_POST['Vacancies']);
        $model->save();
        //I wish :)
    }

    HttpResponse::setContentType('text/html');
    HttpResponse::setData($this->render('create', array( 'model' => $model), true);
    HttpResponse::send();

    flush(); // writes the response out to the client

    if (isset($_POST['Vacancies'])) {
        call_user_func_async('my_long_running_func',$model);
    }
}

Ответ 3

Здесь совершенно другой тип предложения. Как насчет регистрации для события onEndRequest, которое запускается Функция конца() CWebApplication?

public function end($status=0, $exit=true)
{
    if($this->hasEventHandler('onEndRequest'))
        $this->onEndRequest(new CEvent($this));
    if($exit)
        exit($status);
}

Вам нужно будет зарегистрироваться для мероприятия и выяснить, как передать свою модель каким-то образом, но код будет правильно запускаться после того, как все данные будут сброшены в браузер...