I am using Laravel Filament and made a Resource for User model which works fine.
I have a is_admin field in users table which return 0 and 1. I need users with is_admin = 0 but for now , i am getting all of them.
Can i add a where condition in filament to get only required fields.
The proper way to add additional where conditions with the built-in eloquent query are as follows:
public static function getEloquentQuery(): Builder
{
return static::getModel()::query()->where('is_admin', 1);
}
A bit late for the answer, bit here's an in depth guide.
There are two ways to go about it
#1 Pre-filtering query inside the resource
Inside your resource, override the following method:
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()->where('is_admin', 0);
}
#2 Global Scope
Filament uses the eloquent interface, so applying a global scope will do the job aswell.
First, create a global scope class inside App\Models\Scopes\ (not a required path, only a suggestion):
<?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class AncientScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('is_admin', 0);
}
}
Then, modify your user model to apply the scope:
<?php
namespace App\Models;
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::addGlobalScope(new AncientScope);
}
}
you can put ->where('role', '0') after return nameModal::query() at getTablequery(). \
like this
return nameModal::query()->where('role', '0');
Yes you can. You can extend getEloquentQuery() function at related resource class.Since you are talking about User model you can add above function as in the below code example to UserResource.php.
public static function getEleouentQuery () {
return User::where('is_admin',0);
}
Check this for more
Related
i have at least 8 components have same relation with course model i want to un return this component if this course is hidden
i tried to make it in global scope but still need to do in all these component's model whereHas and with how can i do these in scope in which model?
i don't want to repeat these relation in all component maybe once in maybe Global Scope or something like that
Note: i worked with laravel 5.8
i must repeat this in all components like material
$callQuery=function($q) use ($request){
if(!$request->user()->can('course/show-hidden-courses'))
$q->where('show',1);
};
// $material = $materials_query->with(['lesson','course.attachment'])->whereIn('lesson_id',$lessons);
$material = $materials_query->whereHas('course',$callQuery)->with(['lesson','course' => $callQuery]);
I am not sure, if I understood your issue well. But, if you are looking for using the same whereHas everywhere. You can just create a scope in your model, basically like:
public function scopeCourse($query){
$query->whereHas('course', function ($q){
$q->where('show', 1);
});
}
And andywhere you like to have this filter, you can just query like:
$materials_query->course()->with([...]);
Besides, if you want to enforce it everywhere, you can just create a Global Scope in for example; App\Model\Scope with the bellow code:
<?php
namespace App\Model\Scope;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class CourseScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
return $builder->whereHas('course', function ($q){
$q->where('show', 1);
});
}
}
And then in your model, you can just:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new CourseScope);
}
And then, you can just write your query like:
$materials_query->with([...]);
The scope will be automatically enforced without actually calling the scope, in our case course(). There will be times that you won't need the enforce, so you can call like:
$materials_query->withoutGlobalScope();
I am following a tutorial to write 2 classes for filtering threads in a forum application.I got this error in line
$threads = Thread::latest()->filter($filters); // in threadscontroller
Error:
Method Illuminate\Database\Query\Builder::filter does not exist.
ThreadsController with index method:
<?php
namespace App\Http\Controllers;
use App\Thread;
use App\Channel;
use App\Filters\ThreadFilters;
use Illuminate\Http\Request;
class ThreadsController extends Controller
{
public function __construct(){
$this->middleware('auth')->only('store','create');
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Channel $channel,ThreadFilters $filters)
{
$threads = Thread::latest()->filter($filters);
if($channel->exist){
$threads->where('channel_id',$channel->id);
}
$threads = $threads->get();
return view('threads.index',compact('threads'));
}
This is the abstract class Filters:
<?php
namespace App\Filters;
use Illuminate\Http\Request;
abstract class Filters{
protected $request;
protected $builder;
protected $filters = [];
public function __construct(Request $request){
$this->request = $request;
}
public function apply($builder){
$this->builder = $builder;
foreach($this->getFilters() as $filter=>$value){ //filter by,value yunus mesela.
if(method_exist($this,$filter)){
$this->$filter($value);
}
}
return $this->builder;
}
public function getFilters(){
return $this->request->intersect($this->filters);
}
}
Here ThreadFilters.php which extends filters class:
<?php
namespace App\Filters;
use App\User;
use Illuminate\Http\Request;
class ThreadFilters extends Filters
{
protected $filters =['by'];
protected function by($username){
$user = User::where('name',$username)->firstorFail();
return $this->builder->where('user_id',$user->id);
}
}
If I change latest to all, I get this error:
Type error: Argument 1 passed to
Illuminate\Support\Collection::filter() must be callable or null,
object given, called in
Also can anyone explain me what is $builder doing in those classes?
latest() is a modifier shortcut, equivalent to orderBy('created_at', 'desc'). All it does is add the ORDER BY constraint to the query.
filter() is a method on the Collection class. That method does not exist in the query builder, hence the "method not found" error you're receiving.
It does not appear that your filter class should be used with the resulting Collection. Rather, it adds conditionals to your original query. Try implementing it like this:
// Remove the filters() method here.
$threads = Thread::latest();
if ($channel->exist) {
$threads->where('channel_id', $channel->id);
}
// Pass your query builder instance to the Filters' apply() method.
$filters->apply($threads);
// Perform the query and fetch results.
$threads = $threads->get();
Also, for future questions, including the tutorial you're attempting/following can provide beneficial context to those helping you. :)
If you change latest to all, you're getting a Laravel Collection. So you are calling filter() on a Collection ($threads = Thread::all()->filter($filters);).
If you take a look into the code, you'll see, that the where() method of the array class gets called, which calls PHP's array_filter method. As you can see, a callable must be given.
But you are passing an Object to the filter method, $filters, which is an ThreadFilters-Object -> method injection here:
public function index(Channel $channel,ThreadFilters $filters) ...
Your error message answers your question in a great way:
Type error: Argument 1 passed to Illuminate\Support\Collection::filter() must be callable or null, object given, called in
Concept Problem:
I have a very simple problem when using the touches attribute, to automatically update timestamp on a depending model; it correctly does so but also applies the global scopes.
Is there any way to turn this functionality off? Or to ask specifically for automatic touches to ignore global scopes?
Concrete Example:
When an ingredient model is updated all related recipes should be touched. This works fine, except we have a globalScope for separating the recipes based on locales, that also gets used when applying the touches.
Ingredient Model:
class Ingredient extends Model
{
protected $touches = ['recipes'];
public function recipes() {
return $this->belongsToMany(Recipe::class);
}
}
Recipe Model:
class Recipe extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new LocaleScope);
}
public function ingredients()
{
return $this->hasMany(Ingredient::class);
}
}
Locale Scope:
class LocaleScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$locale = app(Locale::class);
return $builder->where('locale', '=', $locale->getLocale());
}
}
If you want to explicitly avoid a global scope for a given query, you may use the withoutGlobalScope() method. The method accepts the class name of the global scope as its only argument.
$ingredient->withoutGlobalScope(LocaleScope::class)->touch();
$ingredient->withoutGlobalScopes()->touch();
Since you're not calling touch() directly, in your case it will require a bit more to make it work.
You specify relationships that should be touched in model $touches attribute. Relationships return query builder objects. See where I'm going?
protected $touches = ['recipes'];
public function recipes() {
return $this->belongsToMany(Recipe::class)->withoutGlobalScopes();
}
If that messes with the rest of your application, just create a new relationship specifically for touching (heh :)
protected $touches = ['recipesToTouch'];
public function recipes() {
return $this->belongsToMany(Recipe::class);
}
public function recipesToTouch() {
return $this->recipes()->withoutGlobalScopes();
}
You can define a relationship in the model and pass parameters to it like following:
public function recipes($isWithScope=true)
{
if($isWithScope)
return $this->belongsToMany(Recipe::class);
else
return $this->recipes()->withoutGlobalScopes();
}
then use it like this recipes->get(); and recipes(false)->get();
I want to replace the Laravels builder class with my own that's extending from it. I thought it would be as simple as matter of App::bind but it seems that does not work. Where should I place the binding and what is the proper way to do that in Laravel?
This is what I have tried:
my Builder:
use Illuminate\Database\Eloquent\Builder as BaseBuilder;
class Builder extends BaseBuilder
{
/**
* Find a model by its primary key.
*
* #param mixed $id
* #param array $columns
* #return \Illuminate\Database\Eloquent\Model|static|null
*/
public function find($id, $columns = array('*'))
{
Event::fire('before.find', array($this));
$result = parent::find($id, $columns);
Event::fire('after.find', array($this));
return $result;
}
}
And next I tried to register the binding in bootstrap/start.php file like this :
$app->bind('Illuminate\\Database\\Eloquent\\Builder', 'MyNameSpace\\Database\\Eloquent\\Builder');
return $app;
Illuminate\Database\Eloquent\Builder class is an internal class and as such it is not dependency injected into the Illuminate\Database\Eloquent\Model class, but kind of hard coded there.
To do what you want to do, I would extend the Illuminate\Database\Eloquent\Model to MyNamespace\Database\Eloquent\Model class and override newEloquentBuilder function.
public function newEloquentBuilder($query)
{
return new MyNamespace\Database\Eloquent\Builder($query);
}
Then alias MyNamespace\Database\Eloquent\Model to Eloquent at the aliases in app/config/app.php
Both of the answers are correct in some way. You have to decide what your goal is.
Change Eloquent Builder
For example, if you want to add a new method only for eloquent models (eg. something like scopes, but maybe a little more advanced so it’s not possible in a scope)
Create a new Class extending the Eloquent Builder, for Example CustomEloquentBuilder.
use Illuminate\Database\Eloquent\Builder;
class CustomEloquentBuilder extends Builder
{
public function myMethod()
{
// some method things
}
}
Create a Custom Model and overwrite the method newEloquentBuilder
use Namespace\Of\CustomEloquentBuilder;
use Illuminate\Database\Eloquent\Model;
class CustomModel extends Model
{
public function newEloquentBuilder($query)
{
return new CustomEloquentBuilder($query);
}
}
Change Database Query Builder
For example to modify the where-clause for all database accesses
Create a new Class extending the Database Builder, for Example CustomQueryBuilder.
use Illuminate\Database\Query\Builder;
class CustomQueryBuilder extends Builder
{
public function myMethod()
{
// some method things
}
}
Create a Custom Model and overwrite the method newBaseQueryBuilder
use Namespace\Of\CustomQueryBuilder;
use Illuminate\Database\Eloquent\Model;
class CustomModel extends Model
{
protected function newBaseQueryBuilder()
{
$connection = $this->getConnection();
return new CustomQueryBuilder(
$connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
);
}
}
Laravel Version: 5.5 / this code is untestet
The answer above doesn't exactly work for laravel > 5 so I done some digging and I found this!
https://github.com/laravel/framework/blob/5.2/src/Illuminate/Database/Eloquent/Model.php#L1868
use this instead!
protected function newBaseQueryBuilder()
{
$conn = $this->getConnection();
$grammar = $conn->getQueryGrammar();
return new QueryBuilder($conn, $grammar, $conn->getPostProcessor());
}
Is there a clean way to enable certain models to be ordered by a property by default?
It could work by extending the laravel's QueryBuilder, but to do so, you'll have to rewire some of it's core features - bad practice.
reason
The main point of doing this is - one of my models get's heavily reused by many others and right now you have to resort the order over and over again. Even when using a closure for this - you still have to call it. It would be much better to be able to apply a default sorting, so everyone who uses this model, and does not provide custom sorting options, will receive records sorted by the default option. Using a repository is not an option here, because it get's eager loaded.
SOLUTION
Extending the base model:
protected $orderBy;
protected $orderDirection = 'ASC';
public function scopeOrdered($query)
{
if ($this->orderBy)
{
return $query->orderBy($this->orderBy, $this->orderDirection);
}
return $query;
}
public function scopeGetOrdered($query)
{
return $this->scopeOrdered($query)->get();
}
In your model:
protected $orderBy = 'property';
protected $orderDirection = 'DESC';
// ordering eager loaded relation
public function anotherModel()
{
return $this->belongsToMany('SomeModel', 'some_table')->ordered();
}
In your controller:
MyModel::with('anotherModel')->getOrdered();
// or
MyModel::with('anotherModel')->ordered()->first();
Before Laravel 5.2
Nowadays we can solve this problem also with global scopes, introduced in Laravel 4.2 (correct me if I'm wrong). We can define a scope class like this:
<?php namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ScopeInterface;
class OrderScope implements ScopeInterface {
private $column;
private $direction;
public function __construct($column, $direction = 'asc')
{
$this->column = $column;
$this->direction = $direction;
}
public function apply(Builder $builder, Model $model)
{
$builder->orderBy($this->column, $this->direction);
// optional macro to undo the global scope
$builder->macro('unordered', function (Builder $builder) {
$this->remove($builder, $builder->getModel());
return $builder;
});
}
public function remove(Builder $builder, Model $model)
{
$query = $builder->getQuery();
$query->orders = collect($query->orders)->reject(function ($order) {
return $order['column'] == $this->column && $order['direction'] == $this->direction;
})->values()->all();
if (count($query->orders) == 0) {
$query->orders = null;
}
}
}
Then, in your model, you can add the scope in the boot() method:
protected static function boot() {
parent::boot();
static::addGlobalScope(new OrderScope('date', 'desc'));
}
Now the model is ordered by default. Note that if you define the order also manually in the query: MyModel::orderBy('some_column'), then it will only add it as a secondary ordering (used when values of the first ordering are the same), and it will not override. To make it possible to use another ordering manually, I added an (optional) macro (see above), and then you can do: MyModel::unordered()->orderBy('some_column')->get().
Laravel 5.2 and up
Laravel 5.2 introduced a much cleaner way to work with global scopes. Now, the only thing we have to write is the following:
<?php namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class OrderScope implements Scope
{
private $column;
private $direction;
public function __construct($column, $direction = 'asc')
{
$this->column = $column;
$this->direction = $direction;
}
public function apply(Builder $builder, Model $model)
{
$builder->orderBy($this->column, $this->direction);
}
}
Then, in your model, you can add the scope in the boot() method:
protected static function boot() {
parent::boot();
static::addGlobalScope(new OrderScope('date', 'desc'));
}
To remove the global scope, simply use:
MyModel::withoutGlobalScope(OrderScope::class)->get();
Solution without extra scope class
If you don't like to have a whole class for the scope, you can (since Laravel 5.2) also define the global scope inline, in your model's boot() method:
protected static function boot() {
parent::boot();
static::addGlobalScope('order', function (Builder $builder) {
$builder->orderBy('date', 'desc');
});
}
You can remove this global scope using this:
MyModel::withoutGlobalScope('order')->get();
In Laravel 5.7, you can now simply use addGlobalScope inside the model's boot function:
use Illuminate\Database\Eloquent\Builder;
protected static function boot()
{
parent::boot();
static::addGlobalScope('order', function (Builder $builder) {
$builder->orderBy('created_at', 'desc');
});
}
In the above example, I order the model by created_at desc to get the most recent records first. You can change that to fit your needs.
Another way of doing this could be by overriding the newQuery method in your model class. This only works if you never, ever want results to be ordered by another field (since adding another ->orderBy() later won't remove this default one). So this is probably not what you'd normally want to do, but if you have a requirement to always sort a certain way, then this will work:
protected $orderBy;
protected $orderDirection = 'asc';
/**
* Get a new query builder for the model's table.
*
* #param bool $ordered
* #return \Illuminate\Database\Eloquent\Builder
*/
public function newQuery($ordered = true)
{
$query = parent::newQuery();
if (empty($ordered)) {
return $query;
}
return $query->orderBy($this->orderBy, $this->orderDirection);
}
Yes you would need to extend Eloquent to always do this as standard for any query. What's wrong with adding an order by statement to the query when you need it ordered? That is the cleanest way, ie, you dont need to 'unhack' Eloquent to get results by natural order.
MyModel::orderBy('created_at', 'asc')->get();
Other than that the closest thing to what you want would be to create query scopes in your models.
public function scopeOrdered($query)
{
return $query->orderBy('created_at', 'asc')->get();
}
You can then call ordered as a method instead of get to retrieve your ordered results.
$data = MyModel::where('foo', '=', 'bar')->ordered();
If you wanted this across different models you could create a base class and just extend it to the models you want to have access to this scoped method.
you should use eloquent global scope that can apply to all queries(also you can set parameter for it).
And for relations you can use this useful trick:
class Category extends Model {
public function posts(){
return $this->hasMany('App\Models\Post')->orderBy('title');
}
}
this will add order by to all posts when we get them from a category.
If you add an order by to your query, this default order by will cancel!
An slightly improved answer given by Joshua Jabbour
you can use the code he offered in a Trait, and then add that trait to the models where you want them to be ordered.
<?php
namespace App\Traits;
trait AppOrdered {
protected $orderBy = 'created_at';
protected $orderDirection = 'desc';
public function newQuery($ordered = true)
{
$query = parent::newQuery();
if (empty($ordered)) {
return $query;
}
return $query->orderBy($this->orderBy, $this->orderDirection);
}
}
then in whichever model you want the data to be ordered you can use use :
class PostsModel extends Model {
use AppOrdered;
....
now everytime you request that model, data will be ordered, that's somehow more organized, but my answers is Jabbour's answer.
I built a mini Laravel package that can add default orderBy in your Eloquent model.
Using the DefaultOrderBy trait of this package, you can set the default column you want to orderBy.
use Stephenjude/DefaultModelSorting/Traits/DefaultOrderBy;
class Article extends Model
{
use DefaultOrderBy;
protected static $orderByColumn = 'title';
}
You can also set the default orderBy direction by setting the $orderByColumnDirection property.
protected static $orderByColumnDirection = 'desc';
A note from my experience, never to use orderBy and GroupBy such term on global scope. Otherwise you will easily face database errors while fetching related models in other places.
Error may be something like:
"ORDER BY "created_at" is ambiguous"
In such case the solution can be giving table name before column names in your query scope.
"ORDER BY posts.created_at"
Thanks.