Working with Entity Manager

The entity manager is the central access point to ORM persistence work.

Repositories are built on top of it. That means the entity manager is not a competing concept. It is the broader coordination layer underneath the more convenient entity-scoped API.

Repository versus entity manager

Use a repository when:

  • the code is centered on one entity type
  • you are writing normal app-level service logic
  • you want the simplest day-to-day CRUD path

Use the entity manager when:

  • the workflow spans multiple entity types
  • you are writing infrastructure or generic package code
  • you are in a standalone script and want one central entry point
  • you want lower-level control without going all the way down to raw SQL

Getting the entity manager

In standalone usage, the entity manager comes from the data source:

<?php

$entityManager = $dataSource->manager;

If you already have a repository, you also have access to the same manager:

<?php

$entityManager = $moviesRepository->manager;

Creating an entity

The entity manager can create an entity instance from plain input:

<?php

use App\Entities\MovieEntity;

$movie = $entityManager->create(MovieEntity::class, [
  'title' => 'Midnight Harbor',
  'synopsis' => 'A ferry terminal becomes the meeting point for strangers with linked pasts.',
  'isNowShowing' => true,
]);

Why this is useful:

  • you get an entity-shaped object
  • defaults and mapping happen in one place
  • you do not have to manually populate every property by hand

If the input type differs from the target entity property type, registered type converters can normalize the value during this hydration step.

Saving an entity

<?php

$saveResult = $entityManager->save($movie);

if ($saveResult->isError()) {
  throw $saveResult->getLatestError();
}

save() is the smooth default write path for most application work:

  • insert when the record is new
  • update when the record already exists

Finding records

<?php

$movies = $entityManager->find(MovieEntity::class, [
  'where' => ['isNowShowing' => true],
  'order' => ['id' => 'DESC'],
  'limit' => 20,
])->getData();

To fetch a single record:

<?php

$movie = $entityManager->findOne(MovieEntity::class, [
  'where' => ['id' => 42],
])->getFirst();

The important thing to notice is that the entity class is explicit on entity-manager calls. Repositories do not need that extra argument because they are already bound to one entity type.

Updating records

With the entity manager, the method shape is:

update(entityClass, partialEntity, conditions)

Example:

<?php

$updateResult = $entityManager->update(
  MovieEntity::class,
  ['isNowShowing' => false],
  ['id' => 42],
);

if ($updateResult->isError()) {
  throw $updateResult->getLatestError();
}

That ordering is worth remembering:

  • first the entity class
  • then the partial update payload
  • then the criteria describing which rows should change

Writing relation IDs

When an entity has an owning relation with a JoinColumn, you can write the foreign key through a relation ID alias without adding a duplicate scalar property to the entity.

For an entity shaped like this:

#[ManyToOne(type: CategoryEntity::class)]
#[JoinColumn(name: 'category_id', referencedColumnName: 'id')]
public ?CategoryEntity $category = null;

this update is valid:

<?php

$entityManager->update(
  ProductEntity::class,
  ['categoryId' => 12],
  ['id' => 42],
);

The ORM maps categoryId to category_id for the write. The alias is write-only unless the entity declares a real public categoryId property, so response shapes stay driven by the entity model instead of exposing duplicate relation fields.

The same alias support applies to insert(), update(), and upsert() calls, including repository inserts that pass through to the entity manager.

Array payloads may also use the database column name directly:

<?php

$entityManager->update(
  ProductEntity::class,
  ['category_id' => 12],
  ['id' => 42],
);

If the relation references a non-id column, the alias follows that referenced column. For example, referencedColumnName: 'uuid' gives you a write alias such as categoryUuid.

Clearing nullable values from object payloads

Array partial updates treat null as an explicit value:

<?php

$entityManager->update(
  MovieEntity::class,
  ['synopsis' => null],
  ['id' => 42],
);

Object DTO payloads preserve existing nullable values by default when a property is null. To clear a nullable column from an object payload, opt in with UpdateOptions::writeNulls:

<?php

use Assegai\Orm\Management\Options\UpdateOptions;

$entityManager->update(
  MovieEntity::class,
  $payload,
  ['id' => 42],
  new UpdateOptions(writeNulls: ['synopsis']),
);

Use writeNulls: true when every mapped null property on the object should be written.

Removing records

<?php

$deleteResult = $entityManager->remove($movie);

If your entity uses soft-delete patterns such as ChangeRecorderTrait, prefer the soft-delete workflow your application has standardized on instead of reaching straight for hard delete every time.

Cross-entity workflows

This is one of the clearest places where the entity manager shines.

Imagine a workflow that creates a cinema, then creates its first showtime:

<?php

use App\Entities\CinemaEntity;
use App\Entities\ShowtimeEntity;

$cinema = $entityManager->create(CinemaEntity::class, [
  'name' => 'Riverside Screens',
]);

$entityManager->save($cinema);

$showtime = $entityManager->create(ShowtimeEntity::class, [
  'startsAt' => '2026-04-12 20:00:00',
]);

$showtime->cinema = $cinema;

$entityManager->save($showtime);

This kind of workflow would still be possible through repositories, but the entity manager makes the coordination role more obvious.

Practical advice

  • Prefer repositories for feature services.
  • Reach for the entity manager when the workflow is broader than one entity.
  • Remember that entity-manager methods take the entity class explicitly.
  • Treat the entity manager as the center of the ORM, not an "advanced feature nobody should use".

Next steps

Once the entity manager feels clear, move on to Query Builder.