EntityWorkspace
EntityWorkspace is Weaver ORM's central unit-of-work service. It tracks entities across a single request, batches all writes, and flushes them to the database in a single coordinated operation. Think of it as the improved successor to Doctrine's EntityManager, redesigned for clarity, worker safety, and explicit intent.
Key differences from Doctrine EntityManager
| Doctrine EntityManager | Weaver EntityWorkspace |
|---|---|
persist($entity) | add($entity) |
flush() | push() |
remove($entity) | delete($entity) |
refresh($entity) | reload($entity) |
detach($entity) | untrack($entity) |
contains($entity) | isTracked($entity) |
clear() | reset() |
One EntityWorkspace instance exists per HTTP request. It holds no shared state between requests and implements ResetInterface so long-running workers (RoadRunner, FrankenPHP) can call reset() between requests instead of rebuilding the container.
Tracking entities
add($entity) — schedule an entity for insertion
Marks a new entity for INSERT on the next push(). No SQL is executed immediately.
<?php
use App\Entity\User;
use Weaver\ORM\EntityWorkspace;
final class RegisterUserHandler
{
public function __construct(
private readonly EntityWorkspace $workspace,
) {}
public function handle(RegisterUserCommand $command): void
{
$user = new User(
email: $command->email,
name: $command->name,
);
$this->workspace->add($user);
$this->workspace->push(); // INSERT executed here
}
}
Calling add() on an already-tracked entity is a no-op — it is safe to call multiple times.
push() — flush all pending changes
Executes all queued INSERTs, UPDATEs, and DELETEs in a single database round-trip (wrapped in an implicit transaction). After push() completes, every tracked entity is in the MANAGED state with an up-to-date identity.
<?php
$order = new Order(customerId: $customerId, total: $total);
$this->workspace->add($order);
foreach ($lineItems as $item) {
$this->workspace->add($item);
}
$this->workspace->push(); // all INSERTs committed atomically
Insert ordering is resolved automatically via topological sort on foreign-key relationships — child entities are always inserted after their parents, regardless of the order in which they were add()-ed.
delete($entity) — schedule an entity for deletion
Marks a tracked entity for DELETE on the next push(). The entity stays in memory until push() is called.
<?php
$post = $this->postRepository->findOrFail($id);
$this->workspace->delete($post);
$this->workspace->push(); // DELETE executed here
If the entity's mapper declares cascade: ['delete'] on a relationship, associated children are automatically scheduled for deletion as well.
Refreshing and detaching
reload($entity) — refresh from the database
Discards any in-memory changes and re-populates the entity from its current row in the database. Useful after an optimistic lock conflict or when another process may have updated the record.
<?php
try {
$this->workspace->push();
} catch (OptimisticLockException $e) {
$this->workspace->reload($product); // reload the latest version
// re-apply changes and retry
}