Laravel Queries: Adding custom feature like Soft Deletes - php

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

Related

Laravel global scope with if check

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.

Pass data in an included view with Laravel 5.6

Using Laravel 5.6, I'm trying to get the number of received links a logged-in user may have in my application.
public function getReceivedLinksCount() {
return $count = App\Link::where([
['recipient_id', Auth::id()],
['sender_id', '!=', Auth::id()]
])->count();
}
So far, so good. Question is not about that piece of code, but where I can use it. I'd like to display this counter on the navigation bar of the website (Facebook's style) which is in my header.blade.php which is included in every page.
I'd like to do it with a clean code, of course. It seems like I need to use View Composers but I'm not sure it's the right way to do it, and not sure on how the code is supposed to look.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
view()->composer('layouts.header', function ($view) {
$view->with('variable_name', \App\Path-To-Your-Model::something());
});
}
You can share a value across all views by using View::share(), see docs
For example
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$linksCount = Link::getReceivedLinksCount();
View::share('linksCount', $linksCount);
}
...
}
This works well if you want to set the value everywhere. Personally, I would set the value in the constructor of a 'BaseController' that gets extended by other controllers. This makes the code more discoverable because most people would expect view values to be set in a controller. And it's also a bit more flexible if you plan on having a section of your app that doesn't require that value to be computed.

inject model in laravel controllers constructor

I want to know if this is a good practice to use my model class in controllers in this way :
public function __construct(Rule $rules)
{
$this->rules = $rules;
}
I do not want to repeat myself in my controllers so I want to know what is the best approach for that
You use Dependency Injection - it is very good practice.
According to documentation:
Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.
namespace App\Http\Controllers;
use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* The user repository implementation.
*
* #var UserRepository
*/
protected $users;
/**
* Create a new controller instance.
*
* #param UserRepository $users
* #return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the profile for the given user.
*
* #param int $id
* #return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
In this example, the UserController needs to retrieve users from a data source. So, we will inject a service that is able to retrieve users. In this context, our UserRepository most likely uses Eloquent to retrieve user information from the database. However, since the repository is injected, we are able to easily swap it out with another implementation. We are also able to easily "mock", or create a dummy implementation of the UserRepository when testing our application.
Read also about Service Container - it is powerful tool:
https://laravel.com/docs/5.6/container
It is a good practice for injecting models in controllers, however, the recommended approach is:
Have a use statement at the top of your controller file
Implement it in the functions that requires access to the model, i would not recommend you do it in your controller
If you have a look at the documentation, you will be able to bind the model directly to your route and eliminate some hassle of Model::find(id) https://laravel.com/docs/5.6/routing#route-model-binding
The constructor approach you presented is recommended in using other classes like repositories, singletons, or whatever functionality you wish to inject, see the docs for more info: https://laravel.com/docs/5.6/container
Hope this helps

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 model relationships and model events

I am building a notification system at the moment, and the notifications get delivered via model events. Some of the notifications are dependent on things happening with the models relationships.
Example: I have a project model that has a one:many relationship with users,
public function projectmanager() {
return $this->belongsToMany('User', 'project_managers');
}
I am wanting to monitor for changes on this relationship in my project model events. At the moment, I am doing this by doing this following,
$dirtyAttributes = $project->getDirty();
foreach($dirtyAttributes as $attribute => $value) {
//Do something
}
This is run in the ::updating event of the model but only looks at the models attributes and not any of it's relational data, is it possible get old relational data and new relational data to compare and process?
You should be using an observer class for this.
This has already been covered fairly simply and well by this SO answer, although that answer uses a slightly older method where the class itself needs to call upon its observer. The documentation for the current version (5.3 as of this answer) recommends registering the observer in your application service provider, which in your example would look similar to:
<?php
namespace App\Providers;
use App\Project;
use App\Observers\ProjectObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Project::observe(ProjectObserver::class);
}
}
For evaluating the differences between the new model values and the old values still in the relational DB, Laravel provides methods for both: getDirty() and getOriginal().
So your observer would look something like:
<?php
namespace App\Observers;
use App\Project;
class ProjectObserver
{
/**
* Listen to the Project updating event.
*
* #param Project $project
* #return void
*/
public function updating(Project $project)
{
$dirtyAttributes = $project->getDirty();
$originalAttributes = $project->getOriginal();
// loop over one and compare/process against the other
foreach ($dirtyAttributes as $attribute => $value) {
// do something with the equivalent entry in $originalAttributes
}
}
}

Categories