I want to write a search query on Laravel on the basis of either "keyword" or "experience" or "location" search query should run if any of these variable exists.
I am making an Ajax call to achieve this
jobsController.php
public function homepageSearch() {
$_POST = json_decode(file_get_contents('php://input'), true);
$jobs = Jobs::latest('created_at')->search()->get();
echo $jobs;
}
Model jobs.php
class Jobs extends Model {
public function scopeSearch($query) {
$query->where('job_title', 'like', '%'.$_POST['keyword'].'%')
->orWhere('job_description', 'like', '%'.$_POST['keyword'].'%');
if(isset($_POST['keyword'])) {
$query->where('job_title', 'like', '%'.$_POST['keyword'].'%')
->orWhere('job_description', 'like', '%'.$_POST['keyword'].'%');
}
if(isset($_POST['experience'])) {
$query->where('min_exp', $_POST['experience'])
->orWhere('max_exp', $_POST['experience']);
}
if(isset($_POST['city'])) {
$query->where('job_location','like','%'.$_POST['city'].'%');
}
}
}
I want to search on the basis of either keyword or city or experience is this correct way to achieve this in laravel?
I am new to Laravel. Can you suggest me with this.
class Job extends Model {
public function scopeSearch($query, $keyword, $experience, $city) {
$query->where(function ($q) {
$q->where('job_title', 'like', '%'.$_POST['keyword'].'%')
->orWhere('job_description', 'like', '%'.$_POST['keyword'].'%');
});
if(isset($keyword)) {
$query->where(function ($q) {
$q->where('job_title', 'like', '%'.$_POST['keyword'].'%')
->orWhere('job_description', 'like', '%'.$_POST['keyword'].'%');
});
}
if(isset($experience)) {
$query->where(function($q) {
$q->where('min_exp', $_POST['experience'])
->orWhere('max_exp', $_POST['experience']);
});
}
if(isset($city)) {
$query->where('job_location','like','%'.$_POST['city'].'%');
}
return $query;
}
}
Call from your controller using the following:
Job::search($request->input('keyword'),
$request->input('experience'), $request->input('city'));
A few observations/suggestions:
Where chaining needs to be correct. When you say $query->where(..a..)->orWhere(..b..)->where(..c..)->orWhere(..d..) it will evaluate to: ((a && c) || b || d). Where you intended ((a || b) && (c || d)). This is why you need to use closures like I have above using parameter grouping
Avoid using $_POST, use the Request object instead as Laravel does quite a lot of work for you when you use $request
Avoid calling your request object from the model. It's not the model's responsibility to check request/post variables, it's your controller's responsibility to do so. Use dynamic scopes instead to segregate the responsibilities
You need to return the query object in scopes
A model is one entity. So "a job" is a model not "jobs". So I renamed the Jobs class to Job :)
Related
I have these 2 linked models: jobs and job_translations. A job have many translations. So in my job model, there is :
/**
* Get the translations for the job.
*/
public function translations()
{
return $this->hasMany('App\Models\JobTranslation');
}
In my controller, I want to build a query dynamically like that :
$query = Job::query();
if ($request->has('translation')) {
$query->translations()->where('external_translation', 'ilike', '%'.$request->translation.'%');
}
$jobs = $query->paginate(10);
I have this error :
Call to undefined method Illuminate\Database\Eloquent\Builder::translations()
Is it possible to do such a dynamic query with Eloquent?
Yes, it is possible. What you are looking for is whereHas('translations', $callback) instead of translations():
$query = Job::query();
if ($request->has('translation')) {
$query->whereHas('translations', function ($query) use ($request) {
$query->where('external_translation', 'ilike', '%'.$request->translation.'%');
});
}
$jobs = $query->paginate(10);
Your query can be improved further by using when($condition, $callback) instead of an if:
$jobs = Job::query()
->when($request->translation, function ($query, $translation) {
$query->whereHas('translations', function ($query) use ($translation) {
$query->where('external_translation', 'ilike', "%{$translation}%");
});
})
->paginate(10);
the issue you should do eager loading to detected on next chained query like this:
$query = Job::with('translations')->query();
I want to create dynamic filters.
for example I want to create this code
$Contact = Contact::whereHas('User', function ($query) use ($searchString) {
$query->where('name', 'like', '%Jhone%')->orwhere('family', '<>' . 'Doe');
})->whereHas('Account', function ($query) use ($searchString) {
$query->where('account_name', '=' , 'test_account' )->orwhere('account_city', 'like', '%test_city%');
})->get();
and all of parameters is variable
name,like,%Jhone%,family,<>,Doe,.....
and I want to pass variables to function and function create above query.
I assume that the relationship functions within your Contact, User and Account models are written in camelCase and not PascalCase like your example shows.
public function getContacts(Request $request)
{
return Contact::when($request->get('username'), function ($query, $val) use ($request) {
$query->whereHas('user', function ($q) use ($val, $request) {
$q->where('name', 'like', '%'.$val.'%');
if ($request->has('familyname')) {
$q->orWhere('family', '<>', $request->get('familyname'));
}
});
})
->when($request->get('accountname'), function ($query, $val) use ($request) {
$query->whereHas('account', function ($q) use ($val, $request) {
$q->where('account_name', $val);
if ($request->has('city')) {
$q->orWhere('account_city', 'like', '%'.$request->get('city').'%');
}
});
})
->get();
}
This function will return all contacts when no GET parameters are given on the request. If a parameter for username is present, it will only return contacts where a user with the given name exists for. If furthermore a familyname parameter is present, it will give contacts with a user that has a matching username or a familyname different from the one given. The very same applies to the account, accountname and city.
In particular, there are two things interesting about this example:
The when($value, $callback) function can be used to build very dynamic queries which only execute the $callback when $value is true. If you use $request->get('something') and something is not available as parameter, the function will return null and the callback is not executed. The callback itself has the form function ($query, $value) { ... }, where $value is the variable you passed to when() as first parameter.
Using $request->has('something') inside the query builder functions to dynamically build constraints on the query is an alternative to when(). I only added it for the purpose of demonstration - in general I'd recomment sticking to one style.
If you would extend on the example, you could also build highly dynamic queries where not only the variable content like Doe for the family name is given as parameters, but also the comparator like =, <> or like. But extending further on this topic is too much for this answer and there are already tutorials about this topic available anyway.
Edit: here an example for a dynamic query with more detailed input
Expected input (slightly different than your request because yours cannot work):
$filters = [
'user' => [
['name','like','%Jhone%'],
['family','<>','Doe'],
],
'account' => [
['account_name','=','test_account'],
['account_city','like','%test_city%'],
]
];
And the function:
public function getContacts(Request $request, array $filters)
{
$query = Contact::query();
foreach ($filters as $key => $constraints) {
$query->whereHas($key, function ($q) use ($constraints) {
if (count($constraints) > 0) {
$q->where($constraints[0][0], $constraints[0][1], $constraints[0][2]);
}
for ($i = 1; $i < count($constraints); $i++) {
$q->orWhere($constraints[$i][0], $constraints[$i][1], $constraints[$i][2]);
}
});
}
return $query->get();
}
This will always use OR for multiple constraints and not AND. Using AND and OR mixed would require a lot more sophisticated system.
I'm using Laravel 5.4
Working code
$cityWithEvents = City::with(['events' => function ($q) {
$q->whereDate('start_time', Carbon::today('America/Montreal'))->orwhereBetween('start_time', [Carbon::today('America/Montreal'), Carbon::tomorrow('America/Montreal')->addHours(4)]);
}])->where('active', 1)->get()->keyBy('id');
Not working code
$cityWithEvents = City::with('todayEventsWithAfterHoursIncluded')
->where('active', 1)
->get()
->keyBy('id');
City model
public function events() {
return $this->hasManyThrough('App\Event', 'App\Venue');
}
public function todayEventsWithAfterHoursIncluded () {
return $this->events()
->whereDate('start_time', Carbon::today('America/Montreal'))
->orwhereBetween('start_time', [
Carbon::today('America/Montreal'),
Carbon::tomorrow('America/Montreal')->addHours(4)
]);
}
Questions
When trying to create a scope method the query gives me different result. I can't see why and what should I change
I've only used scopes a few times, but never within a ->with() clause. On your City model, create a new scope:
public function scopeTodayEventsWithAfterHoursIncluded($query){
return $query->with(["events" => function($subQuery){
$subQuery->whereDate('start_time', Carbon::today('America/Montreal'))->orWhereBetween('start_time', [Carbon::today('America/Montreal'), Carbon::tomorrow('America/Montreal')->addHours(4)]);
});
}
Then, on your City query, add it as a scope function:
$cityWithEvents = City->where('active', 1)
->todayEventsWithAfterHoursIncluded()
->get();
I think the way you are using it requires that your Event model has the scope on it, as you're technically calling with("events") on your base query and your scoped one.
Let me know if this changes you results.
If you do the query, you should do it like this:
$cityWithEvents = City::withTodayEventsWithAfterHoursIncluded()
->where('active', 1)
->get()
->keyBy('id');
You scope in you model should look like this:
public function scopeWithTodayEventsWithAfterHoursIncluded ($query)
{
return $query
->with(['events' => function ($q) {$q
->whereDate('start_time', Carbon::today('America/Montreal'))
->orwhereBetween('start_time', [
Carbon::today('America/Montreal'),
Carbon::tomorrow('America/Montreal')->addHours(4)
]);
}]);
}
Now it should be equal.
I have been building queries and repeating code, is there a way to build this into the eloquent model?
I have a model Transaction where I am selecting specific currencies. How can I add this into the model? Is there a way of changing this:
Transaction::select('*')->where('currency', '=', 'GBP')
So that I can do this:
Transaction::select('*')->currency('GBP')
Then in the model it adds onto the query somehow. I've tried to create Transaction::currency but it didn't work. This is just an example and I plan on adding a few selectors to keep the code clean.
class Transaction extends Model
{
protected $table = 'transactions';
public function currency($query, $currency) {
return $query->where('currency', '=', $currency);
}
}
Laravel has such thing called Query Scopes. It allows you to do exactly what you want. You just need to prefix your currency() method with scope keyword like this:
class Transaction extends Model
{
protected $table = 'transactions';
public function scopeCurrency($query, $currency) {
return $query->where('currency', '=', $currency);
}
}
Then you can do this Transaction::select('*')->currency('GBP')
Read more about scopes here
you are almost done,you have to write currency method as query scope.
public function scopeCurrency($query, $currency) {
return $query->where('currency', '=', $currency);
}
after doing that you can use scope like this
Transaction::select('*')->currency('GBP')
For more details go here https://laravel.com/docs/5.2/eloquent#local-scopes
You can do this by using scope.
Add these code to Transaction.php file
public function scopeCustom($query, $column, $exp, $value)
{
return $query->where('votes', $exp, $value); // ('currency', '=', 'GBP')
}
Now use this scope as like
Transaction::select('*')->custom('currency', '=', 'GBP');
Transaction::select('*')->custom('amount', '>', 1000);
I have two models, App\Song (belongsTo App\Host) and App\Host (hasMany App\Song).
I have the following query in my Controller:
$songs = Song::whereHas('host', function($query) {
$query->where('skip_threshold', '>', \DB::raw('songs.attempts'))
->where('active', 1);
})
->whereNull('downloaded')
->get();
For reusability I would like to turn into a query scope(s).
I'm quite new to Eloquent so I'm not sure this is the correct way to do this being that its two Models as its not returning any results (where there should be).
Song.php
public function scopeEligable($query)
{
$query->where('skip_threshold', '>', \DB::raw('songs.attempts'));
}
public function scopeActiveHost($query)
{
$query->where('active', 1);
}
public function scopeInDownloadQueue($query)
{
$query->whereNull('downloaded');
}
You should put scopes into Models they belong to. Looking at your initial query scopes scopeEligable and scopeActiveHost belongs to Host model, so you should move them into Host model and then you'll be able to use your query using scopes like this:
$songs = Song::whereHas('host', function($query) {
$query->eligable()->activeHost();
})->inDownloadedQueue()->get();
and as already pointed in comment you should add return to each scope so they could be used as they intended.
EDIT
If you would like to make using it shorter, you could create new relationship in Song model:
public function activeHost()
{
return $this->belongsTo(Host:class)->eligable()->activeHost();
}
so now, you could write:
$songs = Song::whereHas('activeHost')->inDownloadedQueue()->get();
I think you're mistaken about 2 models. I think this should work
Song.php
public function scopeEligable($query, $active) {
return $query->whereHas('host', function($q) {
$q->where('skip_threshold', '>', \DB::raw('songs.attempts'))->where('active', $active);
})
}
public function scopeInDownloadQueue($query)
{
$query->whereNull('downloaded');
}
Usage
$songs = Song::eligable(true)->inDownloadQueue()->get();