Middleware before Model injection - laravel

I have a design doubt I would like to share.
I have a model in Laravel with an Observer at retrieved:
class MailingObserver
{
public function retrieved($mailing)
{
// we retrieve HTML content from disk file
$mailing->setAttribute('content', \Illuminate\Support\Facades\Storage::disk('mailings')->get("{$mailing->id}-{$mailing->slug}.html"));
$mailing->syncOriginal();
}
}
which retrieve an attribute stored in a plain text instead of database.
The site is a multibrand platform so disk('mailings') is different per each logged user. This configuration is loaded in a middleware according to the the current logged user.
Up to here all is fine.
Now the "problem". I have a Controller which injects the entity Mailing:
class MailingCrudController extends CrudController
{
/**
* Sends the mailing
* #param Request $request
* #param \App\Mailing $mailing
*/
public function send(Request $request, \App\Mailing $mailing)
{
// WHATEVER
}
}
When the model is injected the retrieved Observer method is fired but the Middleware wasn't still executed so mailings disk is still not set up.
I don't know how to change this order: first execute middleare, then the model injection.
One approach
I tried in AppServiceProvider to add:
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$middleware = new \App\Http\Middleware\CheckBrandHost();
$middleware->setBrandInformation(request());
$middleware->loadBrandConfig(request()->get('brand.code_name'));
}
Would you approve this solution? What problems can cause it to me? Is it the proper way to do it?
Thanks all!

Related

Policies not hitting / working in Laravel 9 with Spatie Permission Package 5

Greetings. I'm having difficulties to use Policy Laravel 9. I have tried all day to figuring this out why POLICY class is not hitting by controller / route but could not find solution that. All possible solutions which I understand already tried.
Here is my code.
Policy class:
class PacketPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can create models.
*
* #param \App\Models\User $user
* #return \Illuminate\Auth\Access\Response|bool
*/
public function create(User $user)
{
dd('hit packet create');
}
}
And controller class
class PacketController extends Controller
{
/**
* Create the controller instance.
* Also check authorization on route level as per associated policies
*
* #return void
*/
public function __construct()
{
$this->authorizeResource(Packet::class, 'packet'); // Option-1 tried. Not working
}
public function create()
{
//request()->user()->can('create', Packet::class); // Option-2 tried. Not working
return inertia('Packet/Create', [
'item' => [],
]);
}
}
and even register policy classes in AuthServiceProvider class like so.
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* #var array<class-string, class-string>
*/
protected $policies = [
'App\Models\Packet' => 'App\Policies\PacketPolicy', // Not working
OutboundOrder::class => OutboundOrderPolicy::class, // Not working either
];
}
Route is
Route::resource('packet', PacketController::class);
Please help me out what i'm missing here. I don't understand why my Policy class not hitting when in visit domain/packet/create route

Laravel - extending Illuminate\Http\Request and using session

I've extended the Illuminate\Http\Request class and am passing it along to my controller.
use Illuminate\Http\Request;
class MyRequest extends Request
{
...
}
Controller
class MyController
{
// Doesnt work
public function something(MyRequest $request) {
var_dump($request->session())
}
// Does work
public function something(Illuminate\Http\Request $request) {
var_dump($request->session())
}
}
So when I'm trying to get session $request->session() I get RuntimeException - Session store not set on request.
I feel it has something to do with not running middlewares on my custom request but I dont know how to make it work. Helping or pionting to the right direction would be much apreciated.
To give a little bit more info. I'm trying to make a wizard. Several pages where content of one page depends on choices on previous pages. I'm storing the data in session and on the final page I do "stuff" with it and clear the session storage of current user.
Because it a lot of lines of code and since session instace lives on request I though it would be elegant to hide all those line it in custom request and in controler simply call $myRequest->storeInputs()
This is what seemed to me as "most elegant" in this particular case so I would prefer to finish it this way but I'm also open to a different solution if there is a better aproach.
Summary: basically where should I hide all those lines which are storing and retriving data from sesison?
Solution: I actually solved it by extending FormRequest since it was solution which was the best fit for what I was trying to do. However I accepted the one offered answer since I believe it is generally better solution and I would use it if not for this very particullar case.
The classic Laravel request already got a bunch of settings you didn't catch on your custom request. To achieve that, you should setup a middleware (maybe global in your use-case) which replaces old request in Laravel's container by yours.
<?php
namespace App\Http\Middleware;
use App\Http\MyRequest;
use Closure;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
class CustomizeRequest
{
/**
* #var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* #var \App\Http\MyRequest
*/
protected $myRequest;
/**
* #param \Illuminate\Contracts\Foundation\Application $app
* #param \App\Http\MyRequest $myRequest
*/
public function __construct(Application $app, MyRequest $myRequest)
{
$this->app = $app;
$this->myRequest = $myRequest;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$this->app->instance(
'request', Request::createFrom($request, $this->myRequest)
);
return $next($this->myRequest);
}
}

Loses Auth :: user () when declaring an Oberserver in AppServiceProvider

I'm trying to use a local scope in one of my models but for this I need to check the user permission, so I try to get the autenticated user by Auth::user().
But it givens me a null because I have an Observer declared for this model, and if I comment the declaration of the Observer the Auth::user() method give me a user authenticated.
There is a correct way or place to declare the Observer and in the model I can get the authenticated user, because I need to use Observers and get in boot method the authenticated user?
Laravel Framework 6.5.2
AppServiceProvider Don't work auth in model
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Conciliador::observe(ConciliadorObserver::class);
Proposta::observe(PropostaObserver::class);
}
AppServiceProvider work auth in model
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//Conciliador::observe(ConciliadorObserver::class);
//Proposta::observe(PropostaObserver::class);
}
Model does not have user logged in when Observer is declared in AppServiceProvider
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
$user = Auth::user();
dd($user); // null if Observer is declared in AppServiceProvider
if($user && $user->tipo == 'admin-gerenciador'){
$conciliadores = $user->conciliadores->pluck('id')->toArray();
static::addGlobalScope('Conciliadores', function (Builder $builder) {
$builder->whereIn('id',$conciliadores);
});
}
}
Don't call Auth::user() anywhere. It may trigger authentication side-effects. It is highly recommended to do that only in controllers and middleware.
But you can safely call Auth::user() if you check by Auth::hasUser() in advance; it checks if the user is ALREADY authenticated.
So your code goes like:
/**
* The "booting" method of the model.
*/
protected static function boot(): void
{
static::addGlobalScope('Conciliadores', function (Builder $query) {
if (Auth::hasUser() && Auth::user()->tipo === 'admin-gerenciador') {
$query->whereKey(Auth::user()->conciliadores->modelKeys());
}
});
}
It's very simple solution. Even middleware is unnecessary.
EDIT
This scope is always available, but actually apply conditions only if the user is already authenticated.
You shouldn't be doing this in your model's boot method like that. The boot method is only called once for the model, not for every model instance. The first time the model is used boot gets called, which would be when you are adding the observer for it in the Service Provider in your case; which would be way before the request is dispatched to a route and through the middleware stack. (There is no session at this point, so no authenticated user.)
You probably want to add your global scope to your model via a middleware.

laravel model - how to dynamically set a column value in the Model?

When a User is created, e.g. with the following line in a controller:
$user = $this->create($request->all());
This will insert a user record with the form values (name, email, password etc).
But if we want to set a "hidden" user fields/colums on the model and DB table, e.g. a special unique generated token (user.token), We dont want to do this in every controller.
If laravel had a service layer, it could be done here, but better would be to do it in the model itself.
e.g. by catching a beforeSave callback, and having some code generate the token and set the corresponding field before it gets written to the DB. I see that the model has saved() event/observers, but this looks like it happens after the save, and I dont want to put this logic in an external class, It belongs in the model, and the documenation doesnt say if the observer can modify the model (by setting columns in this case)
Any suggestions?
It is possible to define event listeners directly within your model. E.g. add a boot method to your User model:
/**
* Define model event callbacks.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::saving(function ($model) {
$model->token = str_random(60);
});
}
Alternative, more verbose implementation:
/**
* Define model event callbacks.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::saving(function ($model) {
if (method_exists($model, 'beforeSave')) $model->beforeSave();
});
}
/**
* Before save event listener.
*
* #return void
*/
public function beforeSave()
{
$this->token = str_random(60);
}

Laravel Architecture - Event vs. Controller

My question is related more about the architecture of an Laravel application. I'm developing an application and I have some problems about the positioning of my codes. Let's assume I have a controller to control the comments of my post, but now, after a while, I need to add an action each time a comment is registered, in this case, I create an event or simply add this new action to my controller action?
Thank you.
As Bogdan's says, you should read Model Events.
A sample approach could be the next.
Create a service provider:
php artisan make:provider CommentServiceProvider
Then a sample CommentServiceProvider class:
<?php
namespace App\Providers;
use App\Comment;
use Illuminate\Support\ServiceProvider;
class CommentServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Comment::creating(function ($comment) {
//... do stuff here
});
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}

Resources