I've recently started working with Laravel 5 as a framework. So far everything's been completely straight-forward and it's just great to work with it. Currently however I'm running into some troubles regarding on of my Eloquent models.
The database table reports
has the following schema:
| id | group_id | score | ... |
group_id
is a foreign key referencing the groups
table. The primary key of this table is a composite of id
and group_id
. id
is also not auto-increasing, as it's an external id I'm using for easier internal processing.
Each external id can be present for every of my groups once and have a different score for each of them, hence the composite primary key.
When visiting one of my pages, I want to fetch the latest records from my external data source and match them with the corresponding database rows. If they don't exist, I want to create them.
My current code for this route is:
public function showReports ($id)
{
$group = Group::findOrFail($id);
foreach ($group->getLatestReports(20) as $reportElement)
{
$report = Report::find($reportElement['id']);
if (is_null($report))
{
$report = new Report;
// Fill values ...
$report->save();
}
}
}
This obviously doesn't work as anticipated, as it only looks for the id
( ::find($reportElement['id'])
), not for the group_id
. As usual with my questions, the answer is probably super easy, yet I can't seem to find it right now.
Model::find
only works with single-column keys. You can pass it an array, but that just makes it look for multiple rows.
You'd need to chain together two where
's to make your query:
$report = Report::where('id', '=', $reportElement['id'])
->where('group_id', '=', $group->id)
->first();
You can make a Trait MutiPrimaryKey, then extend the getKey and find method: 1) In App\Classes\Database create he following file MultiPrimaryKey.php
<?php
namespace App\Classes\Database;
use Illuminate\Database\Eloquent\Builder;
trait MultiPrimaryKey {
protected function setKeysForSaveQuery(Builder $query)
{
$keys = $this->getKeyName();
if(!is_array($keys)){
return parent::setKeysForSaveQuery($query);
}
foreach($keys as $keyName){
$query->where($keyName, '=', $this->getKeyForSaveQuery($keyName));
}
return $query;
}
protected function getKeyForSaveQuery($keyName = null)
{
if(is_null($keyName)){
$keyName = $this->getKeyName();
}
if (isset($this->original[$keyName])) {
return $this->original[$keyName];
}
return $this->getAttribute($keyName);
}
public function getKey()
{
$keys = $this->getKeyName();
if(!is_array($keys)){
return parent::getKey();
}
$pk = [];
foreach($keys as $keyName){
$pk[$keyName] = $this->getAttribute($keyName);
}
return $pk;
}
public function find($id, $columns = ['*'])
{
if (is_array($id) || $id instanceof Arrayable) {
$out = null;
foreach ($id as $key => $value) {
//echo "{$key} => {$value} ";
if ($out == null)
{
$out = $this->where($key, $value);
}
else
{
$out = $out->where($key, $value);
}
}
return $out->first($columns);
}
return $this->whereKey($id)->first($columns);
}
}
Then you have to class for custom relations: 2) In App\Classes\Database create the following file HasMultiPKRelationships.php
<?php
namespace App\Classes\Database;
use App\Classes\Database\Eloquent\Relations\BelongsToMultiPK;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
trait HasMultiPKRelationships {
public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
{
if ($relation === null) {
$relation = $this->guessBelongsToRelation();
}
$instance = $this->newRelatedInstance($related);
if ($foreignKey === null) {
$foreignKey = Str::snake($relation) . '_' . $instance->getKeyName();
}
$ownerKey = $ownerKey ?: $instance->getKeyName();
if ( is_array($ownerKey))
{
return new BelongsToMultiPK($instance->newQuery(), $this, $foreignKey, $ownerKey, $relation);
}
else
{
return new BelongsTo($instance->newQuery(), $this, $foreignKey, $ownerKey, $relation);
}
}
}
Then you need the custom BelongsTo 3) In App\Classes\Database\Eloquent\Relations create the following file BelongsToMultiPK.php
<?php
namespace App\Classes\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
class BelongsToMultiPK extends BelongsTo
{
public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
{
parent::__construct($query, $child, $foreignKey, $ownerKey, $relation);
}
public function addConstraints()
{
if (static::$constraints) {
// For belongs to relationships, which are essentially the inverse of has one
// or has many relationships, we need to actually query on the primary key
// of the related models matching on the foreign key that's on a parent.
$table = $this->related->getTable();
if (! is_array($this->ownerKey) )
{
$this->query->where($table.'.'.$this->ownerKey, '=', $this->child->{$this->foreignKey});
}
else
{
foreach ($this->ownerKey as $key)
{
$this->query->where($table.'.'.$key, '=', $this->child->{$this->foreignKey}[$key]);
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With