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

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?

Related

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);
});
}
}

How to fire an event after successful authentication with JWT

I have a Laravel version 6 application. I use JWT package for authentication.
The routes defined like this:
// route/api.php
Route::middleware('auth:api')->group(function () {
Route::apiResources([
'genders' => 'API\GenderController'
]);
});
Inside the controllers I have 5 functions index, store, show, update and destroy.
I need to set the locale for the current user before running any code inside these functions, something like this:
public function index()
{
$locale = request()->user()->lang;
app()->setLocale($locale);
// Rest of the function
}
My controller class extends a BaseController class. As the Restful API is state less I need to set the locale each time the user send a API request. Now the question is that where is the best place to do it and how can I do it?
I tried to do it inside the constructor of the BaseController but it seems the middleware('auth:api') has not yet checked the token in constructor.
The other option is to set the locale inside a authentication event handler. I found a list of JWT package events here:
// fired when the token could not be found in the request
Event::listen('tymon.jwt.absent');
// fired when the token has expired
Event::listen('tymon.jwt.expired');
// fired when the token is found to be invalid
Event::listen('tymon.jwt.invalid');
// fired if the user could not be found (shouldn't really happen)
Event::listen('tymon.jwt.user_not_found');
// fired when the token is valid (User is passed along with event)
Event::listen('tymon.jwt.valid');
If someone could help me to define a handler for tymon.jwt.valid I would be appreciated. Or even if you have some other solution for running an event before execution of index, store, show, update and destroy functions.
You can easily do it in the middleware. Just put middleware that authenticates user before the one that sets location.
Create your route class and register it in App\Http\Kernel::$routeMiddleware
Then use it like this:
// rouite/api.php
Route::group(['middleware' => ['auth:api', 'locale']], function () {
Route::apiResources([
'genders' => 'API\GenderController'
]);
});
I could find a different way for accessing the current user inside the constructor class. While the request()->user() code returns null in constructor, auth()->user() return the current user even in the constructor.
abstract class BaseController extends Controller
{
public function __construct()
{
$locale = auth()->user()->lang;
app()->setLocale($locale);
}
}
Therefore I could set the locale in my BaseController class.
However, my question regarding working with JWT events is open. Also I would like to know more details about working with middleware as #patryk-karczmarczyk suggested in his answer.

REST - Laravel redirect POST with body to other route

I'm currently re-writing an API with multiple endpoints. However, for legacy purposes it's required that there is a single endpoint which can be used to access all other endpoints. Which endpoint we should redirect to is based upon a custom action header send along with the request.
Example:
Input: Header -> Action A
Output: Redirect to route '/some/url' 'ControllerA#someAction'
Input: Header -> Action B
Output: Redirect to route '/some/other/url' 'ControllerB#someOtherAction'
Normally, I could use the redirect() method but then I lose the body of the POST method. All the endpoints I declared are POST methods.
Basically, the question is how can I properly redirect a POST to another route?
Also note I can't use:
App::call('App\Http\Controllers\PlanningController#addOrUpdate', ['request' => $request]);
Since my method uses a custom Request class to handle the validation. I get an exception telling the argument should be the type of my custom class and Illuminate\Http\Request is given.
I've actually found the answer to my problem. I've created a middleware which will re-create the request based upon the value found in the header.
Here's the handle function of the middleware (only tested on Laravel 5.2):
use Request;
use Route;
use Illuminate\Http\Response;
...
public function handle($request, Closure $next, $guard = null)
{
// Get the header value
$action = $request->header('action');
// Find the route by the action name
$route = Actions::getRouteByName(action); // This returns some route, i.e.: 'api/v1/some/url'
// Perform the action
$request = Request::create(route, 'POST', ['body' => $request->getContent()]);
$response = Route::dispatch($request);
return new Response($response->getContent(), $response->status(), ['Content-Type' => 'text/xml']); // the last param can be any headers you like
}
Please note that this might conflict on your project with other middleware. I've disabled other middleware and created a special routegroup for this. Since we're redirecting the call manually to another route the middleware on that route is called anyway. However, you can also implement this code inside a controller function then there are no conflicting middleware problems!

Using repository inside a form request on Laravel

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 */
}

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