symfonylab/doctrine-orm-extensions-bundle
A Symfony bundle that extends Doctrine ORM with reusable entity behaviors and utilities, helping you cut boilerplate and keep persistence logic clean. Install it via Composer, enable the bundle, and configure the extensions your project needs.
Start by confirming the package version and reviewing the trait source before wiring it into models:
composer require <vendor>/<package>:^1.1
Look first at:
MutableMicrosecondsTrait implementation — specifically which attributes it touches and whether it overrides existing casts, accessors, or mutators.created_at when it is not already set.datetime(6) / timestamp(6) (MySQL/PostgreSQL) rather than second-precision columns.Minimal first use case: add the trait to a model that records time-sensitive events, then create a record without passing created_at:
use Vendor\Package\Concerns\MutableMicrosecondsTrait;
class AuditEvent extends Model
{
use MutableMicrosecondsTrait;
protected $guarded = [];
}
$event = AuditEvent::create([
'type' => 'login',
]);
// created_at should now be populated automatically,
// and should retain microsecond precision if the schema supports it.
dump($event->created_at->format('Y-m-d H:i:s.u'));
Verify the stored value after save, not just the in-memory model. Query the row back and inspect the raw database value:
dump($event->fresh()->getRawOriginal('created_at'));
If the value comes back with .000000, the trait is working but the column precision is not.
For packages with many time-sensitive models, put the trait on a shared base model. For isolated use cases, apply it only to models where sub-second ordering matters.
abstract class BaseModel extends Model
{
use MutableMicrosecondsTrait;
}
Avoid applying it globally if only a few tables use microsecond columns; otherwise you create inconsistent behavior between models.
Use explicit datetime casts so Laravel and the trait agree on the expected type and format:
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'occurred_at' => 'datetime',
];
If the trait changes how date attributes are mutated, keep custom datetime fields in the same cast style to avoid mixed mutable/immutable behavior.
created_atThe new behavior is most useful when creating records through Eloquent:
$event = AuditEvent::create([
'type' => 'payment.captured',
'payload' => $payload,
]);
Do not manually set created_at unless you need to backfill or import historical data. If you do set it, confirm the package respects your value rather than overwriting it:
AuditEvent::create([
'type' => 'imported',
'created_at' => now()->subDay(),
]);
Microsecond precision is most valuable for append-only tables, event logs, and high-frequency audit records. Pair it with a deterministic secondary sort:
AuditEvent::query()
->orderByDesc('created_at')
->orderByDesc('id')
->get();
Do not assume microsecond timestamps are globally unique. Concurrent writes can still share the same microsecond value.
In factories, prefer relative timestamps and only override created_at when the test depends on it:
public function definition(): array
{
return [
'type' => 'test',
// created_at is intentionally omitted so the package fills it.
];
}
For time-sensitive tests, combine the package with Laravel time travel:
$this->travelTo(now()->setMicrosecond(123456));
$event = AuditEvent::create(['type' => 'test']);
$this->assertSame(123456, $event->created_at->microsecond);
If clients depend on microsecond precision, format explicitly in resources:
return [
'created_at' => $this->created_at?->format('Y-m-d\TH:i:s.uP'),
];
Do not assume Laravel’s default JSON date serialization preserves microseconds. It often does not.
The trait can only preserve microseconds if the underlying column stores them. Check migrations:
$table->timestamp('created_at', 6)->nullable();
For MySQL, timestamp without precision truncates to seconds. For PostgreSQL, use timestamp(6). SQLite stores dates as text and behaves differently, so test against the database engine you deploy on.
created_at is a behavioral changeThis release changes model state before save. Code that previously checked is_null($model->created_at) before persistence may now see a value earlier than expected:
$event = new AuditEvent(['type' => 'test']);
// Depending on when the package sets created_at,
// this may no longer be null before save.
dump($event->created_at);
Audit any model observers, policies, or validation rules that assume created_at is null until after save().
A “mutable” trait means date objects may be modified in place. Avoid sharing the same Carbon instance across models or attributes:
$now = now();
$eventA->created_at = $now;
$eventB->created_at = $now;
$eventA->created_at->addMinute();
// $eventB->created_at may also change if the same mutable instance is shared.
Clone when assigning shared values:
$eventB->created_at = $now->copy();
If your model already defines a created_at accessor, mutator, or custom cast, inspect how the trait composes with it. Trait methods can be overridden by the class, but event listeners and boot methods may still run. Use getAttribute, getRawOriginal, and getDirty to debug the final value.
Even if the database stores microseconds, toArray() or toJson() may use a lower-precision format. Override serializeDate() on the model if needed:
protected function serializeDate(DateTimeInterface $date): string
{
return $date->format('Y-m-d\TH:i:s.uP');
}
Microseconds do not solve timezone issues. Continue storing timestamps in UTC and converting at the presentation boundary. Confirm whether the trait uses config('app.timezone') or the model’s $timezone property.
When upgrading to 1.1.0, run tests that cover:
created_at.created_at.created_at with multiple records created in the same request.Check whether the trait exposes overridable methods for timestamp generation or attribute selection. If it does, prefer overriding those methods in a base model over copying trait logic. If it does not, keep the trait application narrow and wrap custom behavior in model events rather than patching vendor code.
How can I help you explore Laravel packages today?