Skip to content

Commit

Permalink
EventCollection::attach() now supports invokable objects
Browse files Browse the repository at this point in the history
  • Loading branch information
olvlvl committed Aug 29, 2015
1 parent 5398784 commit b4e6b15
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 53 deletions.
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,19 +244,44 @@ time.
## Attaching event hooks

Event hooks are attached using the `attach()` method of an event collection. The `attach()` method
is smart enough to create the event type from the parameters type. In the following example, the
event hook is attached to the `ICanBoogie\Operation::process:before` event type.
is smart enough to create the event type from the parameters type. This works with any callable: closure, invokable objects, static class methods, functions.

The following example demonstrates how a closure may be attached to a `ICanBoogie\Operation::process:before` event type.

```php
<?php

use ICanBoogie\Operation;

$events->attach(function(Operation\BeforeProcessEvent $event, Operation $operation) {
$events->attach(function(Operation\BeforeProcessEvent $event, Operation $target) {

// …

});
});
```

The following example demonstrates how an invokable object may be attached to that same event type.

```php

class ValidateOperation
{
private $rules;

public function __construct($rules)
{
$this->rules = $rules;
}

public function __invoke(Operation\BeforeProcessEvent $event, Operation $target)
{
// …
}
}

// …

$events->attach(new ValidateOperation($rules);
```


Expand Down Expand Up @@ -380,9 +405,9 @@ echo $event->count; // 0123



## Breaking an event hooks chain
## Breaking an event hook chain

The processing of an event hooks chain can be broken by an event hook using the `stop()` method:
The processing of an event hook chain can be broken by an event hook using the `stop()` method:

```php
<?php
Expand Down
66 changes: 41 additions & 25 deletions lib/EventCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ public function attach_to($target, $hook)
{
if (!is_object($target))
{
throw new \InvalidArgumentException("Target must be an object");
throw new \InvalidArgumentException("attach_to() target must be an object.");
}

self::assert_callable($hook);
Expand Down Expand Up @@ -281,41 +281,57 @@ static private function resolve_type_and_hook($type, $hook)
*/
static private function resolve_event_type_from_hook($hook)
{
if (is_array($hook))
list($event, $target) = self::resolve_hook_reflection($hook)->getParameters();

return self::get_parameter_class($target) . '::' . self::resolve_type_from_class(self::get_parameter_class($event));
}

/**
* Resolves hook reflection.
*
* @param callable $hook
*
* @return \ReflectionFunction|\ReflectionMethod
*/
static private function resolve_hook_reflection($hook)
{
if (is_object($hook))
{
$reflection = new \ReflectionMethod($hook[0], $hook[1]);
return new \ReflectionMethod($hook, '__invoke');
}
else if (is_string($hook) && strpos($hook, '::'))
{
list($class, $method) = explode('::', $hook);

$reflection = new \ReflectionMethod($class, $method);
}
else
if (is_array($hook))
{
$reflection = new \ReflectionFunction($hook);
return new \ReflectionMethod($hook[0], $hook[1]);
}

list($event, $target) = $reflection->getParameters();
if (is_string($hook) && strpos($hook, '::'))
{
list($class, $method) = explode('::', $hook);

$event_class = self::get_parameter_class($event);
$target_class = self::get_parameter_class($target);
return new \ReflectionMethod($class, $method);
}

$event_class_base = basename('/' . strtr($event_class, '\\', '/'));
$type = substr($event_class_base, 0, -5);
return new \ReflectionFunction($hook);
}

if (strpos($event_class_base, 'Before') === 0)
{
$type = hyphenate(substr($type, 6)) . ':before';
}
else
{
$type = hyphenate($type);
}
/**
* Resolves event type from its class.
*
* @param string $class
*
* @return string
*/
static private function resolve_type_from_class($class)
{
$base = basename('/' . strtr($class, '\\', '/'));

$type = strtr($type, '-', '_');
$type = substr($base, 0, -5);
$type = strpos($base, 'Before') === 0
? hyphenate(substr($type, 6)) . ':before'
: hyphenate($type);

return $target_class . '::' . $type;
return strtr($type, '-', '_');
}

/**
Expand Down
43 changes: 21 additions & 22 deletions tests/EventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

namespace ICanBoogie;

use ICanBoogie\HTTP\Dispatcher;
use ICanBoogie\EventTest\CallableInstance;
use ICanBoogie\EventTest\Hooks;
use ICanBoogie\EventTest\Target;

use ICanBoogie\EventTest\A;
use ICanBoogie\EventTest\AttachTo;
Expand All @@ -34,25 +36,27 @@ public function setUp()
EventCollection::set_instance_provider(function () use ($events) { return $events; });
}

public function testAttachFunction()
/**
* @dataProvider provide_test_event_resolving_from_hook
*
* @param mixed $hook
*/
public function test_event_resolving_from_hook($hook)
{
$eh = $this->events->attach('ICanBoogie\EventTest\hook_callback');

$this->assertEquals('ICanBoogie\HTTP\Dispatcher::dispatch:before', $eh->type);
$this->assertEquals('ICanBoogie\EventTest\Target::practice:before', $this->events->attach($hook)->type);
}

public function testAttachMethod()
public function provide_test_event_resolving_from_hook()
{
$eh = $this->events->attach('ICanBoogie\EventTest\Attach::hook_callback');

$this->assertEquals('ICanBoogie\HTTP\Dispatcher::dispatch:before', $eh->type);
}
return [

public function testAttachClosure()
{
$eh = $this->events->attach(function(Dispatcher\BeforeDispatchEvent $event, Dispatcher $target) { });
[ 'ICanBoogie\EventTest\before_target_practice' ],
[ Hooks::class . '::before_target_practice' ],
[ [ Hooks::class, 'before_target_practice' ] ],
[ function(Target\BeforePracticeEvent $event, Target $target) { } ],
[ new CallableInstance ]

$this->assertEquals('ICanBoogie\HTTP\Dispatcher::dispatch:before', $eh->type);
];
}

/**
Expand Down Expand Up @@ -80,7 +84,7 @@ public function test_attach_to()

});

$this->assertInstanceOf('ICanBoogie\EventHook', $eh);
$this->assertInstanceOf(EventHook::class, $eh);

new AttachTo\ExampleEvent($a);
new AttachTo\ExampleEvent($b);
Expand Down Expand Up @@ -147,7 +151,7 @@ public function testDetachUsingInterface()
*/
public function testDetachTypedEvent()
{
$a = new \ICanBoogie\EventTest\A;
$a = new A;

$done = null;

Expand Down Expand Up @@ -176,7 +180,7 @@ public function testDetachTypedEvent()
*/
public function testDetachTypedEventUsingInterface()
{
$a = new \ICanBoogie\EventTest\A;
$a = new A;

$done = null;

Expand Down Expand Up @@ -425,11 +429,6 @@ protected function process(array $values)
}
}

function hook_callback(Dispatcher\BeforeDispatchEvent $event, Dispatcher $target)
{

}

class Attach
{
static public function hook_callback(Dispatcher\BeforeDispatchEvent $event, Dispatcher $target)
Expand Down
11 changes: 11 additions & 0 deletions tests/EventTest/CallableInstance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace ICanBoogie\EventTest;

class CallableInstance
{
public function __invoke(Target\BeforePracticeEvent $event, Target $target)
{

}
}
11 changes: 11 additions & 0 deletions tests/EventTest/Hooks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace ICanBoogie\EventTest;

class Hooks
{
static public function before_target_practice(Target\BeforePracticeEvent $event, Target $target)
{

}
}
8 changes: 8 additions & 0 deletions tests/EventTest/Target.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace ICanBoogie\EventTest;

class Target
{

}
14 changes: 14 additions & 0 deletions tests/EventTest/Target/BeforePracticeEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace ICanBoogie\EventTest\Target;

use ICanBoogie\Event;
use ICanBoogie\EventTest\Target;

class BeforePracticeEvent extends Event
{
public function __construct(Target $target)
{
parent::__construct($target, 'practice:before');
}
}
14 changes: 14 additions & 0 deletions tests/EventTest/Target/PracticeEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace ICanBoogie\EventTest\Target;

use ICanBoogie\Event;
use ICanBoogie\EventTest\Target;

class PracticeEvent extends Event
{
public function __construct(Target $target)
{
parent::__construct($target, 'practice');
}
}
9 changes: 9 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@
* file that was distributed with this source code.
*/

namespace ICanBoogie;

/* @var $loader \Composer\Autoload\ClassLoader */

$loader = require __DIR__ . '/../vendor/autoload.php';
$loader->addPsr4('ICanBoogie\\EventTest\\', __DIR__ . '/EventTest/');

namespace ICanBoogie\EventTest;

function before_target_practice(Target\BeforePracticeEvent $event, Target $target)
{

}

0 comments on commit b4e6b15

Please sign in to comment.