skip to Main Content

I have the following Model:

<?php

namespace AppDomainModelMeal;

use AppDomainModelHasUuid;
use AppDomainModelTimestampable;
use AppDomainModelUserChild;
use DoctrineORMMapping as ORM;
use RamseyUuidUuid;

#[ORMEntity]
#[ORMTable(name: 'meal_opt_out')]
final class MealOptOut
{
    use HasUuid;
    use Timestampable;

    #[ORMManyToOne(targetEntity: MealItemSchedule::class)]
    #[ORMJoinColumn(name: 'meal_item_schedule_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    private MealItemSchedule $mealItemSchedule;

    #[ORMManyToOne(targetEntity: Child::class)]
    #[ORMJoinColumn(name: 'child_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    private Child $child;

    public function __construct(
        MealItemSchedule $mealItemSchedule,
        Child $child
    )
    {
        $this->id = Uuid::uuid4()->toString();
        $this->mealItemSchedule = $mealItemSchedule;
        $this->child = $child;
    }
    
    // Getters and setters...
}

HasUuid trait:

<?php

namespace AppDomainModel;

use DoctrineORMMapping as ORM;

trait HasUuid
{
    #[ORMId]
    #[ORMColumn(name: 'id', type: 'string', unique: true)]
    protected string $id;

    public function getId(): string
    {
        return $this->id;
    }

    public function setId(string $id): void
    {
        $this->id = $id;
    }
}

I am using a hexagonal architecture, so I have a few interfaces, but the most important file is DoctrineMealOptOutRepository:

<?php

namespace AppInfrastructureRepositoryMeal;

use AppDomainModelMealMealOptOut;
use AppDomainRepositoryMealMealOptOutRepositoryInterface;
use DoctrineBundleDoctrineBundleRepositoryServiceEntityRepository;
use DoctrinePersistenceManagerRegistry;

final class DoctrineMealOptOutRepository extends ServiceEntityRepository implements MealOptOutRepositoryInterface
{
    public function __construct(
        private ManagerRegistry $manager,
    )
    {
        parent::__construct($manager, MealOptOut::class);
    }
    
    /*
    public function save(MealOptOut $mealOptOut): void
    {
        $this->getEntityManager()->persist($mealOptOut);
        $this->getEntityManager()->flush();
    }
     */

    public function save(MealOptOut $mealOptOut): void
    {
        $connection = $this->getEntityManager()->getConnection();
        $sql = '
            INSERT INTO meal_opt_out (id, meal_item_schedule_id, child_id, created_at, updated_at)
            VALUES (:id, :meal_item_schedule_id, :child_id, NOW(), NOW())
        ';
        $params = [
            'id' => $mealOptOut->getId(),
            'meal_item_schedule_id' => $mealOptOut->getMealItemSchedule()->getId(),
            'child_id' => $mealOptOut->getChild()->getId(),
        ];

        $connection->executeStatement($sql, $params);
    }
}

MealOptOutRepositoryInterface:

<?php

namespace AppDomainRepositoryMeal;

use AppDomainModelMealMealOptOut;

interface MealOptOutRepositoryInterface
{
    public function save(MealOptOut $mealOptOut): void;
}

MealOptOutServiceInterface:

<?php

namespace AppDomainServiceMeal;

use AppDomainModelMealMealItemSchedule;
use AppDomainModelMealMealOptOut;
use AppDomainModelUserChild;


interface MealOptOutServiceInterface
{
    public function saveMealOptOut(MealOptOut $mealOptOut): ?MealOptOut;
}

MealOptOutServiceImplementation:

<?php

namespace AppDomainServiceMealImpl;

use AppDomainModelMealMealItemSchedule;
use AppDomainModelMealMealOptOut;
use AppDomainModelUserChild;
use AppDomainRepositoryMealMealOptOutRepositoryInterface;
use AppDomainServiceMealMealOptOutServiceInterface;
use DoctrineBundleDoctrineBundleRepositoryServiceEntityRepository;
use DoctrinePersistenceManagerRegistry;
use RamseyUuidUuid;

final class MealOptOutServiceImplementation extends ServiceEntityRepository implements MealOptOutServiceInterface
{
    public function __construct(
        private MealOptOutRepositoryInterface $mealOptOutRepository,
        private ManagerRegistry $manager,
    )
    {
        parent::__construct($manager, MealOptOut::class);
    }

    public function saveMealOptOut(MealOptOut $mealOptOut): ?MealOptOut
    {
        return $this->mealOptOutRepository->save($mealOptOut);
    }
}

The service is called like this:

$mealOptOut = new MealOptOut($this->selectedMealItemSchedule, $child);
// $mealOptOut->setId(Uuid::uuid4()->toString());
$this->mealOptOutService->saveMealOptOut($mealOptOut);

The problem is that when I use this version of save in DoctrineMealOptOutRepository

    public function save(MealOptOut $mealOptOut): void
    {
        $this->getEntityManager()->persist($mealOptOut);
        $this->getEntityManager()->flush();
    }

I get the following error:

An exception occurred while executing a query: SQLSTATE[HY000]: General error: 1364 Field 'id' doesn't have a default value
...

at DoctrineDBALConnection->convertExceptionDuringQuery(object(Exception), 'INSERT INTO meal_opt_out (created_at, updated_at, meal_item_schedule_id, child_id) VALUES (?, ?, ?, ?)', array(null, null, 'f8ab33de-ab9d-11ef-bcf3-44da78cb937b', 'a440ab82-573d-4475-a9e3-b5bf839352ed'), array('datetime', 'datetime', 'string', 'string'))

You see in the log that the entitymanager is for some reason not inserting the id column. I am 100% sure the id is set though. I am using the exact same architecture and trait for other entities, and it works for them. I tried auto-generating the uuid but also that didn’t help.

Temporarily I have it fixed by executing the SQL manually (which does work, indicating the ID is indeed correctly set).

What am I doing wrong?

2

Answers


  1. Chosen as BEST ANSWER

    I found the solution. I had a Timestampable trait

    <?php
    
    namespace AppDomainModel;
    use DateTime;
    use DoctrineORMMapping as ORM;
    
    trait Timestampable
    {
        #[ORMColumn(type: 'datetime', nullable: true)]
        #[ORMGeneratedValue(strategy: 'AUTO')]
        protected DateTime $createdAt;
        
        #[ORMColumn(type: 'datetime', nullable: true)]
        #[ORMGeneratedValue(strategy: 'AUTO')]
        protected DateTime $updatedAt;
    
        public function setCreatedAt(): void
        {
            $this->createdAt = new DateTime();
        }
    
        public function getCreatedAt(): DateTime
        {
            return $this->createdAt;
        }
    
        public function setUpdatedAt(): void
        {
            $this->updatedAt = new DateTime();
        }
    
        public function getUpdatedAt(): DateTime
        {
            return $this->updatedAt;
        }
    
        public function updateTimestamps(): void
        {
            if ($this->createdAt === null) {
                $this->setCreatedAt();
            }
            $this->setUpdatedAt();
        }
    }
    

    which caused issues, editing the trait solved the issue:

    <?php
    
    namespace AppDomainModel;
    
    use DateTime;
    use DoctrineORMMapping as ORM;
    
    trait Timestampable
    {
        #[ORMColumn(type: 'datetime', nullable: true)]
        protected DateTime $createdAt;
        
        #[ORMColumn(type: 'datetime', nullable: true)]
        protected DateTime $updatedAt;
    
        #[ORMPrePersist]
        public function setCreatedAt(): void
        {
            $this->createdAt = new DateTime();
        }
    
        public function getCreatedAt(): DateTime
        {
            return $this->createdAt;
        }
    
        #[ORMPreUpdate]
        public function setUpdatedAt(): void
        {
            $this->updatedAt = new DateTime();
        }
    
        public function getUpdatedAt(): DateTime
        {
            return $this->updatedAt;
        }
    
        public function updateTimestamps(): void
        {
            if ($this->createdAt === null) {
                $this->setCreatedAt();
            }
            $this->setUpdatedAt();
        }
    }
    
    

  2. It is possible you are missing a GeneratedValue line, like this:

    <?php
    
    namespace AppDomainModel;
    
    use DoctrineORMMapping as ORM;
    
    trait HasUuid
    {
         #[ORMId]
         #[ORMGeneratedValue(strategy: 'CUSTOM')]
         #[ORMColumn(type: 'uuid', unique: true)]
         #[ORMCustomIdGenerator(class: UuidGenerator::class)]
         private ?UuidInterface $id = null;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search