From 5e616b08d4de4d8f4afdd4a4e540bd44e8be9911 Mon Sep 17 00:00:00 2001 From: Jochem Klaver Date: Mon, 1 Nov 2021 15:45:46 +0100 Subject: [PATCH] Add preBatchAction to Admin Extension and add a pre_batch_action event This adds a preBatchAction() method to the AbstractAdminExtension and AdminExtensionInterface (BC). This enables the AdminEventExtension to dispatch a BatchActionEvent of type pre_batch_action. Both the extension and the event provide points to hook into within your own code, just like this is possible for pre/post persist/update/delete. Fixes #7516 "Implement an event for batch actions" Relates to #6550 "Delete Event isn't triggered in Batch delete" --- src/Admin/AbstractAdminExtension.php | 9 ++ src/Admin/AdminExtensionInterface.php | 11 +++ src/Controller/CRUDController.php | 6 ++ src/Event/AdminEventExtension.php | 8 ++ src/Event/BatchActionEvent.php | 122 ++++++++++++++++++++++++ tests/Event/AdminEventExtensionTest.php | 34 +++++++ 6 files changed, 190 insertions(+) create mode 100644 src/Event/BatchActionEvent.php diff --git a/src/Admin/AbstractAdminExtension.php b/src/Admin/AbstractAdminExtension.php index b8162b110ae..3d3ad0b64a8 100644 --- a/src/Admin/AbstractAdminExtension.php +++ b/src/Admin/AbstractAdminExtension.php @@ -96,6 +96,15 @@ public function configureBatchActions(AdminInterface $admin, array $actions): ar return $actions; } + // NEXT_MAJOR: Remove the PHPDoc block as the interface will then specify the types + /** + * @param mixed[] $idx + * @phpstan-param AdminInterface $admin + */ + public function preBatchAction(AdminInterface $admin, string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void + { + } + public function configureExportFields(AdminInterface $admin, array $fields): array { return $fields; diff --git a/src/Admin/AdminExtensionInterface.php b/src/Admin/AdminExtensionInterface.php index 303795d8630..95a077da434 100644 --- a/src/Admin/AdminExtensionInterface.php +++ b/src/Admin/AdminExtensionInterface.php @@ -24,6 +24,10 @@ /** * @author Thomas Rabaix * + * NEXT_MAJOR: Uncomment the actual definition of below methods inside the interface and remove these annotations. + * + * @method void preBatchAction(AdminInterface $admin, string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false) + * * @phpstan-template T of object */ interface AdminExtensionInterface @@ -127,6 +131,13 @@ public function getAccessMapping(AdminInterface $admin): array; */ public function configureBatchActions(AdminInterface $admin, array $actions): array; + // NEXT_MAJOR: Uncomment the method definition + ///** + // * @param mixed[] $idx + // * @phpstan-param AdminInterface $admin + // */ + //public function preBatchAction(AdminInterface $admin, string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void; + /** * Get a chance to modify export fields. * diff --git a/src/Controller/CRUDController.php b/src/Controller/CRUDController.php index fe79f2a47be..470da68dc7d 100644 --- a/src/Controller/CRUDController.php +++ b/src/Controller/CRUDController.php @@ -469,6 +469,12 @@ public function batchAction(Request $request): Response $query->setMaxResults(null); $this->admin->preBatchAction($action, $query, $idx, $allElements); + foreach ($this->admin->getExtensions() as $extension) { + // NEXT_MAJOR: Remove the if-statement around the call to `$extension->preBatchAction()` + if (method_exists($extension, 'preBatchAction')) { + $extension->preBatchAction($this->admin, $action, $query, $idx, $allElements); + } + } if (\count($idx) > 0) { $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx); diff --git a/src/Event/AdminEventExtension.php b/src/Event/AdminEventExtension.php index 2608dd8a9aa..ccf205f6d4d 100644 --- a/src/Event/AdminEventExtension.php +++ b/src/Event/AdminEventExtension.php @@ -126,4 +126,12 @@ public function postRemove(AdminInterface $admin, object $object): void 'sonata.admin.event.persistence.post_remove' ); } + + public function preBatchAction(AdminInterface $admin, string $actionName, ProxyQueryInterface $query, array &$idx, bool $allElements = false): void + { + $this->eventDispatcher->dispatch( + new BatchActionEvent($admin, BatchActionEvent::TYPE_PRE_BATCH_ACTION, $actionName, $query, $idx, $allElements), + 'sonata.admin.event.batch_action.pre_batch_action' + ); + } } diff --git a/src/Event/BatchActionEvent.php b/src/Event/BatchActionEvent.php new file mode 100644 index 00000000000..52a4b879eb0 --- /dev/null +++ b/src/Event/BatchActionEvent.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Sonata\AdminBundle\Event; + +use Sonata\AdminBundle\Admin\AdminInterface; +use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * This event is sent by hook: + * - preBatchAction. + * + * You can register the listener to the event dispatcher by using: + * - sonata.admin.event.batch_action.pre_batch_action) + * - sonata.admin.event.batch_action.[admin_code].pre_batch_action (not implemented yet) + * + * @author Jochem Klaver + * + * @phpstan-template T of object + */ +final class BatchActionEvent extends Event +{ + public const TYPE_PRE_BATCH_ACTION = 'pre_batch_action'; + public const TYPE_POST_BATCH_ACTION = 'post_batch_action'; // not implemented yet + + /** + * @var AdminInterface + * @phpstan-var AdminInterface + */ + private $admin; + + /** + * @var string + * @phpstan-var self::TYPE_* + */ + private $type; + + /** + * @var string + */ + private $actionName; + + /** + * @var ProxyQueryInterface + */ + private $proxyQuery; + + /** + * @var mixed[] + */ + private $idx; + + /** + * @var bool + */ + private $allElements; + + /** + * @param mixed[] $idx + * @phpstan-param AdminInterface $admin + * @phpstan-param self::TYPE_* $type + */ + public function __construct(AdminInterface $admin, string $type, string $actionName, ProxyQueryInterface $proxyQuery, array &$idx, bool $allElements = false) + { + $this->admin = $admin; + $this->type = $type; + $this->actionName = $actionName; + $this->proxyQuery = $proxyQuery; + $this->idx = &$idx; + $this->allElements = $allElements; + } + + /** + * @phpstan-return AdminInterface + */ + public function getAdmin(): AdminInterface + { + return $this->admin; + } + + /** + * @phpstan-return self::TYPE_* + */ + public function getType(): string + { + return $this->type; + } + + public function getActionName(): string + { + return $this->actionName; + } + + public function getProxyQuery(): ProxyQueryInterface + { + return $this->proxyQuery; + } + + /** + * @return mixed[] + */ + public function &getIdx(): array + { + return $this->idx; + } + + public function isAllElements(): bool + { + return $this->allElements; + } +} diff --git a/tests/Event/AdminEventExtensionTest.php b/tests/Event/AdminEventExtensionTest.php index 0d3767f2963..08e276c46e9 100644 --- a/tests/Event/AdminEventExtensionTest.php +++ b/tests/Event/AdminEventExtensionTest.php @@ -24,6 +24,7 @@ use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; use Sonata\AdminBundle\Event\AdminEventExtension; +use Sonata\AdminBundle\Event\BatchActionEvent; use Sonata\AdminBundle\Event\ConfigureEvent; use Sonata\AdminBundle\Event\ConfigureQueryEvent; use Sonata\AdminBundle\Event\PersistenceEvent; @@ -189,4 +190,37 @@ public function testPostRemove(): void static::equalTo('sonata.admin.event.persistence.post_remove'), ])->postRemove($this->createMock(AdminInterface::class), new \stdClass()); } + + public function testPreBatchAction(): void + { + $admin = $this->createMock(AdminInterface::class); + $proxyQuery = $this->createMock(ProxyQueryInterface::class); + $idx = [1, 2, 3]; + + $this->getExtension([ + static::callback( + static function (Event $event) use (&$idx): bool { + if (!$event instanceof BatchActionEvent) { + return false; + } + + if (BatchActionEvent::TYPE_PRE_BATCH_ACTION !== $event->getType()) { + return false; + } + + if ('delete' !== $event->getActionName()) { + return false; + } + + $idx[] = 4; // Test if this was passed by reference correctly everywhere + if ($event->getIdx() !== $idx) { + return false; + } + + return true; + } + ), + static::equalTo('sonata.admin.event.batch_action.pre_batch_action'), + ])->preBatchAction($admin, 'delete', $proxyQuery, $idx, false); + } }