Skip to content

Commit

Permalink
Merge pull request #58 from marvinosswald/event-handling
Browse files Browse the repository at this point in the history
Add Eloquent Model Event Handling Attributes e.g. #[Listener('created')]
  • Loading branch information
WendellAdriel committed Sep 26, 2023
2 parents ee20057 + a4c3f51 commit 0cca806
Show file tree
Hide file tree
Showing 19 changed files with 737 additions and 1 deletion.
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ parameters:

excludePaths:
- src/Support/PropertyInfo.php
- src/Support/MethodInfo.php
17 changes: 17 additions & 0 deletions src/Attributes/Events/Dispatches.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\Lift\Attributes\Events;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class Dispatches
{
public function __construct(
public string $eventClass,
public string $event = ''
) {
}
}
17 changes: 17 additions & 0 deletions src/Attributes/Events/Listener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\Lift\Attributes\Events;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
final class Listener
{
public function __construct(
public string $event = '',
public bool $queue = false
) {
}
}
16 changes: 16 additions & 0 deletions src/Attributes/Events/Observer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\Lift\Attributes\Events;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class Observer
{
public function __construct(
public string $observer
) {
}
}
38 changes: 38 additions & 0 deletions src/Concerns/Events/Events.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\Lift\Concerns\Events;

use WendellAdriel\Lift\Exceptions\EventDoesNotExistException;

trait Events
{
private static array $possibleEvents = [
'retrieved',
'creating',
'created',
'updating',
'updated',
'saving',
'saved',
'restoring',
'restored',
'replicating',
'deleting',
'deleted',
'forceDeleting',
'forceDeleted',
];

/**
* @throws EventDoesNotExistException
*/
private static function eventExists(string $event): void
{
$exists = in_array($event, self::$possibleEvents);
if (! $exists) {
throw new EventDoesNotExistException($event);
}
}
}
72 changes: 72 additions & 0 deletions src/Concerns/Events/ListenerHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\Lift\Concerns\Events;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use WendellAdriel\Lift\Attributes\Events\Listener;
use WendellAdriel\Lift\Exceptions\EventDoesNotExistException;

trait ListenerHandler
{
use Events;

private static ?array $modelEventMethods = null;

/**
* @throws EventDoesNotExistException
*/
private static function eventHandlerMethods(): array
{
if (is_null(self::$modelEventMethods)) {
self::buildEventHandlers(new static());
}

return self::$modelEventMethods;
}

/**
* @throws EventDoesNotExistException
*/
private static function buildEventHandlers(Model $model): void
{
self::$modelEventMethods = [];

$methods = self::getMethodsWithAttributes($model);

$methods = self::getMethodsForAttribute($methods, Listener::class);

foreach ($methods as $method) {
$attr = $method->attributes->first(fn ($attr) => $attr->getName() === Listener::class)->newInstance();
if (! empty($attr->event)) {
self::eventExists($attr->event);
self::$modelEventMethods[$attr->event] = $method;

continue;
}
if (str_starts_with($method->name, 'on')) {
$event = Str::lcfirst(substr($method->name, 2));
self::eventExists($event);
self::$modelEventMethods[$event] = $method;
}
}

}

private static function handleEvent(?Model $model, string $event): void
{
if (array_key_exists($event, self::eventHandlerMethods())) {
$eventHandler = self::eventHandlerMethods()[$event];
$attr = $eventHandler->attributes->first(fn ($attr) => $attr->getName() === Listener::class)->newInstance();
$method = $eventHandler->method;
if (! $attr->queue) {
$method->invoke($model, $model);

return;
}
dispatch(fn () => $method->invoke($model, $model));
}
}
}
65 changes: 65 additions & 0 deletions src/Concerns/Events/RegisterDispatchedEvents.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\Lift\Concerns\Events;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use ReflectionClass;
use WendellAdriel\Lift\Attributes\Events\Dispatches;
use WendellAdriel\Lift\Exceptions\EventDoesNotExistException;

trait RegisterDispatchedEvents
{
use Events;

private static ?array $modelDispatchEvents = null;

/**
* @throws EventDoesNotExistException
*/
private static function modelDispatchEvents(Model $model): array
{
if (is_null(self::$modelDispatchEvents)) {
self::buildModelDispatchEvents($model);
}

return self::$modelDispatchEvents;
}

/**
* @throws EventDoesNotExistException
*/
private static function buildModelDispatchEvents(Model $model): void
{

$classReflection = new ReflectionClass($model);
self::$modelDispatchEvents = collect($classReflection->getAttributes(Dispatches::class))
->map(fn ($attr) => $attr->newInstance())
->map(function ($attrInstance) {
if (! empty($attrInstance->event)) {
return $attrInstance;
}
$shortName = (new ReflectionClass($attrInstance->eventClass))->getShortName();
$event = collect(self::$possibleEvents)->first(fn ($event) => Str::contains($shortName, Str::ucfirst($event)));
if (is_null($event)) {
throw new EventDoesNotExistException("no valid event found in: {$shortName}");
}
$attrInstance->event = $event;

return $attrInstance;
})
->flatMap(fn ($attr) => [$attr->event => $attr->eventClass])
->toArray();

}

/**
* @throws EventDoesNotExistException
*/
private function registerDispatchedEvents(): void
{
$this->dispatchesEvents = [...self::modelDispatchEvents($this), ...$this->dispatchesEvents];
}
}
40 changes: 40 additions & 0 deletions src/Concerns/Events/RegisterObservers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\Lift\Concerns\Events;

use Illuminate\Database\Eloquent\Model;
use ReflectionClass;
use WendellAdriel\Lift\Attributes\Events\Observer;

trait RegisterObservers
{
private static ?array $modelObservers = null;

private static function modelObservers(): array
{
if (is_null(self::$modelObservers)) {
self::buildModelObservers(new static());
}

return self::$modelObservers;
}

private static function buildModelObservers(Model $model): void
{

$classReflection = new ReflectionClass($model);
self::$modelObservers = collect($classReflection->getAttributes(Observer::class))
->map(fn ($attr) => $attr->newInstance()->observer)
->toArray();

}

private static function registerObservers(): void
{
foreach (self::modelObservers() as $observer) {
self::observe($observer);
}
}
}
15 changes: 15 additions & 0 deletions src/Exceptions/EventDoesNotExistException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace WendellAdriel\Lift\Exceptions;

use Exception;

final class EventDoesNotExistException extends Exception
{
public function __construct(string $event)
{
parent::__construct("Cannot register listener / event for unknown: {$event}");
}
}
Loading

0 comments on commit 0cca806

Please sign in to comment.