Classes/Core/Event/Runner/EventRunner.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php
declare(strict_types=1);

/*
 * Copyright (C)
 * Nathan Boiron <nathan.boiron@gmail.com>
 * Romain Canon <romain.hydrocanon@gmail.com>
 *
 * This file is part of the TYPO3 NotiZ project.
 * It is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either
 * version 3 of the License, or any later version.
 *
 * For the full copyright and license information, see:
 * http://www.gnu.org/licenses/gpl-3.0.html
 */

namespace CuyZ\Notiz\Core\Event\Runner;

use Closure;
use CuyZ\Notiz\Core\Definition\Tree\EventGroup\Event\EventDefinition;
use CuyZ\Notiz\Core\Event\Event;
use CuyZ\Notiz\Core\Event\Exception\CancelEventDispatch;
use CuyZ\Notiz\Core\Event\Service\EventFactory;
use CuyZ\Notiz\Core\Notification\Notification;
use CuyZ\Notiz\Core\Notification\NotificationDispatcher;
use CuyZ\Notiz\Service\ExtensionConfigurationService;
use Throwable;
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;

/**
 * This class is used as a bridge between the trigger of an event and a
 * notification dispatch.
 */
class EventRunner
{
    const SIGNAL_EVENT_WAS_DISPATCHED = 'eventWasDispatched';

    const SIGNAL_EVENT_DISPATCH_ERROR = 'eventDispatchError';

    /**
     * @var EventDefinition
     */
    protected $eventDefinition;

    /**
     * @var Dispatcher
     */
    protected $signalDispatcher;

    /**
     * @var ExtensionConfigurationService
     */
    protected $extensionConfigurationService;

    /**
     * @var NotificationDispatcher
     */
    protected $notificationDispatcher;

    /**
     * @var EventFactory
     */
    protected $eventFactory;

    /**
     * @param EventDefinition $eventDefinition
     * @param EventFactory $eventFactory
     * @param NotificationDispatcher $notificationDispatcher
     * @param Dispatcher $signalDispatcher
     * @param ExtensionConfigurationService $extensionConfigurationService
     */
    public function __construct(
        EventDefinition $eventDefinition,
        EventFactory $eventFactory,
        NotificationDispatcher $notificationDispatcher,
        Dispatcher $signalDispatcher,
        ExtensionConfigurationService $extensionConfigurationService
    ) {
        $this->eventDefinition = $eventDefinition;
        $this->signalDispatcher = $signalDispatcher;
        $this->extensionConfigurationService = $extensionConfigurationService;
        $this->notificationDispatcher = $notificationDispatcher;
        $this->eventFactory = $eventFactory;
    }

    /**
     * @param mixed ...$arguments
     */
    public function process(...$arguments)
    {
        $notifications = $this->notificationDispatcher->fetchNotifications($this->eventDefinition);

        foreach ($notifications as $notification => $dispatchCallback) {
            $event = $this->eventFactory->create($this->eventDefinition, $notification);
            $doDispatch = true;

            if (is_callable([$event, 'run'])) {
                try {
                    /** @noinspection PhpUndefinedMethodInspection */
                    $event->run(...$arguments);
                } catch (CancelEventDispatch $exception) {
                    $doDispatch = false;
                }
            }

            if ($doDispatch) {
                $this->dispatchEvent($dispatchCallback, $event, $notification);
            }

            unset($event);
        }
    }

    /**
     * Does the actual dispatch work. Two signals are sent:
     *
     * - When the dispatch ran well;
     * - If an error occurred during the dispatch.
     *
     * @see \CuyZ\Notiz\Core\Event\Runner\EventRunner::SIGNAL_EVENT_WAS_DISPATCHED
     * @see \CuyZ\Notiz\Core\Event\Runner\EventRunner::SIGNAL_EVENT_DISPATCH_ERROR
     *
     * @param Closure $callback
     * @param Event $event
     * @param Notification $notification
     *
     * @throws Throwable
     */
    protected function dispatchEvent(Closure $callback, Event $event, Notification $notification)
    {
        $exception = null;

        try {
            $callback($event);

            $this->signalDispatcher->dispatch(
                self::class,
                self::SIGNAL_EVENT_WAS_DISPATCHED,
                [$event, $notification]
            );
        } catch (Throwable $exception) {
            $this->signalDispatcher->dispatch(
                self::class,
                self::SIGNAL_EVENT_DISPATCH_ERROR,
                [$exception, $event, $notification]
            );

            $gracefulMode = $this->extensionConfigurationService->getConfigurationValue('dispatch.graceful_mode');

            if (!$gracefulMode) {
                throw $exception;
            }
        }
    }

    /**
     * @return callable
     */
    public function getCallable(): callable
    {
        return [$this, 'process'];
    }

    /**
     * @return EventDefinition
     */
    public function getEventDefinition(): EventDefinition
    {
        return $this->eventDefinition;
    }

    /**
     * This object should never be serialized, because it contains services that
     * can have properties filled with closures (a closure can't be serialized).
     *
     * We need to make sure it won't happen, because there are cases where TYPO3
     * can serialize this object, because the TYPO3 slot dispatcher contains a
     * reference to it; if a closure is registered, an exception is thrown.
     *
     * @return array
     */
    public function __sleep(): array
    {
        return [];
    }
}