When you configure your model repo objects you need to define the relationships with other models. This is done with the "Rel" objects which you add to the configuration.
These objects can are:
A BelongsTo relation sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you’d declare the order model this way:
Database Tables:
┌───────────────────────┐
│ Table: Order │
│ BelongsTo: Customer │ ┌───────────────────────┐
├─────────────┬─────────┤ │ Table: Customer │
│ id │ ingeter │ ├───────────────────────┤
│ customerId │ ingeter │───>│ id | ingeter │
│ orderKey │ string │ │ name | string │
└─────────────┴─────────┘ └───────────────────────┘
// Model File
use Harp\Harp\AbstractModel;
use Harp\Harp\Rel\BelongsTo;
class Order extends AbstractModel
{
public static function initialize($config)
{
$config
->addRel(new BelongsTo('customer', $config, Customer::getRepo()));
}
public $id;
public $orderKey;
public $customerId;
public function getCustomer()
{
return $this->get('customer');
}
public function setCustomer(Customer $customer)
{
return $this->set('customer', $customer);
}
}
// Working with BelongsTo relation
$customer = $order->getCustomer();
$order->setCustomer($customer2);Tip Though you could use the
getandsetmethods directly to retrieve / change data, it is better to define specific methods for each relation, as is the case in the example above.
By default the name of the column use for the foreign key is defined as "foreign model" + "Id", but you can configure that with a "key" option e.g.
new BelongsTo('customer', $repo, Customer::getRepo(), ['key' => 'otherId'])HasOne relation also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This relation indicates that each instance of a model possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this:
Database Tables:
┌───────────────────────┐ ┌───────────────────────┐
│ Table: Supplier │ │ Table: Account │
│ HasOne: Account │ ├─────────────┬─────────│
├─────────────┬─────────┤ │ id │ ingeter │
│ id │ ingeter │◄───│ supplierId │ ingeter │
│ name │ string │ │ accountNum │ string │
└─────────────┴─────────┘ └─────────────┴─────────┘
// Model File
use Harp\Harp\AbstractModel;
use Harp\Harp\Rel\HasOne;
class Supplier extends AbstractModel
{
public static function initialize($config)
{
$config
->addRel(new HasOne('account', $config, Account::getRepo()));
}
public $id;
public $name;
public function getAccount()
{
return $this->get('account');
}
public function setAccount(Account $account)
{
return $this->set('account', $account);
}
}
// Working with BelongsTo relation
$account = $supplier->getAccount();
$supplier->setAccount($account2);Tip Though you could use the
getandsetmethods directly to retrieve / change data, it is better to define specific methods for each relation, as is the case in the example above.
By default the name of the column use for the foreign key is defined as "foreign model" + "Id", but you can configure that with a "foreignKey" option e.g.
$rel = new HasOne('account', $this, AccountRepo::get(), ['foreignKey' => 'otherId']);A HasMany relation indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a BelongsTo relation. This relation indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this:
Database Tables:
┌───────────────────────┐ ┌───────────────────────┐
│ Table: Customer │ │ Table: Order │
│ HasMany: Orders │ ├─────────────┬─────────┤
├─────────────┬─────────┤ │ id │ ingeter │
│ id │ ingeter │◄───│ supplierId │ ingeter │
│ name │ string │ │ accountNum │ string │
└─────────────┴─────────┘ └─────────────┴─────────┘
// Model File
use Harp\Harp\AbstractModel;
use Harp\Harp\Rel\HasMany;
class Customer extends AbstractModel
{
public static function initialize($config)
{
$config
->addRel(new HasMany('orders', $config, Order::getRepo()));
}
public $id;
public $name;
public function getOrders()
{
return $this->all('orders');
}
}
$orders = $customer->getOrders();
foreach ($orders as $order) {
var_dump($order);
}
$customer->getOrders()->add($order2);Tip Though you could use the
allmethod directly to retrieve / change data, it is better to define specific methods for each relation, as is the case in the example above.
By default the name of the column use for the foreign key is defined as "foreign model" + "Id", but you can configure that with a "foreignKey" option e.g.
$rel = new HasMany('orders', $this, OrderRepo::get(), ['foreignKey' => 'otherId']);A HasManyThrough relation creates a many-to-many connection with another model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies. This requires a "through" model which is related with a HasMany relation - in this case AssemblyPart model. You could declare it this way:
Database Tables:
┌────────────────────────────┐
│ Table: Assembly │
│ HasManyThrough: parts │
│ HasMany: assemblyParts │
├─────────────┬──────────────┤ ┌────────────────────────┐
│ id │ ingeter │◄──┐ │ Table: AssemblyPart │
│ name │ string │ │ ├─────────────┬──────────┤
└─────────────┴──────────────┘ │ │ id │ integer │
└──│ assemblyId │ ingeter │
┌────────────────────────────┐ ┌──│ partId │ string │
│ Table: Parts │ │ └─────────────┴──────────┘
│ HasManyThrough: assemblies │ │
│ HasMany: assemblyParts │ │
├─────────────┬──────────────┤ │
│ id │ ingeter │◄──┘
│ name │ string │
└─────────────┴──────────────┘
// Model File
use Harp\Harp\AbstractModel;
use Harp\Harp\Rel\HasManyThrough;
use Harp\Harp\Rel\HasMany;
class Assembly extends AbstractModel
{
public static function initialize($config)
{
$config
->addRels([
new HasManyThrough('parts', $config, Part::getRepo(), 'assemblyParts')),
new HasMany('assemblyParts', $config, AssemblyPart::getRepo()))
]);
}
public $id;
public $name;
public function getParts()
{
return $this->all('parts');
}
}
$parts = $customer->getParts();
foreach ($parts as $part) {
var_dump($part);
}
$customer->getParts()->add($part2);Tip Though you could use the
allmethod directly to retrieve / change data, it is better to define specific methods for each relation, as is the case in the example above.
By default the name of the columns use for the foreign keys in the "through" model are "model" + "Id" and "foreign model" + "Id", but you can configure that with a "key" and "foreignKey" options e.g.
new HasManyThrough(
'parts',
$config,
Part::getRepo(),
'assemblyParts',
[
'key' => 'otherAssemblyId',
'foreignKey' => 'otherPartId',
]
));The "HasManyExclusive" relation is exactly the same as HasMany, with one exception. When a model is removed from the relation, it is deleted.
// Repo File
use Harp\Harp\AbstractModel;
use Harp\Harp\Rel\HasManyExclusive;
class Customer extends AbstractModel {
public static function initialize($config)
{
$config
->addRel(new HasManyExclusive('orders', $config, Order::getRepo()));
}
}A slightly more advanced twist on relations is the BlongsToPolymorphic relation. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model. Here's how this could be declared:
Database Tables:
┌───────────────────────────────────┐
│ Table: Employee │
│ HasManyAs: pictures, parent │ ┌──────────────────────────────┐
├─────────────────────────┬─────────┤ │ Table: Pircture │
│ id │ ingeter │◄──┐ │ BelongsToPolymorphic: parent │
│ name │ string │ │ ├────────────────┬─────────────┤
└─────────────────────────┴─────────┘ │ │ id │ ingeter │
│ │ name │ string │
┌───────────────────────────────────┐ ├──│ parentId │ ingeter │
│ Table: Product │ │ │ parentClass │ string │
│ HasManyAs: pictures, parent │ │ └────────────────┴─────────────┘
├─────────────────────────┬─────────┤ │
│ id │ ingeter │◄──┘
│ name │ string │
└─────────────────────────┴─────────┘
class Picture extends AbstractModel
{
public static function initialize($config)
{
$config
->addRel(new BelongsToPolymorphic('parent', $config, Product::getRepo());
}
public $id;
public $name;
public $parentId;
public $parentClass;
}
class Employee extends AbstractModel
{
public static function initialize($config)
{
$config
->addRel(new HasManyAs('pictures', $config, Picture::getRepo(), 'parent');
}
public $id;
public $name;
}
class Product extends AbstractModel
{
public function initialize($config)
{
$config
->addRel(new HasManyAs('pictures', $config, Picture::getRepo(), 'parent');
}
public $id;
public $name;
}You can think of a polymorphic belongsto declaration as setting up an interface that any other model can use. From an instance of the Employee model, you can retrieve a collection of pictures: $employee->getPictures().
Similarly, you can retrieve $product->getPictures().
If you have an instance of the Picture model, you can get to its parent via $picture->getParent().
When you set up relations you might want to specify the "inverse" relation also, so it will be set properly when working with unsaved models. This works only on RelOne inverse relations.
Here's where this is useful:
class User extends AbstractModel
{
public static function initialize($config)
{
$config->addRel(new HasMany('posts', $config, Post::getRepo(), ['inverseOf' => 'user']);
}
}
class Post extends AbstractModel
{
public static function initialize($config)
{
$config->addRel(new HasMany('user', $config, User::getRepo());
}
}
$user = new User();
$post = new Post();
$user->getPosts()->add($post);
// This will be true
$post->getUser() === $user;HasMany, HasManyThrough and other relations to multiple models will return a "LinkMany" object which is used to add, remove or otherwise manipulate the relation. The object is an iterator and implements countable as well, so you can call count($products) as well as put it directly in a foreach.
$products = $store->get('products');
$newProduct = new Product();
$products->add($newProduct);| Method | Description |
|---|---|
| has(AbstractModel $model) | Check if a model is in this collection of models |
| getRel() | Get the relation object |
| get() | Get the internal Models object, holding information of the current state of the relation |
| getOriginal() | Get the internal Models object, with the models that were loaded originally from the database |
| isChanged() | Check if any models have been added / removed from the relation |
| getAdded() | Get a Models object containing all the models added to this relation |
| getRemoved() | Get a Models object containing all the models removed from this relation |
| getFirst() | Get the first model in the collection of this relation. Will return a void model if the collection is empty |
| getNext() | Get the next model in the collection of this relation, after a call getFirst. Will return a void model when the end of the collection is reached |
| addArray(array $models) | Add several models to the relation at once using an array |
| addModels(Models $models) | Add several models to the relation at once using an Models object |
| add(AbstractModel $model) | Add a model to the relation |
| toArray() | Get an array with all the models in the colleciton |
| remove(AbstractModel $model) | Remove a model from the relation |
| isEmpty() | Return true if the relation is empty |
| clear() | Remove all the models from this relation. |
| has(AbstractModel $model) | Return true if the model is in this relation. |
| filter(Closure $filter) | Filter the models and return a new collection with the models, for which the filter closure has returned true |
| invoke($method) | Call a method for each model and return an array of all the results |
| map(Closure $map) | Call a closure for each item and return an array with the result. |