webdevilopers / php-ddd

PHP Symfony Doctrine Domain-driven Design
200 stars 11 forks source link

Catching exceptions and converting them to Symfony form errors #4

Open webdevilopers opened 8 years ago

webdevilopers commented 8 years ago

If anywhere in your application e.g. in your Handler or DomainModel an Exception / DomainException is thrown you can catch and convert the message to use it in your Symfony form:

        $fooCommand = FooCommand::fromBar($bar);

        $form = $this->createForm(FooForm::class, $fooCommand);
        $form->handleRequest($this->getRequest());

        if ($form->isValid()) {
            $fooHandler = $this->get('acme.handler.foo');
            try {
                $fooHandler->handle($fooCommand);

                return $this->redirectToRoute('acme_foo_list');
            } catch (\Exception $e) {
                $form->addError(new FormError($e->getMessage()));
            }
        }

And then inside your TWIG template:

{{ form_errors(form) }}
webdevilopers commented 5 years ago

Normally we add value objects and asserts to our commands. So we thought about extending the try-catch-block to catch the exceptions:

But now we are thinking of moving the value objects to the "Handler": https://github.com/webdevilopers/php-ddd/issues/14

In that case we no longer would have to extend and keep it the original way - see initial comment.

webdevilopers commented 5 years ago

We decided to make our Command DTOs (aka Symfony Form data classes) using primitives only again. The creation of value objects was moved to the Command Handler. Every value object throws a specific Domain Exception if they cannot be created or violate a business rule.

Now we can use a single try-catch block to catch the exceptions and transform them into a Symfony Form error.

This example features the @prooph service-bus. The actual Domain Exception can be accessed by getting the previous exception:

        $form->handleRequest($this->getRequest());

        if ($form->isValid()) {
            try {
                $this->get('prooph_service_bus.acme_command_bus')->dispatch($command);
            } catch (\Exception $busException) {
                switch (get_class($busException->getPrevious()))
                {
                    case InnerWidthIsOutOfRangeException::class:
                        $form->get('window')->addError(new FormError('...'));
                        break;
                }
            }
        }

Is there a better way to get and handle the domain exceptions @prolic @codeliner?

prolic commented 5 years ago

The service bus will throw its own exceptions. If you don't like this behavior and want it to throw your custom exceptions directly instead, attach a listener to finalize event on the bus and replace the exception with the previous one.