Поле Doctrine 2 ORM DateTime в идентификаторе

В наших таблицах БД столбцы refID и date являются составным первичным ключом, причем одно поле идентификатора отображается как datetime:

class corpWalletJournal
{
    /**
     * @ORM\Column(name="refID", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $refID;

    /**
     * @ORM\Column(name="date", type="datetime", nullable=false)     
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $date;

    public function setRefID($refID)
    {
        $this->refID = $refID;
    }

    public function setDate(\DateTime $date)
    {
        $this->date = $date;
    }
}

Если мы опишем их в сущности как @ORM\Id, этот код вернет исключение "не может преобразовать datetime в строку"...

$filter = array(
    'date' => $this->stringToDate($loopData['date']), 
    'refID' => $loopData['refID']
));

$oCorpWJ = $this->em->getRepository('EveDataBundle:corpWalletJournal')->findOneBy($filter);
// ...
$oCorpWJ->setDate($this->stringToDate($loopData['date']));
// ...

Если мы опишем corpWalletJournal#date как простой столбец, код работает нормально. Зачем?

Как мы можем справиться с этим? Мы должны иметь как date, так и refID в первичном ключе.

ДОБАВЛЕНО:

Итак, я создал новый класс

use \DateTime;

class DateTimeEx extends DateTime
{

public function __toString()
{
    return $this->format('Y-m-d h:i:s');
}

}

И новый тип для него

use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Eve\DataBundle\Entity\Type\DateTimeEx;

class DateTimeEx extends Type
{
    const DateTimeEx = 'datetime_ex';

    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
        return 'my_datetime_ex';
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        return new DateTimeEx($value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        return $value->format('Y-m-d h:i:s');
    }

    public function getName()
    {
        return self::DateTimeEx;
    }

    public function canRequireSQLConversion()
    {
        return true;
    }

}

Как я могу использовать их в сущности?

Мой (отредактированный) Тип Класс

    use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;

class DateTimeEx extends Type
{
    const DateTimeEx = 'datetime_ex';

    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
        return 'my_datetime_ex';
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        return $value;
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        return $value;
    }

    public function getName()
    {
        return self::DateTimeEx;
    }

}

Ответы

Ответ 1

Doctrine 2 ORM необходимо преобразовать поля идентификатора в строки в UnitOfWork. Это необходимо для того, чтобы EntityManager мог отслеживать изменения ваших объектов.

Так как объекты типа DateTime не реализуют метод __toString, преобразование их в строки не так просто, как отведение их к строкам.

Поэтому date, datetime и time по умолчанию не поддерживаются как часть идентификатора.

Чтобы справиться с этим, вы должны определить свой собственный пользовательский тип поля mydatetime сопоставленный с вашим собственным классом MyDateTime который реализует __toString. Таким образом, ORM может обрабатывать идентификаторы также, если они содержат объекты.

Вот пример того, как выглядит этот класс:

class MyDateTime extends \DateTime 
{
    public function __toString()
    {
        return $this->format('U');
    }
}

И вот пример того, как будет выглядеть пользовательский тип DBAL:

use Doctrine\DBAL\Types\DateTimeType;
use Doctrine\DBAL\Platforms\AbstractPlatform;

class MyDateTimeType extends DateTimeType
{
    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        $dateTime = parent::convertToPHPValue($value, $platform);

        if ( ! $dateTime) {
            return $dateTime;
        }

        return new MyDateTime('@' . $dateTime->format('U'));
    }

    public function getName()
    {
        return 'mydatetime';
    }
}

Затем вы регистрируете его со своей конфигурацией ORM во время загрузки (в зависимости от используемой структуры). В symfony он документирован в документации по доктрине symfony.

После этого вы можете использовать его в своих объектах:

class corpWalletJournal
{
    // ...

    /**
     * @ORM\Column(name="date", type="mydatetime", nullable=false)     
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $date;

Ответ 2

Будьте осторожны с

return new DateTimeEx('@' . $dateTime->format('U'));

Часовой пояс не будет хорошим. Вы должны сделать:

$val = new DateTimeEx('@' . $dateTime->format('U'));
$val->setTimezone($dateTime->getTimezone());

return $val;