Skip to content

Commit bb48db7

Browse files
committed
Add error handling on write processor
1 parent 5fa87cd commit bb48db7

File tree

12 files changed

+225
-18
lines changed

12 files changed

+225
-18
lines changed

src/Bundle/Resources/config/services/state/processor.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
</imports>
1818

1919
<services>
20+
<service id="sylius.state_processor.main" alias="sylius.state_processor.respond" />
21+
2022
<service id="sylius.state_processor.respond" class="Sylius\Resource\State\Processor\RespondProcessor">
2123
<argument type="service" id="sylius.state_responder" />
2224
</service>
23-
<service id="sylius.state_processor.main" alias="sylius.state_processor.respond" />
2425

2526
<service id="sylius.state_processor.write" class="Sylius\Resource\State\Processor\WriteProcessor" decorates="sylius.state_processor.main" decoration-priority="100">
2627
<argument type="service" id=".inner" />

src/Component/spec/Symfony/Session/Flash/FlashHelperSpec.php

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ function it_is_initializable(): void
4141
$this->shouldHaveType(FlashHelper::class);
4242
}
4343

44+
function it_adds_success_flashes_with_custom_message(
45+
Request $request,
46+
SessionInterface $session,
47+
FlashBagInterface $flashBag,
48+
): void {
49+
$operation = (new Create())->withResource(new ResourceMetadata(alias: 'app.dummy', name: 'dummy', applicationName: 'app'));
50+
$context = new Context(new RequestOption($request->getWrappedObject()));
51+
52+
$request->getSession()->willReturn($session);
53+
54+
$session->getBag('flashes')->willReturn($flashBag);
55+
56+
$flashBag->add('success', 'Custom message.')->shouldBeCalled();
57+
58+
$this->addSuccessFlash($operation, $context, 'Custom message.');
59+
}
60+
4461
function it_adds_success_flashes_with_specific_message(
4562
Request $request,
4663
SessionInterface $session,
@@ -91,7 +108,7 @@ function it_adds_success_flashes_with_fallback_message(
91108
$this->addSuccessFlash($operation, $context);
92109
}
93110

94-
function it_adds_success_flashes_with_custom_message(
111+
function it_adds_success_flashes_with_custom_message_on_its_operation(
95112
Request $request,
96113
SessionInterface $session,
97114
FlashBagInterface $flashBag,
@@ -186,6 +203,73 @@ function it_adds_success_flashes_with_humanized_message_and_plural_name_on_bulk_
186203
$this->addSuccessFlash($operation, $context);
187204
}
188205

206+
function it_adds_error_flashes_with_custom_message(
207+
Request $request,
208+
SessionInterface $session,
209+
FlashBagInterface $flashBag,
210+
): void {
211+
$operation = (new Create())->withResource(new ResourceMetadata(alias: 'app.dummy', name: 'dummy', applicationName: 'app'));
212+
$context = new Context(new RequestOption($request->getWrappedObject()));
213+
214+
$request->getSession()->willReturn($session);
215+
216+
$session->getBag('flashes')->willReturn($flashBag);
217+
218+
$flashBag->add('error', 'Custom error message.')->shouldBeCalled();
219+
220+
$this->addErrorFlash($operation, $context, 'Custom error message.');
221+
}
222+
223+
function it_adds_error_flashes_with_specific_message(
224+
Request $request,
225+
SessionInterface $session,
226+
FlashBagInterface $flashBag,
227+
TranslatorBagInterface $translator,
228+
MessageCatalogueInterface $messageCatalogue,
229+
): void {
230+
$operation = (new Create())->withResource(new ResourceMetadata(alias: 'app.dummy', name: 'dummy', applicationName: 'app'));
231+
$context = new Context(new RequestOption($request->getWrappedObject()));
232+
233+
$request->getSession()->willReturn($session);
234+
235+
$session->getBag('flashes')->willReturn($flashBag);
236+
237+
$translator->getCatalogue()->willReturn($messageCatalogue);
238+
239+
$messageCatalogue->has('app.dummy.create_error', 'flashes')->willReturn(true)->shouldBeCalled();
240+
241+
$translator->trans('app.dummy.create_error', ['%resource%' => 'Dummy'], 'flashes')->willReturn('Cannot create Dummy resource.')->shouldBeCalled();
242+
243+
$flashBag->add('error', 'Cannot create Dummy resource.')->shouldBeCalled();
244+
245+
$this->addErrorFlash($operation, $context);
246+
}
247+
248+
function it_adds_error_flashes_with_fallback_message(
249+
Request $request,
250+
SessionInterface $session,
251+
FlashBagInterface $flashBag,
252+
TranslatorBagInterface $translator,
253+
MessageCatalogueInterface $messageCatalogue,
254+
): void {
255+
$operation = (new Create())->withResource(new ResourceMetadata(alias: 'app.dummy', name: 'dummy', applicationName: 'app'));
256+
$context = new Context(new RequestOption($request->getWrappedObject()));
257+
258+
$request->getSession()->willReturn($session);
259+
260+
$session->getBag('flashes')->willReturn($flashBag);
261+
262+
$translator->getCatalogue()->willReturn($messageCatalogue);
263+
264+
$messageCatalogue->has('app.dummy.create_error', 'flashes')->willReturn(false)->shouldBeCalled();
265+
266+
$translator->trans('sylius.resource.create_error', ['%resource%' => 'Dummy'], 'flashes')->willReturn('Cannot create Dummy resource.')->shouldBeCalled();
267+
268+
$flashBag->add('error', 'Cannot create Dummy resource.')->shouldBeCalled();
269+
270+
$this->addErrorFlash($operation, $context);
271+
}
272+
189273
function it_translates_flashes_from_event_when_translator_is_not_a_bag(
190274
Request $request,
191275
SessionInterface $session,

src/Component/src/Doctrine/Common/State/RemoveProcessor.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313

1414
namespace Sylius\Resource\Doctrine\Common\State;
1515

16+
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
1617
use Doctrine\Persistence\ManagerRegistry;
1718
use Doctrine\Persistence\ObjectManager as DoctrineObjectManager;
1819
use Sylius\Resource\Context\Context;
20+
use Sylius\Resource\Exception\DeleteResourceException;
1921
use Sylius\Resource\Metadata\Operation;
2022
use Sylius\Resource\Reflection\ClassInfoTrait;
2123
use Sylius\Resource\State\ProcessorInterface;
@@ -34,8 +36,12 @@ public function process(mixed $data, Operation $operation, Context $context): mi
3436
return null;
3537
}
3638

37-
$manager->remove($data);
38-
$manager->flush();
39+
try {
40+
$manager->remove($data);
41+
$manager->flush();
42+
} catch (ForeignKeyConstraintViolationException) {
43+
throw new DeleteResourceException();
44+
}
3945

4046
return null;
4147
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Exception;
15+
16+
class DeleteResourceException extends WriteResourceException
17+
{
18+
public function __construct(
19+
?string $resourceName = null,
20+
string $message = '',
21+
int $code = 0,
22+
?\Throwable $previous = null,
23+
) {
24+
if (empty($message)) {
25+
$message = sprintf('Cannot delete, the %s is in use.', $resourceName ?? 'resource');
26+
}
27+
28+
parent::__construct($resourceName, $message, $code, $previous);
29+
}
30+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Exception;
15+
16+
class WriteResourceException extends RuntimeException
17+
{
18+
public function __construct(
19+
private readonly ?string $resourceName = null,
20+
string $message = '',
21+
int $code = 0,
22+
?\Throwable $previous = null,
23+
) {
24+
parent::__construct($message, $code, $previous);
25+
}
26+
27+
public function getResourceName(): ?string
28+
{
29+
return $this->resourceName;
30+
}
31+
}

src/Component/src/State/Processor/FlashProcessor.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Sylius\Resource\Metadata\Operation;
1919
use Sylius\Resource\State\ProcessorInterface;
2020
use Sylius\Resource\Symfony\Session\Flash\FlashHelperInterface;
21+
use Symfony\Component\HttpFoundation\Request;
2122
use Symfony\Component\HttpFoundation\Response;
2223

2324
/**
@@ -50,8 +51,19 @@ public function process(mixed $data, Operation $operation, Context $context): mi
5051
return $this->processor->process($data, $operation, $context);
5152
}
5253

53-
$this->flashHelper->addSuccessFlash($operation, $context);
54+
$this->addFlash($request, $operation, $context);
5455

5556
return $this->processor->process($data, $operation, $context);
5657
}
58+
59+
private function addFlash(Request $request, Operation $operation, Context $context): void
60+
{
61+
if ($request->attributes->has('error')) {
62+
$this->flashHelper->addErrorFlash($operation, $context);
63+
64+
return;
65+
}
66+
67+
$this->flashHelper->addSuccessFlash($operation, $context);
68+
}
5769
}

src/Component/src/State/Processor/WriteProcessor.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
namespace Sylius\Resource\State\Processor;
1515

1616
use Sylius\Resource\Context\Context;
17+
use Sylius\Resource\Context\Option\RequestOption;
18+
use Sylius\Resource\Exception\WriteResourceException;
1719
use Sylius\Resource\Metadata\Operation;
1820
use Sylius\Resource\State\ProcessorInterface;
1921
use Symfony\Component\HttpFoundation\Response;
@@ -39,6 +41,13 @@ public function process(mixed $data, Operation $operation, Context $context): mi
3941
return $this->processor->process($data, $operation, $context);
4042
}
4143

42-
return $this->processor->process($this->locatorProcessor->process($data, $operation, $context), $operation, $context);
44+
try {
45+
return $this->processor->process($this->locatorProcessor->process($data, $operation, $context), $operation, $context);
46+
} catch (WriteResourceException $exception) {
47+
$request = $context->get(RequestOption::class)?->request();
48+
$request?->attributes->set('error', $exception->getMessage());
49+
50+
return $this->processor->process(null, $operation, $context);
51+
}
4352
}
4453
}

src/Component/src/Symfony/Session/Flash/FlashHelper.php

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,14 @@ public function __construct(
3434
) {
3535
}
3636

37-
public function addSuccessFlash(Operation $operation, Context $context): void
37+
public function addSuccessFlash(Operation $operation, Context $context, ?string $message = null): void
3838
{
39-
$this->addFlashFromOperation($operation, $context, 'success');
39+
$this->addFlashFromOperation($operation, $context, 'success', $message);
40+
}
41+
42+
public function addErrorFlash(Operation $operation, Context $context, ?string $message = null): void
43+
{
44+
$this->addFlashFromOperation($operation, $context, 'error', $message);
4045
}
4146

4247
public function addFlashFromEvent(GenericEvent $event, Context $context): void
@@ -46,9 +51,9 @@ public function addFlashFromEvent(GenericEvent $event, Context $context): void
4651
$this->addFlash($message, $event->getMessageType(), $context);
4752
}
4853

49-
private function addFlashFromOperation(Operation $operation, Context $context, string $type): void
54+
private function addFlashFromOperation(Operation $operation, Context $context, string $type, ?string $message): void
5055
{
51-
$message = $this->buildOperationMessage($operation, $type);
56+
$message ??= $this->buildOperationMessage($operation, $type);
5257

5358
$this->addFlash($message, $type, $context);
5459
}
@@ -74,8 +79,12 @@ private function buildOperationMessage(Operation $operation, string $type): stri
7479
$resource = $operation->getResource();
7580
Assert::notNull($resource);
7681

77-
$key = $operation->getNotificationMessage() ?? sprintf('%s.%s.%s', $resource->getApplicationName() ?? '', $resource->getName() ?? '', $operation->getShortName() ?? '');
78-
$fallbackKey = sprintf('sylius.resource.%s', $operation->getShortName() ?? '');
82+
$keySuffix = 'error' === $type ? '_error' : '';
83+
84+
$key = 'success' === $type ? $operation->getNotificationMessage() : null;
85+
$key ??= sprintf('%s.%s.%s', $resource->getApplicationName() ?? '', $resource->getName() ?? '', ($operation->getShortName() ?? '') . $keySuffix);
86+
87+
$fallbackKey = sprintf('sylius.resource.%s', ($operation->getShortName() ?? '') . $keySuffix);
7988

8089
$parameters = $this->getTranslationParameters($operation);
8190

src/Component/src/Symfony/Session/Flash/FlashHelperInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
*/
2323
interface FlashHelperInterface
2424
{
25-
public function addSuccessFlash(Operation $operation, Context $context): void;
25+
public function addSuccessFlash(Operation $operation, Context $context, ?string $message = null): void;
26+
27+
public function addErrorFlash(Operation $operation, Context $context, ?string $message = null): void;
2628

2729
public function addFlashFromEvent(GenericEvent $event, Context $context): void;
2830
}

src/Component/tests/State/Processor/FlashProcessorTest.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Sylius\Resource\State\Processor\FlashProcessor;
2323
use Sylius\Resource\State\ProcessorInterface;
2424
use Sylius\Resource\Symfony\Session\Flash\FlashHelperInterface;
25+
use Symfony\Component\HttpFoundation\ParameterBag;
2526
use Symfony\Component\HttpFoundation\Request;
2627
use Symfony\Component\HttpFoundation\Response;
2728

@@ -47,11 +48,12 @@ protected function setUp(): void
4748
}
4849

4950
/** @test */
50-
public function it_adds_flash(): void
51+
public function it_adds_success_flash(): void
5152
{
5253
$request = $this->prophesize(Request::class);
5354
$operation = $this->prophesize(HttpOperation::class);
5455

56+
$request->attributes = new ParameterBag();
5557
$request->getRequestFormat()->willReturn('html')->shouldBeCalled();
5658
$request->isMethodSafe()->willReturn(false)->shouldBeCalled();
5759

@@ -66,6 +68,27 @@ public function it_adds_flash(): void
6668
$this->flashProcessor->process(['foo' => 'fighters'], $operation->reveal(), $context);
6769
}
6870

71+
/** @test */
72+
public function it_adds_error_flash(): void
73+
{
74+
$request = $this->prophesize(Request::class);
75+
$operation = $this->prophesize(HttpOperation::class);
76+
77+
$request->attributes = new ParameterBag(['error' => 'Cannot delete, the resource is in use.']);
78+
$request->getRequestFormat()->willReturn('html')->shouldBeCalled();
79+
$request->isMethodSafe()->willReturn(false)->shouldBeCalled();
80+
81+
$operation->canWrite()->willReturn(null)->shouldBeCalled();
82+
83+
$context = new Context(new RequestOption($request->reveal()));
84+
85+
$this->decorated->process(['foo' => 'fighters'], $operation, $context)->willReturn(['foo' => 'fighters'])->shouldBeCalled();
86+
87+
$this->flashHelper->addErrorFlash($operation, $context)->shouldBeCalled();
88+
89+
$this->flashProcessor->process(['foo' => 'fighters'], $operation->reveal(), $context);
90+
}
91+
6992
/** @test */
7093
public function it_does_nothing_when_controller_result_is_a_response(): void
7194
{

0 commit comments

Comments
 (0)