Timestamped
#[Silverback\Timestamped] automatically populates createdAt and modifiedAt whenever the API writes the resource (and when CwaFixtureBuilder creates it). 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 when the resource is created through the API; never updated after that |
modifiedAt | ?\DateTime | Set to the current time on every API write (POST, PUT, PATCH) |
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.
The values are set by the API's serializer, not by a Doctrine listener. An entity you create and flush in your own PHP code (outside CwaFixtureBuilder) gets no timestamps, and both columns are NOT NULL, so set them yourself.
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;
}
A draft is cloned from the published resource, so it keeps the original createdAt, and each edit to the draft sets its modifiedAt. Publishing copies the draft's values onto the published resource, timestamps included.
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.