Laravel Eloquent Distant Relationship Setup - php

I have three tables, a user table, documents table, and a favourites table. The idea is a user can favourite a document, but I can't understand the best way to query this using Eloquent.
User.php
class User extends Authenticatable
{
public function documents()
{
return $this->hasMany('App\Document');
}
public function favourites()
{
return $this->hasMany('App\Favourite');
}
}
Document.php
class Document extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
Favourite.php
class Favourite extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
public function document()
{
return $this->belongsTo('App\Document');
}
}
The favourite table is a simple 2 column table with the user_id and the document_id linking each user to an article they have favourited.
Now I can use a method to get the users favourite articles like so:
App\User::with('favourites')->find(1);
The problem is this brings back the two id's from the favourites table when I want the data from the documents table such as the title and id of the document.
It looks like the "has-many-through" relationship is what I might need to achieve this query, but I'm unsure how to implement it in this use case or even if the "has-many-through" relationship is the correct way to do this?

As your relationship is being setup, you can have a user that has multiple documents, and he can also make multiple documents to be his favorites. So it will always return an array. In order to load all the documents for the user that are his favorites, you can do that the same as you started:
$favoriteDocuments = App\User::with('favourites.document')->find($userId = 1)->get();
// this will contain all the favorite documents for the user, so you can the iterate over them:
foreach($favoriteDocuments as $favoriteDocument)
{
// $favoriteDocument->document; is the object you are looking for.
}
Has many through relationship is used in order to get item from a table to which you don't have access to directly. But to both of your tables you have direct connection to the user.

You are correct with has-many-through, so on User:
return $this->hasManyThrough('App\Document', 'App\Favourite');
And on Document:
return $this->hasManyThrough('App\User', 'App\Favourite');
Eloquent Docs

Your favorites table is a Pivot table. You don't need a favorites model.
User.php
public function favoriteDocuments()
{
return $this->BelongsToMany('App\Document', 'favorite_documents');
}
Now you can call $user->favoriteDocuments to get the users documents.
See the docs about many to many relationships. https://laravel.com/docs/5.7/eloquent-relationships#many-to-many.

Related

How to join two tables with a pivot table using Laravel?

I have these three tables:
tbl_lista_contactabilidad tbl_equipo_postventaatc users
------------------------- ----------------------- -----
id id id
usuarios_id asesor_id name
tbl_lista_contactabilidad.usuarios_id should be related with tbl_equipo_postventaatc.asesor_id. asesor_id should be the "pivot" between tbl_lista_contactabilidad.usuarios_id and users.id to make the relation.
I want to make this relation so I tried to do this relation in this way (I will put only the relation of the model)
Tbl_Lista_Contactabilidad (Model 1)
public function postventaatc(){
return $this->belongsTo('App\Models\Tbl_EquipoPostventaatc','usuarios_id');
}
Tbl_Equipo_Postventaatc (Model 2) -> This should be the pivot model
public function contactabilidad(){
return $this->hasMany('App\Models\Tbl_Lista_Contactabilidad','usuarios_id');
}
public function user(){
return $this->belongsTo('App\Models\User','asesor_id');
}
User (Model 3)
public function postventaatc(){
return $this->hasMany('App\Models\Tbl_Lista_Postventaatc','asesor_id');
}
EXAMPLE:
As you see in the image... if I relate usuarios_id with users directly I will get another name and I don't want that... I want the relation just like in the image
A pivot table is a structure used to join two separate models together with a single relationship. This is called a many-to-many relationship in Eloquent.
From what you've described, this is not the case here. Rather, it looks like a has-many-through relationship.
If I'm understanding correctly, your relationships should look like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tbl_Lista_Contactabilidad extends Model {
protected $table = 'tbl_lista_contactabilidad';
public function postventaatc() {
return $this->belongsTo(Tbl_EquipoPostventaatc::class, 'usuarios_id');
}
}
class Tbl_EquipoPostventaatc extends Model {
protected $table = 'tbl_equipo_postventaatc';
public function contactabilidad() {
return $this->hasMany(Tbl_Lista_Contactabilidad::class, 'usuarios_id');
}
}
class User extends Model {
public function postventaatc() {
return $this->belongsTo(Tbl_EquipoPostventaatc::class, 'asesor_id');
}
public function contactabilidad() {
return $this->hasManyThrough(Tbl_Lista_Contactabilidad::class, Tbl_EquipoPostventaatc::class, 'asesor_id', 'usuarios_id');
}
}
Obviously this is easier for a native English speaker, but I cannot stress how much easier this would be if you were following the Laravel rules around naming your models, tables, and columns. Why does usuarios_id column relate to a table called tbl_equipo_postventaatc? Why use asesor_id instead of user_id? 🤷🏽‍♂️ Those names have nothing to do with each other, and make it hard to figure out what is going on.

Laravel 5.3 Polymorphic many to many - how to get all the related rows regardless of their table sorted by pivot column?

I have a model Page and many models called SomethingSection - they're connected through a polymorphic m-m realtionship and the pivot has an additional column 'position'.
I need to write a relationship (or accessor maybe?) on the Page model that will return a collection of all connected Sections, regardless of their model (read: table).
My models:
class Page extends Model {
public function introSections()
{
return $this->morphedByMany(IntroSection::class, 'pagable');
}
public function anotherSections()
{
return $this->morphedByMany(AnotherSection::class, 'pagable');
}
}
class IntroSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable');
}
}
class AnotherSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable');
}
}
The pivot column looks like this:
pagables
-page_id
-pagable_id
-pagable_type
-position
I'm looking for a way to call a method/attribute on the Page model and get all the connected sections in a single collection, sorted too. What would be a good way to go about this?
I understand that the connected sections do not have the same interface, but in my case that's not a problem at all (in terms of what I will do with the data).
I also understand that relationships perform a separate query (for each relationship), so getting all of them with 1 query is impossible (also different interfaces would be a problem here). And for the same reason the sorting will need to be done on the collection level, not in query.
How could I make this as maintainable as possible and preferably with as small a performance hit as possible.
Thanks in advance.
You can use withPivot() method after your relationship to get the pivot columns with relation like this:
class Page extends Model {
public function introSections()
{
return $this->morphedByMany(\HIT\Models\Sections\IntroSection::class, 'pagable')
->withPivot(['position']);
}
public function anotherSections()
{
return $this->morphedByMany(AnotherSection::class, 'pagable');
}
}
class IntroSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable')
->withPivot(['position']);
}
}
and you can use collection's sortBy to sort the collection by using sortBy() method like this:
$sorted_collection = IntroSection::pages->sortBy('pagables.position');
UPDATE:
You can use collection's combine() method to get all the relationships like this, add this method inside your Page Class:
public function getAllSections()
{
return $this->introSections->combine($this->anotherSections-toArray())
->sortBy('pagables.position'):
}
Hope this helps!

Laravel Eloquent Joins

I have below query in core php:
SELECT DISTINCT device_tocken FROM push_details JOIN users ON users.id=push_details.user_id
I have to integrate it in laravel 4
Application already have User extends Eloquent class
I created Push_details class as below
class Push_details extends Eloquent {
public $table = 'push_details';
public function User() {
return $this->hasMany('\User','id');
}
}
Table : users
Primary key : id
Table: push_details
Primary key: id
Foreign key: user_id belongsTo('users.id');
But i m not able to get expected result.
One more thing i didn't write anything in User's model yet.
Only way to join table is.. to join it, as Eloquent relations don't work using joins but separate queries with WHERE IN clauses. So this will do:
DB::table('push_details')
->select('device_tocken')
->distinct()
->join('users','users.id','=','push_details.user_id')
->get();
Above will return array of stdObject's so or if you need Eloquent Collection with Eloquent models as a result replace DB::table('push_details')->select... with PushDetails::select...
Now, correct your relations, as they are wrong:
// PushDetails model (as previously stated, I suggest renaming it to StudlyCase)
public function user() {
return $this->belongsTo('\User','user_id'); // user_id is may be omitted here
}
// User model
public function pushDetails() {
return $this->hasMany('\PushDetails','user_id'); // user_id is may be omitted here as well
}
In your User model, you need to link back to the PushDetails model, like so
class User extends Eloquent {
public function push_details() {
return $this->belongsTo('PushDetails');
}
}
Use CamelCase for Class names, because laravel has several functions, in which CamelCase are changed to snake_case
Change
public function User() {
return $this->hasMany('\User','id');
}
to
public function users() {
return $this->hasMany('User');
}
See the docs 'Eloquent ORM' for more...

Eloquent many-to-many

Using Laravel, I'm having some trouble accessing my other tables, which are all many to many.
So basically, I start out with the user id and want to list the customers that user has.
public function customers()
{
return $this->belongsToMany('Customer', 'user_to_customer');
}
This works, assume my user id is 1:
User::find(1)->customers;
However now I want to say, for each of these customers, list their products. However this needs to be within the same result.
I guess I would need something within the Customer model, such as:
public function products()
{
return $this->belongsToMany('Product', 'user_to_customer');
}
I can't seem to work out how to access this within the same query? Something like:
User::find(1)->customers->products;
Not sure.. any suggestions?
You can look into eager loading to accomplish this behavior. Given the following model relationships:
class User extends Eloquent {
public function customers()
{
return $this->has_many( 'Customer' );
}
}
class Customer extends Eloquent {
public function products()
{
return $this->has_many( 'Product' );
}
}
class Product extends Eloquent {}
The following query will return all products belonging to customers belonging to a specific (in this case, first) user:
User::with(array('customers', 'customers.products'))->first();

Check for a many to many relation when listing a resource

I'm implementing relationships in Eloquent, and I'm facing the following problem:
An article can have many followers (users), and a user can follow many articles (by follow I mean, the users get notifications when a followed article is updated).
Defining such a relationship is easy:
class User extends Eloquent {
public function followedArticles()
{
return $this->belongsToMany('Article', 'article_followers');
}
}
also
class Article extends Eloquent {
public function followers()
{
return $this->belongsToMany('User', 'article_followers');
}
}
Now, when listing articles I want to show an extra information about each article: if the current user is or is not following it.
So for each article I would have:
article_id
title
content
etc.
is_following (extra field)
What I am doing now is this:
$articles = Article::with(array(
'followers' => function($query) use ($userId) {
$query->where('article_followers.user_id', '=', $userId);
}
)
);
This way I have an extra field for each article: 'followers` containing an array with a single user, if the user is following the article, or an empty array if he is not following it.
In my controller I can process this data to have the form I want, but I feel this kind of a hack.
I would love to have a simple is_following field with a boolean (whether the user following the article).
Is there a simple way of doing this?
One way of doing this would be to create an accessor for the custom field:
class Article extends Eloquent {
protected $appends = array('is_following');
public function followers()
{
return $this->belongsToMany('User', 'article_followers');
}
public function getIsFollowingAttribute() {
// Insert code here to determine if the
// current instance is related to the current user
}
}
What this will do is create a new field named 'is_following' which will automatically be added to the returned json object or model.
The code to determine whether or not the currently logged in user is following the article would depend upon your application.
Something like this should work:
return $this->followers()->contains($user->id);

Categories