Compoships
Compoships lets Hyperf's Model ORM define relationships that match two or more columns. It is intended for third-party or legacy schemas where a single foreign key column is not enough to identify the related records.
Compoships only extends relationship handling. It does not make Hyperf's model primary key itself a composite primary key.
The Problem
Eloquent-style relationships normally match one foreign key column to one local or owner key column. Adding a where clause to the relationship is not a replacement, because eager loading builds the relationship query before an individual model instance is available. In the example below, $this->team_id is null while the eager-loading constraints are being prepared.
namespace App;
use Hyperf\Database\Model\Model;
class User extends Model
{
public function tasks()
{
return $this->hasMany(Task::class)->where('team_id', $this->team_id);
}
}Installation
Install the component with Composer:
composer require friendsofhyperf/composhipsThis component targets Hyperf 3.2 and requires hyperf/database and the supporting Hyperf packages declared by the component. The package suggestions in composer.json are optional third-party packages and are not required for Compoships relationships.
Configuration
No publishable configuration is required. The component provides a Hyperf ConfigProvider, but it currently returns an empty configuration array.
Usage
Use the FriendsOfHyperf\Compoships\Database\Eloquent\Model Class
Extend FriendsOfHyperf\Compoships\Database\Eloquent\Model instead of Hyperf\Database\Model\Model. This base model uses the Compoships trait and keeps the normal Hyperf model behavior.
namespace App;
use FriendsOfHyperf\Compoships\Database\Eloquent\Model;
class User extends Model
{
}Use the FriendsOfHyperf\Compoships\Compoships Trait
If your model must extend another base class, use the FriendsOfHyperf\Compoships\Compoships trait on the model.
namespace App;
use FriendsOfHyperf\Compoships\Compoships;
use Hyperf\Database\Model\Model;
class User extends Model
{
use Compoships;
}When a relationship uses an array of keys, the related model must also use Compoships, either by extending FriendsOfHyperf\Compoships\Database\Eloquent\Model or by using the FriendsOfHyperf\Compoships\Compoships trait. Otherwise the relationship definition throws FriendsOfHyperf\Compoships\Exceptions\InvalidUsageException.
Relationship Syntax
Compoships supports composite keys on these relationship methods:
hasOne($related, $foreignKey = null, $localKey = null)hasMany($related, $foreignKey = null, $localKey = null)belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
Pass arrays instead of strings for the key arguments. Keep the arrays in the same order and with the same number of items, because values are matched by array index.
For hasOne and hasMany, the foreign-key array names columns on the related model, and the local-key array names columns on the current model:
namespace App;
use FriendsOfHyperf\Compoships\Compoships;
use Hyperf\Database\Model\Model;
class Team extends Model
{
use Compoships;
public function latestTask()
{
return $this->hasOne(Task::class, ['team_id', 'category_id'], ['id', 'category_id']);
}
public function tasks()
{
return $this->hasMany(Task::class, ['team_id', 'category_id'], ['id', 'category_id']);
}
}For belongsTo, the foreign-key array names columns on the current model, and the owner-key array names columns on the related model:
namespace App;
use FriendsOfHyperf\Compoships\Compoships;
use Hyperf\Database\Model\Model;
class Task extends Model
{
use Compoships;
public function team()
{
return $this->belongsTo(Team::class, ['team_id', 'category_id'], ['id', 'category_id']);
}
}Example
Assume a task list is managed by multiple teams, and each team has one user responsible for each task category:
- A task belongs to a category.
- A task is assigned to a team.
- A team has many users.
- A user belongs to a team.
- A user is responsible for tasks in one category.
The user responsible for a task is the user responsible for that task's category inside the assigned team.
namespace App;
use FriendsOfHyperf\Compoships\Compoships;
use Hyperf\Database\Model\Model;
class User extends Model
{
use Compoships;
public function tasks()
{
return $this->hasMany(Task::class, ['team_id', 'category_id'], ['team_id', 'category_id']);
}
}The inverse relationship uses the same pair of columns:
namespace App;
use FriendsOfHyperf\Compoships\Compoships;
use Hyperf\Database\Model\Model;
class Task extends Model
{
use Compoships;
public function user()
{
return $this->belongsTo(
User::class,
['team_id', 'category_id'],
['team_id', 'category_id']
);
}
}Behavior Notes
Compoships uses a custom query builder so eager loading can apply multi-column whereIn constraints and relationship existence queries can compare multiple columns with whereColumn.
For hasOne and hasMany, save() and create() fill each related foreign-key column from the matching parent local-key value. For belongsTo, associate() fills each child foreign-key column from the matching owner-key value.