Skip to content
Windwalker

ORM

Component
orm
An ORM component with DataMapper / Entity pattern.

Attributes ​

The ORM describes an entity with attributes. The table name, the columns, the casts, the events, and the other behaviors all come from attributes on the class, its properties, and its methods. Most attributes are read once, when the metadata of an entity is built. The cast attributes that run on save are the exception: the ORM reads them again for each row it saves.

This page lists the attributes that the ORM provides, and shows how to add a new one. Most attributes are described on the page of their feature, which is linked in the tables below.

Common Attributes ​

The attributes are grouped by the element they are placed on.

On the Class ​

  • #[Table]: the table name, the alias, the mapper class, and the options. See Entities.
  • #[NestedSet]: marks the entity as a tree node. See Nested Sets.
  • #[UseRealColumns]: reads the columns of the table from the database, instead of the entity, for automatic selections. See Automatic Selections.

On a Method ​

  • #[EntitySetup]: a static method that receives the EntityMetadata. See Events.
  • #[BeforeSaveEvent], #[AfterSaveEvent], #[BeforeDeleteEvent], #[AfterDeleteEvent], #[BeforeUpdateWhereEvent], and #[AfterUpdateWhereEvent]: event listeners. See Events.
  • #[EnergizeEvent]: a static method that attaches values to each entity the ORM creates. See Energize.
  • #[Watch] and #[WatchBefore]: listeners for the change of one column. See Events.

On a Property ​

  • #[Column], #[PK], and #[AutoIncrement]: the column, the primary key, and the auto-increment column. See Entities.
  • #[Cast] and #[CastNullable]: the conversions on load and on save. See Entities.
  • #[JsonArray] and #[JsonObject]: JSON columns. See Entities.
  • #[CurrentTime] and #[CreatedTime]: the time values written on save. See Entities.
  • #[AutoTruncate]: cuts a string to the column length on save. See Entities.
  • #[JsonSerializer] and #[JsonNoSerialize]: the JSON output of one property. See Entities.
  • #[OptimisticLock]: the version column. See Optimistic Locking.
  • #[UUID], #[UUIDBin], and #[UUIDBinNullable]: UUID values. See UUID.

INFO

The relation attributes, such as #[OneToMany] and #[ManyToOne], are not described in this documentation.

Attribute Order ​

The ORM handles the attributes of a property in declaration order. A property attribute can depend on one that comes before it. For example, #[AutoIncrement] needs the #[Column] on the same property, and it raises an error when #[Column] is written after it:

php
#[Column('id'), PK, AutoIncrement]
public ?int $id = null;

Extending With a Cast-For-Save Attribute ​

A property attribute that implements CastForSaveInterface needs no registration. The ORM finds it by reflection when a row is saved, and it calls the caster that the attribute returns. The #[CurrentTime] and #[AutoTruncate] attributes work this way.

The following attribute trims a string before it is saved. For the full CasterInfo interface and the rules of save-time casts, see Casting.

php
use Attribute;
use Windwalker\ORM\Attributes\CastForSaveInterface;

#[Attribute(Attribute::TARGET_PROPERTY)]
class Trimmed implements CastForSaveInterface
{
    public function getCaster(): callable
    {
        return static fn (mixed $value): mixed
            => is_string($value) ? trim($value) : $value;
    }
}
php
#[Column('title')]
#[Trimmed]
public string $title = '';

Extending With a Setup Attribute ​

An attribute that must be read by your own code, or that adds its information to the metadata, is handled once, when the metadata is built. Such an attribute implements AttributeInterface, uses ORMAttributeTrait, and records itself in the metadata with addAttributeMap():

php
use Attribute;
use Windwalker\Attributes\AttributeHandler;
use Windwalker\Attributes\AttributeInterface;
use Windwalker\ORM\Attributes\ORMAttributeTrait;
use Windwalker\ORM\Metadata\EntityMetadata;

#[Attribute(Attribute::TARGET_PROPERTY)]
class Searchable implements AttributeInterface
{
    use ORMAttributeTrait;

    public function handle(
        EntityMetadata $metadata,
        AttributeHandler $handler,
    ): callable {
        $metadata->addAttributeMap($this, $handler->reflector);

        return $handler->get();
    }
}

Register the attribute with the resolver of the ORM, before the entity is used for the first time:

php
use Attribute;

$orm->getAttributesResolver()->registerAttribute(
    Searchable::class,
    Attribute::TARGET_PROPERTY,
);

Read the properties that carry it through the metadata:

php
$members = $orm->getEntityMetadata(Article::class)
    ->getPropertyMembersOfAttribute(Searchable::class);

foreach ($members as $member) {
    echo $member->columnName;
}

Using Services in Windwalker ​

Inside a Windwalker application, the attribute handler exposes the container. A setup attribute can then get a service when the metadata is built:

php
public function handle(
    EntityMetadata $metadata,
    AttributeHandler $handler,
): callable {
    $config = $handler->getContainer()->get(SearchConfig::class);

    // Use $config while the metadata is built

    return $handler->get();
}

INFO

getContainer() exists only in the handler of Windwalker. Outside Windwalker, the handler has no container, so the attribute cannot get a service this way.

Injecting Services in Your own Framework ​

For the full way to write an attribute handler, how the resolver calls callables, and how to supply services outside Windwalker, see the Attributes component. The pages Write Your Own Attribute Handlers and Injecting Services Into Callables cover these topics in detail.

php
$orm->setAttributesResolver(
    new MyServiceResolver(new MyDIContainer())
);

Released under the MIT License.