EntityManagerDecorator::transactional injects wrapped EM into closure #5935

Open
opened 2026-01-22 15:22:28 +01:00 by admin · 9 comments
Owner

Originally created by @Isinlor on GitHub (Mar 28, 2018).

In \Doctrine\ORM\Decorator\EntityManagerDecorator::transactional:

    public function transactional($func)
    {
        return $this->wrapped->transactional($func);
    }

The method is passing $func into wrapped.

However, $func takes entity manager as an argument and therefore we get raw entity manger in $func.

Related issue: https://github.com/doctrine/doctrine2/issues/5820

Originally created by @Isinlor on GitHub (Mar 28, 2018). In `\Doctrine\ORM\Decorator\EntityManagerDecorator::transactional`: ```php public function transactional($func) { return $this->wrapped->transactional($func); } ``` The method is passing `$func` into `wrapped`. However, `$func` takes entity manager as an argument and therefore we get raw entity manger in `$func`. Related issue: https://github.com/doctrine/doctrine2/issues/5820
Author
Owner

@stof commented on GitHub (Apr 27, 2018):

that's the issue with any API passing $this to some other code. As soon as your pass yourselves to some other code, decorators won't have effect at this point (as $this is the inner object).

@stof commented on GitHub (Apr 27, 2018): that's the issue with any API passing `$this` to some other code. As soon as your pass yourselves to some other code, decorators won't have effect at this point (as `$this` is the inner object).
Author
Owner

@stof commented on GitHub (Apr 27, 2018):

btw, here is a workaround: don't use the argument, but the EM you already have:


$em->transactional(function () use ($em) {
    // $em is the outer EM here as it comes from the outside)
});
@stof commented on GitHub (Apr 27, 2018): btw, here is a workaround: don't use the argument, but the EM you already have: ```php $em->transactional(function () use ($em) { // $em is the outer EM here as it comes from the outside) });
Author
Owner

@stof commented on GitHub (Apr 27, 2018):

there is a way to solve it for EntityManagerDecorator: wrap the callable into another callable, and call the original callable with the decorator as argument instead

@stof commented on GitHub (Apr 27, 2018): there is a way to solve it for EntityManagerDecorator: wrap the callable into another callable, and call the original callable with the decorator as argument instead
Author
Owner

@Isinlor commented on GitHub (Apr 27, 2018):

The direct issue I had was with loading fixtures by \Doctrine\Common\DataFixtures\Executor\ORMExecutor::execute

So, I can't pass EM there by myself.

My workaround was adding this to my decorator:

    public function transactional($func)
    {
        if (!is_callable($func)) {
            throw new \InvalidArgumentException('Expected argument of type "callable", got "' . gettype($func) . '"');
        }

        $this->getConnection()->beginTransaction();
        try {
            $return = call_user_func($func, $this);
            $this->flush();
            $this->getConnection()->commit();
            return $return ?: true;
        } catch (\Exception $e) {
            $this->close();
            $this->getConnection()->rollback();
            throw $e;
        }
    }
@Isinlor commented on GitHub (Apr 27, 2018): The direct issue I had was with loading fixtures by [\Doctrine\Common\DataFixtures\Executor\ORMExecutor::execute](https://github.com/doctrine/data-fixtures/blob/master/lib/Doctrine/Common/DataFixtures/Executor/ORMExecutor.php#L66) So, I can't pass EM there by myself. My workaround was adding this to my decorator: ```php public function transactional($func) { if (!is_callable($func)) { throw new \InvalidArgumentException('Expected argument of type "callable", got "' . gettype($func) . '"'); } $this->getConnection()->beginTransaction(); try { $return = call_user_func($func, $this); $this->flush(); $this->getConnection()->commit(); return $return ?: true; } catch (\Exception $e) { $this->close(); $this->getConnection()->rollback(); throw $e; } } ```
Author
Owner

@bramcordie commented on GitHub (Dec 20, 2023):

Hey @Isinlor!

After upgrading to Symfony 5 this workaround was causing some problems because the transactional method has been deprecated in favor of wrapInTransaction. What I ended up doing in a custom EntityManagerDecorator is this:

    public function transactional($func): void
    {
        $func($this);
    }

    public function wrapInTransaction(callable $func): void
    {
        $func($this);
    }
@bramcordie commented on GitHub (Dec 20, 2023): Hey @Isinlor! After upgrading to Symfony 5 this workaround was causing some problems because the `transactional` method has been deprecated in favor of `wrapInTransaction`. What I ended up doing in a custom `EntityManagerDecorator` is this: ```php public function transactional($func): void { $func($this); } public function wrapInTransaction(callable $func): void { $func($this); } ```
Author
Owner

@stof commented on GitHub (Dec 20, 2023):

@bramcordie this is broken though: you are disabling the transaction wrapping...

@stof commented on GitHub (Dec 20, 2023): @bramcordie this is broken though: you are disabling the transaction wrapping...
Author
Owner

@bramcordie commented on GitHub (Dec 20, 2023):

@stof, I thought this was what you were hinting at in this earlier comment: https://github.com/doctrine/orm/issues/7161#issuecomment-385008923

I think I missed a step but can you give an example of what you meant or an alternative approach?

@bramcordie commented on GitHub (Dec 20, 2023): @stof, I thought this was what you were hinting at in this earlier comment: https://github.com/doctrine/orm/issues/7161#issuecomment-385008923 I think I missed a step but can you give an example of what you meant or an alternative approach?
Author
Owner

@stof commented on GitHub (Dec 20, 2023):

    public function wrapInTransaction(callable $func)
    {
        $wrappedFunc = fn () => $func($this);

        return $this->wrapped->wrapInTransaction($wrappedFunc);
    }
@stof commented on GitHub (Dec 20, 2023): ```php public function wrapInTransaction(callable $func) { $wrappedFunc = fn () => $func($this); return $this->wrapped->wrapInTransaction($wrappedFunc); } ```
Author
Owner

@bramcordie commented on GitHub (Dec 20, 2023):

Thanks a lot for the example, works like a charm!

@bramcordie commented on GitHub (Dec 20, 2023): Thanks a lot for the example, works like a charm!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5935