From 3e94c80f1f671f7de244824b25540f7fb8798b54 Mon Sep 17 00:00:00 2001 From: Lorenzo Ruozzi Date: Fri, 15 Dec 2023 15:27:44 +0100 Subject: [PATCH] Allow to choose which product to import on webhook (#194) --- config/services.xml | 1 + docs/architecture_and_customization.md | 40 +++++++++ docs/upgrade/upgrade-2.*.md | 5 ++ src/Controller/WebhookController.php | 93 +++++++++++++------- src/Event/AkeneoProductChangedEvent.php | 67 ++++++++++++++ src/Event/AkeneoProductModelChangedEvent.php | 65 ++++++++++++++ 6 files changed, 241 insertions(+), 30 deletions(-) create mode 100644 src/Event/AkeneoProductChangedEvent.php create mode 100644 src/Event/AkeneoProductModelChangedEvent.php diff --git a/config/services.xml b/config/services.xml index a57cf293..9113bf70 100644 --- a/config/services.xml +++ b/config/services.xml @@ -66,6 +66,7 @@ %webgriffe_sylius_akeneo.webhook.secret% + diff --git a/docs/architecture_and_customization.md b/docs/architecture_and_customization.md index 57ad32ad..fffdceb3 100644 --- a/docs/architecture_and_customization.md +++ b/docs/architecture_and_customization.md @@ -189,6 +189,8 @@ with the same code they have on Akeneo. ## Customize which Akeneo products to import +### Customize Akeneo products to import from the command + Each built-in importer described above implements a `getIdentiferModifiedSince()` method. This method is used to identify which Akeneo entity identifiers should be imported or reconciled when the corresponding CLI commands run. By default, those methods return all the Akeneo identifiers of entities modified since the given date. @@ -229,3 +231,41 @@ final class IdentifiersModifiedSinceSearchBuilderBuiltEventSubscriber implements } } ``` + +### Customize Akeneo products to import from the webhook + +By default, the webhook is configured to import all the Akeneo products and product model that have been created or +updated on Akeneo. But maybe you want to only import a subset of those Akeneo entities. In this case you can define an +event listener or subscriber for the `Webgriffe\SyliusAkeneoPlugin\Event\AkeneoProductChangedEvent` event and change the +value of the property ignorable of the event based on given product: + +```php +// src/EventSubscriber/AkeneoProductChangedEventSubscriber.php + +namespace App\EventSubscriber; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Webgriffe\SyliusAkeneoPlugin\Event\IdentifiersModifiedSinceSearchBuilderBuiltEvent; +use Webgriffe\SyliusAkeneoPlugin\Product\Importer as ProductImporter; +use Webgriffe\SyliusAkeneoPlugin\ProductAssociations\Importer as ProductAssociationsImporter; + +final class AkeneoProductChangedEventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [ + AkeneoProductChangedEvent::class => 'onAkeneoProductChanged', + ]; + } + + public function onAkeneoProductChanged(AkeneoProductChangedEvent $event): void + { + $akeneoProduct = $event->getAkeneoProduct(); + if ($akeneoProduct['family'] === null) { + $event->setIgnorable(true); + } + } +} +``` + +The same can be applied to the `Webgriffe\SyliusAkeneoPlugin\Event\AkeneoProductModelChangedEvent` event. diff --git a/docs/upgrade/upgrade-2.*.md b/docs/upgrade/upgrade-2.*.md index 8a08cf50..f9d33a74 100644 --- a/docs/upgrade/upgrade-2.*.md +++ b/docs/upgrade/upgrade-2.*.md @@ -5,6 +5,11 @@ nav_order: 0 parent: Upgrade --- +# Upgrade from `v2.4.0` to `v2.4.1` + +The v2.4.1 version now allow you to choose which product and product model to import through webhook entry point. +Take a look at the [customization documentation](../architecture_and_customization.html) to see how to configure it. + # Upgrade from `v2.3.0` to `v2.4.0` The v2.4.0 version introduces the Product Model importer. If you are using the webhook no changes are requested as it will be automatically enqueued on every update. diff --git a/src/Controller/WebhookController.php b/src/Controller/WebhookController.php index 8c8db488..340d2dc0 100644 --- a/src/Controller/WebhookController.php +++ b/src/Controller/WebhookController.php @@ -8,9 +8,12 @@ use Psr\Log\LoggerInterface; use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Messenger\MessageBusInterface; +use Webgriffe\SyliusAkeneoPlugin\Event\AkeneoProductChangedEvent; +use Webgriffe\SyliusAkeneoPlugin\Event\AkeneoProductModelChangedEvent; use Webgriffe\SyliusAkeneoPlugin\Message\ItemImport; use Webgriffe\SyliusAkeneoPlugin\Product\Importer as ProductImporter; use Webgriffe\SyliusAkeneoPlugin\ProductAssociations\Importer as ProductAssociationsImporter; @@ -21,7 +24,7 @@ * uuid: string, * identifier: string, * enabled: bool, - * family: string, + * family: ?string, * categories: string[], * groups: string[], * parent: ?string, @@ -64,7 +67,17 @@ public function __construct( private LoggerInterface $logger, private MessageBusInterface $messageBus, private string $secret, + private ?EventDispatcherInterface $eventDispatcher = null, ) { + if ($this->eventDispatcher === null) { + trigger_deprecation( + 'webgriffe/sylius-akeneo-plugin', + 'v2.4.1', + 'Not passing a "%s" instance to "%s" constructor is deprecated and will not be possible anymore in the next major version.', + EventDispatcherInterface::class, + self::class, + ); + } } /** @@ -117,39 +130,59 @@ public function postAction(Request $request): Response foreach ($akeneoEvents['events'] as $akeneoEvent) { $this->logger->debug(sprintf('Received event %s with id "%s"', $akeneoEvent['action'], $akeneoEvent['event_id'])); - $resource = $akeneoEvent['data']['resource']; - if (array_key_exists('identifier', $resource)) { - $productCode = $resource['identifier']; - $this->logger->debug(sprintf( - 'Dispatching product import message for %s', - $productCode, - )); - $this->messageBus->dispatch(new ItemImport( - ProductImporter::AKENEO_ENTITY, - $productCode, - )); - $this->logger->debug(sprintf( - 'Dispatching product associations import message for %s', - $productCode, - )); - $this->messageBus->dispatch(new ItemImport( - ProductAssociationsImporter::AKENEO_ENTITY, - $productCode, - )); + if (array_key_exists('identifier', $akeneoEvent['data']['resource'])) { + /** @var AkeneoEventProduct $resource */ + $resource = $akeneoEvent['data']['resource']; + $event = new AkeneoProductChangedEvent($resource, $akeneoEvent); + $this->eventDispatcher?->dispatch($event); + if (!$event->isIgnorable()) { + $this->importProduct($resource['identifier']); + } } - if (array_key_exists('code', $resource)) { - $productModelCode = $resource['code']; - $this->logger->debug(sprintf( - 'Dispatching product model import message for %s', - $productModelCode, - )); - $this->messageBus->dispatch(new ItemImport( - ProductModelImporter::AKENEO_ENTITY, - $productModelCode, - )); + if (array_key_exists('code', $akeneoEvent['data']['resource'])) { + /** @var AkeneoEventProductModel $resource */ + $resource = $akeneoEvent['data']['resource']; + $event = new AkeneoProductModelChangedEvent($resource, $akeneoEvent); + $this->eventDispatcher?->dispatch($event); + if (!$event->isIgnorable()) { + $this->importProductModel($resource['code']); + } } } return new Response(); } + + private function importProduct(string $productCode): void + { + $this->logger->debug(sprintf( + 'Dispatching product import message for %s', + $productCode, + )); + $this->messageBus->dispatch(new ItemImport( + ProductImporter::AKENEO_ENTITY, + $productCode, + )); + + $this->logger->debug(sprintf( + 'Dispatching product associations import message for %s', + $productCode, + )); + $this->messageBus->dispatch(new ItemImport( + ProductAssociationsImporter::AKENEO_ENTITY, + $productCode, + )); + } + + private function importProductModel(string $productModelCode): void + { + $this->logger->debug(sprintf( + 'Dispatching product model import message for %s', + $productModelCode, + )); + $this->messageBus->dispatch(new ItemImport( + ProductModelImporter::AKENEO_ENTITY, + $productModelCode, + )); + } } diff --git a/src/Event/AkeneoProductChangedEvent.php b/src/Event/AkeneoProductChangedEvent.php new file mode 100644 index 00000000..fe118c4f --- /dev/null +++ b/src/Event/AkeneoProductChangedEvent.php @@ -0,0 +1,67 @@ +, + * created: string, + * updated: string, + * associations: array, + * quantified_associations: array, + * } + * @psalm-type AkeneoEvent = array{ + * action: string, + * event_id: string, + * event_datetime: string, + * author: string, + * author_type: string, + * pim_source: string, + * data: array{ + * resource: AkeneoEventProduct|array + * }, + * } + */ +final class AkeneoProductChangedEvent +{ + private bool $ignorable = false; + + /** + * @param AkeneoEventProduct $akeneoProduct + * @param AkeneoEvent $akeneoEvent + */ + public function __construct( + private array $akeneoProduct, + private array $akeneoEvent, + ) { + } + + public function getAkeneoProduct(): array + { + return $this->akeneoProduct; + } + + public function getAkeneoEvent(): array + { + return $this->akeneoEvent; + } + + public function isIgnorable(): bool + { + return $this->ignorable; + } + + public function setIgnorable(bool $ignorable): void + { + $this->ignorable = $ignorable; + } +} diff --git a/src/Event/AkeneoProductModelChangedEvent.php b/src/Event/AkeneoProductModelChangedEvent.php new file mode 100644 index 00000000..25981f15 --- /dev/null +++ b/src/Event/AkeneoProductModelChangedEvent.php @@ -0,0 +1,65 @@ +, + * created: string, + * updated: string, + * associations: array, + * quantified_associations: array, + * } + * @psalm-type AkeneoEvent = array{ + * action: string, + * event_id: string, + * event_datetime: string, + * author: string, + * author_type: string, + * pim_source: string, + * data: array{ + * resource: array|AkeneoEventProductModel + * }, + * } + */ +final class AkeneoProductModelChangedEvent +{ + private bool $ignorable = false; + + /** + * @param AkeneoEventProductModel $akeneoProductModel + * @param AkeneoEvent $akeneoEvent + */ + public function __construct( + private array $akeneoProductModel, + private array $akeneoEvent, + ) { + } + + public function getAkeneoProductModel(): array + { + return $this->akeneoProductModel; + } + + public function getAkeneoEvent(): array + { + return $this->akeneoEvent; + } + + public function isIgnorable(): bool + { + return $this->ignorable; + } + + public function setIgnorable(bool $ignorable): void + { + $this->ignorable = $ignorable; + } +}