Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
406 views
in Technique[技术] by (71.8m points)

symfony - Symfony2 / Doctrine2 throwing index error when flushing the entity manager

Three entities are involved here: Deployment, DeploymentStep, and DeploymentStatusLog. I'll start by pasting the relevant definitions of those classes

src/My/Bundle/Entity/Deployment.php

<?php

namespace MyBundleEntity;

use DoctrineORMMapping as ORM;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineORMPersistentCollection;

/**
 * @ORMTable(name="deployment")
 * @ORMEntity()
 */
class Deployment
{
  /**
   * Status Log Entries for this deployment
   *
   * @var DoctrineORMPersistentCollection
   *
   * @ORMOneToMany(targetEntity="DeploymentStatusLog", mappedBy="deployment", cascade={"persist","remove"})
   * @ORMOrderBy({"created_at"="DESC"})
   */
  protected $status_logs;

  /**
   * @var DoctrineORMPersistentCollection
   *
   * @ORMOneToMany(targetEntity="DeploymentStep", mappedBy="deployment", cascade={"persist","remove"})
   * @ORMOrderBy({"sequence" = "ASC"})
   */
  protected $steps;

  public function __construct()
  {
    $this->status_logs     = new ArrayCollection();
    $this->steps           = new ArrayCollection();
  }

  /**
   * Add status_logs
   *
   * @param DeploymentStatusLog $statusLogs
   */
  public function addDeploymentStatusLog(DeploymentStatusLog $statusLogs)
  {
      $this->status_logs[] = $statusLogs;
  }

  /**
   * Add steps
   *
   * @param DeploymentStep $steps
   */
  public function addDeploymentStep(DeploymentStep $steps)
  {
    $this->steps[] = $steps;
  }

  // ... 
}

src/My/Bundle/Entity/DeploymentStep.php

<?php

namespace MyBundleEntity;

use DoctrineORMMapping as ORM;

/**
 * @ORMTable(name="deployment_step")
 * @ORMEntity()
 */
class DeploymentStep
{
  /**
   * @var Deployment
   *
   * @ORMManyToOne(targetEntity="Deployment", cascade={"all"})
   * @ORMJoinColumn(name="deployment_id", referencedColumnName="id")
   * @GedmoSortableGroup
   */
  private $deployment;

  /**
   * Set deployment
   *
   * @param Deployment $deployment
   */
  public function setDeployment(Deployment $deployment)
  {
    $this->deployment = $deployment;
  }

  // ...
}

src/My/Bundle/Entity/DeploymentStatusLog.php

<?php

namespace MyBundleEntity;

use DoctrineORMMapping as ORM;

/**
 * @ORMTable(name="deployment_status_log")
 * @ORMEntity()
 */
class DeploymentStatusLog
{
  /**
   * @var Deployment
   *
   * @ORMManyToOne(targetEntity="Deployment", cascade={"all"})
   * @ORMJoinColumn(name="deployment_id", referencedColumnName="id", nullable=false)
   */
  protected $deployment;

  /**
   * Set deployment
   *
   * @param Deployment $deployment
   */
  public function setDeployment( Deployment $deployment)
  {
      $this->deployment = $deployment;
  }

  // ...
}

Now, the problem arises when I attempt to create brand new records for all three of these entities at once. In the controller:

$em = $this->getDoctrine()->getEntityManager();

$deployment = new Deployment();

$form = $this->createForm(new DeploymentType($em), $deployment);

if ($request->getMethod() == 'POST')
{
  $form->bindRequest($request);

  if ($form->isValid())
  {
    $codeStep = new DeploymentStep();
    $codeStep->setDeployment( $deployment );
    // Other setters on DeploymentStep

    $deploymentStatusLog = new DeploymentStatusLog();
    $deploymentStatusLog->setDeployment( $deployment );
    // Other setters on DeploymentStatusLog

    $deployment->addDeploymentStep( $codeStep );
    $deployment->addDeploymentStatusLog( $deploymentStatusLog );

    $em->persist( $deployment );
    $em->flush();
  }
}

What happens when the UnitOfWork processes, it throws a weird-looking exception complaining about an undefined index:

exception 'ErrorException' with message 'Notice: Undefined index: 000000001294f822000000006b6f9f2c in /project/vendor/doctrine/lib/Doctrine/ORM/UnitOfWork.php line 2252' in /project/vendor/symfony/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php:67

Now, if I persist/flush the Deployment entity first, and then persist/flush the associations, it succeeds.

So while I can do that to make this part of the application functional, it feels kinda wrong, since this process should be atomic and well, that's the whole point of transactional queries to begin with.

Any clues?

  • Symfony 2.0.15
  • Doctrine 2.1.7
  • PHP 5.3.3
  • MySQL 5.1.52
  • Apache 2.2.15

EDIT

Full stack trace by request

 exception 'ErrorException' with message 'Notice: Undefined index: 000000004081f5f9000000005f1dbbfc in /project/vendor/doctrine/lib/Doctrine/ORM/UnitOfWork.php line 2252' in /project/vendor/symfony/src/Symfony/Component/HttpKernel/Debug/ErrorHandler.php:67
Stack trace:
#0 /project/vendor/doctrine/lib/Doctrine/ORM/UnitOfWork.php(2252): SymfonyComponentHttpKernelDebugErrorHandler->handle(8, 'Undefined index...', '/mnt/hgfs/mount...', 2252, Array)
#1 /project/vendor/doctrine/lib/Doctrine/ORM/Query.php(321): DoctrineORMUnitOfWork->getEntityIdentifier(Object(MyBundleEntityDeployment))
#2 /project/vendor/doctrine/lib/Doctrine/ORM/Query.php(274): DoctrineORMQuery->processParameterValue(Object(MyBundleEntityDeployment))
#3 /project/vendor/doctrine/lib/Doctrine/ORM/Query.php(243): DoctrineORMQuery->processParameterMappings(Array)
#4 /project/vendor/doctrine/lib/Doctrine/ORM/AbstractQuery.php(607): DoctrineORMQuery->_doExecute()
#5 /project/vendor/doctrine/lib/Doctrine/ORM/AbstractQuery.php(413): DoctrineORMAbstractQuery->execute(Array, 1)
#6 /project/vendor/gedmo-doctrine-extensions/lib/Gedmo/Sortable/SortableListener.php(344): DoctrineORMAbstractQuery->getResult()
#7 /project/vendor/gedmo-doctrine-extensions/lib/Gedmo/Sortable/SortableListener.php(133): GedmoSortableSortableListener->getMaxPosition(Object(DoctrineORMEntityManager), Object(DoctrineORMMappingClassMetadata), Array, Object(MyBundleEntityDeploymentStep))
#8 /project/vendor/gedmo-doctrine-extensions/lib/Gedmo/Sortable/SortableListener.php(100): GedmoSortableSortableListener->processInsert(Object(DoctrineORMEntityManager), Array, Object(DoctrineORMMappingClassMetadata), Object(MyBundleEntityDeploymentStep))
#9 /project/vendor/doctrine-common/lib/Doctrine/Common/EventManager.php(64): GedmoSortableSortableListener->onFlush(Object(DoctrineORMEventOnFlushEventArgs))
#10 /project/vendor/doctrine/lib/Doctrine/ORM/UnitOfWork.php(280): DoctrineCommonEventManager->dispatchEvent('onFlush', Object(DoctrineORMEventOnFlushEventArgs))
#11 /project/vendor/doctrine/lib/Doctrine/ORM/EntityManager.php(334): DoctrineORMUnitOfWork->commit()
#12 /project/src/My/Bundle/Controller/DeploymentController.php(214): DoctrineORMEntityManager->flush()
#13 [internal function]: MyBundleControllerDeploymentController->createAction(Object(MyBundleEntityRelease), Object(SymfonyComponentHttpFoundationRequest))
#14 /project/vendor/bundles/JMS/SecurityExtraBundle/Security/Authorization/Interception/MethodSecurityInterceptor.php(73): ReflectionMethod->invokeArgs(Object(MyBundleControllerDeploymentController), Array)
#15 /project/app/cache/dev/classes.php(9391) : eval()'d code(1): JMSSecurityExtraBundleSecurityAuthorizationInterceptionMethodSecurityInterceptor->invoke(Object(JMSSecurityExtraBundleSecurityAuthorizationInterceptionMethodInvocation), Array)
#16 [internal function]: {closure}(Object(MyBundleEntityRelease), Object(SymfonyComponentHttpFoundationRequest))
#17 /project/app/cache/dev/classes.php(3925): call_user_func_array(Object(Closure), Array)
#18 /project/app/cache/dev/classes.php(3895): SymfonyComponentHttpKernelHttpKernel->handleRaw(Object(SymfonyComponentHttpFoundationRequest), 1)
#19 /project/app/cache/dev/classes.php(4899): SymfonyComponentHttpKernelHttpKernel->handle(Object(SymfonyComponentHttpFoundationRequest), 1, true)
#20 /project/app/bootstrap.php.cache(551): SymfonyBundleFrameworkBundleHttpKernel->handle(Object(SymfonyComponentHttpFoundationRequest), 1, true)
#21 /project/web/app_dev.php(18): SymfonyComponentHttpKernelKernel->handle(Object(SymfonyComponentHttpFoundationRequest))
#22 {main}

EDIT 2

Full code of create action, as requested

/**
 * @Route("/create/{id}", name="deployment_create_id")
 * @ParamConverter("release", class="MyBundle:Release")
 * @Method({"POST","GET"})
 * @Secure(roles="ROLE_DEPLOYMENT_PLANNER")
 * @Template()
 */
public function createAction( Release $release, Request $request )
{
  $em           = $this->getDoctrine()->getEntityManager();
  $sessionUser = $this->get('security.context')->getToken()->getUser();

  $deployment = new Deployment();
  $deployment->setRelease( $release );
  $deployment->setAuthor( $sessionUser );

  $form = $this->createForm(new DeploymentType($em), $deployment);

  if ($request->getMethod() == 'POST')
  {
    $form->bindRequest($request);

    if ($form->isValid())
    {
      $codeStep = new DeploymentStep();
      $codeStep->setDeployment( $deployment );
      $codeStep->setSequence( 0 );
      $codeStep->setTitle( "Update Code" );
      $codeStep->setDetails( "Update codebase per the plan's specifications" );
      $codeStep->setDeploymentStepType(
          $em->getRepository('MyBundle:DeploymentStepType')->findOneBy(
              array( 'name' => DeploymentStepType::TYPE_OTHER )
          )
      );

      $deploymentStatusLog = new DeploymentStatusLog();
      $deploymentStatusLog->setDeployment( $deployment );
      $deploymentStatusLog->setUser( $sessionUser );
      $deploymentStatusLog->setNotes( 'New Deployment Created' );
      $deploymentStatusLog->setDeploymentStatus(
          $em->getRepository('MyBundle:DeploymentStatus')->findOneBy(
              array( 'title' => DeploymentStatus::STATUS_NEW )
          )
      );

      $deployment->addDeploymentStep( $codeStep );
      $deployment->addDeploymentStatusLog( $deploymentStatusLog );

      try {
        $em->persist( $deployment );
           $em->persist( $codeStep );
           $em->persist( $deploymentStatusLog );
        $em->flush();

        return $this->redirectSuccess(
            'Deployment created.'
          , $release->getRouteName()
          , $release->getRouteParameters()
        );
      }
      catch ( Exception $e )
      {
        $this->setFlashErrorMessage( 'Error saving deployment.' );
      }
    }
  }

  return array(
      'release' => $release
    , 'form'    => $form->createView()
  );
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I had this same problem, in my case it was because I was doing an entity remove & flush in a postRemove LifeCycle Event. From what I could grok in the UnitOfWork logic, you cannot call flush in that event. Doing so will flush any actions still pending to be flushed from the first call to flush, which will already have been removed by the flush call in the event. After figuring that out I was able to find this section of the Doctrine manual that confirmed my suspicion:

http://docs.doctrine-project.org/en/latest/reference/events.html#postupdate-postremove-postpersist

You are correct to assume that flushing each step individually is a bad approach, but if you don't make use of LifeCycle events then I'm not sure what could be causing your particular problem. I was able to debug the issue by error logging the object id variable ($oid) in the executeDeletions function of UnitOfWork.php. I noticed the same id was being deleted repeatedly, which once unset from $this->entityIdentifiers, will fail on subsequent deletes.

My solution was to simply catalog each ID in the postRemove event, and then actually remove the entities in a postFlush event, which is not a lifecycle event and therefore can make subsequent persist operations:

http://docs.doctrine-project.org/en/latest/reference/events.html#lifecycle-events

I'm sure you have since moved on, but in case anyone else runs into this problem...


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...