eca-1.0.x-dev/src/Plugin/ECA/Event/EventBase.php
src/Plugin/ECA/Event/EventBase.php
<?php namespace Drupal\eca\Plugin\ECA\Event; use Drupal\Core\Form\FormStateInterface; use Drupal\eca\Attributes\Token; use Drupal\eca\EcaEvents; use Drupal\eca\Entity\Objects\EcaEvent; use Drupal\eca\Plugin\DataType\DataTransferObject; use Drupal\eca\Plugin\ECA\EcaPluginBase; use Drupal\eca\Plugin\ECA\PluginFormTrait; use Drupal\eca\Token\TokenInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\EventDispatcher\Event; /** * Base class for ECA event plugins. */ abstract class EventBase extends EcaPluginBase implements EventInterface { use PluginFormTrait; /** * According system event, if available. * * @var \Symfony\Contracts\EventDispatcher\Event|null */ protected ?Event $event = NULL; /** * An instance holding event data accessible as Token. * * @var \Drupal\eca\Plugin\DataType\DataTransferObject|null */ protected ?DataTransferObject $eventData = NULL; /** * The list of tokens that the event provides when dispatched. * * @var \Drupal\eca\Attributes\Token[]|null */ protected ?array $tokens = NULL; /** * The event dispatcher service. * * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ protected EventDispatcherInterface $eventDispatcher; /** * ECA token service. * * @var \Drupal\eca\Token\TokenInterface */ protected TokenInterface $token; /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->setConfiguration($configuration); $instance->eventDispatcher = $container->get('event_dispatcher'); $instance->token = $container->get('eca.service.token'); return $instance; } /** * {@inheritdoc} */ final public function eventClass(): string { return $this->pluginDefinition['event_class']; } /** * {@inheritdoc} */ final public function eventName(): string { return $this->pluginDefinition['event_name']; } /** * {@inheritdoc} */ final public function subscriberPriority(): int { return $this->pluginDefinition['subscriber_priority']; } /** * {@inheritdoc} */ public function generateWildcard(string $eca_config_id, EcaEvent $ecaEvent): string { // By default return a small wildcard that should match up for every event // that is of the class as returned by ::drupalEventClass. return '*'; } /** * {@inheritdoc} * * @throws \InvalidArgumentException * When the given wildcard does not match up with the expected pattern as * generated by ::generateWildcard. */ public static function appliesForWildcard(Event $event, string $event_name, string $wildcard): bool { // This default implementation should not be called as a parent method // from a child class. When a child class implements this method on its own, // it must contain solely the concrete appliance logic of the plugin itself. if ($wildcard !== '*') { throw new \InvalidArgumentException('The given wildcard is different than expected. If the plugin implements its own appliance logic, the parent method must not be invoked.'); } return TRUE; } /** * {@inheritdoc} */ public function setEvent(Event $event): EventInterface { $this->event = $event; return $this; } /** * {@inheritdoc} */ public function getEvent(): ?Event { return $this->event; } /** * {@inheritdoc} */ public function getTokens(): array { if ($this->tokens === NULL) { $this->getSupportedTokens(); } return $this->tokens; } /** * {@inheritdoc} */ public function defaultConfiguration(): array { return []; } /** * {@inheritdoc} */ public function getConfiguration(): array { return $this->configuration; } /** * {@inheritdoc} */ public function setConfiguration(array $configuration): EventBase { $this->configuration = $configuration + $this->defaultConfiguration(); return $this; } /** * Helper function to get token info. */ private function getSupportedTokens(): void { $sources = $this->eventDispatcher->getListeners(EcaEvents::BEFORE_INITIAL_EXECUTION); array_unshift($sources, [$this::class, 'buildEventData']); array_unshift($sources, [$this::class, 'getData']); foreach ($this->token->getDataProviders() as $dataProvider) { array_unshift($sources, [$dataProvider::class, 'buildEventData']); array_unshift($sources, [$dataProvider::class, 'getData']); } $eventClass = $this->eventClass(); $tokens = []; foreach ($sources as $source) { [$class, $methodName] = $source; try { $reflection = new \ReflectionMethod($class, $methodName); } catch (\ReflectionException) { continue; } do { foreach ($reflection->getAttributes() as $attribute) { if ($attribute->getName() === 'Drupal\eca\Attributes\Token') { /** @var \Drupal\eca\Attributes\Token $token */ $token = $attribute->newInstance(); if ($this->getSupportedProperties($eventClass, $token)) { $tokens[] = $token; } } } try { $reflection = $reflection->getPrototype(); } catch (\ReflectionException) { $reflection = NULL; } } while ($reflection !== NULL); } $this->tokens = $tokens; } /** * Recursive helper function to get token property info. * * @param string $eventClass * The event class. * @param \Drupal\eca\Attributes\Token $token * The token. * * @return bool * TRUE, if the token has any attributes, FALSE otherwise. */ private function getSupportedProperties(string $eventClass, Token $token): bool { if ($this->isClassSupported($eventClass, $token->classes)) { $properties = []; foreach ($token->properties as $property) { if ($this->getSupportedProperties($eventClass, $property)) { $properties[] = $property; } } $token->properties = $properties; return TRUE; } return FALSE; } /** * Determines if the event class is covered by the list of classes. * * @param string $eventClass * The event class. * @param array $classes * The list of classes. * * @return bool * TRUE, if either the list of classes if empty, or if the event class is * an instance of one of the given classes; FALSE otherwise. */ private function isClassSupported(string $eventClass, array $classes): bool { if (empty($classes)) { return TRUE; } foreach ($classes as $class) { if (is_a($eventClass, $class, TRUE)) { return TRUE; } } return FALSE; } /** * Helper function to render a supported token and its properties. * * @param \Drupal\eca\Attributes\Token $token * The token. * @param string $prefix * The prefix. * * @return array * The list of rendered tokens and properties. */ private function renderToken(Token $token, string $prefix = ''): array { $tokens = []; $name = $prefix . $token->name; $tokens[] = '[' . $name . ']: ' . $token->description . ($token->aliases ? ' (Alias: ' . implode(', ', $token->aliases) . ')' : ''); foreach ($token->properties as $property) { $tokens += $this->renderToken($property, $name); } return $tokens; } /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { $i = 0; foreach ($this->getTokens() as $token) { foreach ($this->renderToken($token) as $item) { $i++; $form['eca_token_' . $i] = [ '#markup' => $item, ]; } } return $this->updateConfigurationForm($form); } /** * {@inheritdoc} */ public function validateConfigurationForm(array &$form, FormStateInterface $form_state): void {} /** * {@inheritdoc} */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {} /** * {@inheritdoc} */ public function getData(string $key): mixed { if ($key === 'event') { if (!isset($this->eventData)) { $this->eventData = DataTransferObject::create($this->buildEventData()); } return $this->eventData; } return NULL; } /** * {@inheritdoc} */ public function hasData(string $key): bool { return $this->getData($key) !== NULL; } /** * Builds up associative data of the "event" token. * * At least this should provide a "machine_name" property, so that this may * be accessible as "[event:machine_name]" token. * * @return array * The associative data for the "event" token. */ #[Token( name: 'event', description: 'The event.', properties: [ new Token(name: 'machine_name', description: 'The machine name of the ECA event.'), ], )] protected function buildEventData(): array { return [ 'machine_name' => $this->eventName(), ]; } }