Timestamped
#[Silverback\Timestamped] automatically populates createdAt and modifiedAt on persist and flush. It's a lightweight annotation with no configuration required — add it and forget it.
Setup
use Silverback\ApiComponentsBundle\Annotation as Silverback;
use Silverback\ApiComponentsBundle\Entity\Core\AbstractComponent;
use Silverback\ApiComponentsBundle\Entity\Utility\TimestampedTrait;
#[Silverback\Timestamped]
#[ORM\Entity]
#[ApiResource(mercure: true)]
class Article extends AbstractComponent
{
use TimestampedTrait;
}
What TimestampedTrait Adds
| Property | Type | Behaviour |
|---|---|---|
createdAt | ?\DateTimeImmutable | Set once on first persist; never updated after that |
modifiedAt | ?\DateTime | Updated on every Doctrine flush |
Both are serialized into API responses automatically. Treat them as bundle-managed: modifiedAt is overwritten with the current time on every write, and createdAt is set when the resource is first created — don't send either from the client.
Customising Field Names
The default names are createdAt and modifiedAt, matching TimestampedTrait. If those names conflict with your own fields, override them:
#[Silverback\Timestamped(createdAtField: 'publishedOn', modifiedAtField: 'lastEdited')]
When using custom names you must define the properties and getters/setters yourself rather than using TimestampedTrait:
#[Silverback\Timestamped(createdAtField: 'publishedOn', modifiedAtField: 'lastEdited')]
#[ORM\Entity]
#[ApiResource]
class Article extends AbstractComponent
{
#[ORM\Column(nullable: true)]
public ?\DateTimeImmutable $publishedOn = null;
#[ORM\Column(nullable: true)]
public ?\DateTime $lastEdited = null;
}
Combining with Publishable
Timestamps and publish workflow are commonly used together for blog-style content:
#[Silverback\Publishable]
#[Silverback\Timestamped]
#[ORM\Entity]
#[ApiResource(mercure: true)]
class Article extends AbstractComponent
{
use PublishableTrait;
use TimestampedTrait;
public ?string $headline = null;
}
The createdAt and modifiedAt fields reflect the draft entity's lifecycle. The published twin gets its own timestamps set when it's first created.
Using Timestamps on the Front-End
Access them via resource.value?.data:
// resource from useCwaComponent(props)
const createdAt = computed(() =>
resource.value?.data?.createdAt
? new Date(resource.value.data.createdAt).toLocaleDateString()
: ''
)
Both properties are ISO 8601 strings in the API response. Use Intl.DateTimeFormat or a library like date-fns to format them for display.
Migration Notes
TimestampedTrait adds two NOT NULL columns to your entity's table: createdAt as datetime_immutable and modifiedAt as datetime. The migration is straightforward — no foreign keys or join tables involved.
The bundle also registers NotNull validation constraints on both fields, in the {ShortName}:timestamped group.
nullable: true. When you map the properties yourself the bundle leaves your mapping alone, so you choose the nullability — but a nullable column will still fail the bundle's NotNull constraint if the value is never set.