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
property wrapper – in that case validation should do its property
wrapper.
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
wrappers (e.g. that relationships cannot have them).
Getter method receives the stored value as the first parameter and returns the desired value. Setter method receives the user given value and returns the desired value for storing it in the entity. Getters of virtual properties do not receive any value (receive always a null value).
/**
* ...
* @property string $name
* @property int $siblingsCount
*/
class FamilyMember extends Entity
{
protected function getterName(string $name): string
{
return ucwords($name);
}
protected function setterSiblingsCount(int $siblings): int
{
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}
– makes the property mapped as a
primary key;{primary-proxy}
– makes the property a proxy to map the
primary key; useful for composite primary key easy access;{default now}
– defines a default value;{virtual}
– marks property as “do not persist in
storage”;{embeddable}
– encapsulates multiple properties into one
wrapping object;{wrapper PropertyWrapperClassName}
– sets property
wrapper;{enum self::TYPE_*}
– enables extended validation against
values enumeration; we recommend using object enum types instead of scalar
enum types;{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;
// returns array(1, 2345);
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;
Encapsulates multiple properties into a wrapper container. Read more in Embeddables tutorial.
/**
* @property-read int $cents
* @property-read string $currency
*/
class Money extends Embeddable
{
}
/**
* ...
* @property Money|null $price {embeddable}
*/
class Product extends Nextras\Orm\Entity\Entity
{
}
Property wrapper encapsulates a property value. There are few basic types of property wrappers:
Nextras\Orm\Entity\IProperty
interface; reading property retrieves
the wrapper object, writing into the wrapper is forbidden.
This wrapper is used in “has many” relationships. Reading the property value returns a wrapper object that holds the relationship.
setInjectedValue()
method; reading value from the property is
proxied to getInjectedValue()
wrapper's method.
This feature is used in “has one” relationships. The property value goes through wrapper, user receives/sets the property wrapper inner state.
Property wrappers are created by entity and lazily.
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;
}
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;
}
}