Entity model

The following principles are based on Domain Driven Design.

Entities are anemic models and should be used for persistence only. We want to avoid using the entities for our domain layer and put business logic and rules in it.

Therefore we should use proper models and use entities only to make persistence easier. A model should be a rich model. It means it should always be in a valid state.

But even with this design very often a model maps directly to one table in the database when it comes to persistence. To make it easier to use entities as the persistence layer for those models and reduce boilerplate code we introduced a base entity model which works with a single entity in the background but allows you to add the business logic and make sure it stays in the valid state.

Usage example
class my_example_entity_model extends \core\orm\entity\model {

    /**
     * You have to define the class name of the entity used by this model
     *
     * @return string
     */
    public static function get_entity_class(): string {
        return example_entity::class;
    }

}

$my_model = my_example_entity_model::load_by_id(42);

// If you already have an existing entity you can use it to instantiate the model
$entity = example_entity::repository()->find_or_fail(42);

$my_model = my_example_entity_model::load_by_entity($entity);

Property whitelisting

All properties of the entity are exposed by a magic getter by default, including defined relationships. There's no write access to any of the entity properties.

If you want to restrict access to properties you can define a $entity_attribute_whitelist property which is an array of property names. If the whitelist is defined only those properties will be accessible. 

Entity property whitelist
/**
 * It is recommended to define properties in phpdoc to get IDE autocompletion
 *
 * @property-read int $id
 * @property-read string $name
 * @property-read string $type
 * @property-read int $created_at
 * @property-read int $updated_at
 */
class my_example_entity_model extends \core\orm\entity\model {

    public static function get_entity_class(): string {
        return example_entity::class;
    }

    protected $entity_attribute_whitelist = [
        'id',
        'name',
        'type',
        // 'parent_id', // no access on this one
        'created_at',
        'updated_at',
    ];

}

$my_model = my_example_entity_model::load_by_id(42);
echo $my_model->name;

// This will throw an exception
echo $my_model->parent_id;

The other way to expose properties directly are via getter methods. You can define a $model_accessor_whitelist property which is an array of getter method names available in the model.

Properties defined in the accessor whitelist take precedence over the entity attribute whitelist. 

Accessor whitelist
/**
 * It is recommended to define properties in phpdoc to get IDE autocompletion
 *
 * @property-read my_example_entity_model $parent
 */
class my_example_entity_model extends \core\orm\entity\model {

    protected $model_accessor_whitelist = [
        'parent',
        'created_at'
    ];

    public static function get_entity_class(): string {
        return example_entity::class;
    }

    public function get_parent(): self {
        return self::load_by_id($this->entity->parent_id);
    }

    public function get_created_at(): DateTime {
        return new DateTime($this->entity->created_at);
    }

}

$my_model = my_example_entity_model::load_by_id(42);
// This can now be accessed via a property
$my_parent_model = $my_model->parent;
// or directly via the method, your choice
$my_parent_model = $my_model->get_parent();

Formatter

There is also a base entity_model_formatter which makes it easy to implement formatters for entity models.

Accessor whitelist
class my_example_entity_model_formatter extends \core\orm\formatter\entity_model_formatter {

    protected function get_map(): array {
        return [
            'id' => null,
            'name' => string_field_formatter::class,
            'type' => null,
            'parent_id' => null,
            'entity_class' => null,
            'collection' => null,
            'created_at' => date_field_formatter::class,
            'updated_at' => date_field_formatter::class,
        ];
    }

}

Usage of the formatters is the same as for all other formatters.