Using repository inside a form request on Laravel - php

So I find myself creating form request to validate the request that has been posted from a form. And sometimes it gets too complex that Laravel's Validation Rules can't help, so I make another validation in the data service (which is imported in the Controller).
I'll give an example to make it much clearer:
A writer posts and article. The data request is being processed at App\Http\Requests\Article\CreateArticleRequest. After the validation verifies it's valid, the request is being forwarded to the controller. In the controller, I send the request to the ArticleService for the business logic. So far so good.
But! what if I want to make some specific validation on my own that Laravel's validation rules can't help me. because then I'll have to load a repository for complex queries.
So the big issue here is that I "double check" the request instead of one time. So I thought about merging my 2 authorization (1 from the \Request and second from my Service). But to achieve that, I'll have to load repositories that are bound to their interface. So what's your solution?

When extending the validator factory with a new rule you can pass it a closure that inherits (see Example #3 here) any dependencies that are required.
So in your case it would be something like this:
public function boot()
{
$repository = $this->app->make(Repository::class);
Validator::extend('foo', function ($attribute, $value, $parameters, $validator) use ($repository) {
return /* your validation logic */
});
}
As for the authorization you can simply type-hint the necessary dependencies in your authorize method signature:
public function authorize(Repository $repository)
{
return /* your authorization logic */
}

Related

Laravel - Validate inside Controller without make CustomRequest

I would like to validate the get parameter where i passed throug the route to my controller.
api/route
get /order/{id} -> OrderController::order
public function order($id) {
// validation here (rules= require,between 1 and 1000)
return Order::find($id);
}
how can I validate inside my controller without creating a separate request class?
which validation class do i have to import? (this one: Illuminate\Support\Facades\Validator ? )
Is this a good or common solution?
As #lagbox already wrote, you can check all of your questions inside the Laravel documentation.
Validation inside the controller
use Illuminate\Http\Request;
class MyController extends Controller
{
public function order(Request $request, int $id)
{
$validated = $this->validate([
// .. put your fields and rules here
]);
}
}
If your controller extends the base controller, that is shipped with every Laravel installation you have direct access to the validator via $this->validate.
With injecting the $request you have access to the fields that are send (POSTed) to your server.
If this is a good solution heavily depends on the projects size and other factors. It is definitely a good solution to start with. If your project grows and you need to have the same validation logic in various places you can again think about additional Form Request Validation.
To apply certain rules to the route parameter, f. ex. id, you can use Regular Expression Constraints.
Futher processing of request data
I personally would leave the validation inside the controller (or a form request class).
If there is any problem with the request data, then it should fail there and not continue to the service class.
You could say this is a kind of fail fast approach. Why moving more and more inside your code, if your request items might have an error (are not valid)?
$id is always present so required validation always passes.
So you only need to check between 1 and 1000 condition.
I think using regex constraints in the route is a good idea here.
Route::get('/order/{id}','OrderController#order')
->where(['id'=> '1000|^[1-9]{0,2}[1-9]$']);
If id is less than 1 or more than 1000 or any other random string it won't match the route and if there isn't any other matching routes too, it gives 404 error.
If you really want to validate the route parameter in the controller, you can use this:
$validator = \Illuminate\Support\Facades\Validator::make(['id' => $id],
[
'id' => 'required|integer|between:1,1000'
]
);
$validator->validate();

Laravel 7 User data in __construct

Using Laravel 7.
In the controller constructor, I then hoped to get access to the current user details so I could load main site widgets (buttons links etc) and custom user widgets in to one to be displayed in the view
use Illuminate\Support\Facades\Auth;
...
$widgets = Cache::get("widgets");
$usersdata = Cache::get("userdata");
$this->middleware('auth');
$widgets = array_merge($widgets, $usersdata[Auth::user()->id]["widgets"]);
View::share([
"widgets" => json_encode($widgets)
]);
however at this stage from research the user data is not available (even after authentication ?).
Not sure of best way to access this, or better practice might be to override the middleware auth (where?) so that it could return user id or something eg:
$userid=$this->middleware('auth');
I would like this in the constructor so the same method is in place for all controllers which extend this main controller.
This is intended behavior from laravel, you can read more about it here.
Laravel collects all route specific middlewares first before running
the request through the pipeline, and while collecting the controller
middleware an instance of the controller is created, thus the
constructor is called, however at this point the request isn’t ready
yet.
You can find Taylor's reasoning behind it here:
It’s very bad to use session or auth in your constructor as no request
has happened yet and session and auth are INHERENTLY tied to an HTTP
request. You should receive this request in an actual controller
method which you can call multiple times with multiple different
requests. By forcing your controller to resolve session or auth
information in the constructor you are now forcing your entire
controller to ignore the actual incoming request which can cause
significant problems when testing, etc.
So one solution would be to create a new middleware and then apply it to all routes, something like this, where widgets is your new middleware:
Route::group(['middleware' => ['auth', 'widgets']], function () {
// your routes
});
But if you really want to keep it in the constructor you could implement the following workaround:
class YourController extends Controller
{
public function __construct(Request $request)
{
$this->middleware('auth');
$this->middleware(function ($request, $next) {
$widgets = Cache::get("widgets");
$usersdata = Cache::get("userdata");
$widgets = array_merge($widgets, $usersdata[$request->user()->id]["widgets"]);
View::share([
"widgets" => json_encode($widgets)
]);
return $next($request);
});
}
}

Pass user object to controller through request from jwt middleware?

I have a simple case and I need your advice. I am using tymon jwt package. I have JWT middleware and this is the part of it's code:
$user = JWTAuth::parseToken()->authenticate();
if(!$user){
return response()->json(['message'=>trans("responseMessages.user_not_exists")], 403);
}
$request->request->set('user', $user);
what this middleware does, is that it tries to create $user from given jwt token, if it succeeds, user is good to continue. so here is my question, in this code (final line) I pass user object to controller through request, so I can directly have access to user model in controller. I am just interested, is this a good idea? or maybe this will be problematic?
other option is to write $user = JWTAuth::toUser(JWTAuth::getToken()) in controller function or pass user id through request instead of whole model. but in these cases I communicate with database twice, in middleware and in controller in order to get user object.
also I tried to do something like that in controller constructor : $this->user = JWTAuth::toUser(JWTAuth::getToken()), but controller constructor executes before middleware so this one was problematic. so provide me with your ideas and advices if passing user model is good idea or not.
This is an opinionated question, so don't take my answer as your final solution.
I use Slim and made an authenticate-middleware that adds the user object to the request attributes. This is essentially what you are doing.
Keep in mind the folllowing problems though (at least with immutables Request/Response objects like with PSR7):
when you have middlewares BEFORE your authentication middleware (like catching Exceptions), the request does NOT have the user object, because the middlewares work in layers.
Vice versa: if you have middleware that first executes all other middleware and than executes itself
It's just pseudo-code, but you get the idea.
middlewarefunction($request, $response, $nextmiddleware)
{
$nextmiddleware->do($request, $response);
// from here on the $request has nothing set to the request by the $nextMiddleware
// because it is immutable
}
// Edit
If you look at other middlewares, they are setting the request attribute with the decoded JWT token too:
https://github.com/DASPRiD/Helios/blob/master/src/IdentityMiddleware.php
https://github.com/tuupola/slim-jwt-auth/blob/3.x/src/JwtAuthentication.php

Injecting Request object into controller's constructor, on an API route

Problem
It seems like my API-handling controller cannot process an injected Request object. Details below.
I have a route in my api.php routes file:
Route::namespace('Mailing')->prefix('mailing/webhook')->group(function () {
Route::post('open', 'WebhookController#getOpened');
});
In the WebhookController.php, there's a constructor with the injected Request object:
use Illuminate\Http\Request;
public function __construct(Request $request) {
// Log it in the database
Logger::info('constructor called...');
// Filter the input in the injected Request
$this->input = $this->getFilteredInput($request);
}
Now, when I deploy this code to the server and make the API call against my endpoint, the log message at the beginning of the __construct() method is not even fired. I also get no data stored in the database (which should happen if the webhook is correctly processed).
What I tested
However, when I create a dummy route in web.php routes file:
Route::post('open', 'WebhookController#getOpened');
and then create a form and make a POST call to the getOpened() method - all works as expected: I have the Request injected and can manipulate it.
I checked that:
The API endpoint is correct and available
There are no obsolete use statements at the top of the
WebhookController
Questions
Why does the api.php route fail on me? Doesn't Laravel's dependency injection work for API-handling controllers?

Redirect a route in Laravel 5.3

Im building an API in Laravel 5.3. In my routes/api.php file I have 3 endpoints:
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
Several services will call these routes directly, however, when a request comes in from some services, the input data needs to be processed before it can hit these endpoints.
For example, if a request comes in from JIRA, I need to process the input before it hits these endpoints.
Im thinking that the simplest way to do this would be to have a 4th endpoint like the below:
Route::post('/notify/v1/jira', 'Api\Notify\v1\JiraFormatter#formatJiraPost');
The idea being to hit the /notify/v1/jira endpoint, have the formatJiraPost method process the input and then forward the request to /notify/v1/notifications (/alerts, /incidents) as required.
How can I have the /notify/v1/jira endpoint forward the request to the /notify/v1/notifications endpoint?
Do you see a better way to do this?
Depending how your app will work, you could always have your services pointing to /notify/v1/jira and then do the processing like you suggested.
Another alternative is have the JIRA service pointing at the same routes as all the other services but to use a Before middleware group to pre-process your data. Something like
Route::group(['middleware' => ['verifyService']], function () {
Route::post('/notify/v1/notifications', 'Api\Notify\v1\NotifyApiController#notificationsPost');
Route::post('/notify/v1/alerts', 'Api\Notify\v1\NotifyApiController#alertsPost');
Route::post('/notify/v1/incidents', 'Api\Notify\v1\NotifyApiController#incidentsPost');
});
You could check in your middleware your service.
<?php
namespace App\Http\Middleware;
use Closure;
class verifyService
{
public function handle($request, Closure $next)
{
//You could use switch/break case structure
if (isJIRA($request)) {
//Do some processing, it could be outsourced to another class
//$JiraPost = new formatJiraPost($request);
//Keep the requesting going to the routes with processed data
return $next($request);
}
//You could add extra logic to check for more services.
return $next($request);
}
protected function isJIRA(Request $request){
//Logic to check if it is JIRA.
}
}

Categories