...
 
Commits (5)
language: php
php:
- 5.6
- 7.0
- hhvm
- 7.1
- 7.2
before_script:
- travis_retry composer self-update
......
MIT License
Copyright (c) 2016 mleko
Copyright (c) 2016-2017 Daniel Król
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
# narrator\narrator-bundle
# narrator/narrator-bundle
[![Travis CI](https://travis-ci.org/mleko/narrator-bundle.svg?branch=master)](https://travis-ci.org/mleko/narrator-bundle)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mleko/narrator-bundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mleko/narrator-bundle/?branch=master)
......@@ -50,14 +50,32 @@ If you had service
```
class UserInvitationSender {
...
public function handle($event){
public function handle(\Foo\BarBundle\UserRegistered $event){
...
}
}
```
To use it as a listener add tag `narrator.listener`. Listener tag always have to define parameter `event` which should be FQCN of event listener will listen to.
To use it as a listener add tag `narrator.listener`.
```
<service class="Foo\BarBundle\UserInvitationSender">
// .. arguments, configuration
<tag name="narrator.listener"/>
</service>
```
Listener don't have to define parameter type in handle method.
```
class UserInvitationSender {
...
public function handle($event){
...
}
}
```
Event type can be passed as tag attribute `event` which should be FQCN of event.
```
<service class="Foo\BarBundle\UserInvitationSender">
// .. arguments, configuration
......
......@@ -17,6 +17,7 @@
}
],
"require": {
"php": "^7.0",
"narrator/narrator": "^0.4",
"symfony/config": "^2.3|^3.0",
"symfony/dependency-injection": "^2.3|^3.0",
......
<?php
/**
* @package narrator-bundle
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
......@@ -8,6 +12,7 @@ namespace Mleko\Narrator\Bundle\DependencyInjection\Compiler;
use Mleko\Narrator\Bundle\Listener\ListenerService;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
......@@ -17,9 +22,9 @@ class ListenerPass implements \Symfony\Component\DependencyInjection\Compiler\Co
/**
* You can modify the container here before it is dumped to PHP code.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param ContainerBuilder $container
*/
public function process(\Symfony\Component\DependencyInjection\ContainerBuilder $container)
public function process(ContainerBuilder $container)
{
$services = $container->findTaggedServiceIds('narrator.listener');
foreach ($services as $serviceId => $tags) {
......@@ -30,22 +35,23 @@ class ListenerPass implements \Symfony\Component\DependencyInjection\Compiler\Co
}
/**
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param ContainerBuilder $container
* @param array $tag
* @param string $serviceId
*/
private function processServiceTag(\Symfony\Component\DependencyInjection\ContainerBuilder $container, $tag, $serviceId)
private function processServiceTag(ContainerBuilder $container, $tag, $serviceId)
{
if (!isset($tag['event'])) {
throw new \RuntimeException('The narrator.listener must have event attribute');
$methodName = isset($tag['method']) ? $tag['method'] : "handle";
$eventName = $this->extractEventName($container, $tag, $serviceId, $methodName);
if (null === $eventName) {
throw new UnknownEventName($serviceId);
}
$eventName = $tag['event'];
$busName = isset($tag['bus']) ? $tag['bus'] : 'default';
$methodName = isset($tag['method']) ? $tag['method'] : null;
$this->registerListener($container, $eventName, $busName, $serviceId, $methodName);
}
private function registerListener(\Symfony\Component\DependencyInjection\ContainerBuilder $container, $eventName, $emitterName, $listenerServiceId, $methodName)
private function registerListener(ContainerBuilder $container, $eventName, $emitterName, $listenerServiceId, $methodName)
{
$emitterDefinition = $container->getDefinition('narrator.event_bus.' . $emitterName);
$listeners = $emitterDefinition->getArgument(1);
......@@ -59,4 +65,32 @@ class ListenerPass implements \Symfony\Component\DependencyInjection\Compiler\Co
);
$emitterDefinition->replaceArgument(1, $listeners);
}
private function extractEventName($container, $tag, $serviceId, $methodName)
{
if (!isset($tag['event'])) {
return $this->inferEventName($container, $serviceId, $methodName);
}
return $tag['event'];
}
private function inferEventName(ContainerBuilder $container, $serviceId, $methodName)
{
$listenerClass = $container->getDefinition($serviceId)->getClass();
if (null === $listenerClass) {
return null;
}
$reflectionMethod = new \ReflectionMethod($listenerClass, $methodName);
$parameters = $reflectionMethod->getParameters();
if (count($parameters) < 1) {
return null;
}
$firstParameter = $parameters[0];
$firstParameterType = $firstParameter->getType();
if (null === $firstParameterType) {
return null;
}
return (string)$firstParameterType;
}
}
<?php
/**
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\DependencyInjection\Compiler;
class UnknownEventName extends \RuntimeException
{
/**
* UnknownEventName constructor.
* @param string $serviceId
*/
public function __construct($serviceId)
{
parent::__construct(sprintf("Unknown event name for listener: `%s`, `narrator.listener` tag don't have an `event` attribute and event name could't be inferred", $serviceId));
}
}
<?php
/**
* @package narrator-bundle
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
......
<?php
/**
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\DependencyInjection\Configuration;
......
<?php
/**
* @package narrator-bundle
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\DependencyInjection;
use Mleko\Narrator\Bundle\DependencyInjection\Configuration\EventBusConfiguration;
......
<?php
/**
* @package narrator-bundle
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
......
<?php
/**
* @package narrator-bundle
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
......
<?php
/**
* @package narrator-bundle
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\Tests\DependencyInjection\Compiler\Listener;
use Mleko\Narrator\Bundle\DependencyInjection\Compiler\UnknownEventName;
use Mleko\Narrator\Bundle\Tests\Integration\TestApp\Counter;
use Mleko\Narrator\Bundle\Tests\Integration\TestApp\StdCounter;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
class ListenerPassTest extends \PHPUnit_Framework_TestCase
{
/** @var \Symfony\Component\DependencyInjection\ContainerBuilder | \PHPUnit_Framework_MockObject_MockObject */
/** @var ContainerBuilder | \PHPUnit_Framework_MockObject_MockObject */
private $container;
/** @var \Mleko\Narrator\Bundle\DependencyInjection\Compiler\ListenerPass */
private $compilerPass;
/** @var \Symfony\Component\DependencyInjection\Definition */
/** @var Definition */
private $defaultEmitterDefinition;
public function setUp()
{
$this->container = $this->getMockBuilder(\Symfony\Component\DependencyInjection\ContainerBuilder::class)->getMock();
$this->container = $this->getMockBuilder(ContainerBuilder::class)->getMock();
$this->container = new ContainerBuilder();
$this->defaultEmitterDefinition = new \Symfony\Component\DependencyInjection\Definition(null, [null, []]);
$this->container->method('getDefinition')->with('narrator.event_bus.default')->willReturn($this->defaultEmitterDefinition);
$this->defaultEmitterDefinition = new Definition(null, [null, []]);
$this->container->setDefinition('narrator.event_bus.default', $this->defaultEmitterDefinition);
$this->compilerPass = new \Mleko\Narrator\Bundle\DependencyInjection\Compiler\ListenerPass();
}
public function testProcess()
{
$this->container->expects($this->once())->method('findTaggedServiceIds')
->willReturn([
'service1' => [
[
'event' => 'stdClass'
]
]
]);
$listenerDefinition = new Definition();
$listenerDefinition->addTag("narrator.listener", ["event" => \stdClass::class]);
$this->container->setDefinition("service1", $listenerDefinition);
$this->compilerPass->process($this->container);
$listeners = $this->defaultEmitterDefinition->getArgument(1);
$this->assertCount(1, $listeners);
$this->assertCount(1, $listeners[\stdClass::class]);
}
public function testFailOnMissingEventName()
{
$this->container->expects($this->once())->method('findTaggedServiceIds')
->willReturn([
'service1' => [
[]
]
]);
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('The narrator.listener must have event attribute');
$listenerDefinition = new Definition();
$listenerDefinition->addTag("narrator.listener");
$this->container->setDefinition("service1", $listenerDefinition);
$this->expectException(UnknownEventName::class);
$this->compilerPass->process($this->container);
}
public function testEventClassInferring()
{
$counterDefinition = new Definition(StdCounter::class);
$counterDefinition->addTag("narrator.listener");
$this->container->setDefinition("counter", $counterDefinition);
$this->compilerPass->process($this->container);
$listeners = $this->defaultEmitterDefinition->getArgument(1);
$this->assertCount(1, $listeners[\stdClass::class]);
}
public function testUntypedEventHandlerClassInferring()
{
$counterDefinition = new Definition(Counter::class);
$counterDefinition->addTag("narrator.listener");
$this->container->setDefinition("counter", $counterDefinition);
$this->expectException(UnknownEventName::class);
$this->compilerPass->process($this->container);
}
public function testParameterLessEventHandlerClassInferring()
{
$counterDefinition = new Definition(StdCounter::class);
$counterDefinition->addTag("narrator.listener", ["method" => "handleParameterLess"]);
$this->container->setDefinition("counter", $counterDefinition);
$this->expectException(UnknownEventName::class);
$this->compilerPass->process($this->container);
}
......
<?php
/**
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\Tests\Integration;
......
<?php
/**
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\Tests\Integration;
......
<?php
/**
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\Tests\Integration\TestApp;
......
<?php
/**
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\Tests\Integration\TestApp;
......
<?php
/**
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\Tests\Integration\TestApp;
class StdCounter
{
private $count = 0;
private $lastEvent;
public function handle(\stdClass $event)
{
$this->count++;
$this->lastEvent = $event;
}
public function handleParameterLess()
{
$this->count++;
}
public function getCount()
{
return $this->count;
}
public function getLastEvent()
{
return $this->lastEvent;
}
}
<?php
/**
* @package narrator-bundle
* Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/
namespace Mleko\Narrator\Bundle\Tests\Listener;
class ListenerServiceTest extends \PHPUnit_Framework_TestCase
......