Laravel global scope with if check - php

So I try to have something like a global scope on my model so that I don't have to go and think about some parameters every single time.
The thing is I currently have the following Scopes-class;
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class NeedsPaymentScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param Builder $builder
* #param Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
if ($model->needs_payment) {
$builder->where('is_paid', '=', true);
}
}
}
And I have it included in my model like so:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new NeedsPaymentScope());
}
Now, since in the Scope class, the variable $model is like always empty, I can't execute the if-check (which needs to be done!) How can I have this logic implemented to every get(), find(); findOrFail(), ... method?

You can NEVER access $model->needs_payment because model is not prepared yet, in other words you are in the middle of writing the query statement you can't access the model that it's query has not yet been completed.

try view composer for globle scope of variable.
with the help of AppServiceProvider you can define a globle scope of variable.
public function boot(){
View::share('variable_name','your_variable_logic_code');
}
if you have so many lines of code or logic you can use callback function tooo.
now you can access variable everywhere without passing in blade.
no comapct no with(method) without return to a blade you can access in whole app.
i think its helpfull for you.

Related

Laravel Queries: Adding custom feature like Soft Deletes

All of my tables have a column called isTest. What I want is to be able to set a switch so that my code will either include all records in my queries or [more importantly] exclude all records where isTest is true.
I imagine the code will work similarly to Soft Deletes and include sql code similar to: AND (isTest != TRUE) to SQL generated by Eloquent and the Query Builder.
I am not really familiar with Eloquent events, but I have found this question which might be the right place to start, but I am hoping for guidance before I start down that path. Also, that has no info about Query Builder. If someone has done something similar I would love some advice.
You are looking for Global scopes, you can add a custom scope which will check the isTest value.
<?php
// Your custom scope
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class IsTestScope 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)
{
$builder->where('isTest', true);
}
}
// Your model
namespace App;
use App\Scopes\IsTestScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
// Check for certain criteria, like environment
if (App::environment('local')) {
// The environment is local
static::addGlobalScope(new IsTestScope);
}
}
}
When you have a lot of models, you want to make a trait of this code so you dont have to duplicate it all the time. Like how SoftDeletes work.
See docs for more info https://laravel.com/docs/5.8/eloquent#global-scopes

Laravel: how to write query without a target

Is it possible to create a query first and then assign it to a select?
For Example
$queryWithoutTarget = new Builder();
$queryWithoutTarget->where('x','y')->whereNull('z');
$workersQuery = Worker::select();
// I know this is Totally wrong, because queryWithoutTarget is not a parameter of $workersQuery
$workersQuery = $workersQuery->queryWithoutTarget;
I'm trying to encapsulate the query in functions and then execute them to any model or table
No, first you use $queryWithoutTarget as a variable (at the first and third row), then you use it as a function (at the 8th row ({...}->queryWithoutTarget)
Edit: If you override the Builder class then it may be possible. Open has maybe endless possibilities. But it's up to you to make overrides instead of additional lines in the controller
Use a scope. Scope is just a convenience method you can add to your model which encapsulates the syntax used to execute a query
Create your scope trait
namespace App\Models\Worker\Traits;
/**
* Class WorkerScope.
*/
trait WorkerScope
{
/**
* #param $query
*
* #return mixed
*/
public function scopeActive($query)
{
return $query->where('x','y')->whereNull('z');
}
}
Implement your scope on model
use App\Models\Worker\Traits\WorkerScope;
/**
* Class Worker.
*/
class Worker extends Model
{
use WorkerScope;
...
}
With the scope defined, you can execute it like
$workersQuery = Worker::active()->get();

Laravel: how to create scopes massively?

To create a scope in Laravel, we define manually a public method that matches the name that we want to use when building queries:
/**
* Scope of Microsoft organization.
*
* #param Builder $query
* #return Builder
*/
public function scopeMicrosoft($query)
{
return $query->where('slug', 'microsoft');
}
Usage:
Organization::microsoft()->first();
Now imagine that we have >50 organizations and want to automatically have a scope for every model based on slug attribute. There's a way to create scopes massively?
From docs:
Dynamic Scopes
Sometimes you may wish to define a scope that accepts parameters. To get started, just add your additional parameters to your scope. Scope parameters should be defined after the $query parameter:
namespace App;
use Illuminate\Database\Eloquent\Model;
class SomeModel extends Model
{
/**
* Scope a query to only include users of a given type.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param mixed $type
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOrganization($query, $org)
{
return $query->where('slug', $org);
}
}
Now, you may pass the parameters when calling the scope:
$users = App\SomeModel::organization('Microsoft')->get();
You can use a global scope if you're going to use this scope in all models.
Or add this local scope into a trait and use it in multiple models.
Use this package for dynamic global scope make,
https://github.com/limewell/laravel-make-extender
php artisan make:scope ActiveScope
php artisan make:scope UserScope
php artisan make:scope AgeScope etc...

How do query scopes work in Laravel?

I'm watching the Laracasts: Laravel 5.4 from Scratch series, and have come across the concept of query scopes.
In the video, we set up a class like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
public function scopeIncomplete($query)
{
return $query->where('completed', 0);
}
}
My first question:
Why is a method like: public function scopeIncomplete($query) called like this: App\Task::incomplete() with the :: operator?
Isn't that a non-static method being called statically?
My second question:
Where does $query come from?
From what I can understand this is the "existing" query, but it is called like this: App\Task::incomplete()->where('id', '>', 1)->get();
So I'm not really sure where this variable is coming from.
Maybe it's explained more in depth later but I can't wrap my head around how this works.
This is pretty broad since it is covering two large topics on Eloquent.
The first refers to Eloquent's use of the facade pattern to pass static calls to non-static methods.
The facade pattern utilizes the IoC container to pass a method call to a bound, or new, instance of a class.
The facade pattern makes use of overloading and the magic method __callStatic.
The second question refers to Eloquent's overloading that passes the unregistered method calls to a query builder object (specifically \Illuminate\Database\Eloquent\Builder). This is what allows for where() and various other Query Builder methods to be called on the Eloquent model itself. Inside the Model class, you can see:
/**
* Handle dynamic method calls into the model.
*
* #param string $method
* #param array $parameters
* #return mixed
*/
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
return $this->newQuery()->$method(...$parameters);
}
Since where() or incomplete() are not defined in Model, they'll be passed to the Builder class, which is returned by Model::newQuery().
Inside the Builder __call method, you have:
if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
return $this->callScope([$this->model, $scope], $parameters);
}
So this is checking for the scopes defined inside the model and passing the $parameters which includes this Builder instance.
So in summary, the call for Model::incomplete() will go:
Model __call() -> Builder __call() -> Model scopeIncomplete(Builder $builder)

How to add where condition to eloquent model on relation?

I want to add where condition to my Model when with('category') is called. My relations are like this:
class Post extends Model
{
public function category()
{
return $this->belongsTo(Category::class);
}
}
Now I use this code to display post categories:
Post::where('slug', $slug)->with('category')->get();
I want to add where condition to Post Model when with('category') is called. I should display only posts.status== published if with('category') is called.
I think return $this->belongsTo(Category::class); is where i should add my where condition, but this doesn't work:
return $this->query()->where('posts.status', 'published')->getModel()->belongsTo(User::class)
How can I add where condition to all post queries if with('category') is called?
I know Laravel query scopes, but i think there is a simpler way we can use. (perhaps on $this->belongsTo(Category::class))
Relationships are implemented using additional queries. They are not part of the base query, and do not have access to modify the base query, so you cannot do this inside the relationship method.
The best way to do this is with a query scope:
Class:
class Post extends Model
{
public function scopeWithCategory($query)
{
return $query->with('category')->where('status', 'published');
}
}
Query:
$posts = Post::where('slug', $slug)->withCategory()->get();
Edit
Based on your comment, I think you've probably asked the wrong question. You may want to post another question explaining what you have setup, and what you need to do, and see if anyone has any suggestions from there.
However, to answer this specific question, I believe you should be able to do this using a global query scope. This is different than a local scope described in my original answer above.
Global query scopes are applied when get() is called on the Eloquent query builder. They have access to the query builder object, and can see the items that have been requested to be eager loaded. Due to this, you should be able to create a global query scope that checks if category is to be eager loaded, and if so, add in the status constraint.
class Post extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
// make sure to call the parent method
parent::boot();
static::addGlobalScope('checkCategory', function(\Illuminate\Database\Eloquent\Builder $builder) {
// get the relationships that are to be eager loaded
$eagers = $builder->getEagerLoads();
// check if the "category" relationship is to be eager loaded.
// if so, add in the "status" constraint.
if (array_key_exists('category', $eagers)) {
$builder->where('status', 'published');
}
});
}
}
The code above shows adding in a global scope using an anonymous function. This was done for ease and clarity. I would suggest creating the actual scope class, as described in the documentation linked above.
This should work:
Post::where(['slug' => $slug, 'status' => 'published'])->with('category')->get();
you have to use withPivot() method .
class Post extends Model
{
public function category()
{
return $this->belongsTo(Category::class)->withPivot('status');
}
}
please refer to my question here

Categories