User in Laravel controllers - laravel

I have a system in laravel 5.3 that uses over 40 controllers and probably 200 views.
I am attempting to clean up the code and use best practice. Given that certain calls are made pretty much everywhere, it makes sense to define it somewhere "semi globally". I would assume this would be in the Controller from which all controllers extend.
One object is $user, and has child $user->organisations and $user->organisation->locations.
Loading this at a base controller (or equivalent) way would also give me the advantage that I could ensure child relationships were eager loaded in an optimal way ensuring any foreach style code never results in multiple small database lookups. There are a number of other items I want to do this for with similar ramifications for database optimisation. These all use the Auth::user(), and they affect permissions with child objects.
Given that there are about 20 properties/variables of use to be shared (all dependant on Auth::user()) removing this duplicated code from almost every method is a huge improvement.
My aim is to be able to reference $this->user from any controller, and already have pre-loaded all the child/related objects.
Laravel 5.3 re-organised the loading order, so sharing the logged-in user data as part of Controller::__construct is no longer feasible.
Here's the code attempted so far:
In Controller::__construct
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
view()->share('user', $this->user);
return $next($request);
});
Unsurprisingly, this correctly sets the $user variable at View level, but not Controller level. While I do $user it at view level, this doesn't help.
Given that there are about 20 properties/variables of use to be shared (all dependant on Auth::user()) I decided that one Helper would at least move this into a centralised location. I instantiate the helper to be stored as property across all controllers : $this->authentication_helper
In an example controller: SearchController :
public function index(Request $request): View
{
$this->authentication_helper->getAuthenticationData($this);
//... logic for the search
}
With the AuthenticationHelper doing (amongst other things):
public function getAuthenticationData(Controller $controller) : void
{
$user = Auth::user();
$controller->user = User::with(
organisations.locations', // .. other children .. //
)->find($user->id);
// share to the view
View::share('user', $controller->user);
// ... other $controller property setting
return;
}
I am unsure as to whether this is best practice.
edit - A previously raised second issue been solved - the main question remains:
Is there a problem with this approach - what would be an equivalent way of moving these 20 or so variable assignments to a higher level.

You can get authorized user in controller using Laravel DI, simply your code should be something like this
YourControlle extends Controller
{
public function test(Request $request)
$user = $requset->user(); //use auth user
}

The best approach would be a helper (or multiple helpers, you can create a app\Helpers namespace for that) and have all your logic within it.
The Auth::user() will be accessible from that helper using the Auth Facade, and have your logic there.
An other simpler way is just extending your controllers from a custom base controller you make (which you're going to extend from Controller) and append a $user member to it with a protected visibility, and share it to the view, to be done with in the constructor or in a method to be called through parent::magicMethod()
EDIT
You can override the callAction method used by the controller class
/**
* Execute an action on the controller.
*
* #param string $method
* #param array $parameters
* #return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
// insert your logic here
return call_user_func_array([$this, $method], $parameters);
}
The reason why you can't access the Auth via the constructor is because the session was not fired up yet. You can catch it when it does with this event listener :
Event::listen(Authenticated::class, function ($event) {
$this->user = $event->user;
});
not tested
A middleware with a closure would do the work too. Make it have your logic, and use it in all your controllers.

Related

How to pass multiple string variables to a Laravel Gate

I have a “security” service which I want to gradually move over to a Laravel Gate, so I can benefit from the helper methods that Laravel provides within the rest of the APP.
I defined the gate as follows now:
Gate::define('denja', function($user, $module, $permission) {
// validation of access to $module and $permission goes here
});
This works fine when I do
$user->can('denja', ['accounting', 'invoice.create']);```
for instance, but I don’t see how in my routes, I can define the middleware to properly function...
Route::post( '/accounting/invoices', 'InvoiceController#create')
->middleware("can:denja,accounting,invoice.create");```
Passing these parameters seems to be impossible from the middleware - the page now always returns a 403...
Any thoughts on how I can pass these parameters correctly to the gate from the Middleware? I think it's in fact a problem with the parameters; even with a dd() in the defined gate, I'm getting the 403.
I know I’m a bit “abusing” the system, but since we have an existing service that basically expects a user, module and permission under that module, I just want to delegate to that service for now...
When you are using can middleware :
The first is the name of the action we wish to authorise and the later is the route parameter we wish to pass to the policy method or a Model class path. documentation
For example :
Route::put('/post/{postId}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,postId');
OR
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
In your case :
Route::post( '/accounting/invoices', 'InvoiceController#create')
->middleware("can:denja,accounting,invoice.create");
which is missing the basic parameter signatures as there is no route param with name accounting or invoice.create nor a class.
Solution :
Remove middleware from route declaration :
Route::post( '/accounting/invoices', 'InvoiceController#create');
You can use can() method in your controller :
public function create(Request $request){
// Initialize $model and $permissions
// as per your business logic
if(!$request->user()->can('denja', $module, $permission){
abort(403);
}
// continue your logic for authorised user
}
Even if above solution works, if you have more authorisation rules, its better to make a policy class.
I to had this same problem so I did some digging into the 'can' middleware (Which maps to Illuminate\Auth\Middleware\Authorize)
Once in the class we see the following code
/**
* Get the model to authorize.
*
* #param \Illuminate\Http\Request $request
* #param string $model
* #return \Illuminate\Database\Eloquent\Model|string
*/
protected function getModel($request, $model)
{
if ($this->isClassName($model)) {
return trim($model);
} else {
return $request->route($model, null) ?:
((preg_match("/^['\"](.*)['\"]$/", trim($model), $matches)) ? $matches[1] : null);
}
}
What this means is...
If our string passed in is a class name then return that class name
If it is not a class name then...
1) Try to get it from the route, then return the route param
2) Try to get the model from the string via the regex "/^['\"](.*)['\"]$/"
So now lets say we have the middleware call of
$this->middleware(sprintf("can:create,%s,%s", User::class, Role::SUPPORT));
This will not work because the Role::SUPPORT does not match the regex
To match it we simply need to place the Role::SUPPORT into quotes.
TAKE NOTE OF THE "'" around the second %s
$this->middleware(sprintf("can:create,%s,'%s'", User::class, Role::SUPPORT));
To answer your question specifically, quote your string
Route::post('/accounting/invoices', 'InvoiceController#create')
->middleware("can:'denja','accounting','invoice.create'");

Cache Eloquent query for response

In one of my applications I have a property that is needed throughout the app.
Multiple different parts of the application need access such as requests, local and global scopes but also commands.
I would like to "cache" this property for the duration of a request.
My current solution in my Game class looks like this:
/**
* Get current game set in the .env file.
* #return Game
*/
public static function current()
{
return Cache::remember('current_game', 1, function () {
static $game = null;
$id = config('app.current_game_id');
if ($game === null || $game->id !== $id) {
$game = Game::find($id);
}
return $game;
});
}
I can successfully call this using Game::current() but this solutions feels "hacky" and it will stay cached over the course of multiple requests.
I tried placing a property on the current request object but this won't be usable for the commands and seems inaccessible in the blade views and the objects (without passing the $request variable.
Another example of its usage is described below:
class Job extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('game_scope', function (Builder $builder) {
$builder->whereHas('post', function ($query) {
$query->where('game_id', Game::current()->id);
});
});
}
}
I do not believe I could easily access a request property in this boot method.
Another idea of mine would be to store the variable on a Game Facade but I failed to find any documentation on this practice.
Could you help me find a method of "caching" the Game::current() property accessible in most if not all of these cases without using a "hacky" method.
Use the global session helper like this:
// Retrieve a piece of data from the session...
$value = session('key');
// Store a piece of data in the session...
session(['key' => 'value']);
For configuration info and more options: https://laravel.com/docs/5.7/session

Testing Laravel API resources with dependency injections and custom requests

Type hinted route parameter does not instantiate when called from a test.
I have a Laravel API Resource Route::apiResource('users', 'Api\UserController');
Here's my update method in the controller:
public function update(UpdateUserRequest $request, User $user)
{
//
}
Inside the UpdateUserRequest:
public function rules()
{
dd($this->route("user"));
}
If I call this endpoint from Postman, I get the full user object back. However, if I call it from a test:
$response = $this->actingAs($this->user)->
json('POST', '/api/users/'.$this->user->id, [
'_method' => 'PUT',
'data' => [
// ...
]
]);
I just get the string "1", not the instantiated User object.
This is probably caused by the \Illuminate\Foundation\Testing\WithoutMiddleware trait being used by your test case.
For posterity, should anyone come across this, route model binding is performed by the \Illuminate\Routing\MiddlewareSubstituteBindings middleware. The WithoutMiddleware trait therefore prevents it from running.
The base Laravel test case provides an undocumented withoutMiddleware() method via /Illuminate/Foundation/Testing/WithoutMiddleware which you can use to get around this, however it may be worth noting that the lead developer of Laravel, Taylor Otwell, recommends testing with all middleware active when possible.
Well, one thing that worked, and I don't know if this is the correct or the "Laravel" way of doing things is to force instantiate the model in the custom request constructor, and to bind the instance inside the test:
In the UpdateUserRequest:
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
In the Test:
$this->user = factory(\App\Models\User::class)->create();
$this->app->instance(\App\Models\User::class, $this->user);

Laravel middleware return data for user_id

Is it possible to create a middleware in laravel 5.2x to return data in controller only for specific user_id instead typing everywhere stuff like
->where('access_gallery','=',true)
For example I have a gallery on my webpage where users can upload photos crop them etc.
I check by middleware if their payment_datetime < current datatime, if true next step.
In next step i want to return/edit/delete/crop/..., only photos for specific user, to do that normally i would have to create a query with #up code, because we I dont want user_1 to edit user_2 page.
It's a little annoying to copy it everywhere, and also if i create an Admin account to access everything i have to create next query for every each function to return all data for them.
If it's not possible to create function like that in middleware, is it possible in controller?
I think what you're looking for is a scope - or even a global scope.
https://laravel.com/docs/5.2/eloquent#global-scopes
Create a Scopes directory under App. Create a file like so:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class UserGallery implements Scope
{
/**
* Query if user has gallery
*
* #return void
*/
public function apply(Builder $builder, Model $model)
{
return $builder->where('access_gallery','=',true);
}
}
Then in your model (that you want this scope applied too), add a boot function to the beginning of your model class:
use App\Scopes\UserGallery;
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new UserGallery);
}
You could even put the scope in a trait class...in my opinion, would look cleaner and easier to inject into your models.
PS: Limit the amount of logic you put in a middleware class. Consider middleware as a door to get into your main set of logic. That door is either open or locked for the user to access.

laravel 4 cronjob to trigger controller methods

any advice how to trigger a controller method with a cronjob?
I've created an artisan command
public function schedule(Schedulable $scheduler)
{
return $scheduler->everyMinutes(1);
}
/**
* Execute the console command.
*
* #return mixed
*/
public function fire()
{
if (Auth::check())
{
$users = Auth::user();
Log::info($users);
}
}
Instead of logging I want to call a Controller Method. Is this possible?
The fact that you're trying to call a controller method from elsewhere is indicative that your controller has too much responsibility. Ideally you should move the functionality elsewhere such that both the controller and the job can access it.
However, if you don't want to do that you could create a request and use the router to dispatch it internally.
$request = Request::create('uri/of/controller', 'GET', $params);
return Route::dispatch($request)->getContent();
You may also be able to use the Container to get an instance of the controller and call the method directly.
App::make('YourController')->yourMethod($params);
Better practice is to create trait or helper and use it both inside you controller and artisan commands.

Resources