many to many and one to many relationship in Eloquent - laravel

I have three tables: "courriers" which is connected with "reponses" by one to-many relationship (1 courrier could have many reponses), and "structures" which is connected with "courriers" by many-to-many relationship
I want to find the courriers which are connected to a certain structure and doesn't have a reponse in table "reponses".
For example, for the structures "DMO" that has 1 as identifiant in "structures", I wish find the courriers that belongs to this structure and doesn't appear in "reponses".
Am using Laravel 8, I want to do this with Eloquent ORM.
Am trying this
public function dmoDG()
{
$structure = Structure::find(1);
$cou = $structure->courriers;
$courr = $cou->where('repondre','=',1)-
>where('dmo_sec','<>',NULL);
$courriers = $courr->doesntHave('reponses')->get();
return view("DG\dmoDG", compact('courriers'));
}
Method Illuminate\Database\Eloquent\Collection::doesntHave does not exist.

When you use $model->relation, it fetches all related records and returns them as a Collection.
If you want to use query builder on a relation, you need to use it as a method: $model->relation()
So, if you access your relation as a property, you got Collection.
But if you access your relation as a method, you got query builder and add your where clauses on it.
In your example;
public function dmoDG()
{
$structure = Structure::find(1);
// $cou = $structure->courriers; // for using without parentheses you got a collection, not a query builder
$cou = $structure->courriers(); // now you will have a query builder and Where clauses will work on this
$courr = $cou->where('repondre', '=', 1)->where('dmo_sec', '<>', NULL);
$courriers = $courr->doesntHave('reponses')->get();
return view("DG\dmoDG", compact('courriers'));
}
Actually you can pipe them to one liner:
public function dmoDG()
{
$structure = Structure::find(1);
$courriers = $structure->courriers()->where('repondre', '=', 1)->where('dmo_sec', '<>', NULL)->doesntHave('reponses')->get();
return view("DG\dmoDG", compact('courriers'));
}
Make sure your relations and column names correctly specified.

Related

Laravel / Eloquent: Is it possible to select all child model data without setting a parent?

I have various parent/child relationships, drilling down a few levels. What I want to know is if its possible to do something like this:
$student = Student::find(1);
$student->bursaries()->enrolments()->courses()->where('course','LIKE','%B%');
(With the end goal of selecting the course which is like '%B%'), or if I would have to instead use the DB Query builder with joins?
Models / Relationships
Student:
public function bursaries() {
return $this->hasMany('App\StudentBursary');
}
StudentBursary:
public function enrolments() {
return $this->hasMany('App\StudentBursaryEnrolment');
}
If what you want is to query all courses, from all enrollments, from all bursaries, from a students, then, unfortunately, you are one table too many from getting by with the Has Many Through relationship, because it supports only 3 tables.
Online, you'll find packages that you can import / or answers that you can follow to provide you more though of solutions, for example:
1) How to use Laravel's hasManyThrough across 4 tables
2) https://github.com/staudenmeir/eloquent-has-many-deep
Anyhow, bellow's something you can do to achieve that with Laravel alone:
// Eager loads bursaries, enrolments and courses, but, condition only courses.
$student = Student::with(['bursaries.enrolments.courses' => function($query) {
$query->where('course','LIKE','%B%');
}])->find(1);
$enrolments = collect();
foreach($student->bursaries as $bursary) {
$enrolments = $enrolments->merge($bursary->enrolments);
}
$courses = collect();
foreach ($enrolments as $enrolment) {
$courses = $courses->merge($enrolment->courses);
}
When you do $student->bursaries() instead of $student->bursaries, it returns a query builder instead of relationship map. So to go to enrolments() from bursaries() you need to do a bursaries()->get(). It should look like this.
$student->bursaries()->get()[0]->enrolments(), added the [0] because im using get(), you can use first() to avoid the [0]
$student->bursaries()->first()->enrolments()
But I'm not sure if it will suffice your requirement or not.

Laravel get result from query in side query by Eloquent in one object

I have two tables:
main_presentations
so here i have "id" and "isEnabled";
child_presentations
And here i have "id" , "isEnabled" and "idParent";
I want to select in one object this is my code:
public function MainSlider(MainPresentation $MainPresentations, ChildPresentation $ChildPresentations)
{
$MainPresentations = MainPresentation::where('isEnabled', true)->get();
foreach ($MainPresentations as $MainPresentation) {
$AnArray[] = ChildPresentation::where([
['idParent', $MainPresentation['id']],
['isEnabled', true]
])->get();
}
return $AnArray;
}
but this is the result:
enter image description here
What you are doing is executing a query per result, which can be ineffective when it starts getting bigger.
You can:
Use querybuilder
As it follows, you just build a query starting on ChildPresentation, set a relation to MainPresentation table by id and get the collection
public function MainSlider()
{
$childPresentations = ChildPresentation::join('main_presentations','main_presentations.id','child_presentations.idParent')
->where('child_presentations.isEnabled', true)->where('main_presentations.isEnabled', true)->get();
return $childPresentations;
}
If you want all the MainPresentations with their respective ChildPresentations, only the enables ones.
You can take advantage of Laravel relationships and eager loading.
https://laravel.com/docs/5.6/eloquent-relationships
First, set the relationships in your MainPresentation model
In MainPresentation.php
public function childPresentation {
return $this->hasMany('App\ChildPresentation', 'idParent', 'id');
}
Your MainSlider function would be:
(Btw, no idea why you're receiving two arguments if you're overriding them but doesn't matter)
public function MainSlider() {
$mainPresentations = MainPresentation::with(['childPresentations' => function ($advancedWith) {
child_presentation.isEnabled is true
$advancedWith->where('isEnabled', true);
}])
->where('isEnabled', true)->get()->toArray();
return $mainPresentations;
}
This will return an array of MainPresentations that contain an array of child_presentations, with all their childs.
This translates to two queries:
Select * from main_presentations where isEnabled = true;
Select * from child_presentations where isEnabled= true and id in (in the first query);
Laravel then does background work to create the structure you desire when you write ->toArray()
Note: If you have a $visible array in your MainPresentation model, be sure to add: 'childPresentation' to it, otherwise the toArray will not agregage the childs to the parent.
Second note: I advise following some standards whenever you're writing code, usually functions are named camelCase and variables are camelCase.

filtering making subqueries

I'm on a Laravel project and I'm trying to do a filter:
I have the following tables: pubs, tapps, pub_tapps(pub_id, tapp_id)
I would like to filter an item (pubs, in this case) by some characteristics that are stored in a separate table (tapps, in this case), and both are related by pub_tapps.
An array of chosen tapps id's is given to me, then I want to query on pub_tapps to know the pub_id's that satisfy the conditions.
For example, I'm trying to filter by WIFI (tapp_id=5) and CreditCard (tapp_id=16). If we're strict the result must be pub_id=57 (tapp_id=5,8), if not pub_id=57 and pub_id=16 (tapp_id=5,7,8).
Any ideas to do something like this?Note that I'm triying to filter with different values of a sema field :S
Thanks a lot for your attention.
as long you An array of chosen tapps $ids
choose tapps that satisfy your conditions
$tapIds = Tapps::whereIn('id',$ids)->where('condition',true)->pluck('id')->toArray();
then get The Ids of the pubs in the connected Table
$pubIds = PubTapps::whereIn('tapp_id',$tapIds)->pluck('pub_id')->toArray();
then get the pubs
$Pubs = Pubs::whereIn('id',$pubIds)->get();
Many to Many
class Pubbs extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function tapps()
{
//All but the first argument here are optional so long as you follow Laravel naming conventions
return $this->belongsToMany(Tapps::class, 'pub_tapps', 'pub_id', 'tapp_id', 'id', 'id');
}
}
whereHas
$tappIds = [5, 16];
$strictMode = true;
$pubs = app(App\Models\Pubbs::class)->whereHas('tapps', function (Illuminate\Database\Eloquent\Builder $query) use ($tappIds, $strictMode) {
$tappKey = $query->getModel()->getKeyName();
if (!$strictMode) {
return $query->whereIn($tappKey, $tappIds);
}
foreach ($tappIds as $tappId) {
$query->where($tappKey, $tappId)
}
})->get();

How to fetch two related objects in Laravel (Eloquent) with one SQL query

I am trying to get two related objects in Laravel using eager loading as per documentation.
https://laravel.com/docs/5.4/eloquent-relationships#eager-loading
My models are:
class Lead extends Model {
public function session() {
return $this->hasOne('App\LeadSession');
}
}
class LeadSession extends Model {
public function lead() {
return $this->belongsTo('App\Lead');
}
}
I want to get both objects with one SQL query. Basically I want to execute:
select * from lead_sessions as s
inner join lead as l
on l.id = s.lead_id
where s.token = '$token';
and then be able to access both the LeadSession and Lead objects. Here is the php code I am trying:
$lead = Lead::with(['session' => function ($q) use ($token) {
$q->where('token','=',$token);
}])->firstOrFail();
print($lead->session->id);
I have also tried:
$lead = Lead::whereHas('session', function($q) use ($token) {
$q->where('token','=',$token);
})->firstOrFail();
print($lead->session->id);
and
$session = LeadSession::with('lead')->where('token',$token)->firstOrFail();
print($session->lead->id);
In all three cases I get two queries executed, one for the leads table, and another for the lead_sessions table.
Is such a thing possible in Eloquent? In my view it should be a standard ORM operation, but for some reason I am struggling a whole day with it.
I don't want to use the Query Builder because I want to use the Eloquent objects and their functions afterwards.
I am coming from Python and Django and I want to replicate the behavior of select_related function in Django.
Try this and see if it makes more than one query
$session = LeadSession::join('leads', 'leads.id', '=', 'lead_sessions.lead_id')
->where('token',$token)
->firstOrFail();
I hope it only runs a single query. I didnt test this. Not sure if you have to add a select() to pick the columns. But yeah, try this first.
Updates
Just adding how to use both session and lead data. Try a select and specify the data you need. The reason being that if both tables have similar columns like 'id', one of them will be overwritten. So you have to alias your select like
$session = LeadSession::join('leads', 'leads.id', '=', 'lead_sessions.lead_id')
->where('token',$token)
->select(
'lead_sessions.*',
'leads.id as lead_id',
'leads.name',
'leads.more_stuff'
)
->firstOrFail();
Now all this data belongs to $session variable. For testing you were doing
print($lead->session->id);
//becomes
print($session->lead_id); //we aliased this in the query

Doctrine Query Builder - How to select a many-to-many relationship?

I have a form in Symfony2 that needs to have an entity choice field filled with a collection of Players in a particular tournament. I create the form Type and pass the tournament ID to the query_builder property of the correct field. In the entity repository I have this method:
public function allPlayersInTournamentQuery($tournament_id)
{
$repo = $this->getEntityManager()->getRepository('GameBundle:Tournament');
$tournament = $repo->find($tournament_id);
$players = $tournament->getPlayers();
$playersIds = array();
foreach ($players as $player) {
$playersIds[] = $player->getId();
}
$playersQuery = $this->createQueryBuilder('p')
->in('p.id', $playersIds)
->orderBy('p.real_name', 'ASC');
return $playersQuery;
}
The function in() does not exist in the query builder. I hope the method shows what I am trying to do. Im tying to return the query builder that selects the correct players found in the given tournament.
How can I achieve this?
Thanks!
You can use the helper methods provided by the query builder $playersQuery->expr()->in('p.id', $playersIds)
Your query will be something like that:
$playersQuery = $this->createQueryBuilder('p');
$playersQuery->where($playersQuery->expr()->in('p.id', $playersIds))
->orderBy('p.real_name', 'ASC');
More information about the helper methods here

Resources