Last active
October 18, 2019 07:39
-
-
Save lolychank/75bf5f4e74b48660a8aed951e5716495 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace Kafkiansky\Bot\Bus; | |
use Kafkiansky\Bot\Component\Event\EventDispatcher; | |
use Kafkiansky\Bot\Component\Event\FinishRequestEvent; | |
use Kafkiansky\Bot\Connection; | |
use Kafkiansky\Bot\Http\Request; | |
use Kafkiansky\Bot\Http\Response; | |
use Kafkiansky\Bot\NextHandler; | |
use Kafkiansky\Bot\Security\EventSubscriber\AccessDeniedEvent; | |
use Kafkiansky\Bot\Workflow; | |
use Psr\Container\ContainerInterface; | |
final class CommandBus implements BusHandlerInterface | |
{ | |
/** | |
* @var ContainerInterface | |
*/ | |
private $container; | |
/** | |
* @var CommandCollection | |
*/ | |
private $commands; | |
/** | |
* @var array | |
*/ | |
private $commandsName = []; | |
/** | |
* @var Workflow | |
*/ | |
private $workflow; | |
/** | |
* @var Connection | |
*/ | |
private $connection; | |
/** | |
* @var Request | |
*/ | |
private $request; | |
/** | |
* @var Response | |
*/ | |
private $response; | |
/** | |
* @var EventDispatcher | |
*/ | |
private $dispatcher; | |
/** | |
* @var array | |
*/ | |
private $adminCommands; | |
/** | |
* @var array | |
*/ | |
private $allCommands; | |
public function __construct(ContainerInterface $container, CommandCollection $commands, EventDispatcher $dispatcher) | |
{ | |
$this->container = $container; | |
$this->commands = $commands; | |
$this->dispatcher = $dispatcher; | |
$this->workflow = $container->get(Workflow::class); | |
$this->connection = $container->get(Connection::class); | |
$this->request = $container->get(Request::class); | |
$this->response = $container->get(Response::class); | |
$this->resolveCommands($commands->getCommands()); | |
} | |
/** | |
* @param string $command | |
* @return void | |
* @throws InvalidCommandClassException | |
* @throws NotFoundMethodException | |
* @throws \ReflectionException | |
*/ | |
public function handle(string $command) | |
{ | |
$commandObject = new \ReflectionClass($this->match($command)); | |
if (!$commandObject->implementsInterface(CommandInterface::class)) { | |
throw new InvalidCommandClassException(sprintf('Command %s must implement interface %s', | |
$commandObject->getName(), CommandInterface::class | |
)); | |
} | |
$this->dispatcher->dispatch(new AccessDeniedEvent( | |
$this->getAdminCommands(), | |
$this->getAllCommands(), | |
$this->request->getChatId(), | |
$this->request->getMessageText() | |
)); | |
$commandObject->newInstanceArgs($this->getConstructorArguments($commandObject)) | |
->execute(...$this->getExecuteMethodArguments($commandObject)); | |
$this->dispatcher->dispatch(new FinishRequestEvent( | |
$command, | |
$this->request->getChatId(), | |
$this->request->getMessageText(), | |
$this->getCommandsName() | |
)); | |
} | |
/** | |
* @param string $command | |
* @return string | |
* | |
* @throws \InvalidArgumentException | |
*/ | |
private function match(string $command): string | |
{ | |
/** @var array|Envelope[] $envelope */ | |
foreach ($this->commands->getEnvelopes() as $envelope) { | |
if ($command === $envelope->getName()) { | |
return $envelope->getClass(); | |
} | |
} | |
/** @var NextHandler $handler */ | |
foreach ($this->workflow->read() as $handler) { | |
if ($handler->previousCommand === $this->connection->getLastCommand($this->request->getChatId())) { | |
return $handler->next; | |
} | |
} | |
throw new \InvalidArgumentException(sprintf('The command %s not found in collection %s', $command, \get_class($this->commands))); | |
} | |
/** | |
* @param \ReflectionClass $command | |
* @return array | |
*/ | |
private function getConstructorArguments(\ReflectionClass $command): array | |
{ | |
$arguments = []; | |
if ($command->hasMethod('__construct')) { | |
foreach ($command->getConstructor()->getParameters() as $parameter) { | |
if ($class = $parameter->getClass()) { | |
$arguments[] = $this->container->get($class->getName()); | |
} elseif ($parameter->getClass()->newInstanceWithoutConstructor() instanceof EventDispatcher) { | |
$class = $parameter->getClass()->newInstanceWithoutConstructor(); | |
$arguments[] = new $class($this->container); | |
} else { | |
$arguments[] = $this->container->get($parameter->getName()); | |
} | |
} | |
} | |
return $arguments; | |
} | |
/** | |
* @param \ReflectionClass $command | |
* @return array | |
* @throws NotFoundMethodException | |
* @throws \ReflectionException | |
*/ | |
private function getExecuteMethodArguments(\ReflectionClass $command): array | |
{ | |
if (!$command->hasMethod('execute')) { | |
throw new NotFoundMethodException(sprintf('The %s must define method execute()', $command->getName())); | |
} | |
$arguments = []; | |
foreach ($command->getMethod('execute')->getParameters() as $parameter) { | |
if ($class = $parameter->getClass()) { | |
$arguments[] = $this->container->get($class->getName()); | |
} else { | |
$arguments[] = $this->container->get($parameter->getName()); | |
} | |
} | |
return $arguments; | |
} | |
/** | |
* @return array | |
*/ | |
private function getCommandsName(): array | |
{ | |
/** @var Envelope $envelope */ | |
foreach ($this->commands->getEnvelopes() as $envelope) { | |
$this->commandsName[] = $envelope->getName(); | |
} | |
return $this->commandsName; | |
} | |
/** | |
* @return array | |
*/ | |
public function getAdminCommands(): array | |
{ | |
return $this->adminCommands; | |
} | |
/** | |
* @return array | |
*/ | |
public function getAllCommands(): array | |
{ | |
return $this->allCommands; | |
} | |
private function resolveCommands(array $commands) | |
{ | |
foreach ($commands as $k => $v) { | |
if ($v === 'admin') { | |
$this->adminCommands[] = $k; | |
} else { | |
$this->allCommands[] = $k; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
return [ | |
Kafkiansky\Bot\Commands\StartCommand::class => 'all', | |
Kafkiansky\Bot\Commands\HelpCommand::class => 'all', | |
Kafkiansky\Bot\Commands\ListUsersCommand::class => 'admin', | |
Kafkiansky\Bot\Commands\VacanciesCommand::class => 'all', | |
Kafkiansky\Bot\Commands\CitiesCommand::class => 'all', | |
Kafkiansky\Bot\Commands\TagsCommand::class => 'all', | |
Kafkiansky\Bot\Commands\DebugInfoCommand::class => 'admin', | |
Kafkiansky\Bot\Commands\SetCitiesCommand::class => 'all', | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace Kafkiansky\Bot\Bus; | |
final class CommandCollection implements \Iterator | |
{ | |
/** | |
* @var array | |
*/ | |
private $commands = []; | |
/** | |
* @var array|Envelope[] | |
*/ | |
private $envelopes = []; | |
/** | |
* @var int|null | |
*/ | |
private $i = 0; | |
public function __construct(?array $commands) | |
{ | |
$this->commands = \array_keys($commands); | |
} | |
/** | |
* @return mixed | |
*/ | |
public function current() | |
{ | |
return $this->commands[$this->i]; | |
} | |
/** | |
* @return void | |
*/ | |
public function next(): void | |
{ | |
$this->i++; | |
} | |
/** | |
* @return int|null | |
*/ | |
public function key(): ?int | |
{ | |
return $this->i; | |
} | |
/** | |
* @return bool | |
*/ | |
public function valid(): bool | |
{ | |
return $this->i < \count($this->commands); | |
} | |
/** | |
* @return void | |
*/ | |
public function rewind(): void | |
{ | |
$this->i = 0; | |
} | |
/** | |
* @return array|Envelope[] | |
*/ | |
public function getEnvelopes(): array | |
{ | |
while ($this->valid()) { | |
$this->envelopes[] = new Envelope($this->current()); | |
$this->next(); | |
} | |
$this->rewind(); | |
return $this->envelopes; | |
} | |
/** | |
* @return array | |
*/ | |
public function getCommands(): array | |
{ | |
return $this->commands; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Kafkiansky\Bot\DI\Container; | |
use Kafkiansky\Bot\Workflow; | |
use Symfony\Component\Dotenv\Dotenv; | |
use Telegram\Bot\Api; | |
/** @var Container $container */ | |
if (\file_exists($file = __DIR__ . '/../.env')) { | |
(new Dotenv())->load($file); | |
} | |
$container->set('dbUser', getenv('DB_USER')); | |
$container->set('dbPassword', getenv('DB_PASSWORD')); | |
$container->set('dbName', getenv('DB_NAME')); | |
$container->set('dbCharset', 'utf8'); | |
$container->set('dbHost', getenv('DB_HOST')); | |
$container->set('botToken', getenv('BOT_TOKEN')); | |
$container->set('workflowpath', __DIR__ . '/workflow.json'); | |
$container->set('connection', function () use ($container) { | |
return sprintf('mysql:host=%s;dbname=%s;charset=%s', | |
$container->get('dbHost'), | |
$container->get('dbName'), | |
$container->get('dbCharset') | |
); | |
}); | |
$container->set(PDO::class, function () use ($container) { | |
$pdo = new PDO($container->get('connection'), $container->get('dbUser'), $container->get('dbPassword')); | |
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
return $pdo; | |
}); | |
$container->set(Api::class, function () use ($container) { | |
return new Api($container->get('botToken')); | |
}); | |
$container->set(Workflow::class, function () use ($container) { | |
return new Workflow($container->get('workflowpath')); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace Kafkiansky\Bot\Bus; | |
final class Envelope | |
{ | |
/** | |
* @var string | |
*/ | |
private $name; | |
/** | |
* @var string | |
*/ | |
private $class; | |
public function __construct($command) | |
{ | |
try { | |
$reflectionObject = new \ReflectionClass($command); | |
$constant = $reflectionObject->getConstant('NAME'); | |
if (\is_null($constant) || $constant === false) { | |
throw new \InvalidArgumentException(sprintf('The command %s must define constant NAME', $command)); | |
} | |
$this->name = $constant; | |
$this->class = $reflectionObject->getName(); | |
} catch (\ReflectionException $e) { | |
throw new \InvalidArgumentException(sprintf('%s is not a object', $command)); | |
} | |
} | |
/** | |
* @return string | |
*/ | |
public function getName(): string | |
{ | |
return $this->name; | |
} | |
/** | |
* @return string | |
*/ | |
public function getClass(): string | |
{ | |
return $this->class; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
use Kafkiansky\Bot\Bus\CommandBus; | |
use Kafkiansky\Bot\Bus\CommandCollection; | |
use Kafkiansky\Bot\Component\Event\EventDispatcher; | |
use Kafkiansky\Bot\DI\Container; | |
use Kafkiansky\Bot\Http\Request; | |
require __DIR__ . '/../vendor/autoload.php'; | |
if(!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'wb')); | |
/** @var Container $container */ | |
/** @var EventDispatcher $dispatcher */ | |
$container = require __DIR__ . '/../config/container.php'; | |
$dispatcher = require __DIR__ . '/../config/dispatcher.php'; | |
$commands = require __DIR__ . '/../config/commands.php'; | |
$bus = new CommandBus($container, new CommandCollection($commands), $dispatcher); | |
/** @var Request $request */ | |
$request = $container->get(Request::class); | |
preg_match('/(?<message>[a-zA-Z]+)/', $request->getMessageText(), $m); | |
$bus->handle($m['message']); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment