I am creating an app in Laravel 5.4, where I have a middleware ValidateBooking and then a controller called with a URL like /booking/6/car, which lists all the cars assigned to that booking.
In the ValidateBooking middleware I am validating the booking id 6 in the above URL by using the Booking::find(6) Eloquent function. But what I want is, if the Booking exists then pass that object to the controller, so I do not fetch it again in the controller. I don't want to query the database twice for the same thing.
I tried a few methods to merge the model object with $request in the middleware, but was not able to access it in the controller properly.
My middleware code is:
<?php
namespace App\Http\Middleware\Booking;
use Closure;
use App\Booking\Booking;
class ValidateBooking
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$booking = Booking::find($request->booking_id);
if (!$booking) {
return redirect(route('booking.show', $request->booking_id));
}
$request->attributes->add(['bookingInstance' => $booking]);
return $next($request);
}
}
And then fetching it in the Controller like:
$request->get('bookingInstance')
It works if I pass any string value or something, but not for an object? Please advise what could be the best way for this.
You just be able to use the merge() function on the request object.
public function handle($request, Closure $next)
{
$booking = Booking::find($request->booking_id);
if (!$booking) {
return redirect(route('booking.show', $request->booking_id));
}
$request->merge(['bookingInstance' => $booking]);
return $next($request);
}
Apart from using $request->merge() which is proven to work, simply adding the model object to the request with the following also helps you access the model in the controller:
$request->booking = $booking;
The only thing to be aware of is just simply ensuring that there was never a parameter called booking so as not to override it.
What I think you want to look at is Explicit Route Model bindings.
If you add an explicit binding to the RouteServiceProvider, you can then access that parameter in your middleware and in your controller.
// app/Providers/RouteServiceProvider.php
public function boot()
{
parent::boot();
Route::model('booking', \App\Booking\Booking::class);
}
// routes/web.php
Route::get('/booking/{booking}/car', 'BookingController#car')->middleware('booking.validate');
// app/Http/Middleware/ValidateBooking.php
public function handle($request, Closure $next)
{
$booking = $request->booking;
// ...
return $next($request);
}
// app/Http/Controllers/BookingController.php
public function car(Request $request)
{
dd($request->booking);
}
Here is what you can do. I had this problem once.
public function handle($request, Closure $next)
{
$booking = Booking::find($request->booking_id);
if (!$booking) {
return redirect(route('booking.show', $request->booking_id));
}
sesion()->put('bookingInstance', $booking);
return $next($request);
}
This is a simple.
Related
I wrote a very simple middleware, like this:
class CheckToken
{
private $token='xxx';
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->tokenz == $this->token) {
return response('Unauthorized.', 401);
}
return $next($request);
}
}
Then I register it trough kernel.php, like this:
protected $routeMiddleware = [
.....
'CheckToken' => \App\Http\Middleware\CheckToken::class,
];
then Ive a very simple function in a controller guarded by this controller:
public function __construct()
{
$this->middleware('CheckToken');
}
public function push()
{
return view('home');
}
Now starts what is not clear to me:
how can i "protect" my page using this simple method?
I've tried to put this tag on the header of page but it seems to not works, maybe im in the wrong path:
<meta name="tokenz" content="xxx">
I put it even in the body but no results.
what ive misunderstood?
I believe you need to add the middleware call to the actual route:
use App\Http\Middleware\CheckAge;
Route::get('admin/profile', function () {
//
})->middleware(CheckAge::class);
This was extracted from the Laravel 5.7 documentation: Middleware - Assigning Middleware to Routes
sorry i can't create a comment. but just want to help.
does $request passed a tokenz?
you can use ?tokenz=blablabla
or you can change your method to get the tokenz
I am using a middleware for my resource controller and in that middleware I set some global variables. Now I want to access those variables in the constructor of my controller but I get null value when I try to access those variables in the constructor while I receive not null value if I access them in any other method of that controller. I am using Laravel 5.6. Following is its code.
Controller:
class PhaseController extends ApiController
{
public function __construct(Request $request)
{
parent::__construct();
$this->middleware('myMiddleware');
$myVar = $request->myVar;
}
}
Middleware:
class myMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$explodedPath = explode("/",$request->path());
$request["myVar1"] = $explodedPath[5];
//Code here
return $next($request);
}
}
This is happening because request object is received before calling middleware so even I am modifying request object in middleware, Constructor is still using the old state of request object. Here I need to refresh the request object to get its new state. But I don't know how.
Thanks in advance.
Try adding it to the request ParameterBag:
public function handle($request, Closure $next)
{
$parts = explode('/', $request->path());
$request->attributes->add(['myVar1' => data_get($parts, 5)]);
// Code here
return $next($request);
}
Then in your controller:
$myVar = $request->get('myVar1');
Edit:
If you would require route params in your controller, why not use
$request->route()->parameters();
or
$request->route('parameter_name'); // where name is based on the name in your route definition.
?
I request api to check user , and the backurl will add a query param token like this :
www.test.com?store_id=2&token = 123
I want to show this
www.test.com?store_id=2
I handle it in middleware , I wish there is a mothod to remove token before return $next($request)
but I didn't find the method. And I can't just use some method to delte this params and redirect , it will make a redirect loop.
if there is no better method, maybe I will create a new method in LoginController to remove token and redirect to where the page I from.
You can have some sort of global middleware:
class RedirectIfTokenInRequest {
public function handle($request,$next) {
if ($request->token) {
return redirect()->to(url()->current().'?'.http_build_query($request->except("token")));
}
return $next($request);
}
}
This will just redirect if there's a token parameter there. If you need to store it somehow you can use session(["token" => $request->token]); to store it before your redirect.
Middleware is the best option. You can attach middleware class, to routes, in web or to single method. My middleware proposal:
namespace App\Http\Middleware;
use Closure;
class ClearFromAttributes
{
/**
* Remove some attributes which makes some confusion.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request->get('unwanted_param')) {
return $this->removeFromQueryAndRedirect($request, 'unwanted_param');
}
if ($request->has('second_unwanted')) {
return $this->removeFromQueryAndRedirect($request, 'second_unwanted');
}
return $next($request);
}
/**
* Remove and make redirection.
*
* #param \Illuminate\Http\Request $request
* #param string $parameter
* #return mixed
*/
public function removeFromQueryAndRedirect($request, string $parameter)
{
$request->query->remove($parameter);
return redirect()->to($request->fullUrlWithQuery([]));
}
}
Of course, I have more complicated conditions in the handle method, in reality.
Usage in controller constructor without touching Kernel file:
$this->middleware(ClearFromAttributes::class)->only('index');
This is a nice option, for single usage.
Laravel 7
You can remove parameter(s) from url by passing null to fullUrlWithQuery function like below:
request()->fullUrlWithQuery(['token ' => null])
Laravel 8 added fullUrlWithoutQuery($keys)
class RemoveParameterFromRequest
{
public function handle(Request $request, Closure $next)
{
if ($request->has('unwanted_parameter')) {
return redirect()->to($request->fullUrlWithoutQuery('unwanted_parameter'));
}
return $next($request);
}
}
I'm trying to check if the user has permission to a certain model. Up until now (with Laravel 5.2), I added this code at the constructor:
public function __construct()
{
if (!Auth::user()->hasPermission('usergroups')) {
abort(404);
}
}
Now, after upgrading to Laravel 5.3, Auth::user() returns null when being called from the controller's constructor. If I call it within any other method of the class, it returns the currently logged in user.
Any Ideas why?
See here:
Session In The Constructor
In previous versions of Laravel, you could access session variables or
the authenticated user in your controller's constructor. This was
never intended to be an explicit feature of the framework. In Laravel
5.3, you can't access the session or authenticated user in your controller's constructor because the middleware has not run yet.
As an alternative, you may define a Closure based middleware directly
in your controller's constructor. Before using this feature, make sure
that your application is running Laravel 5.3.4 or above:
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
class ProjectController extends Controller
{
/**
* All of the current user's projects.
*/
protected $projects;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->projects = Auth::user()->projects;
return $next($request);
});
}
}
Of course, you may also access the request session data or
authenticated user by type-hinting the Illuminate\Http\Request class
on your controller action:
/**
* Show all of the projects for the current user.
*
* #param \Illuminate\Http\Request $request
* #return Response
*/
public function index(Request $request)
{
$projects = $request->user()->projects;
$value = $request->session()->get('key');
//
}
class StudentController extends Controller
{
public $role;
public function __construct()
{
$this->middleware(function ($request, $next) {
if (!Auth::user()->hasPermission('usergroups')) {
abort(404);
}
return $next($request);
});
}
}
Hope help you!!!
Update:
Get your code inside
$this->middleware(function ($request, $next) {
//your code
return $next($request);
});
Because you're attempting to access the instance before the middleware even fires. You can use request()->user() instead.
tried to implement a simple user register/login function on my site using the laravel default controllers (auth/password), but as soon as I login, the class RedirectIfAuthenticated handle function prevents all access to auth url's, thus I cannot logout anymore. Is there a bug and I need to write an exception on the handle function or have I missed something?
Here is how the class looks like by default:
class RedirectIfAuthenticated
{
/**
* 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)
{
//dd($next($request));
if (Auth::guard($guard)->check()) {
return redirect('/articles');
}
return $next($request);
}
}
The AuthController's constructor should look similar to this:
public function __construct()
{
$this->middleware('guest', ['except' => 'logout']);
}
The guest middleware is handled by the RedirectIfAuthenticated class and, in order to have the logout functionality working, you should choose one:
call the logout method from your AuthController.
call whichever method you use for logout and exclude it in AuthController's constructor:
public function __construct()
{
$this->middleware('guest', ['except' => '<whichever_method>']);
}
For potentially more-advanced reasons and needs, I will show a different idea.
Inside any middleware, a person could implement their own except list. Here is a reference:
<?php
namespace App\Http\Middleware;
use Closure;
class CustomThing
protected $except = [
'api/logout',
'api/refresh',
];
public function handle($request, Closure $next)
{
foreach ($this->except as $excluded_route) {
if ($request->path() === $excluded_route) {
\Log::debug("Skipping $excluded_route in this middleware...");
return $next($request);
}
}
\Log::debug('Doing middleware stuff... '. $request->url());
}
}
I will leave it up to imagination to extend that to support other types of URLs. For example, investigate matchers such as $request->url(), $request->fullUrl(), and $request->is('admin/*').
A person could investigate the vendor code for the VerifyCsrfToken middleware and make their custom one support something like this:
protected $except = [
'api/logout',
'api/refresh',
'foo/*',
'http://www.external.com/links',
];
If you want it to be a reuseable solution, make that matching algorithm a Trait and import it into any middleware you want to exclude routes from.