Laravel/Eloquent - Eager Load using other than Primary Key - php

Eager Loading uses Primary Keys from the parent table inside the IN(...) clause, as clearly stated in Laravel's documentation:
select * from books
select * from authors where book_id in (1, 2, 3, 4, 5, ...)
Is it possible not to use the Primary Key for this, but another key from the books table? Or even a key retrieved from a pivot table?

Yeah we can do eager loading on non-primary columns. For this you need to define db-data relationships in models:-
Define your models Book & Author like this:-
class Books extends Eloquent {
public static $table = 'books';
public function bookauthors(){
return $this->has_many('authors','book_id');
}
}
class Authors extends Eloquent {
public static $table = 'authors';
public function books()
{
return $this->belongs_to('books','book_id');
}
}
Now you can use following line of code to eager load books with authors on a key "book_id":-
$books = Books::with(array('bookauthors'))->get();
I hope this helps you...i haven't tested on my local or test db. But had used similar eager loading for posts-tags relationship.

Related

Speed up query through laravel relationships

There are 3 models:
First (first_id)
Connection (connection_id, first_id, product_id)
Second (second_id, product_id)
I would like to connect the three models together using laravel relationships
First->joined to Connection though first_id
Connection->joined to First through first_id, & joined to Second through product_id
Second -> joined to Connection through product_id
So: First joined to Second through Connection first_id, product_id
Is this possible to do using something like HasManyThrough?
Thanks for your time
On your First model:
public function second () {
return $this->hasManyThrough(
Second::class,
Connection::class,
'product_id', // Foreign key on connection table
'product_id', // Foreign key on second table
'first_id', // Local key on first table
'first_id' // Local key on connection table
);
}
Based on your description the column names should work.
In tinker you can validate if it's hooked up correctly by doing something like First::first()->second.
It depends on what type of relationship Your first model and second model shares as well as what type of relation second and third model shares.
if consider your first model First and second model Second shares a one-to-one relation, as well as Second model and Third models shares one-to-one relationships.
It will be $first->second->third; //no has many through relationship requires
If your First model and Second models shares as hasMany-belongs to relation than you need to use hasManyThrough relationship
example from doc
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(
'App\Post',
'App\User',
'country_id', // Foreign key on users table...
'user_id', // Foreign key on posts table...
'id', // Local key on countries table...
'id' // Local key on users table...
);
}
}
You can try using nested relationships.
Nested Eager Loading
To eager load nested relationships, you may use "dot" syntax. For example, let's eager load all of the book's authors and all of the author's personal contacts in one Eloquent statement:
$books = App\Book::with('author.contacts')->get();
Book Model:
public function author()
{
return $this->hasOne('App\Author');
}
Author Model:
public function contacts()
{
return $this->hasMany('App\Author');
}
Documentation:
https://laravel.com/docs/5.8/eloquent-relationships

Eloquent ORM Many-to-many relationship

I've just started using Eloquent ORM (Without Laravel) and I am having issues with the many to many relationships.
I have a table where I store Families (Article categories), another one for the Articles, and a third one as a "pivot". I want to be able to get all the articles a Family has, and all the families an article belongs to. So I have coded this models.
Families
class Families extends Model {
public $table = 'Families';
public function Articles() {
return $this->belongsToMany('Articles', 'articles_families', 'families_id', 'articles_id');
}
}
Articles
class Articles extends Model {
public $table = 'Articles';
public function Families() {
return $this->belongsToMany('Families', null, 'articles_id', 'families_id');
}
}
Then I am trying to retrieve the data like this:
$families = Families::all();
echo $families[1]->Articles;
However, it just returns an empty array, when it should return a couple of articles. I have tripled checked that all the values are correct in the three tables. If I echo the Eloquent query debugger I can see that it is looking for a null value and I'm pretty sure that's the problem, but I don't quite know how to fix it. Here:
{"query":"select * from `Families`","bindings":[],"time":49.13},{"query":"select `Articles`.*, `articles_families`.`families_id` as `pivot_families_id`, `articles_families`.`articles_id` as `pivot_articles_id` from `Articles` inner join `articles_families` on `Articles`.`id` = `articles_families`.`articles_id` where `articles_families`.`families_id` is null","bindings":[],"time":38.93}
The null value is at the end of the last query.
I just found the solution myself. As my primary key columns are called Id, and Eloquent by default assumes the primary key is called id, I needed to override that by adding a class property protected $primaryKey = "Id"; and it now retrieves the data properly.

Laravel Eloquent HasManyThrough returning empty array

I am having trouble getting a query from HasManyThrough relation in Eloquent.
These are my tables
PAGES
id
columns
slideshow_fk
SLIDESHOWS
id
columns
SLIDES
id
columns
slideshow_id
My Page model:
class Page extends Model
{
public function slideshow_id(){
return $this->belongsTo(Slideshow::class);
}
public function slides(){
return $this->hasManyThrough('App\Slide','App\Slideshow','id','slideshow_id','slideshow_fk');
}
}
Controller
$page=Page::where("slug","=",$slug)->with('slides')->first();
Query log: I am not Page ID:3 with slideshow_fk:1, [? = 1]
select `slides`.*, `slideshows`.`id` from `slides` inner join `slideshows` on `slideshows`.`id` = `slides`.`slideshow_id` where `slideshows`.`id` in (?)
page->slides array:
[]
PhpMyAdmin SQL copy/paste:
http://prntscr.com/e0hy4e
Which are the correct 3 slides I need for my page.
Why do I get an empty array?
You cannot you hasManyThrough() here as your implementation doesn't support the need for one.
Say you have three models A, B and C and A want to connect to C via B.
Then if you want to use hasManyThrough()
B needs to have A's primary key as a foreign key
C needs to have B's primary key as a foreign key
That is A <- B <- C
If I put this to your example, what you have is
A have B's primary key as a foreign key
C have B's primary key as a foreign key
Which is A -> B <- C.
What you can do here is define models like this.
class Pages extends Model
{
public function slideshow()
{
return $this->belongsTo(Slideshow::class);
}
}
class Slideshow extends Model
{
public function slides()
{
return $this->hasMany(Slides::class);
}
}
class Slides extends Model
{
}
And query like
$pags = Pages::with('slideshow.slides')->where('slug', $slug)->first();
I suggest using proper naming conventions.

Laravel belongsToMany relationship defining local key on both tables

So the belongsToMany relationship is a many-to-many relationship so a pivot table is required
Example we have a users table and a roles table and a user_roles pivot table.
The pivot table has two columns, user_id, foo_id... foo_id referring to the id in roles table.
So to do this we write the following in the user eloquent model:
return $this->belongsToMany('Role', 'user_roles', 'user_id', 'foo_id');
Now this looks for an id field in users table and joins it with the user_id field in the user_roles table.
Issue is I want to specify a different field, other than id to join on in the users table. For example I have bar_id in the users table that I want to use as the local key to join with user_id
From laravel's documentation, it is not clear on how to do this. In other relationships like hasMany and belongsTo we can specify local key and foriegn key but not in here for some reason.
I want the local key on the users table to be bar_id instead of just id.
How can I do this?
Update:
as of Laravel 5.5 onwards it is possible with generic relation method, as mentioned by #cyberfly below:
public function categories()
{
return $this->belongsToMany(
Category::class,
'service_categories',
'service_id',
'category_id',
'uuid', // new in 5.5
'uuid' // new in 5.5
);
}
for reference, previous method:
I assume id is the primary key on your User model, so there is no way to do this with Eloquent methods, because belongsToMany uses $model->getKey() to get that key.
So you need to create custom relation extending belongsToMany that will do what you need.
A quick guess you could try: (not tested, but won't work with eager loading for sure)
// User model
protected function setPrimaryKey($key)
{
$this->primaryKey = $key;
}
public function roles()
{
$this->setPrimaryKey('desiredUserColumn');
$relation = $this->belongsToMany('Role', 'user_roles', 'user_id', 'foo_id');
$this->setPrimaryKey('id');
return $relation;
}
On Laravel 5.5 and above,
public function categories()
{
return $this->belongsToMany(Category::class,'service_categories','service_id','category_id', 'uuid', 'uuid');
}
From the source code:
public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null,
$parentKey = null, $relatedKey = null, $relation = null)
{}
This is a recently added feature. I had to upgrade to 4.1 because I was also looking for this.
From the API documentation:
public BelongsToMany belongsToMany(string $related, string $table = null, string $foreignKey = null, string $otherKey = null, string $relation = null)
The $otherKey and $relation parameters were added in 4.1. Using the $foreignKey and $otherKey parameters allows you to specify the keys on both sides of the relation.
The best way is set the primary key.
class Table extends Eloquent {
protected $table = 'table_name';
protected $primaryKey = 'local_key';
belongsToMany allows to define the name of the fields that are going to store che keys in the pivot table but the method insert always the primary key values into these fields.
You have to:
define in the method belongsToMany the table and the columns;
then using protected $primaryKey = 'local_key'; you can choose which value store.
I recently went through the same problem where I needed to have an associated table that used ID's to link two tables together that were not Primary Keys. Basically what I did was create a copy of my model that models the pivot table and set the Primary Key to the value that I wanted it to use. I tried creating a model instance, settings the primary key and then passing that to the relation but Laravel was not respecting the primary key I had set ( using the ->setPrimaryKey() method above ).
Making a copy of the model and setting the primary key feels a little bit 'hackish' but in the end it works as it should and since Pivot table models are generally very small I don't see it causing any problems in the future.
Would love to see a third key option available in the next release of Laravel that lets you get more specific with your linking.

Eloquent ORM query -- newbie

I am just starting with ORM. I had a question, this is my tabel --
table a - (aid, aname, atag);
table b - (bid, aid, bname, .. );
It is One to Many relationship - that is One aid can belong to many bid but one bid can belong to only one aid.
So I was trying out this code, In the out put I want -- (bname,aname) for all the records.
A model --
class A extends Eloquent {
protected $table = 'a';
protected $primaryKey = 'aid';
public function brelation() {
$this->belongsToMany('B','aid');
}
}
B model --
class B extends Eloquent {
protected $table = 'b';
protected $primaryKey = 'bid';
public function getANames() {
$this->hasOne('A','aid');
}
}
In Controller --
foreach(B::with('getANames')->get() as $b_item){
echo $b_item->bname." , ".$b_item->aname;
}
Couple of points to clarify --
1) I have to specify the foreign key to make sure they map. Because in my actual case they are named differently.
2) I am using Laravel 4.
Can someone show me what I did wrong and how I can get the desired result.
===== Update =====
class A extends Eloquent {
protected $table = 'a';
protected $primaryKey = 'aid';
public function brelation() {
$this->belongsTo('B','aid');
}
}
I still cannot access the aname column i.e ($b_item->aname) in the controller.
A couple of things you should be aware of:
If you have a custom primary key, you need to set the $primaryKey property on your eloquent models to the primary keys you have in the DB.
You can't mix and match belongsToMany relationships with anything other than belongsToManys. A belongsToMany is exclusively for the case where you have two tables that are connected by a pivot table. In your case, B belongsTo A, and A hasMany B.

Categories