Orwhere has method does not allow null - laravel

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);
}

Related

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

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();
}

Eloquent - Get Model with relationship as boolean

My Model has the following one-to-many relationship
class Comment extends Model
{
public function likes(): HasMany
{
return $this->hasMany(CommentLike::class);
}
}
In my Controller I want to get all Comments alongside with a boolean value which indicates if the current user exists in the likes table
$result = Comment::with(['user', 'likes' => function($q) use($user){
$q->where('user_id', $user->id);
}])
->withCount('likes')
->where('post_id', $postId)
->get();
Currently the above query will return the correct result alongside with a row from the likes table if the user is found.
I'm looking for a proper way to return a boolean value to indicate if the user has liked the comment instead.
First, you can find how many likes does user make to the comment, in this way.
$likes = Comment::whereHas(['likes' => function($q) use ($user){
$q->where('user_id', $user->id);
})->where('post_id', $postId)->count();
Then using $likes variable value, you can conditionally create a boolean value and assign to new variable or same $likes variable.
$hasLike = $likes ? true : false;
You can make use of withExists() method. It's not one of the best documented ones, but I think it's quite simple.
In your example:
$result = Comment::with(['user', 'likes' => function($q) use($user){
$q->where('user_id', $user->id);
}])
->withCount('likes')
->where('post_id', $postId)
->get();
Consider changing it to:
$result = Comment::with(['user', 'likes'])
->withCount('likes')
->withExists(['likes' => function ($query) use ($user) {
$query->where('user_id', $user->id);
}])
->where('post_id', $postId)
->get();
Result model will contain additional bool property likes_exists where true means that $user liked the comment and false means that they didn't do it.
Here is how I achieved this
in my model.php file
public function likedByCurrentUser(){
return $this->hasOne(Like::class, "tweet_id", "id")
->where("user_id", auth()->user()->id);
}
likedByCurrentUser method will either return null if no rows match else it will return an eloquent object. So in the controller method we can do something like below to get a boolean.
In the controller method by using withExists
$tweets = TweetModel::with(["user", "likes", "comments.user"])
->withExists([
"likedByCurrentUser as liked_by_current_user" => function($result){
return $result === null;
}
])
->orderBy("updated_at", "desc")
->get();
return response()->json([
"tweets" => $tweets
]);

retrieving related field in controller index function gives error but ok in show function

I define the relation in Company table (where I added the plural):
protected $table = 'companies';
public function country() {
return $this->belongsTo(Country::class, "country_id")->withDefault(['country' => 'unknown']);
}
I also did the same in the Country model.
When I use the following code in the controller show function it works:
public function show (Company $company) {
$company->country = $company->country()->pluck('country');
But if I use the same code in the index function in a loop, I get an error "Call to undefined method stdClass::country()":
public function index (Company $company) {
if (request('tag')) {
$companies = Tag::where('name',request('tag'))->firstOrFail()->companies;
$companies->page_title = "Businesses matching tag '".request('tag')."'";
} else {
$companies = DB::table('companies')
->where([['is_active', '=', '1']])
->orderBy('company')
->get();
}
foreach($companies as $key => $thisCompany) {
...
$thisCompany->country = $company->country()->pluck('country');
}
I guess it is due to the fact that $company is created in the loop and not passed through the function like in show(Company $company)... but I could not find how to solve this issue... so help will be appreciated.
I have added the model in the argument of the function and change the name of the $company variable in the loop by $thisCompany to avoid confusion with the $company model.
No error but the field $country->country does not contain the name of the country but "Illuminate\Support\Collection {#443 …1}"
Why is it so complicated? Please help...
Paul, sorry, I think I didn't explain myself well in the comments.
What I meant by "What about if you change DB::table('companies') by Company?", is to stop using DB Query Builder to use the Eloquent Company model.
Specifically in this segment of code:
$companies = DB::table('companies')
->where([['is_active', '=', '1']])
->orderBy('company')
->get();
So, it could be:
$companies = Company::where([['is_active', '=', '1']])
->orderBy('company')
->get();
The explanation is that in the first way (with DB Query Builder), the query will return a collection of generic objects (the PHP stdClass object) that do not know anything about the Company and its relationships.
On the other hand, if you use the Eloquent model Company, it will return a collection of Company objects, which do know about relationships, and specifically the relationship that you have defined as country.
Then, when you loop over the collection, you will be able to access the country relation of each Company object:
foreach($companies as $key => $company) {
//...
$company->country = $company->country()->pluck('country');
}
Finally, your code could looks like:
public function index () {
if (request('tag')) {
$companies = Tag::where('name',request('tag'))->firstOrFail()->companies;
$companies->page_title = "Businesses matching tag '".request('tag')."'";
} else {
$companies = Company::where([['is_active', '=', '1']])
->orderBy('company')
->get();
}
foreach($companies as $key => $company) {
//...
$company->country = $company->country()->pluck('country');
}
//...
}

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);

Laravel Error: Trying to get property of non-object

I am getting an error
Trying to get property of non-object (View: C:\xampp\htdocs\laravel\proj\resources\views\mycases.blade.php)
I have defined a relationship between two models Ccase and Hiring.
public function hirings()
{
return $this -> hasMany('App\Hiring', 'case_ID')->orderBy('id','desc');
}
and paginating the results using a method below
public function getHiringsPaginateAttribute($perPage)
{
return $this->hirings()->paginate($perPage);
}
The other model 'Hiring' has a method to define relationship with Ccase as follows:
public function ccase()
{
return $this->belongsTo('App\Ccase', 'id');
}
In my controller, I have following code:
if(isset($search_term))
{
$search_term = preg_replace('/\s+/', ' ', $search_term);
$search_term = trim($search_term);
if (strlen($search_term) > 0 && strlen(trim($search_term)) == 0)
$search_term = NULL;
$search_terms = explode(' ',$search_term);
$fields = array('id', 'title', 'case');
$hirings = $hirings->whereHas('ccase', function($q) use ($search_terms, $fields){
foreach ($search_terms as $term)
{
foreach ($fields as $field)
{
$q->orWhere($field, 'LIKE', '%'. $term .'%');
}
}
});
}
$hirings = $hirings->getHiringsPaginateAttribute($results_per_page);
In mycases.blade.php, my code is
{{$hiring->ccase->id}}
This line is throwing the above said error while the output of {{$hiring->ccase}} is:
{"id":1,"case":"HI this is a sample case i am putting just for test.","created_at":"2015-02-22 11:54:09","updated_at":"2015-02-22 11:54:09"}
What might be wrong with the code?
Unfortunately, you can't use related models in views. Here's the detailed explanation why.
Your case can be solved by specifying the name of the associated column on the parent table:
return $this->belongsTo('App\Ccase', 'ccaseId', 'id');
In model Hiring, it will be like
public function ccase()
{
return $this->belongsTo('App\Ccase', 'case_ID', 'id');
}
And now in view use it like this:
{{ $hiring->ccase->id }}
Not sure, but i think that you could use relations in view, you should use an eager loading in controller where you are quering for $hirings, just add a:
with(['hirings.ccase'])
Could you please provide a peace of code from controller where you make a query to Hiring model for clear?

Resources