Binding Primitives in Laravel Scope class - laravel

I have a global scope that is used for a few models. Here is the scope class:
<?php
namespace App\Scopes;
use App\Models\UserMeta;
use Auth;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class WorkplaceScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$user_active_workplace = UserMeta::where(['user_id' => Auth::id(), 'meta_key' => 'active_workplace'])->first();
$builder->where('workplace_id', $user_active_workplace->meta_value);
}
}
I am assigning the scope by overriding the model's boot method:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new WorkplaceScope);
}
The problem is that I make a query inside the scope and that query is executed a lot of times for no reason. I was thinking of using primitive binding inside a service provider, but I can't figure out how to do it properly. I tried adding this inside the AppServiceProvider:
$this->app->when('App\Scopes\WorkplaceScope')
->needs('$user_active_workplace_meta_value')
->give(1);
but I don't know how to hint the service provider that the WorkplaceScope expects a $user_active_workplace_meta_value variable. Any idea how can I do this?

Related

Laravel Livewire error when I add a constructor to call a service class

I've got a piece of code I want to reuse. I've read this Laravel cleaner code article and this other Laravel Services Pattern article, where I have realized I can reuse code in several places of the application by using services classes.
In this case, I created a new MyService class, inside a new folder app/Services/MyService.
namespace App\Services;
class MyService
{
public function reuse_code($param){
return void;
}
}
The problem comes when I want to call the class through the constructor inside a Livewire class component, as follows:
<?php
namespace App\Http\Livewire;
use App\Services\MyService;
use Livewire\Component;
use Livewire\WithPagination;
class LivewireTable extends Component
{
use WithPagination;
private $myClassService;
public function __construct(MyService $myService)
{
$this->myClassService = $myService;
}
public function render()
{
$foo = $this->myClassService->reuse_code($param);
return view('my.view',compact('foo'));
}
}
The error displayed is the following:
Argument 1 passed to App\Http\Livewire\LivewireTable::__construct()
must be an instance of App\Services\MyService, string given
(However, If I use a trait, there are no problems. But I am afraid then my traits collide as previous experiences)
How do I fix it? What am I missing?
Livewire's boot method Runs on every request, immediately after the component is instantiated, but before any other lifecycle methods are called
Here's the solution worked for me.
Solved
Just like #IGP said, reading in the livewire docs it says:
In Livewire components, you use mount() instead of a class constructor
__construct() like you may be used to.
So, my working code is as follows:
<?php
namespace App\Http\Livewire;
use App\Services\MyService;
use Livewire\Component;
use Livewire\WithPagination;
class LivewireTable extends Component
{
use WithPagination;
private $myClassService;
public function mount(MyService $myService)
{
$this->myClassService = $myService;
}
public function render()
{
$foo = $this->myClassService->reuse_code($param);
return view('my.view',compact('foo'));
}
}

Enforce Global Scope Across all models

we are developing an application based on Laravel Spark. as part of this we want to tie resources to a specfic team.
I know that we can add a global scope such as:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class TeamScope 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('team_id', '=',Auth()->user()->currentTeam->id );
}
}
but according to the docs we have to add that to each model that we want to restrict like so:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new TeamScope);
}
my issue with this is that it will be possible to create future models and forget to apply this code. Which could give us a security hole?
is there any way to enforce the scope across the board?
I am not sure if there's a way to globally add the Scope.
In my particular application, we have had to add more responsiblities to our Models. So we created a BaseModel class that extends Laravel's Illuminate\Database\Eloquent\Model.
All new Models then extends the BaseModel instead of Laravel's one.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new TeamScope);
}
}
For example:
<?php
namespace App;
class Attribute extends BaseModel
{
}
You could also have a trait that you can just use to add this scope to your Model. For example:
trait HasTeamScope
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new TeamScope);
}
}
}
... and then you can easily re-use that in your Model.
For example:
<?php
namespace App;
class Attribute extends BaseModel
{
use HasTeamScope;
}
Now, based on your question, you might also forget to extend the BaseModel in the first instance or add the Trait in the second one whenever you create a new model.
To solve this, you could easily create a new command to produce models that will use your own stub (which extends the BaseModel or adds the trait whenever you create a new model)
You could create your own base model with the desired global scope that future models would extend.
You should create trait with boot function. Trait named BelongsToTeam.
And in all models add only: use BelongsToTeam;

Laravel: Class declaration Compatible error

I am using laravel 5.6, when create controller and when run controller through route, I am facing error like
Declaration of App\Http\Controllers\XyzController::xyz(Illuminate\Http\Request $request) should be compatible with App\Http\Controllers\Controller::xyz($job)
My Code is
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class XyzController extends Controller
{
public function xyz(Request $request)
{
return view('xyz.xyz');
}
}
Missing route parameter: $job
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class XyzController extends Controller
{
public function xyz(Request $request, $job)
{
return view('xyz.xyz');
}
}
The base Controller that XyzController extends from defines a method named xyz with a different signature than the one you are defining.
You will need to adjust the method in XyzController to match the signature of xyz in the base Controller or adjust the base Controller to have a different signature.
Example of the problem:
class A
{
public function xyz($obj) {}
}
class B extends A
{
public function xyz(Illuminate\Http\Request $request) {}
}
Declaration of B::xyz(Illuminate/Http/Request $request) should be compatible with A::xyz($obj)
You forgot to use controller?
use App\Http\Controllers\Controller as Controller

Can not call Auth class inside Scope class in Laravel 5.2

I added External Scope for User Model. Simply created Scope, with the name DeveloperScope
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Auth;
class DeveloperScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
if(Auth::user()->id != 1){
$builder->where('id', '<>', 1);
}
}
}
Then, I called this scope for User model.
User Model
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Scopes\DeveloperScope;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use SoftDeletes;
use Authenticatable, CanResetPassword;
protected $table = 'users';
protected $fillable = ['first_name', 'last_name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
protected $dates = ['deleted_at'];
protected static function boot()
{
parent::boot();
static::addGlobalScope(new DeveloperScope);
}
}
It works well, when I don't use Auth::class inside DeveloperScope class. The reason is that, I just want to hide the main user for another users for all Eloquent methods. Of course, I can use session instead of Auth and retrieve user id. But it is still interesting for me, why browser gives an error: ERR_EMPTY_RESPONSE while using Auth::class ?
The method what you are looking for is exactly Auth::hasUser(). The method was added in Laravel 5.6 through my PR. (So you need to upgrade Laravel first)
[5.6] Add ability to determine if the current user is ALREADY authenticated without triggering side effects by mpyw · Pull Request #24518 · laravel/framework
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
class DeveloperScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
if (!Auth::hasUser() || Auth::user()->id != 1) {
$builder->where('id', '<>', 1);
}
}
}
Just call Auth::hasUser() to prevent Auth::user() from causing side effects.
Try injecting the Illuminate\Auth\Guard class and then call the user method on it. e.g.:
$this->auth->user();
You should be fine with just type-hinting it since Laravel will automatically new up an instance for you.
For more info check out the API: https://laravel.com/api/5.2/Illuminate/Auth/Guard.html

Laravel 5.3 Custom Class

Create a custom class in laravel when I am call in controller construct then
Auth::user() not return any data
When call from in a function then it's work
Class Code
<?php namespace App\Libraries;
use App\User;
use Auth;
use Illuminate\Support\Facades\DB;
use App\Friends;
class AppLibrarie
{
private static $friends_ids = array();
public function __construct()
{
self::$friends_ids=Auth::user();
}
public function getfriends(){
return self::$friends_ids;
}
}
And Controller
<?php
namespace App\Http\Controllers;
use App\Libraries\AppLibrarie;
use Illuminate\Http\Request;
use App\Http\Requests;
class LiveController extends Controller
{
protected $lib;
public function __construct(AppLibrarie $appLibrarie)
{
$this->lib = $appLibrarie;
}
public function search(Request $request){
return response()->json($this->lib->searchdata($request->get('query')));
}
}
Accessing authenticated user sessions has been deprecated in Laravel 5.3. Here is the paragraph in the upgrade guide
In previous versions of Laravel, you could access session variables or the authenticated user in your controller's constructor. This was never intended to be an explicit feature of the framework. In Laravel 5.3, you can't access the session or authenticated user in your controller's constructor because the middleware has not run yet.
As an alternative, you may define a Closure based middleware directly in your controller's constructor. Before using this feature, make sure that your application is running Laravel 5.3.4 or above:
You will need to rethink your Authentication structure a bit if you are to upgrade

Resources