Ordered Results Set within a relationship using Laravel - php

I have a table structure that has this structure:
User (has many) Subjects (has many) Modules (has many) Topics (has many) Notes
I have foreign keys in each table like so:
Subject (user_id), Modules(subject_id), Topics(module_id), Notes(topic_id)
I want to have a way to access all the notes created by a user ordered by the date created. Is this possible using the query builder?
Note.php
class Note extends Model implements SluggableInterface
{
use SluggableTrait;
protected $sluggable = [
'build_from' => ['question', 'id'],
'save_to' => 'slug'
];
protected $fillable = ['question', 'answer', 'topic_id'];
public function topic()
{
return $this->belongsTo('App\Topic');
}
}
Topic.php
class Topic extends Model implements SluggableInterface
{
use SluggableTrait;
protected $sluggable = [
'build_from' => ['title', 'id'],
'save_to' => 'slug'
];
protected $fillable = ['title', 'module_id'];
public function notes()
{
return $this->hasMany('App\Note');
}
public function module()
{
return $this->belongsTo('App\Module');
}
}

There are a few ways to do this. If you wish to use your relationships, you will need to nest a few loops to make your way through the entire "tree" of relationships.
$user = User::with('subjects.modules.topics.notes')->find($user_id);
$notes = new Illuminate\Database\Eloquent\Collection;
foreach($user->subjects as $subject) {
foreach($subject->modules as $module) {
foreach($module->topics as $topic) {
foreach($topic->notes as $note) {
$notes->add($note);
}
}
}
}
$notes = $notes->sortBy('created_at');
As for making it easier through database design, that's a tough question without knowing what the rest of your app is doing. If this is the only place where you have any notes, then I would say probably it's the ideal solution. If other places need notes too, then it might be a problem.
Another approach would have to been to use the query builder which wouldn't require you to loop through any result sets but would require you write the sql. I wouldn't say it's simpler or easier though.
$notes = \DB::table('users')
->select('notes.*')
->join('subjects', 'users.id', '=', 'subjects.user_id')
->join('modules', 'subjects.id', '=', 'modules.subject_id')
->join('topics', 'modules.id', '=', 'topics.module_id')
->join('notes', 'topics.id', '=', 'notes.topic_id')
->where('users.id', $user_id)
->get();

I am not 100% sure because of naming conventions:
$user = User::with(['subjects.modules.topics.notes' => function($query)
{
$query->orderBy('created_at')
}])->whereId($user_id)->first();
dd($user);
Give me feedback in comments what you get.

Related

Laravel Relationship with condtion on foreign key (toggle foreign key on relation)

For a small chat purpose, I am using below relationship
Model
class Chat extends Model
{
protected $table = 'chats';
protected $primaryKey = 'chat_id';
protected $filllable = [
'chat_id',
'sender_id',
'reciever_id',
'content',
'sender_type',
'reciever_type',
'view_status'
];
class Admin extends Authenticatable
{
public function chats()
{
return $this->hasMany(Chat::class, 'sender_id', 'admin_id');
}
}
but the issue is both user's are in the same table some times it is sender_id sometimes it is reciever_id so I want to return the above relationship with the condition (if the receiver type in chat table is 1 it should be reciever_id else it should be the sender_id)
Controller
$seller_id = auth()->guard('seller')->user()->seller_id;
$chatLists = Admin::whereHas('chats', function ($q) {
$q->where('reciever_type', 2);
$q->orWhere('sender_type', 2);
})
->with(['chats' => function ($q) {
$q->where('reciever_type', 2);
$q->orWhere('sender_type', 2);
}])
->orderBy('created_at', 'desc')
->get();
return view('seller.chat.index', compact('chatLists'));
}
sender_type and receiver_type don't seem to do much.
If you retrieve $seller_id and intend to get all chats from $seller_id,
both chats with $seller_id as sender
and chats with $seller_id as receiver
Then your query could look like this.
$seller_id = auth()->guard('seller')->user()->seller_id;
$chatLists = Admin::whereHas('chats', function ($q) use ($seller_id) {
$q->where('receiver_id', $seller_id)
->orWhere('sender_id', $seller_id);
})
->with(['chats' => function ($q) use ($seller_id) {
$q->where('receiver_id', $seller_id)
->orWhere('sender_id', $seller_id);
}])
->latest()
->get();

Laravel where on relationship object

I'm developing a web API with Laravel 5.0 but I'm not sure about a specific query I'm trying to build.
My classes are as follows:
class Event extends Model {
protected $table = 'events';
public $timestamps = false;
public function participants()
{
return $this->hasMany('App\Participant', 'IDEvent', 'ID');
}
public function owner()
{
return $this->hasOne('App\User', 'ID', 'IDOwner');
}
}
and
class Participant extends Model {
protected $table = 'participants';
public $timestamps = false;
public function user()
{
return $this->belongTo('App\User', 'IDUser', 'ID');
}
public function event()
{
return $this->belongTo('App\Event', 'IDEvent', 'ID');
}
}
Now, I want to get all the events with a specific participant.
I tried with:
Event::with('participants')->where('IDUser', 1)->get();
but the where condition is applied on the Event and not on its Participants. The following gives me an exception:
Participant::where('IDUser', 1)->event()->get();
I know that I can write this:
$list = Participant::where('IDUser', 1)->get();
for($item in $list) {
$event = $item->event;
// ... other code ...
}
but it doesn't seem very efficient to send so many queries to the server.
What is the best way to perform a where through a model relationship using Laravel 5 and Eloquent?
The correct syntax to do this on your relations is:
Event::whereHas('participants', function ($query) {
return $query->where('IDUser', '=', 1);
})->get();
This will return Events where Participants have a user ID of 1. If the Participant doesn't have a user ID of 1, the Event will NOT be returned.
Read more at https://laravel.com/docs/5.8/eloquent-relationships#eager-loading
#Cermbo's answer is not related to this question. In that answer, Laravel will give you all Events if each Event has 'participants' with IdUser of 1.
But if you want to get all Events with all 'participants' provided that all 'participants' have a IdUser of 1, then you should do something like this :
Event::with(["participants" => function($q){
$q->where('participants.IdUser', '=', 1);
}])
N.B:
In where use your table name, not Model name.
for laravel 8.57+
Event::whereRelation('participants', 'IDUser', '=', 1)->get();
With multiple joins, use something like this code:
$userId = 44;
Event::with(["owner", "participants" => function($q) use($userId ){
$q->where('participants.IdUser', '=', 1);
//$q->where('some other field', $userId );
}])
Use this code:
return Deal::with(["redeem" => function($q){
$q->where('user_id', '=', 1);
}])->get();
for laravel 8 use this instead
Event::whereHas('participants', function ($query) {
$query->where('user_id', '=', 1);
})->get();
this will return events that only with partcipats with user id 1 with that event relastionship,
I created a custom query scope in BaseModel (my all models extends this class):
/**
* Add a relationship exists condition (BelongsTo).
*
* #param Builder $query
* #param string|Model $relation Relation string name or you can try pass directly model and method will try guess relationship
* #param mixed $modelOrKey
* #return Builder|static
*/
public function scopeWhereHasRelated(Builder $query, $relation, $modelOrKey = null)
{
if ($relation instanceof Model && $modelOrKey === null) {
$modelOrKey = $relation;
$relation = Str::camel(class_basename($relation));
}
return $query->whereHas($relation, static function (Builder $query) use ($modelOrKey) {
return $query->whereKey($modelOrKey instanceof Model ? $modelOrKey->getKey() : $modelOrKey);
});
}
You can use it in many contexts for example:
Event::whereHasRelated('participants', 1)->isNotEmpty(); // where has participant with id = 1
Furthermore, you can try to omit relationship name and pass just model:
$participant = Participant::find(1);
Event::whereHasRelated($participant)->first(); // guess relationship based on class name and get id from model instance
[OOT]
A bit OOT, but this question is the most closest topic with my question.
Here is an example if you want to show Event where ALL participant meet certain requirement. Let's say, event where ALL the participant has fully paid. So, it WILL NOT return events which having one or more participants that haven't fully paid .
Simply use the whereDoesntHave of the others 2 statuses.
Let's say the statuses are haven't paid at all [eq:1], paid some of it [eq:2], and fully paid [eq:3]
Event::whereDoesntHave('participants', function ($query) {
return $query->whereRaw('payment = 1 or payment = 2');
})->get();
Tested on Laravel 5.8 - 7.x

laravel querying multiple classes and or tables

How do I query more than two tables. What I want to do is have a list of products queryed by a genre and then query productvariations - where a product variation has a producttype_id = $producttype->id
Route::get('browse/{producttype_slug}/{genre_slug}', array(
'as' => 'products.viewbygenre',
function($productype_slug, $genre_slug)
{
$producttype = ProductTypes::where('slug', '=', $productype_slug)->firstOrFail();
$genre = GenreTypes::where('slug', '=', $genre_slug)->firstOrFail();
// error below
$products = Product::with(array('ProductVariations', 'GenreTypes'))
->where('genre', '=', $genre->id)->get();
return View::make('products.viewbygenre')->with(compact('productvariations', 'genre', 'producttype'));
}))->where('producttype_slug', '[A-Za-z\-]+')->where('genre_slug', '[A-Za-z\-]+');
products class
class Product extends \Eloquent {
protected $table = "products";
protected $fillable = ['keywords', 'description', 'title', 'slug', 'release_date', 'clip'];
public function productvariations() {
return $this->hasMany("ProductVariations");
}
public function variations() {
$variations = $this->productvariations()->get();
return $variations;
}
public function genres() {
return $this->belongsToMany('GenreTypes', 'product_genretypes', 'product_id', 'genretype_id')->withTimestamps();
}
}
I didn't quite understand the issue so I will just refer to this bit:
// error below
$products = Product::with(array('ProductVariations', 'GenreTypes'))
->where('genre', '=', $genre->id)->get();
I believe you cannot use a where clause this way on a 'with' array. I am not 100% sure if this works when you have more than one parameter in your with method but this is worth trying:
$products = \Product::with(array('ProductVariations', 'GenreTypes' => function ($query)
{
// Note I think you also had a mistake in your column name
// (genre should be genretype_id?)
// Also ... not sure how you are getting the $genre->id variable
// as I cannot see the the $genre object / collection in this method you provided !
$query->where('genretype_id', '=', $genre->id);
}))->get();
Let us know how you get on.

Ordering of nested relation by attribute with laravel

Hi there i'm trying to sort a collection by attribute of the relation.
This is my model
class Song extends \Eloquent {
protected $fillable = ['title', 'year'];
public function artist(){
return $this->hasOne('Artist','id', 'artist_id');
}
}
class SongDance extends \Eloquent {
protected $table = 'song_dances';
protected $fillable = ['rating'];
public function dance(){
return $this->belongsTo('Dance', 'dance_id');
}
public function song(){
return $this->belongsTo('Song', 'song_id');
}
}
class Dance extends \Eloquent {
protected $fillable = ['name'];
public function song_dances(){
return $this->hasMany('SongDance','dance_id','id');
}
public function songs(){
return $this->belongsToMany('Song', 'song_dances', 'dance_id', 'song_id');
}
}
this is how far i'm by now:
$dance = Dance::find(1);
$songs = $dance->songs()
->with('artist')->whereHas('artist', function ($query) {
$query->where('urlName','LIKE','%robbie%');})
->where('song_dances.rating', '=', $rating)
->orderBy('songs.title','asc')
->where('songs.year', '=', 2012)
->get();
Yeah i just could add a ->sortBy('artist.name'); to the query, but he result-collection can be quite big (about 6000 items) therefore i would prefer a databased sorting.
is there a possibility to do this?
Since Eloquent's relations are all queried separately (not with JOINs), there's no way to achieve what you want in the database layer. The only thing you can do is sort the collection in your app code, which you've already dismissed.
If you feel you must sort it in the database then you should write your own join queries instead of relying on Eloquent's relations.

Laravel 4 eloquent - Getting the id of self based on constraints of parents

I need to get the id of a row based on the constraints of the parents. I would like to do this using eloquent and keep it elegant. Some things to note when this process starts:
I have - country_code(2 digit iso), lang_code(2 digit abbreviation for language)
i need - country_id, lang_id (primary keys)
so i can get - market_id (needed for last query)
I am able to retrieve the data I need with the following, sorry for the naming of the variables (client had weird names):
// Only receive desired inputs
$input_get = Input::only('marketCode','langCode');
// Need the country based on the "marketCode"
$countryId = Country::where('code',$input_get['marketCode'])->pluck('id');
// Get the lang_id from "langCode"
$languageId = Language::where('lang_abbr',$input_get['langCode'])->pluck('lang_id');
// Get the market_id from country_id and lang_id
$marketId = Market::where('country_id', $countryId)
->where('lang_id',$languageId)->pluck('market_id');
// Get All Market Translations for this market
$marketTranslation = MarketTranslation::where('market_id',$marketId)->lists('ml_val','ml_key');
I've tried the following, but this only eager loads the country and language based on the constraints. Eager Loading only seems to be helpful if the market_id is already known.
class Market extends Eloquent {
protected $primaryKey = 'market_id';
public function country() {
return $this->belongsTo('Country');
}
public function language(){
return $this->belongsTo('Language','lang_id');
}
}
$markets = Market::with(array(
'country' => function($query){
$query->where('code','EE');
},
'language'=> function($query){
$query->where('lang_abbr','et');
}
))->get();
You'd have to use joins in order to do that.
$market = Market::join( 'countries', 'countries.id', '=', 'markets.country_id' )
->join( 'languages', 'languages.id', '=', 'markets.language_id' )
->where( 'countries.code', '=', 'EE' )
->where( 'languages.lang_abbr', 'et' )
->first();
echo $market->id;
If this is something that happens frequently then I'd probably add a static method to the Market model.
// in class Market
public static function lookup_id( $country_code, $language_abbreviation ) { ... }
// then later
$market_id = Market::lookup_id( 'EE', 'et' );
So after looking at the relationships, I was able to get it working without the use of manual joins or queries, just the relationships defined in the ORM. It seems correct, in that it uses eager loading and filters the data needed in the collection.
// Get A country object that contains a collection of all markets that use this country code
$country = Country::getCountryByCountryCode('EE');
// Filter out the market in the collection that uses the language specified by langCode
$market = $country->markets->filter(function($market) {
if ($market->language->lang_abbr == 'et') {
return $market;
}
});
// Get the market_id from the market object
$marketId = $market->first()->market_id;
Where the models and relationships look like this:
class Country extends Eloquent {
public function markets() {
return $this->hasMany('Market')->with('language');
}
public static function getCountryByCountryCode($countryCode)
{
return Country::with('markets')->where('code',$countryCode)->first();
}
}
class Market extends Eloquent {
protected $primaryKey = 'market_id';
public function country() {
return $this->belongsTo('Country');
}
public function language(){
return $this->belongsTo('Language','lang_id');
}
}
class Language extends Eloquent {
protected $primaryKey = 'lang_id';
}

Categories