What is the best way to implement the items filter functionality?
I have a table with some items, and each item has some fields.
How can I select items with fields filtering, like e-commerce filtering, using Laravel 5 and Eloquent?
I've always used eloquent scopes to filter eloquent results: https://laravel.com/docs/5.1/eloquent#query-scopes
Your Controller:
use App\Item;
class ItemController extends Controller
{
public function index(Request $request)
{
$items = Item::name($request->name)->price($request->price)->paginate(25);
return view('items.index', compact('items'));
}
}
Your Model:
class Item extends Model
{
public function scopeName($query, $name)
{
if (!is_null($name)) {
return $query->where('name', 'like', '%'.$name.'%');
}
return $query;
}
public function scopePrice($query, $price)
{
if (!is_null($price)) {
return $query->where(compact('price'));
}
return $query;
}
}
Your View:
<form action="{{ route('items.index') }}" method="get">
<input type="text" name="price" />
<input type="text" name="name" />
<input type="submit" value="Search">
</form>
#foreach($items as $item)
{{ $item->name }}
#endforeach
{!! $items->render() !!}
In your scopes, you can check if the given value is null before limiting your query results. This effectively allows users to search and filter paginated results.
You could further enhance the search by automatic validation using Laravel's Form Requests so you can sure that the input they are searching for matches the data type for your query.
Keep in mind you'll have to modify the getRedirectUrl() method since your users will be executing a GET request and by default the getRedirectUrl() will contain the GET URL parameters resulting in an infinite loop since the one of the params failed validation.
Here's an example using the above:
class ItemSearchRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'nullable|string|min:2|max:100',
'price' => 'nullable|numeric',
];
}
protected function getRedirectUrl()
{
return url('/items');
}
}
I wrote a package for exactly this here: https://github.com/Tucker-Eric/EloquentFilter
It allows you to do something like:
use App\Item;
class ItemController extends Controller
{
public function index(Request $request)
{
$items = Item::filter($request->all())->paginateFilter(25);
return view('items.index', compact('items'));
}
}
You can use this awesome laravel package:
github.com/abdrzakoxa/laravel-eloquent-filter
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index(Request $request)
{
$users = Item::filter($request->all())->paginateFilter(10);
return view('users.index', compact('users'));
}
}
You can use package for regular index workflow to filter, sort, search and paginate
https://packagist.org/packages/abyss403/request-meta
class UserController extends Controller
{
...
public function index(Request $request){
// Define model
$model = new User();
// Obtain collection based on request metadata
$collection = \RequestMeta::load($model, $request)->data();
// And just return it
return $collection;
// OR using resources
return new UserResourceCollection($collection);
}
...
}
That's it
Related
Please I need help, I am working on a project in Laravel, I encounter a problem when T tried to use a pivot table in manay-to-many
In my category model Category.php, I have the function below
public function users()
{
return $this->belongsToMany(user::class);
}
In my user model User.php, I have the function below
public function authors()
{
return $this->belongsToMany(author::class);
}
In web.php, I have this
Route::post('/onboarding', 'OnboardsController#categoryUser');
In my onboarding view, I have a form:
<form action="{{ url('/onboarding') }}" method="POST">
#csrf
#foreach($categories as $category)
<div class="radio-item">
<input class="form control" name="category_id" type="checkbox" id="category_id" required>
<label for="name">{{ $category -> title }}</label>
</div>
#endforeach
<div class="form-row">
<div class="form-group col-md-12 text-center">
<button type="submit">Proceed to Authors</button>
</div>
</div>
</form>
In my OnboardsController
I have this
public function categoryUser (Request $request)
{
$user = User::findOrFail(Auth::user()->id);
$user = categories()->attach($request->input('category_id'));
return redirect ( route ('onboarding_author'));
}
This is the returned error:
Call to undefined function App\Http\Controllers\categories()
When I change the code in my controller to
$category = Category::find($request->input('category_id'));
$user = User::where('user_id', Auth::id());
$category->users()->attach($user->id);
This is the returned error:
Call to undefined function App\Http\Controllers\users()
Here is my full OnboardsController.php code
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use App\Author;
use App\User;
use App\Category;
class OnboardsController extends Controller
{
public function index()
{
//
return view('onboarding')
->with('categories', Category::all());
}
public function author()
{
//
return view('onboarding_author')
->with('authors', Author::all());
}
public function categoryUser (Request $request)
{
dd(request('category_id'));
Category::findOrFail(request('category_id'))
->users()
->attach(auth()->user()->id);
return redirect ( route ('onboarding_author'));
}
Please I am new to Laravel and I have been on this for 3 days, I don't know how to solve this. I need help.
Your problem is right here:
$user = User::findOrFail( Auth::user()->id );
$user = categories()->attach($request->input('category_id'));
^^^^^^
You are calling a method categories() on... what? on nothing. I'm assuming that you want to call the categories() method inside your User.php model. So try updating your code like this:
$user = User::findOrFail( Auth::user()->id );
$user->categories()->attach($request->input('category_id'));
^^^^^
This way, you will relate both objects.
Side notes
As I mentioned, I'm assuming that you have the categories method in your User model:
# User.php
public function categories()
{
return $this->belongsToMany(Category::class);
}
Remember that models should be capitalized.
Also, you could improve your code getting the User directly from the Auth facade
This:
$user = User::findOrFail(Auth::user()->id);
is the equivalente of:
$user = Auth::user();
You can achieve this with the following:
public function categoryUser()
{
Category::findOrFail(request('category_id'))
->users()
->attach(auth()->user()->id);
return redirect(route('onboarding_author'));
}
Here, you get the Category by the request's category_id, and attach the logged in user (using the auth() helper to get the user's id).
replace
$category->users()->attach($user->id);
return BACK()->with('message', "message!");
I tried changing the routes and using a specific name and several things in the action attribute but there is no way the data doesn't appear when I use the form for the delete and the dd() function on my delete route
here is what the dd shows me, no info at all
My routes:
Route::get('/home/Collections', 'CollectionController#index')->name('collection.index');
Route::get('/home/Collections/{Collection}', 'CollectionController#show')->name('collection.show');
Route::get('/home/Collections/{Collection}/edit', 'CollectionController#edit')->name('collection.edit');
Route::put('/home/Collections/{Collection}', 'CollectionController#update')->name('collection.update');
Route::get('/home/Collections/crear', 'CollectionController#create')->name('collection.create');
Route::delete('/home/Collections/{Collection}', 'CollectionController#destroy')->name('collection.destroy');
Route::post('/home/Collections', 'CollectionController#store')->name('collection.store');
My controller:
public function destroy(Collection $collection)
{
$collection->delete();
return redirect('/home'.'/Collections');
}
and my form:
#foreach ($collections as $collection)
<div id="{{$collection->id}}">
<img/>
<p>{{$collection->name}}</p>
<p>{{$collection->description}}</p>
<form action="/home/Collections/{{$collection->id}}" method="POST">
#csrf
#method('DELETE')
<input type="submit" value="ELIMINAR" class = "btn btn-outline-danger mt-2">
</form>
</div>
#endforeach
my collection model:
class Collection extends Model implements Searchable
{
protected $fillable = ['id', 'name', 'description', 'user_id', 'category_id', 'certificate_id', 'img_id'];
public function items()
{
return $this->hasMany(Item::class);
}
public function certificate()
{
return $this->hasOne(Certificate::class);
}
public function image()
{
return $this->hasOne(Image::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
public function getSearchResult(): SearchResult
{
$url = route('collection.show', $this->id);
return new SearchResult(
$this,
$this->name,
$url
);
}
}
Problem solved a while ago, i forgot to reply to this post.
Solved it changing the Routes URL so they dont match, it seems there was a problem with them even tho it was working like exposed on another project.
I put logic from my function index() of UserController in trait taht i created:
public function index()
{
$this->authorize('view', Auth::user());
$users = QueryBuilder::for(User::class)
->allowedIncludes('kids','roles','articles','recordings')
->allowedFilters('first_name', 'last_name', 'email')
->get();
return UserResource::collection($users);
}
and this is my trait :
<?php
namespace App\Http\Traits;
use App\Models\User;
use Spatie\QueryBuilder\QueryBuilder;
trait Filterable
{
public function filter()
{
$users = QueryBuilder::for(User::class)
->allowedIncludes('kids','roles','articles','recordings')
->allowedFilters('first_name', 'last_name', 'email')
->get();
return $users;
}
}
So now my function index() looks like this:
use Filterable;
public function index()
{
$this->authorize('view', Auth::user());
$users = $this->filter();
return UserResource::collection($users);
Now when i do this in my postman
{{url}}/api/users?filter[first_name]=anna
it works and it returns anna from my database but when I try
{{url}}/api/users?include=roles
it return every user from database but does not include roles.
Can somebody help me with this?
This is taken straight from the github page: https://github.com/spatie/laravel-query-builder#custom-filters
Custom filters
use Spatie\QueryBuilder\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
class FiltersUserPermission implements Filter
{
public function __invoke(Builder $query, $value, string $property) : Builder
{
return $query->whereHas('permissions', function (Builder $query) use ($value) {
$query->where('name', $value);
});
}
}
use Spatie\QueryBuilder\Filter;
// GET /users?filter[permission]=createPosts
$users = QueryBuilder::for(User::class)
->allowedFilters(Filter::custom('permission', FiltersUserPermission::class))
->get();
// $users will contain all users that have the `createPosts` permission
I have a table sites with list of sites with following columns ('id', 'path', 'site_link'). I've written in a Site model public $timestamps = false; so that it won't try to work with time what I don't need.
Also I have the following routes
$api = app('Dingo\Api\Routing\Router');
$api->version('v1', function ($api) {
$api->get('sites', 'App\Http\Controllers\SiteController#index');
$api->get('sites/{site}', 'App\Http\Controllers\SiteController#show');
});
The first one is working fine and returning all the data, however the second one is returning just [].
I have a controller which is below
namespace App\Http\Controllers;
use Illuminate\Http\Request;
Use App\Site;
class SiteController extends Controller
{
public function index()
{
return Site::all();
}
public function show(Site $site)
{
return Site::findOrFail($site);
}
public function store(Request $request)
{
$site = Site::create($request->all());
return response()->json($site, 201);
}
public function update(Request $request, Site $site)
{
$site->update($request->all());
return response()->json($site, 200);
}
public function delete(Site $site)
{
$site->delete();
return response()->json(null, 204);
}
}
The show method in your SiteController is taking a Site object. However the route is set up to only take the siteId. The code below should work for you based on how you've set up your routes.
public function show($site)
{
return Site::findOrFail($site);
}
Apply same to all your other controller methods since you want pass the site id via the url to the controller methods.
I have the following models.
class User extends Eloquent {
public function comments() {
return $this->hasMany('Comment');
}
}
class Comment extends Eloquent {
public function user() {
return $this->belongsTo('User');
}
}
For the sake of this example, a user could have 1,000s of comments. I am trying to limit them to just the first 10. I have tried doing it in the User model via
class User extends Eloquent {
public function comments() {
return $this->hasMany('Comment')->take(10);
}
}
and via UserController via closures
$users = User::where('post_id', $post_id)->with([
'comments' => function($q) {
$q->take(10);
}
]);
Both methods seem to only work on the first record of the result. Is there a better way to handle this?