Database tables are often related to one another. For example, a blog post may have many comments, or an order could be related to the user who placed it. Framy supports several types of relationships.
Relationships are defined as methods on your model classes. They also serve as powerful query builders, defining relationships as methods provides powerful method chaining and querying capabilities
A one-to-one relationship is a very basic relation. For example, a User model might be associated with one Phone. To define this relationship, we place a phone method on the User model. The phone method should call the hasOne method and return its result:
<?php
namespace app\custom\Models;
use app\framework\Component\Database\Model\Model;
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne(Phone::class);
}
}
Framy determines the foreign key of the relationship based on the model name. In this case, the Phone
model is automatically assumed to have a user_id
foreign key. If you wish to override this convention, you may pass a second argument to the hasOne
method:
return $this->hasOne('App\Phone', 'foreign_key');
Additionally, Framy assumes that the foreign key should have a value matching the id
(or the custom $primaryKey
) column of the parent. In other words, Framy will look for the value of the user’s id
column in the user_id
column of the Phone
record. If you would like the relationship to use a value other than id
, you may pass a third argument to the hasOne
method specifying your custom key:
return $this->hasOne(‘App\Phone’, ‘foreign_key’, ‘local_key’);
So, we can access the Phone
model from our User
. Now, let’s define a relationship on the Phone
model that will let us access the User
that owns the phone. We can define the inverse of a hasOne
relationship using the belongsTo
method:
<?php
namespace app\custom\Models;
use app\framework\Component\Database\Model\Model;
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
In the example above, Framy will try to match the user_id
from the Phone
model to an id
on the User
model. Framy determines the default foreign key name by examining the name of the relationship method and suffixing the method name with _id
. However, if the foreign key on the Phone
model is not user_id
, you may pass a custom key name as the second argument to the belongsTo
method:
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo(User::class, 'foreign_key');
}
If your parent model does not use id
as its primary key, or you wish to join the child model to a different column, you may pass a third argument to the belongsTo
method specifying your parent table’s custom key:
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo(User::class, 'foreign_key', 'other_key');
}
A one-to-many relationship is used to define relationships where a single model owns any amount of other models. For example, a blog post may have an infinite number of comments. Like all other database relationships, one-to-many relationships are defined by placing a function on your model class:
<?php
namespace app\custom\Models;
use app\framework\Component\Database\Model\Model;
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Remember, Framy will automatically determine the proper foreign key column on the Comment
model. By convention, Framy will take the “snake case” name of the owning model and suffix it with _id
. So, for this example, Framy will assume the foreign key on the Comment
model is post_id
.
Since all relationships also serve as query builders, you can add further constraints to which comments are retrieved by calling the comments
method and continuing to chain conditions onto the query:
$comment = App\Post::find(1)->comments()->where('title', 'foo')->first();
Like the hasOne
method, you may also override the foreign and local keys by passing additional arguments to the hasMany
method:
return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
Now that we can access all of a post’s comments, let’s define a relationship to allow a comment to access its parent post. To define the inverse of a hasMany
relationship, define a relationship function on the child model which calls the belongsTo
method:
<?php
namespace App;
use app\framework\Component\Database\Model\Model;
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo(Post::class);
}
}
In the example above, Framy will try to match the post_id
from the Comment
model to an id
on the Post
model. Framy determines the default foreign key name by examining the name of the relationship method and suffixing the method name with a _
followed by the name of the primary key column. However, if the foreign key on the Comment
model is not post_id
, you may pass a custom key name as the second argument to the belongsTo
method:
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo(Post::class, 'foreign_key');
}
If your parent model does not use id
as its primary key, or you wish to join the child model to a different column, you may pass a third argument to the belongsTo
method specifying your parent table’s custom key:
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo(Post::class, 'foreign_key', 'other_key');
}
Many-to-many relations are slightly more complicated than hasOne
and hasMany
relationships. An example of such a relationship is a user with many roles, where the roles are also shared by other users. For example, many users may have the role of “Admin”. To define this relationship, three database tables are needed: users
, roles
, and role_user
. The role_user
table is derived from the alphabetical order of the related model names, and contains the user_id
and role_id
columns.
Many-to-many relationships are defined by writing a method that returns the result of the belongsToMany
method. For example, let’s define the roles
method on our User
model:
<?php
namespace app\custom\Models;
use app\framework\Component\Database\Model\Model;
class User extends Model
{
/**
* The roles that belong to the user.
*/
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
Like all other relationship types, you may call the roles method to continue chaining query constraints onto the relationship:
$roles = User::find(1)->roles()->orderBy('name')->get();
override this convention. You may do so by passing a second argument to the belongsToMany
method:
return $this->belongsToMany(Role::class, 'role_user');
In addition to customizing the name of the joining table, you may also customize the column names of the keys on the table by passing additional arguments to the belongsToMany
method. The third argument is the foreign key name of the model on which you are defining the relationship, while the fourth argument is the foreign key name of the model that you are joining to:
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
Since all of the relationships are defined in methods, you may call these methods to obtain an instance of the relationship without actually executing the relationship queries. In addition all relationships serve as QueryBuilder, allowing you to continue to chain constrains onto the relationship query before finally executing the SQL against the database.
For example imagine a blog system in which a User has manny related posts:
<?php
namespace app\custom\Models;
use app\framework\Component\Database\Model\Model;
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
You may query the posts relationship and add additional constraints to the relationship like so:
$user = User::find(1);
$user->posts()->get();
You are able to use any of the QueryBuilder methods on the relationship, so be sure to explore the query builder documentation to learn about all of the methods that are available to you.