Laravel Controller - how to get Model's query object directly? - laravel

The below Controller method changes the query based on the flags which are activated.
public function index(Request $request)
{
$q = new ProductModel();
if($request->has('flag1')) {
$q = $q->includeFlag1();
}
if($request->has('flag2')) {
$q = $q->doFlag2();
}
if($request->has('flag3')) {
$q = $q->doFlagthing3();
}
return $q->paginate();
}
Most example code I've seen will call a where() from the beginning instead of creating a new Model instance and looks something like this:
$q = ProductModel::where('available', true);
if($request->has('flag1')) {
$q->includeFlag1();
}
But in my case based on the table fields it isn't possible for me to start from a where like this so it seems I must do $q = $q every time in every case... It's not neat, neither would doing something hacky like attempting to use a where true clause.
How can I clean either get the query object neatly from the beginning and use that or otherwise avoid having to do $q = $q inside each if()?

You can use query() and when() methods :
public function index(Request $request)
{
$query = ProductModel::query()
->when($request->has('flag1'), function ($q) {
$q->includeFlag1();
})
->when($request->has('flag2'), function ($q) {
$q->doFlag2();
})
->when($request->has('flag3'), function ($q) {
$q->doFlagthing3();
})
->paginate();
}
The official documentation is here: https://laravel.com/docs/9.x/queries#conditional-clauses
Sometimes you may want certain query clauses to apply to a query based on another condition. (...) The when method only executes the given closure when the first argument is true. If the first argument is false, the closure will not be executed.
You have also a more concise alternative using arrow functions (thanks #miken32's comment):
public function index(Request $request)
{
$query = ProductModel::query()
->when($request->has('flag1'), fn ($q) => $q->includeFlag1())
->when($request->has('flag2'), fn ($q) => $q->doFlag2())
->when($request->has('flag3'), fn ($q) => $q->doFlagthing3())
->paginate();
}

Related

Eloquent query : Retrieve the list of offices where the user possess all the desks, not just one (nested whereHas)

I want to retrieve all the offices ( with the desks eager loaded) but I only want offices where the user possess all the desks in the office
I have the following models and relationships between them :
I came up with the following query which seems to almost work :
<?php
Office::query()
->whereHas('desks', function ($query) {
$query->whereHas('possessedDesks', function ($query) {
$query->where('user_id', auth()->id);
});
})
->with(['desks'])
->get();
The current query seems to return a result where if a user own a single desk in the office then the office is returned in the query. Am I missing something ? Is there a way to be more strict in the whereHas to have some kind of and instead of a or
Thanks in advance for your help ;)
Edit :
Thanks to Tim Lewis's comment I tried this with not more result :
<?php
Office::query()
->withCount('desks')
->whereHas('desks', function ($query) {
$query
->whereHas('possessedDesks', function ($query) {
$query->where('user_id', auth()->id);
})
->has('possessedDesks', '=', 'desks_count');
})
->with(['desks'])
->get();
Edit 2 :
I managed to get exactly what I need, outside of an Eloquent query. The problem is still persist since I need it to be in an Eloquent query because I need this for a query string request (Search engine).
<?php
$offices = Office::query()
->with(['desks'])
->get();
$possessedDeskIds = auth()->user->with('possessedDesks.desk')->possessedDesks()->get()->pluck('desk.id');
$fullyOwnedOffices = [];
foreach($offices as $office) {
$officeDeskIds = $office->desks()->pluck('id');
$atLeastOneDeskIsNotPossessed = false;
foreach($officeDeskIds as $officeDesk) {
if ($possessedDeskIds->doesntContain($officeDesk)) {
$atLeastOneAromaIsNotPossessed = true;
break;
}
}
if (!$atLeastOneDeskIsNotPossessed) {
$fullyOwnedOffices[] = $office;
}
}
Edit 3 :
Ok, With the previous edit and the need to have some kind of one line query (for the query string of a search engine) I simplified the request since the nested whereHas where hard to make sense of.
It's not the prettiest way to do it, It add more query for the process, but with the code from the Edit2 I can generate an array of Ids of the Office where all the Desk are possessed by the user. With that I can just say that when this option is required in the search engine, I just select the ones my algorithm above gave me and no more logic in the query.
If some genius manage to find a way to optimize this query to add the logic back inside of it, I'll take it but for now it works as expected.
Thanks Tim for your help
<?php
class SearchEngineController extends Controller
{
public function index(Request $request) {
$officesWithAllDesksPossessed = collect([]);
if ($request->has('with_possessed_desks') && $request->input('with_possessed_desks')) {
$publicOffices = Office::query()
->isPublic()
->with(['desks'])
->get();
$possessedDeskIds = currentUser()
->possessedDesks()
->with('desk')
->get()
->pluck('desk.id');
foreach($publicOffices as $office) {
$publicOfficesDeskIds = $office->desks()->pluck('id');
$atLeastOneDeskIsNotPossessed = false;
foreach($publicOfficesDeskIds as $officeDesk) {
if ($possessedDeskIds->doesntContain($officeDesk)) {
$atLeastOneDeskIsNotPossessed = true;
break;
}
}
if (!$atLeastOneDeskIsNotPossessed) {
$officesWithAllDesksPossessed->push($office);
}
}
$officesWithAllDesksPossessed = $officesWithAllDesksPossessed->pluck('id');
}
return Inertia::render('Discover', [
'offices'=> OfficeResource::collection(
Office::query()
->isPublic()
->with(['desks'])
->when($request->input('search'), function ($query, $search) {
$query->where('name', 'like', "%{$search}%");
})
->when($request->input('with_possessed_desks'), function ($query, $active) use($officesWithAllDesksPossessed) {
if ($active === 'true') {
$query->whereIn('id', $officesWithAllDesksPossessed);
}
})
->paginate(10)
->withQueryString()
),
'filters' => $request->only(['search', 'with_possessed_desks']),
]);
}
}

Reset local scope

I am (trying) to use scopes to handle some "filtering" within my models.
I got two scopes on the model: verified and unverified. Unfortunately the verification is not a single field in the database, but a query like:
return $query->whereHas('profile', function ($q) {
$q->whereHas('invitations', function ($i) {
...
});
Now I got the following issue: If I scope like this
$verified_members = $members->verified();
and work with $verified_members everything works fine:
$verified_members->whereHas('car', function ($q) {
$q->where('stolen', true);
})->count();
but if I do something like:
$verified_members = $members->verified();
$unverified_members = $members->unverified();
$verified_members->whereHas('car', function ($q) {
$q->where('stolen', true);
})->count();
$unverified_members->whereHas('car', function ($q) {
$q->where('stolen', true);
})->count();
I get 0 as result for $unverified_members, as it seems like the scopes are being chained. If I remove $verified_members again, I am getting results for $unverified_members.

How to use whereHas method in Laravel scout,

I have been trying to search in both the name column and that's relationship. I have coded as below;
///
$posts = ContentForSearch::with("content_type","content_sub_type")->exclude($exclude)->user($user_id_filter)
->where('name', 'LIKE', "%{$search}%")
->orWhereHas('tags', function ($q) use ($search) {
$q->where('tag', 'LIKE', "%{$search}%");
})
->orderBy($sort, $order)
->paginate($limit);
I have set a searchable Model. I want to change with search method as below;
$posts = ContentForSearch::search($search)->orWhere(function ($query) use ($search) {
$query->whereHas('tags', function ($q) use ($search) {
$q->where('tag', 'LIKE', "%{$search}%");
});
})
->orderBy($sort, $order)
->paginate($limit)->load("content_type","content_sub_type");
Why isnt the code working ? How can i fix it ?
Model is:
class ContentForSearch extends Model{
use Searchable;
protected $table = 'content';
public function searchableAs()
{
return 'contents_index';
}
public function toSearchableArray()
{
$array = $this->toArray();
// Customize array...
return ["name"=> null];
}
public function content_type()
{
return $this->belongsTo(ContentType::class, 'content_type_id');
}
public function tags()
{
return $this->belongsToMany(ContentTag::class, 'content_content_tags', 'content_id', 'content_tags_id')->withTimestamps();
}
// * * *
The way fulltext search works with algolia and elastic, it seems weird that you want to preserve whereHas() relationship method. In my opinion it is an either or case, either you want sql relation search or you want full text, if you want full text, i would map all the tags at index building instead. So something like this would work.
public function toSearchableArray()
{
$customArray = $this->toArray();
$index = 1;
$this->tags->each(function(Tag $tag) use (&$customArray, &index) {
$customArray['tag' . $index++] = $tag->tag;
});
return $customArray;
}
Which will result in an object similar to this being indexed.
{
'name' => 'x',
'tag1' => 'comment',
'tag2' => 'post',
}
Now searching would provide a similar functionality.
ContentForSearch::search($search);

Orwhere has method does not allow null

enter image description hereI am trying to implement a many to many relationship search with 2 models.
i get input from multiple checkbox values and want to search for items that match A or B when there is an input of data.
I read this url and wrote the same logic.
https://laracasts.com/discuss/channels/laravel/many-to-many-relationship-with-2-pivot-table-data-search
public function search(Request $request)
{
$languages = $request->lang;
$fields = $request->field;
$agencies = Agency::with('languages')->with('specialized_fields')
->orWhereHas('languages', function($query) use ($languages) {
$query->whereIn('language_id', $languages);
})
->orWhereHas('specialized_fields', function($query) use ($fields) {
$query->whereIn('specialized_field_id', $fields);
})
->get();
dd($agencies);
}
i expected to achieve A or B search but instead I got this error.
Argument 1 passed to Illuminate\Database\Query\Builder::cleanBindings() must be of the type array, null given, called in /var/www/jtf/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php on line 907
it seems that it causes this error if either A or B is null, but why??? Does the OrWhereHas method work only when theres an input??
/added info/
my error message
my agency model
class Agency extends Model {
protected $guarded = [
'id'
];
public function languages(){
return $this->belongsToMany('App\Language');
}
public function specialized_fields(){
return $this->belongsToMany('App\SpecializedField');
}
public function region(){
return $this->hasOne('App\Region');
} }
I believe it's because either $languages or $fields is null.
Since ->whereIn() is expecting an array, but you're passing null.
You just need to make sure you're passing an array.
$languages = array_filter((array) $request->lang); // cast to array & remove null value
$fields = array_filter((array) $request->field);
$agencies = Agency::with('languages', 'specialized_fields')
->orWhereHas('languages', function($query) use ($languages) {
$query->whereIn('language_id', $languages);
})
->orWhereHas('specialized_fields', function($query) use ($fields) {
$query->whereIn('specialized_field_id', $fields);
})
->get();
I'm speculating that you started your where query chain with an orWhereHas() which may have caused the problem, try starting with whereHas() instead.
public function search(Request $request){
$languages = $request->lang;
$fields = $request->field;
$agencies = Agency::with('languages', 'specialized_fields') // you can get away by just using one with(), not needed but its cleaner this way
->whereHas('languages', function($query) use ($languages) { // previously orwherehas
$query->whereIn('language_id', $languages);
}) ->orWhereHas('specialized_fields', function($query) use ($fields) {
$query->whereIn('specialized_field_id', $fields);
})
->get();
dd($agencies);
}

Sort parents regarding child attribut using Eloquent

I have a model 'job' linked with the model 'job_translation' like that :
/**
* Get the translations for the job.
*/
public function translations()
{
return $this->hasMany('App\Models\JobTranslation');
}
I want to build a dynamic api route which can accept several parameters to filter ou sorter a query on the jobs. To do that I have this kind of controller (extract) :
public function index(Request $request)
{
$queryJob = Job::query();
// filter on the job short_name
if ($request->has('short_name')) {
$queryJob->where('short_name', 'ilike', '%'.$request->short_name.'%');
}
// filter on the translation
if ($request->has('translation')) {
$queryJob->whereHas('translations', function ($query) use ($request) {
$queryJob->where('internal_translation', 'ilike', '%'.$request->translation.'%');
});
}
// sort by the external translation
if ($request->has('order.external_translation')) {
// var_dump($request->input('order.external_translation'));
// var_dump($request->server('HTTP_ACCEPT_LANGUAGE'));
$queryJob->whereHas('translations', function ($query) use ($request) {
$query->where('language_id', $request->server('HTTP_ACCEPT_LANGUAGE'));
$query->orderBy('external_translation', 'asc');
});
/*
$queryJob->with(['translations' => function ($query) use ($request) {
$query->where('language_id', $request->server('HTTP_ACCEPT_LANGUAGE'));
$query->orderBy('external_translation', 'asc');
}]);
*/
}
// security for the qty of items per page
$qtyItemsPerPage = 15;
if ($request->has('qtyItemsPerPage')) {
if (is_numeric($request->qtyItemsPerPage) && $request->qtyItemsPerPage <= 50) {
$qtyItemsPerPage = $request->qtyItemsPerPage;
}
}
$jobs = $queryJob->paginate($qtyItemsPerPage);
return JobResource::collection($jobs);
My problem is for the sort condition (the others are OK). When I use this kind of url:
jobs?order[external_translation]=asc
I tried a lot of things without success (commented in the code), the result is never sorted like I want. I think my problem is in the relation between the two models.
So how to sort the parents regarding an attribute of their children, using Eloquent?
You cannot sort nested relationships with the query builder alone. Use the Collection's sortBy method instead.

Resources