Skip to content

Commit c888f54

Browse files
author
roadiz-ci
committed
refactor(controller): align bulk node sources deletion logic with classic deletion
1 parent e0fb607 commit c888f54

3 files changed

Lines changed: 151 additions & 15 deletions

File tree

src/Controller/AbstractAdminController.php

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
2323
use Symfony\Contracts\Translation\TranslatorInterface;
2424

25+
/**
26+
* @template TEntity of PersistableInterface
27+
*/
2528
abstract class AbstractAdminController extends AbstractController
2629
{
2730
protected array $assignation = [];
@@ -46,11 +49,17 @@ protected function additionalAssignation(Request $request): void
4649
];
4750
}
4851

52+
/**
53+
* @param TEntity $item
54+
*/
4955
protected function prepareWorkingItem(PersistableInterface $item): void
5056
{
5157
// Add or modify current working item.
5258
}
5359

60+
/**
61+
* @return ObjectRepository<TEntity>
62+
*/
5463
protected function getRepository(): ObjectRepository
5564
{
5665
return $this->managerRegistry->getRepository($this->getEntityClass());
@@ -157,7 +166,7 @@ public function addAction(Request $request): ?Response
157166

158167
public function editAction(Request $request, int|string $id): ?Response
159168
{
160-
/** @var mixed|object|null $item */
169+
/** @var TEntity|null $item */
161170
$item = $this->getRepository()->find($id);
162171
if (!($item instanceof PersistableInterface)) {
163172
throw $this->createNotFoundException();
@@ -211,7 +220,7 @@ public function editAction(Request $request, int|string $id): ?Response
211220

212221
public function deleteAction(Request $request, int|string $id): ?Response
213222
{
214-
/** @var mixed|object|null $item */
223+
/** @var TEntity|null $item */
215224
$item = $this->getRepository()->find($id);
216225

217226
if (!($item instanceof PersistableInterface)) {
@@ -269,14 +278,17 @@ abstract protected function supports(PersistableInterface $item): bool;
269278
*/
270279
abstract protected function getNamespace(): string;
271280

281+
/**
282+
* @return TEntity
283+
*/
272284
abstract protected function createEmptyItem(Request $request): PersistableInterface;
273285

274286
abstract protected function getTemplateFolder(): string;
275287

276288
abstract protected function getRequiredRole(): string;
277289

278290
/**
279-
* @return class-string<PersistableInterface>
291+
* @return class-string<TEntity>
280292
*/
281293
abstract protected function getEntityClass(): string;
282294

@@ -327,6 +339,9 @@ protected function getDefaultRouteParameters(): array
327339

328340
abstract protected function getEditRouteName(): string;
329341

342+
/**
343+
* @param TEntity $item
344+
*/
330345
protected function getPostSubmitResponse(
331346
PersistableInterface $item,
332347
bool $forceDefaultEditRoute = false,
@@ -374,13 +389,19 @@ protected function getPostSubmitResponse(
374389
));
375390
}
376391

392+
/**
393+
* @param TEntity $item
394+
*/
377395
protected function getEditRouteParameters(PersistableInterface $item): array
378396
{
379397
return [
380398
'id' => $item->getId(),
381399
];
382400
}
383401

402+
/**
403+
* @param TEntity $item
404+
*/
384405
protected function getPostDeleteResponse(PersistableInterface $item): Response
385406
{
386407
return $this->redirect($this->urlGenerator->generate(
@@ -390,11 +411,11 @@ protected function getPostDeleteResponse(PersistableInterface $item): Response
390411
}
391412

392413
/**
393-
* @template T of object|Event
414+
* @template TEvent of object|Event
394415
*
395-
* @param T|iterable<T>|array<int, T>|null $event
416+
* @param TEvent|iterable<TEvent>|list<TEvent>|null $event
396417
*
397-
* @return T|iterable<T>|array<int, T>|null
418+
* @return TEvent|iterable<TEvent>|list<TEvent>|null
398419
*/
399420
protected function dispatchSingleOrMultipleEvent(mixed $event): object|array|null
400421
{
@@ -406,7 +427,7 @@ protected function dispatchSingleOrMultipleEvent(mixed $event): object|array|nul
406427
}
407428
if (\is_iterable($event)) {
408429
$events = [];
409-
/** @var T|null $singleEvent */
430+
/** @var TEvent|null $singleEvent */
410431
foreach ($event as $singleEvent) {
411432
$returningEvent = $this->dispatchSingleOrMultipleEvent($singleEvent);
412433
if ($returningEvent instanceof Event) {
@@ -460,15 +481,23 @@ protected function createDeleteEvent(PersistableInterface $item)
460481
}
461482

462483
/**
484+
* @param TEntity $item
485+
*
463486
* @return Event|Event[]|null
464487
*/
465488
protected function createPostDeleteEvent(PersistableInterface $item)
466489
{
467490
return null;
468491
}
469492

493+
/**
494+
* @param TEntity $item
495+
*/
470496
abstract protected function getEntityName(PersistableInterface $item): string;
471497

498+
/**
499+
* @param TEntity $item
500+
*/
472501
protected function denyAccessUnlessItemGranted(PersistableInterface $item): void
473502
{
474503
// Do nothing

src/Controller/AbstractAdminWithBulkController.php

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@
1616
use Symfony\Component\HttpFoundation\Request;
1717
use Symfony\Component\HttpFoundation\Response;
1818
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
19+
use Symfony\Contracts\EventDispatcher\Event;
1920
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
2021
use Symfony\Contracts\Translation\TranslatorInterface;
2122

23+
/**
24+
* @template TEntity of PersistableInterface
25+
*
26+
* @extends AbstractAdminController<TEntity>
27+
*/
2228
abstract class AbstractAdminWithBulkController extends AbstractAdminController
2329
{
2430
public function __construct(
@@ -108,8 +114,9 @@ protected function parseFormBulkIds(?FormInterface $form): array
108114
}
109115

110116
/**
111-
* @param callable(string): FormInterface $createBulkFormWithIds
112-
* @param callable(PersistableInterface, FormInterface): void $alterItemCallable
117+
* @param callable(string): FormInterface $createBulkFormWithIds
118+
* @param callable(TEntity, FormInterface): void $alterItemCallable
119+
* @param (callable(TEntity): (Event|Event[]|null))|null $onEachItemEventCallable
113120
*
114121
* @throws \Twig\Error\RuntimeError
115122
*/
@@ -123,6 +130,7 @@ protected function bulkAction(
123130
string $confirmMessageTemplate,
124131
callable $alterItemCallable,
125132
string $bulkFormName,
133+
?callable $onEachItemEventCallable = null,
126134
): Response {
127135
$this->denyAccessUnlessGranted($requiredRole);
128136
$bulkForm->handleRequest($request);
@@ -151,16 +159,29 @@ protected function bulkAction(
151159
if (count($ids) < 1) {
152160
$form->addError(new FormError('No item selected.'));
153161
} else {
154-
/** @var PersistableInterface[] $items */
162+
/** @var TEntity[] $items */
155163
$items = $this->getRepository()->findBy([
156164
'id' => $ids,
157165
]);
158166
foreach ($items as $item) {
159167
if ($this->supports($item)) {
168+
/*
169+
* Method to alter each item before persisting it.
170+
*/
160171
$alterItemCallable($item, $form);
161-
$updateEvent = $this->createUpdateEvent($item);
162-
if (null !== $updateEvent) {
163-
$this->dispatchSingleOrMultipleEvent($updateEvent);
172+
173+
/*
174+
* Dispatch event for each item
175+
*/
176+
if (is_callable($onEachItemEventCallable)) {
177+
$events = $onEachItemEventCallable($item);
178+
} else {
179+
// Default to update event
180+
$events = $this->createUpdateEvent($item);
181+
}
182+
183+
if (null !== $events) {
184+
$this->dispatchSingleOrMultipleEvent($events);
164185
}
165186
$msg = $this->translator->trans(
166187
$confirmMessageTemplate,
@@ -203,10 +224,17 @@ public function bulkDeleteAction(Request $request): Response
203224
]),
204225
$this->getTemplateFolder().'/bulk_delete.html.twig',
205226
'%namespace%.%item%.was_deleted',
227+
/**
228+
* @param TEntity $item
229+
*/
206230
function (PersistableInterface $item) {
207231
$this->removeItem($item);
208232
},
209-
'bulkDeleteForm'
233+
'bulkDeleteForm',
234+
/**
235+
* @param TEntity $item
236+
*/
237+
fn (PersistableInterface $item) => $this->createDeleteEvent($item),
210238
);
211239
}
212240

@@ -224,6 +252,9 @@ public function bulkPublishAction(Request $request): Response
224252
]),
225253
$this->getTemplateFolder().'/bulk_publish.html.twig',
226254
'%namespace%.%item%.was_published',
255+
/**
256+
* @param TEntity $item
257+
*/
227258
function (PersistableInterface $item) {
228259
$this->setPublishedAt($item, new \DateTime('now'));
229260
},
@@ -245,6 +276,9 @@ public function bulkUnpublishAction(Request $request): Response
245276
]),
246277
$this->getTemplateFolder().'/bulk_unpublish.html.twig',
247278
'%namespace%.%item%.was_unpublished',
279+
/**
280+
* @param TEntity $item
281+
*/
248282
function (PersistableInterface $item) {
249283
$this->setPublishedAt($item, null);
250284
},

src/Controller/AbstractSingleNodeTypeController.php

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@
88
use Doctrine\Persistence\ManagerRegistry;
99
use Doctrine\Persistence\ObjectRepository;
1010
use RZ\Roadiz\Core\AbstractEntities\PersistableInterface;
11+
use RZ\Roadiz\Core\Handlers\HandlerFactoryInterface;
1112
use RZ\Roadiz\CoreBundle\Bag\NodeTypes;
1213
use RZ\Roadiz\CoreBundle\Entity\Node;
1314
use RZ\Roadiz\CoreBundle\Entity\NodesSources;
1415
use RZ\Roadiz\CoreBundle\Entity\Tag;
16+
use RZ\Roadiz\CoreBundle\EntityHandler\NodeHandler;
1517
use RZ\Roadiz\CoreBundle\Enum\NodeStatus;
1618
use RZ\Roadiz\CoreBundle\Event\Node\NodeCreatedEvent;
19+
use RZ\Roadiz\CoreBundle\Event\Node\NodeDeletedEvent;
20+
use RZ\Roadiz\CoreBundle\Event\Node\NodeUpdatedEvent;
1721
use RZ\Roadiz\CoreBundle\Event\NodesSources\NodesSourcesCreatedEvent;
22+
use RZ\Roadiz\CoreBundle\Event\NodesSources\NodesSourcesDeletedEvent;
23+
use RZ\Roadiz\CoreBundle\Event\NodesSources\NodesSourcesUpdatedEvent;
1824
use RZ\Roadiz\CoreBundle\ListManager\EntityListManagerFactoryInterface;
1925
use RZ\Roadiz\CoreBundle\Node\NodeDuplicator;
2026
use RZ\Roadiz\CoreBundle\Node\NodeNamePolicyInterface;
@@ -29,6 +35,7 @@
2935
use Symfony\Component\HttpFoundation\Request;
3036
use Symfony\Component\HttpFoundation\Response;
3137
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
38+
use Symfony\Contracts\EventDispatcher\Event;
3239
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
3340
use Symfony\Contracts\Translation\TranslatorInterface;
3441

@@ -37,6 +44,8 @@
3744
*
3845
* @template TEntity of NodesSources
3946
* @template TInputDto of object
47+
*
48+
* @extends AbstractAdminWithBulkController<TEntity>
4049
*/
4150
abstract class AbstractSingleNodeTypeController extends AbstractAdminWithBulkController
4251
{
@@ -50,6 +59,7 @@ public function __construct(
5059
protected readonly AllStatusesNodeRepository $nodeRepository,
5160
protected readonly NodeNamePolicyInterface $nodeNamePolicy,
5261
protected readonly NodeWorkflow $workflow,
62+
protected readonly HandlerFactoryInterface $handlerFactory,
5363
FormFactoryInterface $formFactory,
5464
UrlGeneratorInterface $urlGenerator,
5565
EntityListManagerFactoryInterface $entityListManagerFactory,
@@ -61,6 +71,9 @@ public function __construct(
6171
parent::__construct($formFactory, $urlGenerator, $entityListManagerFactory, $managerRegistry, $translator, $logTrail, $eventDispatcher);
6272
}
6373

74+
/**
75+
* @return NodesSourcesRepository<TEntity>
76+
*/
6477
#[\Override]
6578
protected function getRepository(): ObjectRepository
6679
{
@@ -259,7 +272,7 @@ protected function getEntityName(PersistableInterface $item): string
259272
/**
260273
* @param TEntity $item
261274
*
262-
* @return \Symfony\Contracts\EventDispatcher\Event[]
275+
* @return Event[]
263276
*/
264277
#[\Override]
265278
protected function createCreateEvent(PersistableInterface $item): array
@@ -270,6 +283,20 @@ protected function createCreateEvent(PersistableInterface $item): array
270283
];
271284
}
272285

286+
/**
287+
* @param TEntity $item
288+
*
289+
* @return Event[]
290+
*/
291+
#[\Override]
292+
protected function createUpdateEvent(PersistableInterface $item): array
293+
{
294+
return [
295+
new NodeUpdatedEvent($item->getNode()),
296+
new NodesSourcesUpdatedEvent($item),
297+
];
298+
}
299+
273300
/**
274301
* @param TEntity $item
275302
*/
@@ -354,6 +381,52 @@ public function editAction(Request $request, int|string $id): RedirectResponse
354381
return $this->redirectToEditPage($item);
355382
}
356383

384+
#[\Override]
385+
public function bulkDeleteAction(Request $request): Response
386+
{
387+
$this->additionalAssignation($request);
388+
389+
return $this->bulkAction(
390+
$request,
391+
$this->getRequiredDeletionRole(),
392+
$this->createDeleteBulkForm(true),
393+
$this->createDeleteBulkForm(),
394+
fn (string $ids) => $this->createDeleteBulkForm(false, [
395+
'id' => $ids,
396+
]),
397+
$this->getTemplateFolder().'/bulk_delete.html.twig',
398+
'%namespace%.%item%.was_deleted',
399+
/**
400+
* @param TEntity $item
401+
*/
402+
function (NodesSources $item) {
403+
$this->deleteNodesSources($item);
404+
},
405+
'bulkDeleteForm',
406+
// Do not trigger events, they are triggered in deleteNodesSources method
407+
fn (PersistableInterface $item) => [],
408+
);
409+
}
410+
411+
/**
412+
* Delete node sources and possibly its node if it was the last source.
413+
*/
414+
private function deleteNodesSources(NodesSources $data): void
415+
{
416+
$node = $data->getNode();
417+
418+
if (1 === $node->getNodeSources()->count()) {
419+
$this->eventDispatcher->dispatch(new NodeDeletedEvent($node));
420+
421+
/** @var NodeHandler $nodeHandler */
422+
$nodeHandler = $this->handlerFactory->getHandler($node);
423+
$nodeHandler->softRemoveWithChildren();
424+
} else {
425+
$this->eventDispatcher->dispatch(new NodesSourcesDeletedEvent($data));
426+
$this->removeItem($data);
427+
}
428+
}
429+
357430
#[\Override]
358431
public function deleteAction(Request $request, int|string $id): ?Response
359432
{

0 commit comments

Comments
 (0)