File "Projectionist.php"
Full Path: /var/www/html/back/vendor/spatie/laravel-event-sourcing/src/Projectionist.php
File size: 10.86 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace Spatie\EventSourcing;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Spatie\EventSourcing\EventHandlers\EventHandler;
use Spatie\EventSourcing\EventHandlers\EventHandlerCollection;
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;
use Spatie\EventSourcing\Events\EventHandlerFailedHandlingEvent;
use Spatie\EventSourcing\Events\FinishedEventReplay;
use Spatie\EventSourcing\Events\StartingEventReplay;
use Spatie\EventSourcing\Exceptions\InvalidEventHandler;
use Spatie\EventSourcing\StoredEvents\Repositories\StoredEventRepository;
use Spatie\EventSourcing\StoredEvents\StoredEvent;
class Projectionist
{
protected EventHandlerCollection $projectors;
protected EventHandlerCollection $reactors;
protected bool $catchExceptions;
protected bool $isProjecting = false;
protected bool $isReplaying = false;
public function __construct(array $config)
{
$this->projectors = new EventHandlerCollection();
$this->reactors = new EventHandlerCollection();
$this->catchExceptions = $config['catch_exceptions'];
}
public function fake(string $originalHandlerClass, string $fakeHandlerClass): void
{
$this
->removeEventHandler($originalHandlerClass)
->addEventHandler($fakeHandlerClass);
}
public function addProjector(string | Projector $projector): Projectionist
{
if (is_string($projector)) {
$projector = app($projector);
}
if (! $projector instanceof Projector) {
throw InvalidEventHandler::notAProjector($projector);
}
$this->projectors->addEventHandler($projector);
return $this;
}
public function removeProjector(string $projectorClass): Projectionist
{
$this->projectors->remove([$projectorClass]);
return $this;
}
public function allEventHandlers(): EventHandlerCollection
{
return $this->projectors->merge($this->reactors);
}
public function withoutEventHandlers(?array $eventHandlers = null): Projectionist
{
if (is_null($eventHandlers)) {
$this->projectors = new EventHandlerCollection();
$this->reactors = new EventHandlerCollection();
return $this;
}
$eventHandlers = Arr::wrap($eventHandlers);
$this->projectors->remove($eventHandlers);
$this->reactors->remove($eventHandlers);
return $this;
}
public function withoutEventHandler(string $eventHandler): Projectionist
{
return $this->withoutEventHandlers([$eventHandler]);
}
public function addProjectors(array $projectors): Projectionist
{
foreach ($projectors as $projector) {
$this->addProjector($projector);
}
return $this;
}
public function getProjectors(): Collection
{
return $this->projectors;
}
public function getProjector(string $name): ?Projector
{
return $this->projectors->first(fn (Projector $projector) => $projector->getName() === $name);
}
public function getAsyncProjectorsFor(StoredEvent $storedEvent): Collection
{
return $this->projectors
->forEvent($storedEvent)
->asyncEventHandlers($storedEvent);
}
public function addReactor($reactor): Projectionist
{
if (is_string($reactor)) {
$reactor = app($reactor);
}
if (! $reactor instanceof EventHandler) {
throw InvalidEventHandler::notAnEventHandler($reactor);
}
$this->reactors->addEventHandler($reactor);
return $this;
}
public function addReactors(array $reactors): Projectionist
{
foreach ($reactors as $reactor) {
$this->addReactor($reactor);
}
return $this;
}
public function removeReactor(string $reactorClass): Projectionist
{
$this->reactors->remove([$reactorClass]);
return $this;
}
public function getReactors(): Collection
{
return $this->reactors;
}
public function getReactorsFor(StoredEvent $storedEvent): Collection
{
return $this->reactors->forEvent($storedEvent);
}
public function addEventHandler($eventHandlerClass)
{
if (! is_string($eventHandlerClass)) {
$eventHandlerClass = get_class($eventHandlerClass);
}
if (is_subclass_of($eventHandlerClass, Projector::class)) {
$this->addProjector($eventHandlerClass);
return;
}
if (is_subclass_of($eventHandlerClass, EventHandler::class)) {
$this->addReactor($eventHandlerClass);
return;
}
throw InvalidEventHandler::notAnEventHandlingClassName($eventHandlerClass);
}
public function removeEventHandler(string $eventHandlerClass): self
{
if (is_subclass_of($eventHandlerClass, Projector::class)) {
$this->removeProjector($eventHandlerClass);
return $this;
}
if (is_subclass_of($eventHandlerClass, EventHandler::class)) {
$this->removeReactor($eventHandlerClass);
return $this;
}
throw InvalidEventHandler::notAnEventHandlingClassName($eventHandlerClass);
}
public function addEventHandlers(array $eventHandlers): self
{
foreach ($eventHandlers as $eventHandler) {
$this->addEventHandler($eventHandler);
}
return $this;
}
/**
* @param array|Collection|LazyCollection $events
*/
public function handleStoredEvents($events): void
{
collect($events)
->each(fn (StoredEvent $storedEvent) => $this->handleWithSyncEventHandlers($storedEvent))
->each(fn (StoredEvent $storedEvent) => $this->handle($storedEvent));
}
public function handle(StoredEvent $storedEvent): void
{
$projectors = $this->projectors
->forEvent($storedEvent)
->asyncEventHandlers($storedEvent);
$this->applyStoredEventToProjectors(
$storedEvent,
$projectors
);
$reactors = $this->reactors
->forEvent($storedEvent)
->asyncEventHandlers($storedEvent);
$this->applyStoredEventToReactors(
$storedEvent,
$reactors
);
}
public function handleWithSyncEventHandlers(StoredEvent $storedEvent): void
{
$projectors = $this->projectors
->forEvent($storedEvent)
->syncEventHandlers($storedEvent);
$this->applyStoredEventToProjectors($storedEvent, $projectors);
$reactors = $this->reactors
->forEvent($storedEvent)
->syncEventHandlers($storedEvent);
$this->applyStoredEventToReactors($storedEvent, $reactors);
}
public function isProjecting(): bool
{
return $this->isProjecting;
}
private function applyStoredEventToProjectors(StoredEvent $storedEvent, Collection $projectors): void
{
$this->isProjecting = true;
$projectors
->sortBy(fn (EventHandler $eventHandler) => $eventHandler->getWeight($storedEvent))
->each(function (EventHandler $projector) use ($storedEvent): void {
$this->callEventHandler($projector, $storedEvent);
});
$this->isProjecting = false;
}
private function applyStoredEventToReactors(StoredEvent $storedEvent, Collection $reactors): void
{
$reactors
->sortBy(fn (EventHandler $eventHandler) => $eventHandler->getWeight($storedEvent))
->each(function (EventHandler $reactor) use ($storedEvent): void {
$this->callEventHandler($reactor, $storedEvent);
});
}
private function callEventHandler(EventHandler $eventHandler, StoredEvent $storedEvent): bool
{
/**
* We "refresh" an event handler every time it's called, to ensure that its dependencies are properly re-injected.
* The underlying problem is with tests: if we're faking an injected dependency when running tests and the handler is already resolved beforehand,
* we won't get those faked dependencies.
*
* A better solution is to store event handler class names instead of an instantiated version of them in the list of handlers, which requires a larger and complex refactor.
*
* More info here: https://github.com/spatie/laravel-event-sourcing/discussions/181
*
* Note that we provided `Projectionist::fake` to counter this issue, but it turned out to be very cumbersome to use in complex examples,
* which is why we reverted back to the original idea.
*
* @var \Spatie\EventSourcing\EventHandlers\EventHandler $eventHandler
*/
$eventHandler = app($eventHandler::class);
try {
$eventHandler->handle($storedEvent);
} catch (Exception $exception) {
if (! $this->catchExceptions) {
throw $exception;
}
$eventHandler->handleException($exception);
event(new EventHandlerFailedHandlingEvent($eventHandler, $storedEvent, $exception));
return false;
}
return true;
}
public function isReplaying(): bool
{
return $this->isReplaying;
}
public function replay(
Collection $projectors,
int $startingFromEventId = 0,
?callable $onEventReplayed = null,
?string $aggregateUuid = null
): void {
$events = collect($projectors->toArray())->map(fn (Projector $projector) => $projector->getEventHandlingMethods()->keys())->flatten()->toArray();
$projectors = (new EventHandlerCollection($projectors))
->sortBy(fn (EventHandler $eventHandler) => $eventHandler->getWeight(null));
$this->isReplaying = true;
if ($startingFromEventId === 0) {
$projectors->each(function (Projector $projector) use ($aggregateUuid) {
if (method_exists($projector, 'resetState')) {
$projector->resetState($aggregateUuid);
}
});
}
event(new StartingEventReplay($projectors));
$projectors->call('onStartingEventReplay');
app(StoredEventRepository::class)
->runForAllStartingFrom($startingFromEventId, function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
$this->applyStoredEventToProjectors(
$storedEvent,
$projectors->forEvent($storedEvent)
);
if ($onEventReplayed) {
$onEventReplayed($storedEvent);
}
}, 1000, $aggregateUuid, $events);
$this->isReplaying = false;
event(new FinishedEventReplay());
$projectors->call('onFinishedEventReplay');
}
}