Relations
Relations are convenient ways to access related, parent, and child entities from
another loaded entity object. An example might be $post->comments to query for
all the comments related to the current $post object.
Live Query Objects
All relations are returned as instances of relation classes that extend
Spot\Relation\RelationAbstract. This class holds a Spot\Query object
internally, and allows you to chain your own query modifications on it so you
can do custom things with relations, like ordering, adding more query
conditions, etc.
$mapper->hasMany($entity, 'Entity\Comment', 'post_id') ->where(['status' => 'active']) ->order(['date_created' => 'ASC']);All of these query modifications are held in a queue, and are run when the
relation is actually executed (on count or foreach iteration, or when
execute is explicitly called).
Eager Loading
All relation types are lazy-loaded by default, and can be eager-loaded to
solve the N+1 query problem using the with method:
$posts = $posts->all()->with('comments');Multiple relations can be eager-loaded using an array:
$posts = $posts->all()->with(['comments', 'tags']);Relation Types
Entity relation types are:
HasOneBelongsToHasManyHasManyThrough
HasOne
HasOne is a relation where the related object has a field which points to the
current object - an example might be User has one Profile.
Method
$mapper->hasOne(Entity $entity, $foreignEntity, $foreignKey)$entity- The current entity instance$foreignEntity- Name of the entity you want to load$foreignKey- Field name on the$foreignEntitythat matches up with the primary key of the current entity
Example
namespace Entity;class User extends \Spot\Entity{ protected static $table = 'users'; public static function fields() { return [ 'id' => ['type' => 'integer', 'autoincrement' => true, 'primary' => true], 'username' => ['type' => 'string', 'required' => true], 'email' => ['type' => 'string', 'required' => true], 'status' => ['type' => 'integer', 'default' => 0, 'index' => true], 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] ]; } public static function relations(Mapper $mapper, Entity $entity) { return [ 'profile' => $mapper->hasOne($entity, 'Entity\User\Profile', 'user_id') ]; }}In this scenario, the Entity\User\Profile entity has a field named user_id
which the Entity\User‘s id field as a value. Note that no field exists on
this entity for this relation, but rather the related entity.
BelongsTo
BelongsTo is a relation where the current object has a field which points to
the related object - an example might be Post belongs to User.
Method
$mapper->belongsTo(Entity $entity, $foreignEntity, $localKey)$entity- The current entity instance$foreignEntity- Name of the entity you want to load$localKey- Field name on the current entity that matches up with the primary key of$foreignEntity(the one you want to load)
Example
namespace Entity;class Post extends \Spot\Entity{ protected static $table = 'posts'; public static function fields() { return [ 'id' => ['type' => 'integer', 'autoincrement' => true, 'primary' => true], 'user_id' => ['type' => 'integer', 'required' => true], 'title' => ['type' => 'string', 'required' => true], 'body' => ['type' => 'text', 'required' => true], 'status' => ['type' => 'integer', 'default' => 0, 'index' => true], 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] ]; } public static function relations(Mapper $mapper, Entity $entity) { return [ 'user' => $mapper->belongsTo($entity, 'Entity\User', 'user_id') ]; }}In this scenario, the Entity\Post entity has a field named user_id which is
the Entity\User‘s id field’s value. Note that the field exists on this
entity for this relation, but not on the related entity.
HasMany
HasMany is used where a single record relates to multiple other records - an
example might be Post has many Comments.
Method
$mapper->hasMany(Entity $entity, $entityName, $foreignKey, $localValue = null)$entity- The current entity instance$entityName- Name of the entity you want to load a collection of$foreignKey- Field name on the$entityNamethat matches up with the current entity’s primary key
Example
We start by adding a comments relation to our Post object:
namespace Entity;class Post extends Spot\Entity{ protected static $table = 'posts'; public static function fields() { return [ 'id' => ['type' => 'integer', 'autoincrement' => true, 'primary' => true], 'title' => ['type' => 'string', 'required' => true], 'body' => ['type' => 'text', 'required' => true], 'status' => ['type' => 'integer', 'default' => 0, 'index' => true], 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] ]; } public static function relations(Mapper $mapper, Entity $entity) { return [ 'comments' => $mapper->hasMany($entity, 'Entity\Comment', 'post_id')->order(['date_created' => 'ASC']), ]; }}And add a Entity\Post\Comment object with a ‘belongsTo’ relation back to the post:
namespace Entity;class Comment extends \Spot\Entity{ // ... snip ... public static function relations() { return [ 'post' => $mapper->belongsTo($entity, 'Entity\Post', 'post_id') ]; }}HasManyThrough
HasManyThrough is used for many-to-many relationships. An good example is tagging. A post has many tags, and a tag has many posts. This relation is a bit more complex than the others, because a HasManyThrough requires a join table and mapper.
Method
$mapper->hasManyThrough(Entity $entity, string $hasManyEntity, string $throughEntity, string $selectField, string $whereField)$entity- The current entity instance$hasManyEntity- This is the target entity you want a collection of. In this case, we want a collection ofEntity\Tagobjects.$throughEntity- Name of the entity we are going through to get what we want - In this case,Entity\PostTag.$selectField- Name of the field on the$throughEntitythat will select records by the primary key of$hasManyEntity.$whereField- Name of the field on the$throughEntityto select records by the current entities’ primary key (we have a post, so this will be theEntity\PostTag->post_idfield).
Example
We need to add the tags relation to our Post entity, specifying query
conditions for both sides of the relation.
namespace Entity;class Post extends Spot\Entity{ protected static $table = 'posts'; public static function fields() { return [ 'id' => ['type' => 'integer', 'autoincrement' => true, 'primary' => true], 'title' => ['type' => 'string', 'required' => true], 'body' => ['type' => 'text', 'required' => true], 'status' => ['type' => 'integer', 'default' => 0, 'index' => true], 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] ]; } public static function relations(Mapper $mapper, Entity $entity) { return [ 'tags' => $mapper->hasManyThrough($entity, 'Entity\Tag', 'Entity\PostTag', 'tag_id', 'post_id'), ]; }Explanation
The result we want is a collection of Entity\Tag objects where the id equals
the post_tags.tag_id column. We get this by going through the
Entity\PostTags entity, using the current loaded post id matching
post_tags.post_id.