Skip to content

Commit

Permalink
Refactor to a single $targets array
Browse files Browse the repository at this point in the history
  • Loading branch information
inxilpro committed Dec 19, 2024
1 parent b178c35 commit 0374d75
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/Attributes/Hooks/Apply.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public function __construct(

public function applyToHook(Hook $hook): void
{
$hook->states[] = $this->state_type;
$hook->targets[] = $this->state_type;
}
}
2 changes: 1 addition & 1 deletion src/Attributes/Hooks/Listen.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public function __construct(

public function applyToHook(Hook $hook): void
{
$hook->events[] = $this->event_type;
$hook->targets[] = $this->event_type;
}
}
11 changes: 5 additions & 6 deletions src/Lifecycle/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,8 @@ public function __construct(
public function register(object $target): void
{
foreach (Reflector::getHooks($target) as $hook) {
foreach ($hook->events as $event_type) {
$this->hooks[$event_type][] = $hook;
}
foreach ($hook->states as $state_type) {
$this->hooks[$state_type][] = $hook;
foreach ($hook->targets as $fqcn) {
$this->hooks[$fqcn][] = $hook;
}
}
}
Expand Down Expand Up @@ -166,7 +163,9 @@ protected function hooksWithPrefix(Event|State $target, Phase $phase, string $pr
/** @return Collection<int, Hook> */
protected function hooksFor(Event|State $target, ?Phase $phase = null): Collection
{
return Collection::make($this->hooks[$target::class] ?? [])
return Collection::make($this->hooks)
->only(Reflector::getClassInstanceOf($target))
->flatten()
->when($phase, fn (Collection $hooks) => $hooks->filter(fn (Hook $hook) => $hook->runsInPhase($phase)));
}

Expand Down
9 changes: 3 additions & 6 deletions src/Lifecycle/Hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ public static function fromClassMethod(object $target, ReflectionMethod|string $

$hook = new static(
callback: Closure::fromCallable([$target, $method->getName()]),
events: Reflector::getEventParameters($method),
states: Reflector::getStateParameters($method),
targets: Reflector::getParameterTypes($method),
name: $method->getName(),
);

Expand All @@ -34,17 +33,15 @@ public static function fromClosure(Closure $callback): static
{
$hook = new static(
callback: $callback,
events: Reflector::getEventParameters($callback),
states: Reflector::getStateParameters($callback),
targets: Reflector::getParameterTypes($callback),
);

return Reflector::applyHookAttributes($callback, $hook);
}

public function __construct(
public Closure $callback,
public array $events = [],
public array $states = [],
public array $targets = [],
public SplObjectStorage $phases = new SplObjectStorage,
public ?string $name = null,
) {}
Expand Down
31 changes: 31 additions & 0 deletions src/Support/Reflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ public static function getStateParameters(ReflectionFunctionAbstract|Closure $me
return static::getParametersOfType(State::class, $method)->values()->all();
}

public static function getParameterTypes(ReflectionFunctionAbstract|Closure $method): array
{
$method = static::reflectFunction($method);

if (empty($parameters = $method->getParameters())) {
return [];
}

return Collection::make($parameters)
->map(fn (ReflectionParameter $parameter) => static::getParameterClassNames($parameter))
->flatten()
->filter()
->unique()
->values()
->toArray();
}

public static function applyHookAttributes(ReflectionFunctionAbstract|Closure $method, Hook $hook): Hook
{
$method = static::reflectFunction($method);
Expand Down Expand Up @@ -80,6 +97,20 @@ public static function getParametersOfType(string $type, ReflectionFunctionAbstr
->map(fn (array $names) => Arr::first($names));
}

/** @return class-string[] */
public static function getClassInstanceOf(string|object $class): array
{
$reflection = new ReflectionClass($class);

$class_and_interface_names = array_unique($reflection->getInterfaceNames());

do {
$class_and_interface_names[] = $reflection->getName();
} while ($reflection = $reflection->getParentClass());

return $class_and_interface_names;
}

protected static function reflectFunction(ReflectionFunctionAbstract|Closure $function): ReflectionFunctionAbstract
{
if ($function instanceof Closure) {
Expand Down
60 changes: 60 additions & 0 deletions tests/Feature/HooksClassHierarchyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

use Thunk\Verbs\Attributes\Hooks\On;
use Thunk\Verbs\Event;
use Thunk\Verbs\Lifecycle\Dispatcher;
use Thunk\Verbs\Lifecycle\Phase;

it('can match events by type-hinting a specific class', function () {
app(Dispatcher::class)->register(new HooksClassHierarchyTestOneListener);

expect(fn () => HooksClassHierarchyTestEvent1::fire())->toThrow(RuntimeException::class, 'one')
->and(fn () => HooksClassHierarchyTestEvent2::fire())->not->toThrow(RuntimeException::class);
});

it('can match events by type-hinting an interface', function () {
app(Dispatcher::class)->register(new HooksClassHierarchyTestInterfaceListener);

expect(fn () => HooksClassHierarchyTestEvent1::fire())->not->toThrow(RuntimeException::class)
->and(fn () => HooksClassHierarchyTestEvent2::fire())->toThrow(RuntimeException::class, 'interface');
});

it('can match all events by type-hinting the base class', function () {
app(Dispatcher::class)->register(new HooksClassHierarchyTestEveryListener);

expect(fn () => HooksClassHierarchyTestEvent1::fire())->toThrow(RuntimeException::class, 'every')
->and(fn () => HooksClassHierarchyTestEvent2::fire())->toThrow(RuntimeException::class, 'every');
});

interface HooksClassHierarchyTestInterface {}

class HooksClassHierarchyTestEvent1 extends Event {}

class HooksClassHierarchyTestEvent2 extends Event implements HooksClassHierarchyTestInterface {}

class HooksClassHierarchyTestOneListener
{
#[On(Phase::Validate)]
public static function one(HooksClassHierarchyTestEvent1 $event)
{
throw new RuntimeException('one');
}
}

class HooksClassHierarchyTestInterfaceListener
{
#[On(Phase::Validate)]
public static function interface(HooksClassHierarchyTestInterface $event)
{
throw new RuntimeException('interface');
}
}

class HooksClassHierarchyTestEveryListener
{
#[On(Phase::Validate)]
public static function every(Event $event)
{
throw new RuntimeException('every');
}
}

0 comments on commit 0374d75

Please sign in to comment.