erDiagram
USER ||--|| PROFILE : has
USER ||--o{ ORDER : places
USER }o--o{ GROUP : belongs
To define the relations in the Active Record model, you need to override relationQuery() method. This method should
return an instance of ActiveQueryInterface interface for the relation name.
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\ActiveQueryInterface;
final class User extends ActiveRecord
{
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'profile' => $this->hasOne(Profile::class, ['id' => 'profile_id']),
'orders' => $this->hasMany(Order::class, ['user_id' => 'id']),
default => parent::relationQuery($name),
};
}
}Also, you can use relationQuery() method to get a relation query by name.
$user = new User();
$profileQuery = $user->relationQuery('profile');
$ordersQuery = $user->relationQuery('orders');Alternatively, you can optionally use MagicRelationsTrait trait to define relations
in the Active Record model. This trait allows you to define relation methods directly in the model without overriding
relationQuery() method. The relation methods should have a specific naming convention to be recognized by the trait.
The method names should have prefix get and suffix Query and returns an object implementing ActiveQueryInterface
interface.
Note
Using MagicRelationsTrait is optional. The recommended approach is to use explicit relationQuery() method
(shown above) for better clarity and type safety.
use Yiisoft\ActiveRecord\ActiveQueryInterface;
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\MagicRelationsTrait;
final class User extends ActiveRecord
{
use MagicRelationsTrait;
public function getProfileQuery(): ActiveQueryInterface
{
return $this->hasOne(Profile::class, ['id' => 'profile_id']);
}
public function getOrdersQuery(): ActiveQueryInterface
{
return $this->hasMany(Order::class, ['user_id' => 'id']);
}
}ActiveRecord class has two methods to define different relation types: hasOne() and hasMany(). Both methods have
the same signature:
public function hasOne(string|ActiveRecordInterface $class, array $link): ActiveQueryInterface;
public function hasMany(string|ActiveRecordInterface $class, array $link): ActiveQueryInterface;$classparameter is the class name of the related record or an instance of the related record. For example:Profile::classornew Profile().$linkparameter is an array that defines the foreign key constraint. The keys of the array refer to the attributes of the record associated with$classmodel, while the values of the array refer to the corresponding attributes in the current Active Record class. For example:['id' => 'profile_id'], whereidattribute of the related record is linked toprofile_idattribute of the current record.
erDiagram
USER ||--|| PROFILE : has
USER {
int id
int profile_id
}
PROFILE {
int id
}
To define a one-to-one relation, use hasOne() method.
$this->hasOne(Profile::class, ['id' => 'profile_id']);erDiagram
USER ||--o{ ORDER : places
USER {
int id
}
ORDER {
int id
int user_id
}
To define a one-to-many relation, use hasMany() method.
$this->hasMany(Order::class, ['user_id' => 'id']);erDiagram
ORDER }o--|| USER : belongs
USER {
int id
}
ORDER {
int id
int user_id
}
This type of relation is the same as the one-to-one relation, but the related record has many records associated
with it. Use the hasOne() method to define this relation.
$this->hasOne(User::class, ['id' => 'user_id']);Use relationships when you need to link multiple records from one table to multiple records from another table. This is common when entities have a bidirectional relationship, such as users belonging to multiple groups and groups having multiple users.
erDiagram
USER }o--o{ GROUP : belongs
This is a complex relation type that сan be implemented in several ways. To define this relation use hasMany() method.
erDiagram
USER ||--o{ USER-GROUP : belongs
GROUP ||--o{ USER-GROUP : belongs
USER {
int id
}
GROUP {
int id
}
USER-GROUP {
int user_id
int group_id
}
This is the most common way to implement a many-to-many relation. You need to create a junction table that contains foreign keys that reference the primary keys of the related tables.
$this->hasMany(Group::class, ['id' => 'group_id'])->viaTable('user_group', ['user_id' => 'id']);In the example, user_group table contains two foreign keys:
user_idattribute references theidattribute of the current record ofUsermodel;group_idattribute references theidattribute of the related record ofGroupmodel.
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\ActiveQueryInterface;
final class User extends ActiveRecord
{
public int $id;
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'groups' => $this->hasMany(Group::class, ['id' => 'group_id'])->viaTable('user_group', ['user_id' => 'id']),
default => parent::relationQuery($name),
};
}
}
final class Group extends ActiveRecord
{
public int $id;
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'users' => $this->hasMany(User::class, ['id' => 'user_id'])->viaTable('user_group', ['group_id' => 'id']),
default => parent::relationQuery($name),
};
}
}Use this method when you don't need to store additional information in the junction table.
This is the most flexible way to implement a many-to-many relation. You need to create a junction model that represents the junction table.
$this->hasMany(Group::class, ['id' => 'group_id'])->via('userGroup');In the example, userGroup is a relation name associated with the junction model. You need to define this relation
in the Active Record model.
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\ActiveQueryInterface;
final class User extends ActiveRecord
{
public int $id;
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'groups' => $this->hasMany(Group::class, ['id' => 'group_id'])->via('userGroup'),
'userGroup' => $this->hasMany(UserGroup::class, ['user_id' => 'id']),
default => parent::relationQuery($name),
};
}
}
final class Group extends ActiveRecord
{
public int $id;
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'users' => $this->hasMany(User::class, ['id' => 'user_id'])->via('userGroup'),
'userGroup' => $this->hasMany(UserGroup::class, ['group_id' => 'id']),
default => parent::relationQuery($name),
};
}
}
final class UserGroup extends ActiveRecord
{
public int $user_id;
public int $group_id;
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'user' => $this->hasOne(User::class, ['id' => 'user_id']),
'group' => $this->hasOne(Group::class, ['id' => 'group_id']),
default => parent::relationQuery($name),
};
}
}Use this method when you need to store additional information in the junction table.
erDiagram
USER }o--o{ GROUP : belongs
USER {
int id
int[] group_ids
}
GROUP {
int id
}
This is the simplest way to implement a many-to-many relation. You don't need to create a junction table or a junction model to represent the junction table. Instead, you can use an array of related keys.
$this->hasMany(Group::class, ['id' => 'group_ids']);In the example, group_ids attribute of the current record is an array of related keys that reference id attribute
of the related record. The array attribute can be represented in the database as an array type (currently supported
by PgSQL driver only) or as a JSON type.
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\ActiveQueryInterface;
final class User extends ActiveRecord
{
public int $id;
public array $group_ids = [];
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'groups' => $this->hasMany(Group::class, ['id' => 'group_ids']),
default => parent::relationQuery($name),
};
}
}
final class Group extends ActiveRecord
{
public int $id;
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'users' => $this->hasMany(User::class, ['group_ids' => 'id']),
default => parent::relationQuery($name),
};
}
}Use this method when you don't need to store additional information in the junction table.
You can define relations that link through other relations using the ActiveQueryInterface::via() method.
This allows to access related records that are connected through intermediate relations.
For example, consider the following diagram:
erDiagram
direction LR
PROFILE ||--|| USER : has
USER ||--o{ USER-GROUP : belongs
USER-GROUP }o--|| GROUP : belongs
PROFILE {
int id
}
USER {
int id
int profile_id
}
GROUP {
int id
}
USER-GROUP {
int user_id
int group_id
}
To define a relation from Profile to Group through User and UserGroup, you can use the via() method as follows:
use Yiisoft\ActiveRecord\ActiveQueryInterface;
use Yiisoft\ActiveRecord\ActiveRecord;
final class Profile extends ActiveRecord
{
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'user' => $this->hasOne(User::class, ['profile_id' => 'id']),
'userGroups' => $this->hasMany(UserGroup::class, ['user_id' => 'id'])->via('user'),
'groups' => $this->hasMany(Group::class, ['id' => 'group_id'])->via('userGroups'),
default => parent::relationQuery($name),
};
}
}Now you can access the groups relation from the Profile model as follows:
$profile = Profile::query()->findByPk(1);
/** @var Group[] $groups */
$groups = $profile->relation('groups');An inverse relation is a relation defined in the related record to link back to the current record. It associates the related record(s) with the current record in a more efficient way by avoiding additional queries.
To define an inverse relation, use the ActiveQueryInterface::inverseOf() method.
$this->hasMany(Order::class, ['user_id' => 'id'])->inverseOf('user');In the example, user is the inverse relation name associated with the Order model.
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\ActiveQueryInterface;
final class User extends ActiveRecord
{
public int $id;
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'orders' => $this->hasMany(Order::class, ['user_id' => 'id'])->inverseOf('user'),
default => parent::relationQuery($name),
};
}
}
final class Order extends ActiveRecord
{
public int $id;
public int $user_id;
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'user' => $this->hasMany(User::class, ['id' => 'user_id'])->inverseOf('orders'),
default => parent::relationQuery($name),
};
}
}Relations are loaded lazily, meaning that the related record(s) aren't loaded until you access them. This allows you to load only the data you need and avoid unnecessary queries.
However, there are cases when you need to load the related record(s) in advance to avoid the N+1 query problem.
To do this, use the ActiveQueryInterface::with() method.
$users = User::query()->with('profile', 'orders')->all();In the example, profile and orders are the relation names that you want to load in advance.
To get the related record, use ActiveRecordInterface::relation() method. This method returns the related record(s)
or null (empty array for AbstractActiveRecord::hasMany() relation type) if the record(s) not found.
You can define getter methods to access the relations.
use Yiisoft\ActiveRecord\ActiveRecord;
use Yiisoft\ActiveRecord\ActiveQueryInterface;
final class User extends ActiveRecord
{
public function relationQuery(string $name): ActiveQueryInterface
{
return match ($name) {
'profile' => $this->hasOne(Profile::class, ['id' => 'profile_id']),
'orders' => $this->hasMany(Order::class, ['user_id' => 'id']),
default => parent::relationQuery($name),
};
}
public function getProfile(): Profile|null
{
return $this->relation('profile');
}
/** @return Order[] */
public function getOrders(): array
{
return $this->relation('orders');
}
}Now you can use $user->getProfile() and $user->getOrders() to access the relations.
$user = User::query()->where(['id' => 1])->one();
$profile = $user->getProfile();
$orders = $user->getOrders();Back to