Entity is a data crate which, basically, contains data for one table row.
Each entity has to implement Nextras\Orm\Entity\IEntity
interface.
Orm has predefined class Nextras\Orm\Entity\Entity
, which
implements the interface and provides other useful features.
Data are accessible through properties. You have to annotate all properties that should be available. Properties are defined by Phpdoc annotations. Let's start with a basic entity:
/**
* @property int $id {primary}
* @property string $name
* @property DateTimeImmutable $born
* @property string|null $web
* @property-read int $age
*/
class Member extends Nextras\Orm\Entity\Entity
{
}
Phpdoc property definition consists of its type and name. If you would like
to use read-only property, define it with @property-read
annotation; such annotation is useful to define properties which are based on
values of other properties. Properties could be optional/nullable; to do that,
just provide another type – null
.
If you put some value into the property, the value will be validated by
property type annotation. Type casting is performed if it is possible and safe.
Supported types are null
, string
, int
,
float
, array
, mixed
and object types.
Validation is provided on all properties, except for properties defined with
container – in that case validation should do the container.
Nextras Orm also provides enhanced support for date time handling. However,
only “safe” DateTimeImmutable
instances are supported as a
property type. You may put common DateTime
instance as a value, but
it will be automatically converted to DateTimeImmutable. Also, auto date string
conversion is supported.
“Property access” is the easiest way to work with the data, although,
such feature is not defined in IEntity
interface. To conform the
interface, you must use “method access”: getValue()
method for
reading, setValue()
method for writing, hasValue()
,
etc. There is a special getRawValue()
method, which returns raw
representation of the value. The raw representation is basically the stored
value (a primary key for relationship property).
$member = new Member();
$member->name = 'Jon';
$member->setValue('name', 'Jon');
$member->born = 'now'; // will be automatically converted to DateTimeImmutable
echo $member->name;
echo $member->getValue('name');
echo isset($member->web) ? 'has web' : '-';
echo $member->hasValue('web') ? 'has web' : '-';
$member->isPersisted(); // false
Attaching entities to the repository is letting Orm know about your entities, it does not store the entity. Attaching to repository injects the required dependencies into your entity (through inject property annotations or inject methods). If you need some dependency before attaching entity to the repository, feel free to pass the dependency through the constructor, which is by default empty.
Each entity can be created “manually”. Entities can be simply connected together. Let's see an example:
$author = new Author();
$book = new Book();
$book->author = $author;
$book->tags->set([new Tag(), new Tag()]);
If a instance Author is attached to the repository, all other new connected entities are automatically attached to their repositories too. See more in relationships chapter.
Entity allows you to implement own getters and setter to modify the passed
value. These methods are optional and should be defined as
protected
. The method name consists of the getter
prefix and a property name, setter
prefix respectively. You can
define just one of them. Getters and setters are not supported for property
containers, so that relationships cannot have them.
Getter method receives the stored value as the first parameter and should return the optionally modified value. “Virtual getters” do not receive any value. Setter method receives the user given value and should return the modified value for storing it in the entity.
/**
* ...
* @property string $name
* @property int $siblingsCount
*/
class FamilyMember extends Entity
{
protected function getterName($name)
{
return ucwords($name);
}
protected function setterSiblingsCount($siblings)
{
return max((int) $siblings, 0);
}
}
Each property can be annotated with a modifier. Modifiers are optional and provide possibility to extend entity properties' behavior. Modifiers are written after the property name. Each modifier is surrounded by curly braces. The first compulsory token is the modifier name, other tokens are optional and depend on the specific modifier type. Orm comes with few predefined property modifiers:
{primary}
– marks the property; it is mapped to
primary key.{primary-proxy}
– marks the property to map the primary key;
useful for easy access to composite primary key.{enum self::TYPE_*}
– enables extended validation against
values enumeration.{default now}
– defines property default value.{virtual}
– marks property as “do not persist in
storage”.{container ContainerClassName}
– sets property
container/injection.{1:m TargetEntity::$property}
– see [relationships].{m:1 TargetEntity::$property}
– see [relationships].{m:m TargetEntity::$property}
– see [relationships].{1:1 TargetEntity::$property}
– see [relationships].Each entity has to have defined the $id
property. By default,
the $id
property is the only primary key of the entity; the
$id
property is defined in Nextras\Orm\Entity\Entity
class, but it is not marked as primary, because this is the default behavior,
which can be changed by the {primary}
modifier. By adding the
modifier to property, you mark it as the new primary key. You can use the
modifier multiple times to create a composite primary key. If the modifier is
applied to a relationship property, the relationship's primary key is
automatically used.
/**
* @property int $id {primary}
* @property string $name
*/
class Tag extends Nextras\Orm\Entity\Entity
{
}
/**
* @property mixed $id {primary-proxy}
* @property Tag $tag {m:1 Tag::$followers} {primary}
* @property User $follower {m:1 User::$followedTags} {primary}
*/
class TagFollower extends Nextras\Orm\Entity\Entity
{
}
$tag = new Tag();
$tag->id = 1;
$user = new User();
$user->id = 2345;
$tagFollower = new TagFollower();
$tagFollower->tag = $tag;
$tagFollower->user = $user;
return $tagFollower->id;
// retruns array(1, 2345);
You can easily validate passed value by value enumeration. To set the
enumeration validation, use enum
modifier with the list of
constants (separated by a space); or pass a constant name with a wildcard.
/**
* ...
* @property int $type {enum self::TYPE_*}
*/
class Event extends Nextras\Orm\Entity\Entity
{
const TYPE_PUBLIC = 0;
const TYPE_PRIVATE = 1;
const TYPE_ANOTHER = 2;
}
You can easily set the default value. The default modifier also accepts a reference to constant.
/**
* ...
* @property string $name {default "Jon Snow"}
* @property int $type {default self::TYPE_PUBLIC}
*/
class Event extends Nextras\Orm\Entity\Entity
{
const TYPE_PUBLIC = 0;
}
Virtual modifier marks specific property as virtual – such property won't
be stored in the mapper; this modifier is useful to use with
property-read
annotation together.
/**
* ...
* @property DateTimeImmutable $born
* @property-read int $age {virtual}
*/
class Member extends Nextras\Orm\Entity\Entity
{
protected function getterAge()
{
return date('Y') - $this->born->format('Y');
}
}
$member = new Member();
$member->born = new DateTimeImmutable('2000-01-01');
echo $member->age;
Containers encapsulate the property value. There are two basic types of containers:
Nextras\Orm\Entity\IProperty
interface; reading and writing value
do not call any special methods, retrieve the container object directly.
This feature is used in “has many” relationships. Reading the property value returns object container, which holds and directs the relationship.
setInjectedValue()
method, and retrieved by
getInjectedValue()
method.
This feature is used in “has one” relationships. Reading the property
value calls getInjectedValue()
method, writing some value
internally calls setInjectedValue()
method.
Property containers are created by entity itself lazily (when needed).
Your entity can require some dependency to work. Orm comes with
Nextras\Orm\Repository\IDependencyProvider
interface, which takes
care about injecting needed dependencies. If you use OrmExtension
for Nette\DI
, it will automatically call standard DI injections
(injection methods and @inject
annotation). Dependencies are
injected when entity is attached to repository.
/**
* ...
*/
class Book extends Nextras\Orm\Entity\Entity
{
/** private EanSevice */
private $eanService;
public function injectEanService(EanService $service)
{
$this->eanService = $service;
}
}