Giter Club home page Giter Club logo

yii2-command-bus's Introduction

Yii2 Command Bus

Command Bus for Yii2

Build Status

What is Command Bus?

The idea of a command bus is that you create command objects that represent what you want your application to do. Then, you toss it into the bus and the bus makes sure that the command object gets to where it needs to go. So, the command goes in -> the bus hands it off to a handler -> and then the handler actually does the job. The command essentially represents a method call to your service layer.

Shawn McCool ©

Installation

The preferred way to install this extension is through composer.

Either run

php composer.phar require --prefer-dist trntv/yii2-command-bus

or add

"trntv/yii2-command-bus": "^1.0"

to your composer.json file

Setting Up

1. Command Bus Service

After the installation, first step is to set the command bus component.

return [
    // ...
    'components' => [
        'commandBus' => [
            'class' => 'trntv\bus\CommandBus'
        ]
    ],
];

2. Background commands support (optional)

Install required package:

php composer.phar require symfony/process:^3.0

For the background commands worker, add a controller and command bus middleware in your config

'controllerMap' => [
    'background-bus' => [
        'class' => 'trntv\bus\console\BackgroundBusController',
    ]
],

'components' => [
        'commandBus' =>[
            ...
            'middlewares' => [
                [
                    'class' => '\trntv\bus\middlewares\BackgroundCommandMiddleware',
                    'backgroundHandlerPath' => '@console/yii',
                    'backgroundHandlerRoute' => 'background-bus/handle',
                ]                
            ]
            ...            
        ]        
],

Create background command

class ReportCommand extends Object implements BackgroundCommand, SelfHandlingCommand
{
    use BackgroundCommandTrait;
    
    public $someImportantData;
    
    public function handle($command) {
        // do what you need
    }
}

And run it asynchronously!

Yii::$app->commandBus->handle(new ReportCommand([
    'async' => true,
    'someImportantData' => [ // data // ]
]))

3. Queued commands support (optional)

3.1 Install required package:

php composer.phar require yiisoft/yii2-queue

3.2 Configure extensions

If you need commands to be run in queue, you need to setup middleware and yii2-queue extensions.

'components' => [
    'queue' => [
        // queue config
    ],
    
    'commandBus' =>[
        ...
        'middlewares' => [
            [
                'class' => '\trntv\bus\middlewares\QueuedCommandMiddleware',
                // 'delay' => 3, // You can set default delay for all commands here
            ]                
        ]
        ...            
    ]     
]

More information about yii2-queue config can be found here

3.4 Run queue worker

yii queue/listen More information here

3.5 Create and run command

class HeavyComputationsCommand extends Object implements QueuedCommand, SelfHandlingCommand
{
    use QueuedCommandTrait;
    
    public function handle() {
        // do work here
    }
}

$command = new HeavyComputationsCommand();
Yii::$app->commandBus->handle($command)

4. Handlers

Handlers are objects that will handle command execution There are two possible ways to execute command:

4.1 External handler

return [
    // ...
    'components' => [
        'commandBus' => [
            'class' => 'trntv\bus\CommandBus',
            'locator' => [
                'class' => 'trntv\bus\locators\ClassNameLocator',
                'handlers' => [
                    'app\commands\SomeCommand' => 'app\handlers\SomeHandler'
                ]
            ]
        ]
    ],
];

// or
$handler = new SomeHandler;
Yii::$app->commandBus->locator->addHandler($handler, 'app\commands\SomeCommand');
// or
Yii::$app->commandBus->locator->addHandler('app\handlers\SomeHandler', 'app\commands\SomeCommand');

4.1 Self-handling command

class SomeCommand implements SelfHandlingCommand
{
    public function handle($command) {
        // do what you need
    }
}

$command = Yii::$app->commandBus->handle($command);

yii2-command-bus's People

Contributors

bhoft avatar davidjeddy avatar mpirogov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yii2-command-bus's Issues

Background commands not work for this case

Hi,

I follow the readme "2. Background commands support (optional)"

ReportCommand:
`class ReportCommand extends Object implements BackgroundCommand, SelfHandlingCommand
{
use BackgroundCommandTrait;

public $someImportantData;

public function handle($command) {
    // do what you need
    // echo 'handle command';
    $m = new TimelineEvent();
    $m->application = 'application';
    $m->category = 'test';
    $m->event = 'test';
    $m->data = json_encode(['k'=>'v'], JSON_UNESCAPED_UNICODE);
    $r = $m->save(false);
    return $r;
}

}`

Controller:
$process = Yii::$app->commandBus->handle(new ReportCommand([ 'async' => true, 'someImportantData' => [], ])); sleep(5); // Notice this line

If use sleep here, it will work(a new record in DB). If not use sleep, it will not work(I can't find new record in DB). What's the difference?
Need your help, thank you!

Потеря данных в фоновых процессах.

Здравствуйте. Решил попробовать использовать ваше расширение и получилось так, что оно не запускает команды в фоне.

Конфигурация:

    'controllerMap' => [
       ...
        'background-bus' => [
            'class' => 'trntv\bus\console\BackgroundBusController',
        ]
    ],

    'components' => [
     ...
        'commandBus' => [
            'class' => 'trntv\bus\CommandBus',
            'middlewares' => [
                [
                    'class' => '\trntv\bus\middlewares\BackgroundCommandMiddleware',
                    'backgroundHandlerBinary' => '/usr/bin/php',
                    'backgroundHandlerPath' => '@app/../yii',
                    'backgroundHandlerRoute' => 'background-bus/handle',
                ],
                [
                    'class' => '\trntv\bus\middlewares\LoggingMiddleware',
                    'level' => 1,
                ],
            ],
        ],
],

Команда:

<?php

namespace app\commands\handles;

use trntv\bus\middlewares\BackgroundCommandTrait;
use trntv\bus\interfaces\BackgroundCommand;
use trntv\bus\interfaces\SelfHandlingCommand;
use yii\base\Object;
use Yii;

class TestCommand extends Object implements BackgroundCommand, SelfHandlingCommand
{
    use BackgroundCommandTrait;

    public $setTo;

    public $subject;

    public $view;

    public $data;

    /**
     * @param $command
     */
    public function handle($command)
    {
        for($i = 0; $i <= 100000000; $i++) {

        }

        // sleep(5);

        echo 'test';

        /*$mail = Yii::$app->getMailer();

        $mail->compose($this->view, [])
            ->setTo($this->setTo)
            ->setSubject($this->subject)
            ->send();*/
    }
}

Запуск

        Yii::$app->commandBus->handle(new \app\commands\handles\TestCommand([
            'setTo' => '[email protected]',
            'subject' => 'Тест',
            'view' => 'test',
            'data' => []
        ]));

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

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

Error: Argument 1 passed to trntv\bus\locators\ClassNameLocator::addHandler() must be an instance of trntv\bus\interfaces\Handler, string given

Hello,

Thanks for this command bus. I'm Trying to use the following syntax:

Yii::$app->commandBus->locator->addHandler('app\handlers\SomeHandler', 'app\commands\SomeCommand');

And I've got that error:

Error: Argument 1 passed to trntv\bus\locators\ClassNameLocator::addHandler() must be an instance of trntv\bus\interfaces\Handler, string given

It seems to be due by the type define in function declaration in ClassNameLocator.php#L43

public function addHandler(Handler $handler, $className)

Should remove the Handler type or remove the line in documentation. Or am I mistaken on the way to use the syntax ?

Regards,

DelayedCommand feature request

Hi, first of all, thank you for extension.

I use RedisQueue and self-handling commands, and they're great, BUT I need to set custom delay for some kind of commands. Now I have to temporary change $delay property of QueueMiddleware, and this is not great. May be we could make some kind of DelayedCommand with getDelay() method?

details description of background process

I have configure background command process as per your docs.

'middlewares' => [
    [
         'class' => '\trntv\bus\middlewares\BackgroundCommandMiddleware',
         'backgroundHandlerPath' => '@app/yii',
         'backgroundHandlerRoute' => 'background-bus/handle',
    ]                
 ]

But not given details description of background-bus/handle.

Please give me which code is required in handle method or give reference.

Issue with PHP_BINARY

I'm running PHP-FPM on my server, so when I try and use this commandbus library with the background async process it never actually runs anything because the PHP_BINARY is set to "php-fpm" and php doesn't run commands on that. Any ideas?

Использование другой очереди заданий

Добрый день!
Хочу использовать другую очередь заданий, нежели yii/yii2-queue.
Вот этот: https://github.com/zhuravljov/yii2-queue

Прописал свой middleware:

'middlewares' => [
                [
                    'class' => '\common\commands\middlewares\QueuedCommandMiddleware',
                ]
            ]

Внутри middleware прописал нужные проверки класса.
Но не знаю, какой указать локатор или как сконфигурировать существующий?

**Call to a member function locate() on null**

        if ($command instanceof SelfHandlingCommand) {
            $handler = $command;
        } else {
            $handler = $this->locator->locate($command, $this);
        }

Спасибо.

Problem with LoggingMiddleware in combination with QueuedCommandMiddleware

Hi there is a problem with the logging mechanism in combination with QueuedCommandMiddleware.
The problem is the default Logger has a flush Interval of 1000.
http://www.yiiframework.com/doc-2.0/yii-log-logger.html#$flushInterval-detail

So only after 1000 logs logs like

Yii::getLogger()->log("Command execution started: {$class}", $this->level, $this->category);"

would be written to the log when a QueuedCommandMiddleware is used. Because the Process doesn't stop so the logger isn't flushed at the end.

Also there should be mentioned that if you want a logging mechanism almost realtime you also have to set the 'exportInterval' => 1, in your log target.

http://www.yiiframework.com/doc-2.0/yii-log-target.html#$exportInterval-detail

Otherwise also after 1000 log messages the log would be really written.

There should also be a flush added after each Logger::log call in the LoggingMiddleware.

Still issues with logging and loop on exceptions

Hi trntv
as mentioned in #12 id had issues with the log in queued background commands. The log was either only flushed after the default exportInterval of 1000.

And you have fixed it.
But this is still not working correctly or to be correct only works if the log target has set it´s exportInterval to 1 to export after each log.

Otherwise it also won't be exported directly.

I would prefer a function in the LoggingMiddleware which does the log and flush with final = true

    public function log($message, $level = null, $category = null)
    {
        // set default level if level isn't set
        if (!$level) {
            $level = $this->level;
        }

        // set default category if level isn't set
        if (!$category) {
            $category = $this->category;
        }

        // log
        $this->logger->log($message, $level, $category);

        // force with final = true so export is done afterwards
        if ($this->forceFlush) {
            $this->logger->flush($final = true);
        }
    }

    public function execute($command, callable $next)
    {
        $class = get_class($command);
        $this->log("Command execution started: {$class}", Logger::LEVEL_INFO);
        $result = $next($command);
        $this->log("Command execution ended: {$class}", Logger::LEVEL_INFO);
        return $result;
    }

With this code the log is done directly if forceFlush is set to true.
So at the start and end of a command the log is correctly exported.

My only problem is now I also wanted to have the log mechanism inside of the command.

I wanted to create a "live-log" to see which emails are already been sent out.

It would be cool if the running command could somehow call
$this->log("whatever") and it is automatically also exported to the log with the correct category through the LoggingMiddleware.
This was also why I have created a log function in the middleware.

Is it somehow possible to get to the LoggingMiddleware object from the Command and use the log function?

Also if I wanted to get all information of e.g. occurred errors of the QueueBusController those errors are only logged to the console output.
e.g. on actionListen console::error is used instead of log also to the Yii logger with Yii::error.

As far as i see it is also not possible to pause or abort a running background command by setting some flags.

Do you remember this issue #5 ?
There I wanted to log the onError, onSuccess etc. messages.

I still have the problem if my comand does this

  Yii::error('I am an error', 'command-bus');
  return false;

The console still outputs

Listening queue "some-queue-name"
New job ID#71
Job ID#71 was deleted from queue
Job ID#71 has been successfully done

and my Log from my LogginMiddleware

2016-09-08 15:20:54 [console][][info][command-bus] Command execution started: common\commands\SendQueuedEmailsCommand
2016-09-08 15:20:54 [console][][error][command-bus] i am an error
2016-09-08 15:20:54 [console][][info][command-bus] Command execution ended: common\commands\SendQueuedEmailsCommand

So I won't see that there was an error in my command "return was false" in the log created by LogginMiddleware.

And what`s more problematic if somehow a exception is throwed in the QueuedCommand (or the functions called) the command will be running in a loop and won't stop.
Just call in an QueuedCommand

 throw new Exception("Error Processing Request", 1);

And the process not ended or the command aborted.
I guess this is some issue in the CommandBus::createMiddlewareChain and could be fixed like below.

 protected function createMiddlewareChain($command, array $middlewareList) {

        $lastCallable = $this->createHandlerCallable($command);

        while ($middleware = array_pop($middlewareList)) {
            if (!$middleware instanceof Middleware) {
                throw new InvalidConfigException;
            }
            $lastCallable = function ($command) use ($middleware, $lastCallable) {
                try {
                    return $middleware->execute($command, $lastCallable);
                } catch (\Exception $e) {
                    yii\helpers\Console::error("Exception: ".$e->getMessage());
                    Yii::error("Exception: ".$e->getMessage(), 'command-bus');
                    Yii::getLogger()->flush($final = true);
                }
                //return $middleware->execute($command, $lastCallable);
            };
        }
        return $lastCallable;
    }

Also here it would be better if the LogginMiddleware could be called directly to directly flush the error message to the logs.

So why could the commandbus not log directly so he could log the on start/end/error/abort directly and the command should also be able to call this log function somehow through a reference to the commandbus.

Question regarding Logging

Hi i am testing out your extension yii2-command-bus.

So far everything works fine.
On the first tests I thought that I don't have to wait for the process when using Background commands with symfony but the Queued fulfilled my requirement to execute external code independent on the page rendered.

I wanted to sent e.g. 1000 email via a background process.
The process is now running with your command bus extension and an queued Command like this:

class SendQueuedEmailsCommand extends Object implements SelfHandlingCommand, QueuedCommand

My question regarding your extension are:

  • How can I log the job status (e.g. running, successful, failed) and also log the "result" or the output of my job e.g. in my job how many emails were successfully sent or failed to sent.
    I really would like to add an entry to the database table with the job id and that the result and status is updated on change. So I could have an ajax action which shows the status of the running job or have an controller to just list the scheduled jobs and their status + result.
    I looked into other extension which implemented PSR-3 Compliant Logging e.g. https://github.com/chrisboulton/php-resque/pull/115/files

If you have an example how to log the job status and result with your extension I would really appreciate this.

  • Another thing I detected that i have to restart the background console command every time i made changed to the job class. If I not restart the process the code will just run with the old and not the changed code.
    Is that a normal behavior and can this be changed somehow?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.