...
 
Commits (5)
language: php language: php
php: php:
- 5.6
- 7.0 - 7.0
- hhvm - 7.1
- 7.2
before_script: before_script:
- travis_retry composer self-update - travis_retry composer self-update
......
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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) [![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) [![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 ...@@ -50,14 +50,32 @@ If you had service
``` ```
class UserInvitationSender { 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"> <service class="Foo\BarBundle\UserInvitationSender">
// .. arguments, configuration // .. arguments, configuration
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
} }
], ],
"require": { "require": {
"php": "^7.0",
"narrator/narrator": "^0.4", "narrator/narrator": "^0.4",
"symfony/config": "^2.3|^3.0", "symfony/config": "^2.3|^3.0",
"symfony/dependency-injection": "^2.3|^3.0", "symfony/dependency-injection": "^2.3|^3.0",
......
<?php <?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; ...@@ -8,6 +12,7 @@ namespace Mleko\Narrator\Bundle\DependencyInjection\Compiler;
use Mleko\Narrator\Bundle\Listener\ListenerService; use Mleko\Narrator\Bundle\Listener\ListenerService;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
...@@ -17,9 +22,9 @@ class ListenerPass implements \Symfony\Component\DependencyInjection\Compiler\Co ...@@ -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. * 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'); $services = $container->findTaggedServiceIds('narrator.listener');
foreach ($services as $serviceId => $tags) { foreach ($services as $serviceId => $tags) {
...@@ -30,22 +35,23 @@ class ListenerPass implements \Symfony\Component\DependencyInjection\Compiler\Co ...@@ -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 array $tag
* @param string $serviceId * @param string $serviceId
*/ */
private function processServiceTag(\Symfony\Component\DependencyInjection\ContainerBuilder $container, $tag, $serviceId) private function processServiceTag(ContainerBuilder $container, $tag, $serviceId)
{ {
if (!isset($tag['event'])) { $methodName = isset($tag['method']) ? $tag['method'] : "handle";
throw new \RuntimeException('The narrator.listener must have event attribute'); $eventName = $this->extractEventName($container, $tag, $serviceId, $methodName);
if (null === $eventName) {
throw new UnknownEventName($serviceId);
} }
$eventName = $tag['event'];
$busName = isset($tag['bus']) ? $tag['bus'] : 'default'; $busName = isset($tag['bus']) ? $tag['bus'] : 'default';
$methodName = isset($tag['method']) ? $tag['method'] : null;
$this->registerListener($container, $eventName, $busName, $serviceId, $methodName); $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); $emitterDefinition = $container->getDefinition('narrator.event_bus.' . $emitterName);
$listeners = $emitterDefinition->getArgument(1); $listeners = $emitterDefinition->getArgument(1);
...@@ -59,4 +65,32 @@ class ListenerPass implements \Symfony\Component\DependencyInjection\Compiler\Co ...@@ -59,4 +65,32 @@ class ListenerPass implements \Symfony\Component\DependencyInjection\Compiler\Co
); );
$emitterDefinition->replaceArgument(1, $listeners); $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 <?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; namespace Mleko\Narrator\Bundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder;
......
<?php <?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; namespace Mleko\Narrator\Bundle\DependencyInjection\Configuration;
......
<?php <?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; namespace Mleko\Narrator\Bundle\DependencyInjection;
use Mleko\Narrator\Bundle\DependencyInjection\Configuration\EventBusConfiguration; use Mleko\Narrator\Bundle\DependencyInjection\Configuration\EventBusConfiguration;
......
<?php <?php
/** /**
* @package narrator-bundle * Narrator Bundle
*
* @link http://github.com/mleko/narrator-bundle
* @copyright Copyright (c) 2017 Daniel Król
* @license MIT
*/ */
......
<?php <?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; namespace Mleko\Narrator\Bundle;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
......
<?php <?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; 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 class ListenerPassTest extends \PHPUnit_Framework_TestCase
{ {
/** @var \Symfony\Component\DependencyInjection\ContainerBuilder | \PHPUnit_Framework_MockObject_MockObject */ /** @var ContainerBuilder | \PHPUnit_Framework_MockObject_MockObject */
private $container; private $container;
/** @var \Mleko\Narrator\Bundle\DependencyInjection\Compiler\ListenerPass */ /** @var \Mleko\Narrator\Bundle\DependencyInjection\Compiler\ListenerPass */
private $compilerPass; private $compilerPass;
/** @var \Symfony\Component\DependencyInjection\Definition */ /** @var Definition */
private $defaultEmitterDefinition; private $defaultEmitterDefinition;
public function setUp() 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->defaultEmitterDefinition = new Definition(null, [null, []]);
$this->container->method('getDefinition')->with('narrator.event_bus.default')->willReturn($this->defaultEmitterDefinition);
$this->container->setDefinition('narrator.event_bus.default', $this->defaultEmitterDefinition);
$this->compilerPass = new \Mleko\Narrator\Bundle\DependencyInjection\Compiler\ListenerPass(); $this->compilerPass = new \Mleko\Narrator\Bundle\DependencyInjection\Compiler\ListenerPass();
} }
public function testProcess() public function testProcess()
{ {
$this->container->expects($this->once())->method('findTaggedServiceIds') $listenerDefinition = new Definition();
->willReturn([ $listenerDefinition->addTag("narrator.listener", ["event" => \stdClass::class]);
'service1' => [ $this->container->setDefinition("service1", $listenerDefinition);
[
'event' => 'stdClass'
]
]
]);
$this->compilerPass->process($this->container); $this->compilerPass->process($this->container);
$listeners = $this->defaultEmitterDefinition->getArgument(1); $listeners = $this->defaultEmitterDefinition->getArgument(1);
$this->assertCount(1, $listeners); $this->assertCount(1, $listeners[\stdClass::class]);
} }
public function testFailOnMissingEventName() public function testFailOnMissingEventName()
{ {
$this->container->expects($this->once())->method('findTaggedServiceIds') $listenerDefinition = new Definition();
->willReturn([ $listenerDefinition->addTag("narrator.listener");
'service1' => [ $this->container->setDefinition("service1", $listenerDefinition);
[]
] $this->expectException(UnknownEventName::class);
]);
$this->compilerPass->process($this->container);
$this->expectException(\RuntimeException::class); }
$this->expectExceptionMessage('The narrator.listener must have event attribute');
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); $this->compilerPass->process($this->container);
} }
......
<?php <?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; namespace Mleko\Narrator\Bundle\Tests\Integration;
......
<?php <?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; namespace Mleko\Narrator\Bundle\Tests\Integration;
......
<?php <?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; namespace Mleko\Narrator\Bundle\Tests\Integration\TestApp;
......
<?php <?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; 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 <?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; namespace Mleko\Narrator\Bundle\Tests\Listener;
class ListenerServiceTest extends \PHPUnit_Framework_TestCase class ListenerServiceTest extends \PHPUnit_Framework_TestCase
......