Hi,
is there a smooth way to implement an association where it will match either of two foreign keys?
My problem is best described by using an example:
In a football match (MatchesTable
), you have two teams (TeamsTable
) competing, so you’d need to have two foreign keys (home_id
and away_id
), which would each contain a Team
entity. A match would normally have one home team and one away team, which gives the foreign key name as such.
You’d need to create two belongsTo
associations in the MatchesTable
and a hasMany
association in the TeamsTable
.
What I need:
Whenever I call the method $this->Teams->get($id, ['contain' => 'Matches'])
, it should contain all records that either match the home_id
or the away_id
foreign keys.
That way I can get all the matches a team has played, not only its home games.
What happens:
Right now, I can only set one foreign key (for instance home_id
) and then it loads only the team’s home matches.
What I’ve tried:
I have added an hasMany
association to Matches
with two foreign keys, but then I also need to add bindingKeys
.
// MatchesTable
$this->hasMany('Matches', [
'foreignKey' => ['home_id', 'away_id'],
'bindingKey' => ['id', 'id'],
]);
// TeamsController
public function view(string $id = null): void
{
$team = $this->Teams->get(1, ['contain' => 'Matches']);
$this->set(compact('team'));
}
The resulting query gives me this condition:
WHERE
(
Matches.home_id, Matches.away_id
) IN (
(1, 1)
)
This query will never be true, as a team won’t play against itself.
I tried adding a custom finder:
// TeamsTable
$this->hasMany('Matches', [
'foreignKey' => 'home_id',
'finder' => 'relative',
]);
// MatchesTable
public function findRelative(Query $query, array $options): Query
{
$or = $query->newExpr()->or([
'home_id' => 1,
'away_id' => 1,
]);
return $query->where($or);
}
Which results in the following query:
WHERE
(
(
home_id = 1
OR away_id = 1
)
AND Matches.home_id in (1)
)
As you might see, the Cake built query appends the foreignKey condition to my custom finder method.
My current workaround
//MatchesTable
public function findRelative(Query $query, array $options): Query
{
if (!isset($options['id'])) {
throw new MissingArgumentException('Missing `id` when find relative matches.');
}
$or = $query->newExpr()->or([
'team_id' => $options['id'],
'opponent_id' => $options['id'],
]);
return $query->where($or);
}
// TeamsController
public function view(string $id = null): void
{
$team = $this->Teams->get($id);
$matches = $this->Teams->Matches->find('relative', compact('id'));
$team->matches = $matches->toArray();
$this->set(compact('team'));
}