Symfony2 Doctrine2 transactions

Featured Image

Like everything else, using transactions in Symfony2 is easy task.
I had a case that I had to use transactions to make sure that everything or none is saved to database, because I didn’t wand anything to break at the half way and leave data unsaved.There are two basic different ways to use transactions with Doctrine 2. In official documentation there is one approach suggested for transactions, but if you find necessary like I did, use second one. The important thing is not to forget rollback in your code if you are using approach that is not suggested.

Example from Doctrine2 official documentation:

// $em instanceof EntityManager
$em->transactional(function($em) {
    //... do some work
    $user = new User;

I found this approach inconvenient for my code because variables defined outside transactional function are not accessible directly and I had many variables defined in my controller action and didn’t want to refractor all the code. Because of that I used the second possibility in my code:

     * @Route("/admin/tags/ajax_save_new_tag", name="_admin_tags_ajax_save_new_tag")
    public function ajaxSaveNewTagAction() {
        $request = $this->get('request');
        $isAjax = $request->isXmlHttpRequest();
        /* BEGIN If is ajax call... */
        if ($isAjax == true) {
            $em = $this->get('doctrine.orm.entity_manager');
            $user = $this->container->get('security.context')->getToken()->getUser();
            $userId = $user->getId();
            $localizations = '';
            $tagname = '';
            if (isset($_POST['localization']))
                $localizations = $_POST['localization'];
            if (isset($_POST['tag']['tag_name']))
                $tagname = $_POST['tag']['tag_name'];
            $tag = new \Surgeworks\AdminBundle\Entity\Tag();
            $q = $em->createQuery("select max(a.sort_order) from Surgeworks\AdminBundle\Entity\Tag a");
            $maxorder = $q->getSingleScalarResult();
            if (null === $maxorder) {
                $maxorder = 0;
            $tmp = $maxorder - ($maxorder % 10);
            $tag->setSortOrder($tmp + 10);
            $validator = $this->container->get('validator');
            $errorList = $validator->validate($tag);
            $msg = "";
            if (count($errorList) > 0) {
                foreach ($errorList as $err) {
                    $msg.= $err->getMessage() . "\n";
                return new Response($msg, '400');
            /*             * ******** BEGIN TRANSACTION ******** */
            try {
                if (!empty($localizations)) {
                    foreach ($localizations as $langsymbol => $tagname) {
                        $loc = new \Surgeworks\AdminBundle\Entity\TagLocalization();
                        $errorList = $validator->validate($loc);
                        $msg = "";
                        if (count($errorList) > 0) {
                            foreach ($errorList as $err) {
                                $msg.= $err->getMessage() . "\n";
                            return new Response($msg, '400');
                $msg = "Tag saved successfully!";
                $code = "OK";
            } catch (\Exception $e) {
                throw $e;
            //get languages list for select box
            $languages = $em->getRepository('Surgeworks\AdminBundle\Entity\Language')->findAll();
            $langsarr = array();
            foreach ($languages as $val) {
                array_push($langsarr, array('langsymbol' => $val->getLanguageSymbol(), 'langname' => $val->getLanguageName()));
            $response = new Response(json_encode(array('langlist' => $langsarr)), '200');
            $response->headers->set('Content-Type', 'application/json');
            return $response;
        //If not ajax call
        return new Response('Silence is golden');

As I sad: don’t forget to write rollback when you are using this approach. 🙂


  1. Quick question regarding validation concept. Lets assume youre validationg against field uniqueness. Based on your example you are validating for this using validate which happens before transaction.

    If validate returns ‘all valid’ you are starting transaction then but uniqueness of values can be violated right after your validate() and before transaction start or even during the transaction. You approach generates exception then which is very far from expected behavior

  2. I really enjoyed the blog post. I think you may find usefull to read some of Derek Strobe blog post. He outline a very clever way to make things thinner.

    2. and

    I prefer the the $em->transactionnal(); way though. More explicit.

    My last observation.

    You forgot to add the $em->flush(); at the end.

    I had a lifecycle callback event on something and because of the flush not done… I was loosing some of my features.

    Otherwise, you helped me a lot!! 🙂

  3. Hi,

    Thanks for your reply.
    Think about updating a user balance:
    – initial balance is 100;
    – I have one request to update the balance with -10;
    – I have another request to update it with +20;

    I’d assume the code is like this?

    $userId1 = 1;
    $userId2 = 2;
    $quantity = 10;
    $em->transactional(function($em) use ($userId1 $userId2, $quantity){
                $u1 = $em->find('User', $userId1, LockMode::OPTIMISTIC);
                $u2 = $em->find('User', $userId2, LockMode::OPTIMISTIC);
                $u1->setBalance($u1->getBalance() + $quantity);
                $u2->setBalance($u2->getBalance() + $quantity);

    I do have a version column in the table for the user.

  4. Hello,

    One question would be how can you use transactions to update already existing records as I’ve been trying for this all night and I still couldn’t figure out how to have something like lets say: lowering the stock for a product while having 5 threads for the same product that want to update it’s stock.

    Any help would be much appreciated.

    Thanks 🙂

    1. @Florin: Please, can you be more specific in your problem so I can understand the problem better. If all of transactions you need to implement are in the same controller action, then it’s not problem to put every database interaction inside that transaction and commit after all of changes are done.

  5. Did you try to pass your context into an anonymous function? something like this:

    $em->transactional(function($em) use ($object1, $object2){
      //... do some work
      $user = new User;


    1. This would be also very good solution for use safer way to implement transactions. Thanks, Kuba! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.