Errors not caught while validating nested objects

My website is build with flow 3.1.1
I have a class Employee with several simple properties (like name, birthday, etc.) and a property ‘teams’.
Property ‘teams’ holds a collection of Team instances and is declared like this

 /** 
 * The teams an employee belongs to
 *
 * @var \Doctrine\Common\Collections\Collection<Aoe\Core\Domain\Model\Team>
 * @ORM\ManyToMany(targetEntity="\Aoe\Core\Domain\Model\Team", mappedBy="employees")
 * @ORM\OrderBy({"name" = "ASC"})
 * @Flow\IgnoreValidation()
 * @Flow\Lazy
 */
protected $teams;

In class ‘Team’ the declaration looks like that:

/**
 * @var \Doctrine\Common\Collections\Collection<Aoe\Core\Domain\Model\Employee>
 * @ORM\ManyToMany(targetEntity="\Aoe\Core\Domain\Model\Employee", inversedBy="teams", cascade={"persist"})
 * @ORM\OrderBy({"firstName" = "ASC", "lastName" = "ASC"})
 * @Flow\IgnoreValidation()
 * @Flow\Lazy
 */
protected $employees;

There is also an customized object validator for Employee that ensures that at least one Team is assigned to the Employee.

Assuming during creation of a new Employee instance the only field not filled in is the ‘teams’ field then all properties are validated correctly and an error occures. The errorAction in employeeController forwards to the referring method as expected.
But as soon as ‘teams’ is filled in correctly and the field ‘name’ (or any other field) is missing the errorAction does not forward to the referring method anymore. Instead a flow error is thrown which includes all errors.

I guess it’s related to the doctrine annotation …cascade={“persist”}… cause the error is throwen in TYPO3\Flow\Persistence\Doctrine\PersistenceManager::validateObject(). It looks like instance Team trys to persist the invalid instance of class Employee. But of course validation fails… :frowning:

Did anyone face a similar behaviour. Any hint or idea is appreciated :wink:

I digged a deeper into this problem and found some irritating behaviour.

  • validation runs and errors are caught through validators
  • errorAction calls AbstractController->forward() wich in turn throws an \TYPO3\Flow\Mvc\Exception\ForwardException()
  • this exception is caught in the \TYPO3\Flow\Mvc\Dispatcher -> initiateDispatchLoop():
} catch (StopActionException $exception) {
$this->emitAfterControllerInvocation($request, $response, $controller);
if ($exception instanceof ForwardException) {
    $request = $exception->getNextRequest();
} elseif (!$request->isMainRequest()) {
    $request = $request->getParentRequest();
}

This call

$this->emitAfterControllerInvocation($request, $response, $controller);

is a signal which is connected to a closure in \TYPO3\Flow\Package.

$dispatcher->connect(\TYPO3\Flow\Mvc\Dispatcher::class, 'afterControllerInvocation', function ($request) use ($bootstrap) {
    if ($bootstrap->getObjectManager()->hasInstance(\TYPO3\Flow\Persistence\PersistenceManagerInterface::class)) {
        if (!$request instanceof Mvc\ActionRequest || $request->getHttpRequest()->isMethodSafe() !== true) {
            $bootstrap->getObjectManager()->get(\TYPO3\Flow\Persistence\PersistenceManagerInterface::class)->persistAll();
        } elseif ($request->getHttpRequest()->isMethodSafe()) {
            $bootstrap->getObjectManager()->get(\TYPO3\Flow\Persistence\PersistenceManagerInterface::class)->persistAll(true);
        }
    }
});

This final call to persistAll() causes an error in the persistence layer which I can not catch anymore :frowning:

How could I avoid this persistAll() call at this point as here all validation is allready done and FLOW should redirect to a given Action?? Any help is grateful appreciated.

Validation effectively happens two times. Once in the controller, that’s the point were you want to act. The second time it happens in persist, this is to ensure data integrity before finally saving to DB. At this point you cannot interact, the exception will happen if validation fails. You need to make sure that the data validates at this point. So for some reason the first validation is not triggered or does not fail.

The question is why that happens and here I can only guess.
To debug check:
\TYPO3\Flow\Mvc\Controller\ActionController::callActionMethod
and specifically the line
$validationResult = $this->arguments->getValidationResults();

That’s the validation that should fail.

Maybe with some more input I can have more ideas about what is going wrong there.

I checked that thing out.

After this line

all expected errors are properly caught at this point and a few lines below the errorAction is called.

if ($shouldCallActionMethod) {
    $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
} else {
    $actionResult = call_user_func(array($this, $this->errorMethodName)); <--- is called
}

which is the default errorAction from TYPO3\Flow\Mvc\Controller\AbstractController. Following it’s method calls you end up in TYPO3\Flow\Mvc\Controller\AbstractController–>forwardToReferringRequest()

protected function forwardToReferringRequest()
{
$referringRequest = $this->request->getReferringRequest();
if ($referringRequest === null) {
return;
}
$packageKey = $referringRequest->getControllerPackageKey();
$subpackageKey = $referringRequest->getControllerSubpackageKey();
if ($subpackageKey !== null) {
$packageKey .= ‘\’ . $subpackageKey;
}
$argumentsForNextController = $referringRequest->getArguments();
$argumentsForNextController[’__submittedArguments’] = $this->request->getArguments();
$argumentsForNextController[’__submittedArgumentValidationResults’] = $this->arguments->getValidationResults();

$this->forward($referringRequest->getControllerActionName(), $referringRequest->getControllerName(), $packageKey, $argumentsForNextController);

At this point the $referringRequest->getControllerActionName() returns the correct action (in this case ‘new’) and the forwarding should do fine. Next Step is the TYPO3\Flow\Mvc\Controller\AbstractController–>forward() method which throws an ForwardException

$this->arguments->removeAll();

$forwardException = new \TYPO3\Flow\Mvc\Exception\ForwardException();
$forwardException->setNextRequest($nextRequest);
throw $forwardException;

Finally end up here…

In this Signal emitAfterControllerInvocation() the persistAll() method is called and leads to an validation error in persistence layer which prevents the code requesting the newAction().
The final error is thrown in TYPO3\Flow\Persistence\Doctrine\PersistenceManager–>validateObject() method.

But why is it validated again at this place? Shouldn’t the request just forward to the next action method?
How can I avoid that?

The persistAll should not be called there, I have to look through that to see why it happens…

Thank you Christian for your help.
What more can I serve you for gathering information and digging into this?

I figured out that the persistAll() call at this place is there since ever and probably makes sense. What is wrong is the fact that your object is registered for being persisted. Actually the action (in which you do something like $tihs->fooRepository->update($object)) should never be called as validation failed. That means persistAll should have nothing to persist. Apparently the object is registered for persistence at some other place but I don’t know exactly where. Do you have any other repository->update or persistenceManager->update calls?

That gave me the proper hint! There was indeed a teamRepository->update() call in the Employee class that caused the error. Ironically I never ran into that method during debugging.

Thank you for your patience and help Christian. :thumbsup: