I am making a repository pattern in Laravel, and I have made an AbstractRepository class which is extended by any repository to get the most used CRUD methods which can be shared.
Now I can extend the main functionality by adding additional methods to concrete repositories if I need some more complex queries.
For example:
public function eagerWhere($column, $value, $relation, $orderBy = 'name')
{
return Region::with($relation)->where($column, $value)->orderBy($orderBy);
}
Now the part I'm having trouble with is this part in the main code which uses my repository:
$regions = $this->regionRepository->eagerWhere('name', $term, 'country');
if ($includeCountry) { //<-- from here
$regions->orWhereHas('country', function ($query) use ($term) {
$query->where('name', 'LIKE', '%' . $term . '%');
});
}
How can I write that part in the repository so that ultimately I can make it look like:
$regions = $this->regionRepository->eagerWhere('name', $term, 'country');
if ($includeCountry) {
$regions->orWhereHas($term, 'country');
}
I tried copying that part of the code to repository, but then I can't chain methods because when $region is fetched, it is no longer considered to be a repository instance, but Eloquent one. And now it is expecting Eloquent method instead.
I think your level of abstractation its a bit mixed, since you are not abstracting the model itself also, but anyways. A solution might be like this :
public function eagerWhere($column, $value, $relation)
{
$builder = Region::with($relation)->where($column, $value);
return $builder
}
then:
$regions = $this->regionRepository->eagerWhere('name', $term, 'country');
if ($includeCountry) {
$regions->orWhereHas($term, 'country');
}
return $regions->orderBy('name')->get();
I didn't manage to do quite what I wanted, but the fix that was satisfying solution was to put the complete logic inside the method, so now I have this:
public function eagerWhereLike($column, $value, $relation, $searchOnRelation = false, $orderBy = 'name')
{
$regions = Region::with($relation)->where($column, 'LIKE', '%' . $value . '%')->orderBy($orderBy);
if ($searchOnRelation) {
$regions->orWhereHas($relation, function ($query) use ($column, $value) {
$query->where($column, 'LIKE', '%' . $value . '%');
});
}
return $regions;
}
Related
I try to make Filters.
When I have:
public function index(OrderFilter $filter): View
{
$items = Order::withTrashed()->filter($filter)->paginate(10);
return view($this->viewsPath . self::INDEX_ACTION, [
'items' => $items,
'perPage' => 10,
]);
}
I take mistake Call to undefined method Illuminate\Database\Eloquent\Builder::filter()
But when I delete filter($filter) I have not any mistake but my filtration does not work.
How can I make filtration correctly?
OrderFilter
<?php
namespace App\Filters\Orders;
use App\Filters\QueryFilter;
class OrderFilter extends QueryFilter
{
public function id(string $id)
{
$this->builder->where('id', $id);
}
public function name(string $name)
{
$this->builder->where('name', 'like', '%' . $name . '%');
}
public function address(string $address)
{
$this->builder->where('address', 'like', '%' . $address . '%');
}
}
filter() is a method that is available on collections. So when you do
$query = Order::withTrashed()->filter($filter)->paginate(10);
what you are doing is appending the filter method to the query builder instance and this will give you error.
In order to fix this, you can do:
$query = Order::withTrashed()->paginate(10)->filter($filter);
and, this won't give you error because now you're applying filters on the collection.
If you want to conditionally apply filters by modifying the query builder, maybe consider the EloquentFilter package by Tucker-Eric.
I have that project that have orders that have status, and every status can be seen depending on if the user have the permission to manage it. I've tried to use custom collection but the downside is it's not paginatiable by default,
here's the code
class OrdersCollection extends Collection
{
public function allowedForUser(User $user)
{
return $this->filter(function ($order) use ($user) {
return $user->can(sprintf('manage %s orders', strtolower($order->status)));
});
}
}
here's how I'm trying to use it
$orders = Order::withoutTrashed()
->allowedForUser(Auth::user())
->paginate(50);
EDIT:
to avoid filtering through 20k+ rows in the table I came up with this custom builder
class OrderBuilder extends Builder
{
public function WhereAllowedForUser(User $user)
{
$permissions = $user->permissions()
->where('name', 'like', 'manage % orders')
->pluck('name')
->map(function ($item) {
return str_replace('manage ', '', str_replace(' orders', '', $item));
});
return $this->whereIn('status', $permissions);
}
}
You have to use scoped queries for your Model.
The idea is for you to pass a value to the scope and do what you want/need with it:
class Order extends Model
{
public function scopeWhereAllowedForUser($query, User $user)
{
$permissions = $user->permissions()
->where('name', 'like', 'manage % orders')
->pluck('name')
->map(function ($item) {
return str_replace('manage ', '', str_replace(' orders', '', $item));
});
return $query->whereIn('status', $permissions);
}
}
So you use it like this:
$orders = Order::whereAllowedForUser(Auth::user())->paginate(50);
I am not 100% sure what you are trying to do with that $permissions query, I am not sure what you tried to do, so explain it a little more and I can help you with it (better code).
public function index(Request $request) {
if ($request->has('show_type') && $request->show_type == 'deleted') {
$digital_cases = DigitalCase::onlyTrashed()->get();
} else {
$digital_cases = DB::table('digital_cases');
if ($request->has('caseName')) {
$digital_cases = $digital_cases->where('name', 'LIKE', $request->caseName . '%');
if ($request->has('addedBy')) {
$addedBy = $request->addedBy;
$digital_cases = $digital_cases->whereIn('added_by', function ($query) use ($addedBy) {
$query->select('id')->from('assistants')->where('firstname', 'LIKE', $addedBy . '%');
});
}
} else if ($request->has('addedBy')) {
$addedBy = $request->addedBy;
$digital_cases = $digital_cases->whereIn('added_by', function ($query) use ($addedBy) {
$query->select('id')->from('assistants')->where('firstname', 'LIKE', $addedBy . '%');
});
}
$digital_cases = $digital_cases->get();
}
$transformation = fractal()->transformWith(new DigitalCaseTransformer())->collection($digital_cases)->toArray();
return response()->json($transformation, 200);
}
I really tired of asking questions. But i have last problem is; i couldnt convert digital_cases to DigitalCase Model instance.
I got an error :
Type error: Argument 1 passed to App\Transformers\DigitalCaseTransformer::transform() must be an instance of App\DigitalCase, instance of stdClass given, called in
If it work in IF condition everything is ok. But when it work in ELSE condition it returns an error. How can i convert digital_cases variable to instance of DigitalCase ?
Laravel 5.6
Using $digital_cases = DB::table('digital_cases'); will not give you an instance (or Collection of) the DigitalCase Model. You need to use DigitalCase. To start a query that you can append conditional clauses to, begin with
$digital_cases = DigitalCase::query();
Then, continue appending clauses in the same way. When you pass a closure (->get(), etc), you'll end up with a Collection of DigitalCase Models (or a single DigitalCase if using ->first()).
You can really simplify and clean up the conditional queries using when:
DigitalCase::when($request->has('caseName'), function ($q) {
return $q->where('name', 'LIKE', request()->caseName . '%');
})->when($request->has('addedBy'), function ($q) {
return $q->whereIn('added_by', function ($query) {
$query->select('id')->from('assistants')->where('firstname', 'LIKE', request()->addedBy . '%');
});
}})->get();
That will yield the same result as all the if () { .. } else if () { .. }
I have an Eloquent\Builder $query that I want to use additional where() calls on, where the amount of the calls is indefinite and is taken from an array $filter, example below:
$filter = [
'or:email:=:ivantalanov#tfwno.gf',
[
'or:api_token:=:abcdefghijklmnopqrstuvwxyz',
'and:login:!=:administrator',
],
];
The strings, when parsed, produce valid SQL conditions, but the problem lies into sticking them into closures where a group is present (like strings 2 and 3 in the example - the array is their 'group').
I know of Laravel's functionality that allows sticking a Closure function into $query->where() to achieve what I want, but the problem I'm facing is actually building those complex closures. I have to iterate through every string in the group and pass it into the closure generated like so (where $item is the result of parsing a condition string):
$closure = function ($query) use ($item)
{
call_user_func_array(
[$query, $item['function']], [$item['field'], $item['operator'], $item['values']]
);
};
Now the obvious problem with this is while it makes simple closures easily, passing more than one condition is plain impossible.
My question is, what could I use to prepare a complex statement to be executed on a query inside a closure?
Okay, I think I figured it out.
This is the method that will return the end result.
public function parse_filter(Builder &$query, array $filter)
{
$groups = $this->_prepare_groups($filter);
return $this->_parse_groups($query, $groups);
}
These methods will parse the initial array into something more usable.
private function _prepare_groups(array $filter)
{
foreach ($filter as $key => $item) {
if (is_array($item)) {
$groups[] = $this->_prepare_groups($item);
}
if (is_string($item)) {
$simple_filter = $this->_parse_simple_filter($item);
$groups[] = $simple_filter;
$simple_filter = null;
}
}
return $groups;
}
private function _parse_simple_filter(string $filter)
{
$filter_data = explode(':', $filter);
$simple_filter['function'] = $filter_data[0] === 'and' ? 'where' : 'orWhere';
$simple_filter['field'] = $filter_data[1];
$simple_filter['operator'] = $filter_data[2];
$simple_filter['values'] = $filter_data[3];
return $simple_filter;
}
And here is where the most of the magic happens. Closures are recursive calls to this method, as you can see.
private function _parse_groups(Builder &$query, array $groups)
{
foreach ($groups as $operator => $group) {
if (!array_key_exists('function', $group)) {
$closure = function ($query) use ($group)
{
$this->_parse_groups($query, $group);
};
$query->where($closure);
} else {
$query->{$group['function']}($group['field'], $group['operator'], $group['values']);
}
}
return $query;
}
Using this, you can modify an Eloquent\Builder object however you like with deeply nested filters that are declared dynamically (for example, received within a GET/POST request).
I guess this will help for you:
In model create a scope:
public static function scopeGetResultList($query) {
return $query->where(function ($query) use ($item) {
$query->where('group_user_holder_type', '=', 1)
->orWhere('group_user_holder_type', '=', 0);
});
}
OR
Example:
public static function getSearchedUserAuto($search_key, $user_id)
{
$users = DB::table((new User)->getTable().' as U')
->select('U.*', 'CT.city_name', 'C.nicename')
->leftJoin((new Country)->getTable().' as C', 'C.country_id', '=', 'U.user_country')
->leftJoin((new City)->getTable().' as CT', 'CT.city_id', '=', 'U.user_city')
->where(function($query) use ($search_key){
$query->where('U.user_full_name', 'like', '%'.$search_key.'%')
->orWhere('U.user_email', 'like', '%'.$search_key.'%');
})
->where(function($query) use ($search_key){
$query->where('U.user_full_name', 'like', '%'.$search_key.'%')
->orWhere('U.user_email', 'like', '%'.$search_key.'%');
})
->where('U.status', '=', 1)
->where('U.user_id', '!=', $user_id)
->get();
return $users;
}
See if it is work for you.
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 :)