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
I found the solution. I had a Timestampable trait
which caused issues, editing the trait solved the issue:
It is possible you are missing a GeneratedValue line, like this: