Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Eloquent find for composite key

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.

like image 836
padarom Avatar asked Mar 06 '15 17:03

padarom


2 Answers

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();
like image 82
John Flatness Avatar answered Sep 21 '22 16:09

John Flatness


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]);
                }
            }                   
        }
    }
}
like image 42
togobites Avatar answered Sep 22 '22 16:09

togobites