Botman: Using PSR-11 ContainerInterface for resolving command dependencies

Created on 15 Feb 2018  路  4Comments  路  Source: botman/botman

Hello. BotMan's commands act like Controllers in MVC. It would be nice if BotMan could support controller-as-a-service pattern with ability to inject dependencies into command constructor.

Benefits:

  • Better separation of concerns
  • Easier unit testing

Examples:

We can use framework-agnostic interface for DI container:
https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md

It is possible to implement this feature without BC break:
1) Add setContainer(ContainerInterface $container) method to BotMan class.
2) Retrieve a service from container only if container is set and not null. I suggest to modify this part: https://github.com/botman/botman/blob/master/src/BotMan.php#L634+L651

    protected function getCallable($callback)
    {
        if ($callback instanceof Closure) {
            return $callback;
        }

        if (is_array($callback)) {
            return $callback;
        }

        if (strpos($callback, '@') === false) {
            $callback = $this->makeInvokableAction($callback);
        }

        list($class, $method) = explode('@', $callback);

-      return [new $class($this), $method];
+      $command = $this->container ? $this->container->get($class) : new $class($this);
+
+      return [$command, $method];
    }

I can create PR with tests for this feature.

All 4 comments

Hey,

yeah, a PR for this would be great.
This is a great addition to the library.

Just encountered this PSR-11 container-based functionality, awesome!

Is it correct that such an approach is not mentioned in the documentation?

$botman->setContainer($dependencyInjectionContainer);
$botman->hears('hello', \Project\BotMan\Callables\Hello::class);

namespace Project\BotMan\Callables;
use BotMan\BotMan\BotMan;
class Hello
{
    public function __invoke(BotMan $bot): void
    {
        $bot->reply('Hello World');
    }
};

@holtkamp Yes, this is not mentioned in the documentation. Autowiring only works for constructor arguments, for example:

class SendEmailCommand
{
    public function __construct(EmailSender $emailSender)
    {
        $this->emailSender = $emailSender;
    }

    public function __invoke(BotMan $bot): void
    {
        $this->emailSender->send(...);
    }
}

$botman->setContainer($dependencyInjectionContainer);
$botman->hears('send', \App\Command\SendEmailCommand::class);

Thanks! Works great in combination with http://php-di.org

@mpociot might be an idea to open a "low-priority" issue to add documentation for this? Maybe the examples above already suffice to get people on the right path 馃

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lostertschnig picture lostertschnig  路  3Comments

kenkit picture kenkit  路  5Comments

Ahmard picture Ahmard  路  5Comments

iNilo picture iNilo  路  3Comments

hossinasaadi picture hossinasaadi  路  4Comments