Fork me on GitHub

Relationships#

Orm provides very efficient way to work with entity relationships. Orm recognizes 4 types of relationship:

  • 1:m – one has many: author has many books
  • m:1 – many has one: book has one author
  • m:m – many has many: book has many tags, tag is associated with many books
  • 1:1 – one has one: user has one settings, the reference for related entity is stored only on the side that is marked as main.

Use a relationship modifier to define relationship property. Modifiers require to define a target entity, some modifiers need to be defined on the both sides, then the reverse property definition is compulsory. If you want to define only one-sided relationship, use oneSided=true parameter. Other parameters are optional: ordering, setting a cascade, or making the current side primary (persisting is driven by the primary side). At least one side of m:m or 1:1 has to be defined as the primary. Relationships do not support getters and setters as other entity properties.

{1:1 EntityName::$reversePropertyName}
{m:1 EntityName::$reversePropertyName}
{1:m EntityName::$reversePropertyName, orderBy=property}
{m:m EntityName::$reversePropertyName, isMain=true, orderBy=[property=DESC, anotherProperty=ASC]}

1:m and m:m relationships can define collection's default ordering by orderBy property. You may provide either a column name, or an assocciated array where the key is a column expression and the value is an ordering direction.

Cascade#

All relationships can have defined cascade behaviour. Cascade behavior defined if peristance or removal of entity should affect connected entities. By default, all relationship have defined cascade for persistence. Cascade is defined as an array of keywords: persist and remove are currently supported options. Cascade works for every type of relationship.

// persist cascade is the default
{relModifier EntityName::$reversePropertyName} // equals to
{relModifier EntityName::$reversePropertyName, cascade=[persist]}

// adding remove cascade; you have to redefine persist cascade
{relModifier EntityName::$reversePropertyName, cascade=[persist, remove]}

// to disable persist cascade, provide empty cascade definition
{relModifier EntityName::$reversePropertyName, cascade=[]}

By default, the persist and remove methods process entity with its cascade. You can turn off this behavior by the second method argument:

$usersRepository->persist($user, false);
$usersRepository->remove($user, false);

1:M / M:1 – Bidirectional#

use Nextras\Orm\Relationships\OneHasMany;

/**
 * @property int                $id               {primary}
 * @property OneHasMany|Book[]  $books            {1:m Book::$author}
 * @property OneHasMany|Book[]  $translatedBooks  {1:m Book::$translator}
 */
class Author extends Nextras\Orm\Entity\Entity
{}

/**
 * @property int     $id          {primary}
 * @property Author  $author      {m:1 Author::$books}
 * @property Author  $translator  {m:1 Author::$translatedBooks}
 */
class Book extends Nextras\Orm\Entity\Entity
{}

1:M – One-sided#

/**
 * @property int     $id          {primary}
 * @property Author  $author      {m:1 Author, oneSided=true}
 * @property Author  $translator  {m:1 Author, oneSided=true}
 */
class Book extends Nextras\Orm\Entity\Entity
{}

1:M / M:1 – Self-referencing#

use Nextras\Orm\Relationships\OneHasMany;

/**
 * @property int                    $id          {primary}
 * @property Category               $parent      {m:1 Category::$categories}
 * @property OneHasMany|Category[]  $categories  {1:m Category::$parent}
 */
class Category extends Nextras\Orm\Entity\Entity
{}

M:M – Bidirectional#

use Nextras\Orm\Relationships\ManyHasMany;

/**
 * @property int                $id    {primary}
 * @property ManyHasMany|Tag[]  $tags  {m:m Tag::$books, isMain=true}
 */
class Book extends Nextras\Orm\Entity\Entity
{}

/**
 * @property int                 $id     {primary}
 * @property ManyHasMany|Book[]  $books  {m:m Book::$tags}
 */
class Tag extends Nextras\Orm\Entity\Entity
{}

M:M – One-sided#

Only the non-primary side is optional.

use Nextras\Orm\Relationships\ManyHasMany;

/**
 * @property int                $id    {primary}
 * @property ManyHasMany|Tag[]  $tags  {m:m Tag, isMain=true, oneSided=true}
 */
class Book extends Nextras\Orm\Entity\Entity
{}

M:M – Self-referencing#

use Nextras\Orm\Relationships\ManyHasMany;

/**
 * @property int                 $id             {primary}
 * @property ManyHasMany|User[]  $myFriends      {m:m User::$friendsWithMe}
 * @property ManyHasMany|User[]  $friendsWithMe  {m:m User::$myFriends}
 */
class User extends Nextras\Orm\Entity\Entity
{}

1:1 – Bidirectional#

Reference will be stored in book.ean_id.

/**
 * @property int      $id   {primary}
 * @property EanCode  $ean  {1:1 Ean::$book, isMain=true}
 */
class Book extends Nextras\Orm\Entity\Entity
{}

/**
 * @property int   $id    {primary}
 * @property Book  $book  {1:1 Book::$ean}
 */
class Tag extends Nextras\Orm\Entity\Entity
{}

1:1 – One-sided#

Only the non-primary side is optional. Reference will be stored in book.ean_id.

/**
 * @property int      $id   {primary}
 * @property EanCode  $ean  {1:1 Ean, isMain=true, oneSided=true}
 */
class Book extends Nextras\Orm\Entity\Entity
{}

1:1 – Self-referencing#

Reference will be stored in book.next_volume_id.

/**
 * @property int        $id              {primary}
 * @property Book|null  $nextVolume      {1:1 Book::$previousVolume, isMain=true}
 * @property Book|null  $previousVolume  {1:1 Book::$nextVolume}
 */
class Book extends Nextras\Orm\Entity\Entity
{}

Relationship interfaces#

The example above introduces classes which weren't mentioned before: OneHasMany and ManyHasMany. Instances of these classes are injected into the property and provide some cool features. The main responsibility is the implementation of \Traversable interface. You can easily iterate over the property to get the entities in the relationship.

foreach ($author->books as $book) {
    $book instanceof Book; // true
}

Also, you can use very clever interface to add, remove, and set entities in relationship. Sometimes, it is useful to work with the relationship collection as with the ICollection. Just use get() method to get it.

use Nextras\Orm\Collection\ICollection;

$author->books->add($book);
$author->books->remove($book);
$author->books->set([$book]);
$author->books->get() instanceof ICollection; // true

$book->tags->add($tag);
$book->tags->remove($tag);
$book->tags->set([$tag]);
$book->tags->get() instanceof ICollection; // true

These methods accept both entity instances and primary values. If you pass primary value, Orm will load the proper entity automatically.

$book->author = 1;
$book->author->id === 1; // true