Search code examples
phpdoctrine-ormzend-framework2one-to-onelaminas-api-tools

How to get the Doctrine cascade persisting working?


I'm working on a ZF2/Apigility application using Doctrine v2.4.2.

The model structure looks like this: an Asset has an Attachment (so, it's a 1:1 relationship):

enter image description here

The problem is that I cannot persist an Asset with an embedded Attachment and get the error

23000, 1048, Column 'attachment_linkassetuuid' cannot be null

It seems that Doctrine tries to save the Attachment first, cannot find a value for the column attachment_linkassetuuid, sets it to null, and the foreign key constraint is broken.

How to define the entities correctly and get the cascade persisting working?


Asset

namespace MyDoctrineDataAccess\Model\Entity;
...
/**
 * Asset
 *
 * @ORM\Table(name="tbl_asset")
 * @ORM\Entity
 */
class Asset
{
    /**
     * @var string @ORM\Column(name="asset_uuid", type="string", length=36, nullable=false)
     * @ORM\Id
     */
    private $uuid;

    /**
     * @var \MyDoctrineDataAccess\Model\Entity\Attachment
     * @ORM\OneToOne(targetEntity="MyDoctrineDataAccess\Model\Entity\Attachment", mappedBy="asset", cascade={"remove", "persist"}, orphanRemoval=true)
     */
    private $attachment;
    ...
}

/**
 * @param \MyDoctrineDataAccess\Model\Entity\Attachment $attachment
 */
public function setAttachment($attachment) {
    $this->attachment = $attachment;
    return $this;
}

/**
 * @return the $attachment
 */
public function getAttachment() {
    return $this->attachment;
}

Attachment

namespace MyDoctrineDataAccess\Model\Entity;
...
/**
 * Attachment
 *
 * @ORM\Table(name="tbl_attachment", indexes={@ORM\Index(name="fk_attachment_uuid", columns={"attachment_linkassetuuid"})})
 * @ORM\Entity
 */
class Attachment
{
    /**
     *
     * @var string @ORM\Column(name="attachment_uuid", type="string", length=36, nullable=false)
     *      @ORM\Id
     */
    private $uuid;

    /**
     *
     * @var \MyDoctrineDataAccess\Model\Entity\Asset
     * @ORM\OneToOne(targetEntity="\MyDoctrineDataAccess\Model\Entity\Asset", inversedBy="attachment", cascade={"persist"})
     * @ORM\JoinColumn(name="attachment_linkassetuuid", referencedColumnName="asset_uuid")
     */
    private $asset;
    ...
}

/**
 *
 * @param \MyDoctrineDataAccess\Model\Entity\Asset $asset
 */
public function setAsset($asset)
{
    $this->asset = $asset;
    return $this;
}

/**
 *
 * @return the $asset
 * 
 */
public function getAsset()
{
    return $this->asset;
}

AssetService

namespace MyApi\V1\Rest\Asset;
...
class AssetService implements ServiceManagerAwareInterface
{
    ...
    public function saveAssets($data)
    {
        $entityManager = $this->serviceManager->get('Doctrine\ORM\EntityManager');
        $assetRepository = $entityManager->getRepository('My\Model\Entity\Asset');
        $hydratorManager = $this->serviceManager->get('hydratormanager');
        $hydrator = $hydratorManager->get('My\\Model\\Entity\\Hydrator\\EntityHydrator');
        foreach ($data as $assetData) {
            $asset = new Asset();
            $hydrator->hydrate($assetData, $asset);
            $entityManager->persist($asset);
            $entityManager->flush();
        }
    }
    ...
}

Solution

  • Since @JoinColumn defaults nullable to true according to the Doctrine documentation I don't really understand the error. But I do see that in your Attachment entity @table definition you declare an index:

    indexes={@ORM\Index(name="fk_attachment_uuid", columns={"attachment_linkassetuuid"})}
    

    Try once to remove this, rebuild your database and check if it solves your issue. If not then leave a comment and I will have another look.

    Check the OneToOne example in the Doctrine documentation. Adding an @index like you do is not mentioned there. Doctrine will add the necessary indexes automatically to your relationship columns.

    UPDATE:

    I think the problem might be that Asset is not the owning side of the relationship. Check the documentation here. Try to change your setAttachment method like this:

    public function setAttachment($attachment) 
    {
        $this->attachment = $attachment;
        $attachment->setAsset($this);
        return $this;
    }