Middleware in Laravel can be used to add app wide logic (or specific to specific routes or group of routes) before any application/business logic is applied. I want to do the same, but after all the application/business logic is done. What I love about middleware is that it centralizes the place where said logic is applied. Is there a way to do that at the end of the request/response lifecycle?
One option is using transformers, but I don't find it as clean as middleware for some reason (maybe because it's done by a third party?)
sample use case: I want to have a group of endpoints always return values in an alternate currency rather than USD only when such requests are made from a certain type of shoppers from a certain geographical area (which I already know). So I will need to perform business logic, and then right before I send the json response back, I want to "hijack" said response and replace all USD values with another currency of my choosing.
Ideas? (I'm using Laravel 5.5)
You can do it in.... the middleware right after the call for the closure
/**
* #param Request $request
* #param \Closure $next
* #return Response
*/
public function handle(Request $request, \Closure $next)
{
//middleware logic
$response = $next($request); //dont return it
//post application/business logic here
return $response;
}
Edit from #ceejayoz comment:
You can use terminate() method in your middleware class to run code After that the response has been sent to the client with the condition that your web server is using FastCGI.
public function handle(Request $request, \Closure $next)
{
//middleware logic
return $next($request); //dont return it
}
public function terminate($request, $response)
{
//post application/business logic here
//no need for return instruction
}
Edit 2
For transformation of the json output, the best solution would be to use the ResourceCollection.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class Product extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$multiplier = $isUs?1.2:1;
return [
'price' = $this->price * $multiplier,
];
}
}
In your controller use this class as the response for the controller
public function show($productId)
{
return new \App\Http\Resources\Product(Product::find($productId));
}
Related
Is there anyway to apply rate limiting to the route but for only success responses. Like for example if user sends request to send/code endpoint 5 times and if all of them was successful then block the user to send request again. But if 2 of them was unsuccessful (like validation error or something) but 3 was successful then user should have 2 more attempts for the given time.
I know rate limiting checks before request get executed, then block or let the user to continue. But is there anyway to apply my logic or should I try to approach differently?
You would probably need to make your own middleware, but you can extend the ThrottleRequests class and just customize how you want to handle responses:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Arr;
class ThrottleSuccess extends ThrottleRequests
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param array $limits
* #return \Symfony\Component\HttpFoundation\Response
*
* #throws \Illuminate\Http\Exceptions\ThrottleRequestsException
*/
protected function handleRequest($request, Closure $next, array $limits)
{
$response = $next($request); // call the controller first
if ($response->statusCode === 200) { // only hit limiter on successful response
foreach ($limits as $limit) {
if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) {
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
}
$this->limiter->hit($limit->key, $limit->decayMinutes * 60);
}
}
foreach ($limits as $limit) {
$response = $this->addHeaders(
$response,
$limit->maxAttempts,
$this->calculateRemainingAttempts($limit->key, $limit->maxAttempts)
);
}
return $response;
}
}
Then add your middleware to Kernel.php:
protected $routeMiddleware = [
// ...
'throttle.success' => ThrottleSuccess::class,
// ...
];
Then use it in a route like the original throttle middleware:
Route::middleware('throttle.success:5,1')->group(function () {
// ...
});
Note: you may have to override handleRequestUsingNamedLimiter if you want to return a custom response built from RateLimiter::for, I have not done anything for that here.
I am not sure the title of the question is clear, so I will try to explain it in details.
I want to execute a piece of code in every controller automatically, assign the result to a variable that will be globally accessible everywhere.
So the code that need to be run will be like this:
function getLanguage() {
session('lang') ? session('lang') : 'en';
}
$LANG = getLanguage();
In any controller I need to access that variable like this:
myModel::all($LANG);
Also in the view, it would be helpful to access that variable as well (if possible)
<div> User language: {{$LANG}}</div>
Is there any place where I can execute that piece of code automatically?
Create a middleware
Add new middleware to App\Http\Kernels $middleware property, if you want it to run on every request. You may also put into $middlewareGroups property's web key.
Your middleware's handle method will be like this
public function handle(Request $request, Closure $next)
{
Config::set('some-name.some-sub-name', session('lang') ?: 'en');
return $next($request);
}
You will be updating a config in your middleware. This config has to be set only in this middleware to prevent possible problems of shared global state. (it is also important to be unique)
Then you can use it with config('some-name.some-sub-name')
In your use-case, you should implement a global middleware which sets the locale as you wish
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Session\SessionManager;
use Illuminate\Contracts\Foundation\Application;
class CheckLocale
{
/**
* The application instance.
*
* #var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The session manager instance.
*
* #var \Illuminate\Session\SessionManager
*/
protected $sessionManager;
public function __construct(Application $app, SessionManager $sessionManager)
{
$this->app = $app;
$this->sessionManager = $sessionManager;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
$this->app->setLocale($this->sessionManager->get('lang', 'en'));
return $next($request);
}
}
After setting it as a global middleware, you can access it wherever you need it from a controller or view
Controller
public function foo(Application $app)
{
$lang = $app->getLocale();
}
In a Blade view
#inject('app', Illuminate\Contracts\Foundation\Application::class)
{{ $app->getLocale() }}
For any other variable, you may directly use Laravel container
In a service provider register method:
$this->app->singleton('lang', function ($app) {
return $app['session']->get('lang', 'en');
});
And wherever else
app('lang');
My Laravel controller has an index method on it which accepts an optional query string parameter.
How should this be represented in the method's PHPDoc block?
EmployerController.php:
/**
* #param Request $request
* #return Response
* Returns list of Employers
*/
public function index(Request $request) {
// This is the optional parameter which needs to be represented
// in the PHPDoc block
$collectiveAgreement = $request->query('collective_agreement');
...
}
If your purpose is to document those fields, I'd recommend to create a FormRequest that handles all that logic, and then inject the form request into the controller. This way, you know where the request is formed and then go to that class to see the fields and even better: the rules for them to pass the validation.
php artisan make:request ListUsersRequest
ListUsersRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ListUsersRequest extends FormRequest {
public function rules()
{
return [
'collective_agreement' => ['here', 'goes', 'your', 'rules'],
'another_field' => ['here', 'goes', 'more', 'rules'],
];
}
public function authorize()
{
return true;
}
}
Then, back to your controller
UserController.php
public function index(ListUsersRequest $request)
{ // ^^^^^^^^^^^^^^^^
$data = $request->validated();
$collectiveAgreement = $data['collective_agreement'];
// Or as you did:
// $collectiveAgreement = $request->query('collective_agreement');
}
Keep description in first line and #param and #return under that. It would be sort of standard order. Feel free to add description as you would like and how it would help other who read the code. In this case you've already documented param since that query string is part of $request object but you can extend description to be like:
/**
* Returns list of Employers.
* Request object may have optional query string parameter 'collective_agreement'
* used for this and this and that and that
*
* #param Request $request
* #return Response
*/
public function index(Request $request)
{
// This is the optional parameter which needs to be represented
// in the PHPDoc block
$collectiveAgreement = $request->query('collective_agreement');
...
}
My use case is that an user is/owns a company, which has employees.
Using form controllers along with model policies i am trying to figure out what the best/proper way to do it should be.
routes:
Route::resource('company', \App\Http\Controllers\Api\v1\CompanyController::class);
Route::resource('employee', \App\Http\Controllers\Api\v1\EmployeeController::class);
employee store request:
namespace App\Http\Requests;
use App\Models\Employee;
use Illuminate\Foundation\Http\FormRequest;
class EmployeeStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return $this->user()->can('create', Employee::class);
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'company_id' => 'required|integer|exists:companies,id'
];
}
}
employee policy:
...
/**
* Determine whether the user can create employees.
*
* #param \App\User $user
* #return mixed
*/
public function create(User $user)
{
return $user->can('update', Company::find(
app('request')->get('company_id')
));
}
...
So i am not particularly happy in how the policy checks if the user can edit the company the employee will belong to, since this only happens on http, for console/tests this will break.
Then the most logical way to add this check is in the form request's authorize() function, but then you are checking permissions outside the policies, which sounds illogical.
So in short, the question: how & why would you do this using form requests & model policies?
You only need to add the id of the company to the EmployeePolicy#create method, and you will be able to use it outside http
EmployeePolicy
public function create(User $user, $companyId)
{
return $user->can('update', $companyId);
}
EmployeeStoreRequest
public function authorize()
{
return $this->user()->can('create', Employee::class, $this->request->get('company_id'));
}
You can test it outside http with tinker
php artisan tinker
$user = User::find(2); // or whatever user you want to test with
$user->can('create', Employee::class, 3); // 3 = company_id
In Drupal there is a simple url rewrite system that stores path aliases and the real route in the database.
For example:
/category/hello => node/5
I would like to imitate this system in Laravel.
I know how to create the database structure. What I would like suggestions for is actually overriding and remapping the incoming request.
I've taken a glance at the router. No events are really sticking out. What I would like to avoid is adding every permutation as a static route. I would like to for this to be completely dynamic.
I was reading middleware with a redirect would work but don't know if that is the best route to go. Keep in mind that the aliases could be anything. There isn't any set pattern.
The actual business case for this is the application has a hierarchy of categories like for a catalog on an ecommerce site. For every path a dynamic page will need to exist and possibly also allow pass-thrus to other pages.
Ex.
/sports/football/nfl => \App\Http\Controllers\Category::lp(2)
Even something like:
/sports/football/nfl/:game/lines => \App\Http\Controllers\Lines::lp(:game)
However, I don't want to have every permutation in the db. Just the base one and allow everything after /sports/football/nfl/* pass thru to a completely different location.
If I do recall in Symfony this could be done with a custom route matcher. However, I don't see anything like that in Laravel. Unless I'm just missing something. It looks like you either add a static route or nothing all but I haven't taken the deep dive into that code yet so could be wrong.
I was able to implement a dynamic routing system by creating my own custom route and adding to the route collection manually.
Custom Route
use Illuminate\Routing\Route as BaseRoute;
use Modules\Catalog\Routing\Matching\CategoryValidator;
use Illuminate\Routing\Matching\MethodValidator;
use Illuminate\Routing\Matching\SchemeValidator;
use Illuminate\Routing\Matching\HostValidator;
use Illuminate\Http\Request;
use Modules\Event\Repositories\CategoryRepository;
use Illuminate\Routing\ControllerDispatcher;
/**
* Special dynamic touting for catalog categories.
*/
class CategoryRoute extends BaseRoute {
protected $validatorOverrides;
/**
* #param CategoryRepository
*/
protected $categoryRepository;
/**
* Create a new Route instance.
*
* #param CategoryRepository $categoryRepository
* The category repository.
*/
public function __construct(CategoryRepository $categoryRepository)
{
$this->categoryRepository = $categoryRepository;
$action = [
'uses'=> function() use ($categoryRepository) {
$path = app('request')->path();
$category = $categoryRepository->findOneByHierarchicalPath($path);
$controller = app()->make('Modules\Catalog\Http\Controllers\Frontend\CategoryController');
return $controller->callAction('getIndex', ['categoryId'=>$category->getId()]);
}
];
$action['uses']->bindTo($this);
parent::__construct(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'],'_catalog_category',$action);
}
/**
* Determine if the route matches given request.
*
* #param \Illuminate\Http\Request $request
* #param bool $includingMethod
* #return bool
*/
public function matches(Request $request, $includingMethod = true)
{
$this->compileRoute();
$validators = $this->getValidatorOverrides();
foreach ($validators as $validator) {
/*if (! $includingMethod && $validator instanceof MethodValidator) {
continue;
}*/
if (! $validator->matches($this, $request)) {
return false;
}
}
return true;
}
/**
* Get the route validators for the instance.
*
* #return array
*/
public function getValidatorOverrides()
{
if (isset($this->validatorOverrides)) {
return $this->validatorOverrides;
}
$this->validatorOverrides = [
new MethodValidator, new SchemeValidator,
new HostValidator, /*new UriValidator,*/
new CategoryValidator($this->categoryRepository)
];
return $this->validatorOverrides;
}
}
Custom Route Validator
<?php
namespace Modules\Catalog\Routing\Matching;
use Illuminate\Routing\Matching\ValidatorInterface;
use Illuminate\Routing\Route;
use Illuminate\Http\Request;
use Modules\Event\Repositories\CategoryRepository;
class CategoryValidator implements ValidatorInterface
{
protected $categoryRepository;
public function __construct(CategoryRepository $categoryRepository) {
$this->categoryRepository = $categoryRepository;
}
/**
* Validate a given rule against a route and request.
*
* #param \Illuminate\Routing\Route $route
* #param \Illuminate\Http\Request $request
* #return bool
*/
public function matches(Route $route, Request $request)
{
$path = $request->path() == '/' ? '/' : '/'.$request->path();
$category = $this->categoryRepository->findOneByHierarchicalPath($path);
return $category?true:false;
}
}
To satisfy the requirements of the category repository dependency I had to also create a subscriber that adds the route after all the providers had been booted. Simply placing it in the routes.php file would not work because there was no guarantee that all the dependencies for IoC would be configured when that file gets loaded.
Bootstrap Subscriber
use Modules\Catalog\Routing\CategoryRoute;
use Modules\Event\Repositories\CategoryRepository;
use Illuminate\Support\Facades\Route as RouteFacade;
class BootstrapSubscriber {
public function subscribe($events) {
$events->listen(
'bootstrapped: Illuminate\Foundation\Bootstrap\BootProviders',
'Modules\Catalog\Subscribers\BootstrapSubscriber#onBootstrappedBootProviders'
);
}
public function onBootstrappedBootProviders($event) {
$categoryRepository = app(CategoryRepository::class);
RouteFacade::getRoutes()->add(new CategoryRoute($categoryRepository));
}
}
I will probably expand on this but that is the basic way to do it.