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