Skip to content

Instantly share code, notes, and snippets.

@bpolaszek
Created September 24, 2015 09:43
Show Gist options
  • Save bpolaszek/902066672c9eb7944a7d to your computer and use it in GitHub Desktop.
Save bpolaszek/902066672c9eb7944a7d to your computer and use it in GitHub Desktop.
Doctrine : Fix ManyToOne relationships when the value in database is 0 instead of NULL
<?php
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* Trait CleanAssociationsTrait
*
* This trait is intended to fix the Doctrine ManyToOne relationships when the stored value in database is a 0 instead of NULL.
* Sometimes you plug Doctrine on an existing database without foreign key support.
*
* When you have a Book entry with Id 1 and a id_author with value 1, Doctrine that the book #1 refers to the author #1. $book->getAuthor() should return an Author entity.
* When you have a Book entry with Id 2 and a NULL id_author, Doctrine knows that the book #1 doesn't have an author (provided you authorized a ManyToOne with nullable = true). $book->getAuthor() should return null.
* When you have a Book entry with Id 3 and a id_author with value 0, Doctrine throws an "Entity not found" exception.
*
* This trait, used in your Book entity, will force the author property to be set to null instead of 0.
* Then, $book->getAuthor() will return null instead of throwing an "Entity not found" exception.
*
* To use it, add the @ORM\HasLifecycleCallbacks() annotation on top of your entity, and use the trait in it (see example usage below)
*/
trait CleanAssociationsTrait {
/**
* @ORM\PostLoad()
*/
public function cleanAssociations(LifecycleEventArgs $eventArgs) {
$entity = $eventArgs->getEntity();
$data = $eventArgs->getEntityManager()->getUnitOfWork()->getOriginalEntityData($entity);
# For each association
foreach ($eventArgs->getEntityManager()->getClassMetadata(get_class($entity))->getAssociationMappings() AS $associationMapping) {
# Check if it's a ManyToOne relationship
if (isset($associationMapping['fieldName']) && isset($associationMapping['joinColumns']) && is_array($associationMapping['joinColumns']) && count($associationMapping['joinColumns']) === 1 && isset($associationMapping['joinColumns'][0]['name'])) {
# Get relation fieldName and the associated column name
$fieldName = $associationMapping['fieldName'];
$columnName = $associationMapping['joinColumns'][0]['name'];
# Check raw data == 0 => set field to null
if ($data[$columnName] == 0 && is_callable([$entity, 'set' . $fieldName]))
$entity->{'set'.$fieldName}(null);
}
}
}
}
# Example usage :
#
#/**
# * @ORM\Table(name="book")
# * @ORM\Entity
# * @ORM\HasLifecycleCallbacks()
# */
#class Book {
#
# use CleanAssociationsTrait;
#
# /**
# * @var integer
# *
# * @ORM\Column(name="id", type="integer", nullable=false)
# * @ORM\Id
# * @ORM\GeneratedValue(strategy="IDENTITY")
# */
# protected $idBook;
#
# /**
# * @var Author
# * @ORM\ManyToOne(targetEntity="Author")
# * @ORM\JoinColumn(name="id_author", referencedColumnName="id")
# */
# protected $author;
#
#}
@toby-griffiths
Copy link

In case it's useful, here's my updated version using property types, and attributes…

<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;

/**
 * Trait CleanAssociationsTrait
 *
 * This trait is intended to fix the Doctrine ManyToOne relationships when the stored value in database is a 0 instead
 * of NULL.
 * Sometimes you plug Doctrine on an existing database without foreign key support.
 *
 * - When you have a Book entry with Id 1 and a id_author with value 1, Doctrine that the book #1 refers to the author
 *   #1. $book->getAuthor() should return an Author entity.
 * - When you have a Book entry with Id 2 and a NULL id_author, Doctrine knows that the book #1 doesn't have an author
 *   (provided you authorized a ManyToOne with nullable = true). $book->getAuthor() should return null.
 * - When you have a Book entry with Id 3 and a id_author with value 0, Doctrine throws an "Entity not found" exception.
 *
 * This trait, used in your Book entity, will force the author property to be set to null instead of 0.
 * Then, $book->getAuthor() will return null instead of throwing an "Entity not found" exception.
 *
 * To use it, add the #[HasLifecycleCallbacks] annotation on top of your entity, and use the trait in it (see
 * example usage below)
 */
trait CleanAssociationsTrait
{
    #[ORM\PostLoad]
    public function cleanAssociations(LifecycleEventArgs $eventArgs): void
    {
        $entity = $eventArgs->getObject();
        $entityManager = $eventArgs->getObjectManager();
        $data = $entityManager->getUnitOfWork()->getOriginalEntityData($entity);

        $classMetadata = $entityManager->getClassMetadata(get_class($entity));
        # For each association
        foreach ($classMetadata->getAssociationMappings() as $associationMapping) {
            # Check if it's a ManyToOne relationship
            if (
                isset($associationMapping['fieldName'], $associationMapping['joinColumns'][0]['name'])
                && is_array($associationMapping['joinColumns'])
                && count($associationMapping['joinColumns']) === 1
            ) {
                # Get relation fieldName and the associated column name
                $fieldName = $associationMapping['fieldName'];
                $columnName = $associationMapping['joinColumns'][0]['name'];

                # Check raw data == 0 => set field to null
                if (0 === $data[$columnName] && is_callable([$entity, 'set' . $fieldName])) {
                    $entity->{'set' . $fieldName}(null);
                }
            }
        }
    }
}

//  Example usage :
//
// #[ORM\Table(name="book")]
// #[ORM\Entity]
// #[ORM\HasLifecycleCallbacks()]
// class Book {
//
//     use CleanAssociationsTrait;
//
//     #[ORM\Column(name="id", type="integer", nullable=false)]
//     #[ORM\Id]
//     #[ORM\GeneratedValue(strategy="IDENTITY")]
//     protected integer $idBook;
//
//     #[ORM\ManyToOne(targetEntity="Author")]
//     #[ORM\JoinColumn(name="id_author", referencedColumnName="id")]
//     protected Author $author;
// }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment