wawa
wawa

Reputation: 5066

Doctrine ignoring IS NULL/IS NOT NULL on nullable Date type

I've noticed a strange behavior on a Symfony app.

We have an entity containing a nullable date field:

/**
 * @var \DateTime
 *
 * @ORM\Column(name="foo", type="date", nullable=true)
 */
private $foo;

The corresponding query to get only entries where it's set looks like this:

public function findFoo()
{
    $qb = $this->_em->createQueryBuilder();
    $qb
        ->select('e')
        ->from($this->_entityName, 'e')
        ->where('e.foo IS NOT NULL');

    return $qb->getQuery()->getResult();
}

This query however returns all entries, even those who are set to Null in the db.

When dumping the value of the foo field, we get this:

object(DateTime)[549]
  public 'date' => string '-0001-11-30 00:00:00.000000' (length=27)
  public 'timezone_type' => int 3
  public 'timezone' => string 'UTC' (length=3)

We tried to modify the query to use this where clause instead:

->where($qb->expr()->isNotNull('e.foo'))

But the result was still the same. We've now settled for this "hack":

->where('e.foo > :dateSet')
->setParameter('dateSet', new \DateTime('1970-01-01'))

Which does the trick but kind of feels wrong.

Question: What is the correct way to check for IS NOT NULL within a Doctrine Query, on a date field that's nullable? Is this a bug in doctrine or expected behavior? If it's expected, what's the reason for it?


Edit: Followup question, how can we test for IS NULL? Neither ->where('e.foo IS NULL'); nor ->where('e.foo < :dateSet'); or ->where('e.foo <= :dateSet'); seams to work in this case. Note: I've also tried to use the current date for dateSet. Sure I could now use a NOT IN( ... ) and in there a query for the not null ones, but that would be even more hacky. For now I'll probably have to decide on the PHP side.

Edit: As requested, the relevant part of the class including the constructor

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * FooBar
 *
 * @ORM\Table(name="foo_bar")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\FooBarRepository")
 */
class FooBar
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    // [...]

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="foo", type="date", nullable=true)
     */
    private $foo;

    public function __construct()
    {
        $this->foo = null;
        // [...]
    }

    // [...]

    public function isFoo(): bool
    {
        return !is_null($this->foo);
    }

    // [...]

    /**
     * Set foo
     *
     * @param \DateTime $foo
     *
     * @return FooBar
     */
    public function setFoo($foo)
    {
        $this->foo = $foo;

        return $this;
    }

    /**
     * Get foo
     *
     * @return \DateTime|null
     */
    public function getFoo()
    {
        return $this->foo;
    }
}

Upvotes: 1

Views: 1839

Answers (1)

wawa
wawa

Reputation: 5066

So the issue in the end was, that the field in question, used to be a not nullable boolean, which I changed into a nullable date. I thought all the values should be null, and indeed when checking with a SQL tool, it showed all values as null.

However internally it seams that they weren't null after all. I run a update foo_bar set foo = NULL; which fixed all my issues.

I'm not sure if it's a bug in doctrine per se, I rather think it's a issue on the MySQL side.

Upvotes: 1

Related Questions