l0wskilled/api-platform-traits
API Platform Traits offers reusable ORM and ODM traits for identifiers, timestamps, and soft deletes, with setup guidance for Gedmo/Stof Doctrine extensions to simplify common entity properties in API Platform projects.
First, a reality check: despite being filed under a Laravel assessment, this is not a Laravel package. It is a Symfony / API Platform (Doctrine ORM & ODM) trait bundle, published by epubli and forked under l0wskilled. If you spend your day in Eloquent, the closest equivalents are already built into the framework ($timestamps, SoftDeletes, HasUuids). You would only reach for this package if you have a Symfony + API Platform service in your portfolio, a sidecar service, or you're evaluating it as reference code.
Assuming you actually need it:
composer require l0wskilled/api-platform-traits (or the upstream epubli/api-platform-traits — see Gotchas).
If you use OrmTimestampableTrait or OrmSoftDeletableTrait, you also need the Gedmo extension bundle:
composer require stof/doctrine-extensions-bundle
Register the listeners in config/packages/stof_doctrine_extensions.yaml:
stof_doctrine_extensions:
default_locale: en_US
orm:
default:
softdeleteable: true
timestampable: true
Use the traits on an entity and add the Gedmo annotation at the class level:
use Epubli\ApiPlatform\TraitBundle\OrmSoftDeletableTrait;
use Epubli\ApiPlatform\TraitBundle\OrmTimestampableTrait;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false, hardDelete=false)
*/
class Post
{
use OrmTimestampableTrait;
use OrmSoftDeletableTrait;
}
Where to look first: read the six trait files themselves — they are 10–20 lines each and that is the entire package. Don't look for docs; the README is the docs. The linked epubli/api-platform-test repo is the only worked example. A good first use case is an API Platform resource that needs id, createdAt, updatedAt, and deletedAt without copy-pasting the same Doctrine annotations into every entity.
Compose into a base entity. The traits are designed to be mixed into every resource. Instead of sprinkling use OrmIdentificatorTrait; into 40 entities, make one abstract base and let Doctrine's mapped superclass inheritance do the rest:
/** @MappedSuperclass */
abstract class AbstractEntity
{
use OrmIdentificatorTrait;
use OrmTimestampableTrait;
}
Pick ORM or ODM, never both. Each trait is duplicated for Doctrine ORM annotations and MongoDB ODM annotations. Mixing OrmIdentificatorTrait with OdmTimestampableTrait will produce mapping errors at doctrine:schema:validate.
Keep the Gedmo annotation on the class, not the trait. @Gedmo\SoftDeleteable(fieldName="deletedAt", ...) is a class-level annotation. The trait only declares the $deletedAt property. Forgetting the annotation is the single most common failure mode — the column exists, nothing ever sets it.
Pair with API Platform serialization groups. These fields are read-only for clients, so put them in a read group on the resource, e.g. #[Groups(['read'])] on createdAt/updatedAt and exclude deletedAt from output entirely unless you're exposing a trash-bin endpoint.
Expose soft-deleted rows only where intentional. Gedmo installs a global SQL filter that hides deleted rows from every query. For an admin endpoint you'd disable it per-query:
$em->getFilters()->disable('softdeleteable');
// ... query including deleted rows
$em->getFilters()->enable('softdeleteable');
Overriding a column name. If your DBA insists on date_created rather than created_at, don't fight the trait — either redeclare the property with your own annotation in the entity (traits lose to class-level declarations) or copy the trait into your codebase and rename. Copying is usually the saner choice here.
Use it as a template, not a dependency. Given the release cadence, most teams will get more value by pasting the six traits into src/Entity/Traits/ and owning them. That also removes the transitive dependency on stof/doctrine-extensions-bundle being pinned correctly.
It is effectively unmaintained. Last release April 2020, one star, zero dependents. There is a good chance the upstream epubli/api-platform-traits is the more canonical package and this is a stale fork. Check both before choosing; prefer requiring the upstream if it's still on Packagist, or just vendor the code.
Symfony version drift. The 2020 release predates API Platform 3.x. Annotation-based config (@Gedmo\SoftDeleteable) and even XML mappings were deprecated in favour of PHP 8 attributes. With doctrine/annotations removed or gedmo/doctrine-extensions bumped to v3.10+, the old @Gedmo\... syntax will fail to parse unless you register the annotations reader. Verify your gedmo version before assuming the README example still works.
Timestamps come from the Gedmo listener, not Doctrine lifecycle callbacks. Anything that bypasses the entity manager — raw SQL, DQL UPDATE, doctrine:query:sql, bulk imports via --append — leaves updatedAt stale. If you need audit-grade timestamps, wire @PreUpdate/@PrePersist callbacks or a Doctrine event subscriber instead.
Soft delete and joins. The Gedmo filter applies to the queried entity by default. Manual LEFT JOINs, native queries, and some QueryBuilder paths can leak soft-deleted rows back into results. If you see "deleted" records in a listing, suspect the query builder rather than the trait.
hardDelete=false means rows accumulate forever. There is no built-in purge. Plan a scheduled command (app:purge-soft-deleted) if your table grows — otherwise a busy resource will quietly become multi-million rows.
The identifier trait assumes auto-increment integers. If you need UUIDs or ULIDs (the modern default for public API resources), skip OrmIdentificatorTrait entirely and use symfony/uid with #[ORM\Id] #[ORM\GeneratedValue(strategy: 'NONE')].
Debugging missing timestamps: confirm the listener is actually registered with bin/console doctrine:mapping:info (look for Gedmo metadata) and bin/console debug:event-dispatcher doctrine.orm.default_entity_manager.event_manager — you should see Gedmo\Timestampable\TimestampableListener. In Symfony 5.4+ the command is debug:event-dispatcher; in 4.x it was debug:event-dispatcher too, but the tag lookup differs.
Config quirk: stof_doctrine_extensions.yaml lives under config/packages/ in Flex apps. If you're on an older non-Flex skeleton, the file is app/config/config.yml under a stof_doctrine_extensions: key. If you forget default_locale, Gedmo throws at container compile time on some versions.
No tests, no CI, no changelog. Treat every line as untested reference code. That's fine for six tiny traits — read them, understand them, then decide whether owning them in your repo beats a Packagist dependency on a 1-star, 2020-era package. In most cases, it does.
How can I help you explore Laravel packages today?