I'm trying to do something but i cant even imagine how to do it.
To pass parameters to a middlware I'm doing this:
Route::put('post/{id}', ['middleware' => 'role:editor', function ($id) {
//
}]);
From the laravel documentation.
Then get the parameter in the handle function...
But laravel suggest to declare the middleware use in the __construct in the Controller, insted in the route.
So...
public function __construct() {
$this->middleware('auth');
}
But i dont know how to pass parameters to the Controller.
I would thank any help.
You can access url parameters into middleware shown as below:
routes.php
Route::get('/test/{id}', ['uses' => 'Test#test']);
Test Controller
<?php
namespace App\Http\Controllers;
class Test extends Controller
{
/**
* Test constructor.
*/
public function __construct()
{
$this->middleware('test');
}
public function test()
{
return 'sample';
}
Test Middleware
<?php namespace App\Http\Middleware;
use Closure;
class Test
{
public function handle($request, Closure $next)
{
dd($request->id);
return $next($request);
}
}
** dont forget update your Kernel.php to activate middleware
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'test' => \App\Http\Middleware\Test::class,
];
Don't put the middleware in the controller. Keep business logic out of the controller as often as possible.
Instead, make your middleware file.
php artisan make:middleware MyMiddlewareDoesThings
Now inside of your file, locate the $handle function.
public function handle($request, Closure $next)
Just add arguments after the request and closure
public function handle($request, Closure $next, $role)
Now $role will contain the value of role:value which from your example would be $editor
#muhammad-sumon-molla-selim answer is actually the best one according to the question, but it leaks into explanation.
With my knowledge I was able to follow his guideline so here is a full example plus a usecase where Route's middleware or Request parameters is not possible.
I have an abstract CRUDControllerBase, which is extended by many child controllers.
Each child controllers need a different permission to perform any action on the model excepts index/show.
So I was forced to dynamically pass the permission (string) from the controller to the Middleware, here is the base structure:
// App\Http\Controllers\CRUDControllerBase.php
abstract class CRUDControllerBase extends Controller
{
/**
* #var string The needed permission to call store or update
* If null, permission is granted to every users
*/
protected ?string $permission = null;
public function __construct()
{
// Here I need to pass to VerifyUserPermission the controller's permission
$this->middleware(VerifyUserPermission::class)
->except(['index', 'show']);
}
}
// App\Http\Controllers\DocumentController.php
class DocumentController extends CRUDControllerBase
{
protected ?string $permission = 'documents';
}
// App\Http\Controllers\EventController.php
class EventController extends CRUDControllerBase
{
protected ?string $permission = 'events';
}
And here is how I tackled this following the #muhammad-sumon-molla-selim answer:
// App\Http\Middleware\VerifyUserPermission.php
// Create the VerifyUserPermission middleware, which accepts the permission string
class VerifyUserPermission
{
public function handle(Request $request, Closure $next, ?string $permission): mixed
{
$user = $request->user();
// If a permission is needed but the user doesn't have it, abort
if ($permission && !$user->$permissionField) {
abort(401);
}
return $next($request);
}
}
// App\Http\Kernel.php
// Register the middleware
class Kernel extends HttpKernel
{
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
// [...]
'permission' => \App\Http\Middleware\VerifyUserPermission::class
];
}
// App\Http\Controllers\CRUDControllerBase.php
// Then just register the middleware into the CRUDControllerBase's constructor
abstract class CRUDControllerBase extends Controller
{
protected ?string $permission = null;
public function __construct()
{
// 'documents' permission will be needed for Documents' edition
// 'events' permission will be needed for Events' edition
// And so on for many others controllers
$this->middleware("permission:$this->permission")
->except(['index', 'show']);
}
}
If you want to get the parameters in the __construct of your controller you could do this:
class HomeController extends \BaseController
{
public function __construct()
{
$this->routeParamters = Route::current()->parameters();
}
}
Related
Why is it that whenever I redirect something through the constructor of my Codeigniter 4 controller is not working?
<?php namespace App\Controllers\Web\Auth;
class Register extends \App\Controllers\BaseController
{
function __construct()
{
if(session('username')){
return redirect()->to('/dashboard');
}
}
public function index()
{
// return view('welcome_message');
}
}
But if I put it inside index it's working as expected.
public function index()
{
if(session('username')){
return redirect()->to('/dashboard');
}
}
The thing is, I do not want to use it directly inside index because I it need on the other method of the same file.
As per the Codeigniter forum, you can no longer use the redirect method in the constructor to redirect to any of the controllers.
Please refer the below link for more information
https://forum.codeigniter.com/thread-74537.html
It clearly states that redirect() will return a class instance instead of setting a header and you cannot return an instance of another class while instantiating a different class in PHP.
So that's why you can't use redirect method in constructor.
Instead, what I can suggest to you is that use the header method and redirect to your desired controller.
<?php namespace App\Controllers\Web\Auth;
class Register extends \App\Controllers\BaseController
{
function __construct()
{
if(session('username')){
header('Location: /dashboard');
}
}
}
If that's not feasible or difficult to achieve you can follow the below code
<?php namespace App\Controllers\Web\Auth;
class Register extends \App\Controllers\BaseController
{
function __construct()
{
//call to session exists method
$this->is_session_available();
}
private function is_session_available(){
if(session('username')){
return redirect()->to('/dashboard');
}else{
return redirect()->to('/login');
}
}
}
The 2nd solution will be more interactive than the first one. And make sure the method is private. So that it should not be called from other class instances.
The community team has also given a solution to look into the controller filter.
https://codeigniter4.github.io/CodeIgniter4/incoming/filters.html
Please refer to the thread. I hope it may help you in finding a better solution.
In this case you shouldn't even be doing this kind of logic in your controllers. This should be done in a filter and not your controllers.
So you have your controller Register.
You should create a filter in your app/filters folder something like checkLogin.php
That filter should have the following structure:
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class CheckLogin implements FilterInterface
{
/**
* Check loggedIn to redirect page
*/
public function before(RequestInterface $request, $arguments = null)
{
$session = \Config\Services::session();
if (session('username')) {
return redirect()->to('/dashboard');
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// Do something here
}
}
Then in your app/config/Filters.php your should add the filter to the desired controller.
public $aliases = [
'csrf' => \CodeIgniter\Filters\CSRF::class,
'toolbar' => \CodeIgniter\Filters\DebugToolbar::class,
'honeypot' => \CodeIgniter\Filters\Honeypot::class,
'checkLogin' => \App\Filters\CheckLogin::class,
];
// List filter aliases and any before/after uri patterns
public $filters = [
'checkLogin' => ['before' => ['Register']],
];
For more information on filters and how to use then please check the documentation.
https://codeigniter.com/user_guide/incoming/filters.html?highlight=filters
You can then even create filters to your other controllers that would redirect to this one in case the user is not logged in.
Codeigniter 4 using initController() to create constructor.
You can't use redirect() inside __construct() or initController() function.
But you can use $response parameter or $this->response to call redirect in initController() before call another function in controller;
<?php namespace App\Controllers\Web\Auth;
class Register extends \App\Controllers\BaseController
{
public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger)
{
// Do Not Edit This Line
parent::initController($request, $response, $logger);
if(session('username')){
$response->redirect(base_url('dashboard')); // or use $this->response->redirect(base_url('dashboard'));
}
}
public function index()
{
// return view('welcome_message');
}
}
I just got into CI 4 and i had the same issue as you did, since i've been approaching the login system as with CI 3.
So here is the proper way of doing it right. Enjoy.
Codeigniter 4 do not has any return on Constructor, but you can use like this
public function __construct()
{
if (!session()->has('user_id')) {
header('location:/home');
exit();
}
}
Don't forget to use exit()
But, you better use Filters
Laravel version: 5.1.46
routes.php
Route::get('/rocha', 'RochaController#index');
Kernel.php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'Age' => \App\Http\Middleware\AgeMiddleware::class,
'Role' => \App\Http\Middleware\RoleMiddleware::class,
];
RochaController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class RochaController extends Controller
{
public function __construct() {
$this->middleware('Role');
}
public function index() {
echo '<br>Hi I am index';
}
}
RochaMiddleware.php
namespace App\Http\Middleware;
use Closure;
class RoleMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
echo '<br>Hi I am middleware';
return $next($request);
}
public function terminate($request, $response) {
echo '<br>Shtting down...';
}
}
Result:
Hi I am middleware
Hi I am index
When I use middleware inside the controller via its constructor $this->middleware('Role') the terminate() function does not get called. When I switch the code taking out the constructor in the controller and change the route to the following the terminate() function gets called:
Route::get('/rocha', [
'middleware' => 'Role',
'uses' => 'RochaController#index'
]);
Result:
Hi I am middleware
Hi I am index
Shtting down...
Why does the constructor version ($this->middleware('Role')) prevent the terminate() function from being called?
Why does the route version work and the terminate() function is called as opposed to the above?
If you define a terminate method on your middleware, it will automatically be called after the response is ready to be sent to the browser.
from terminable-middleware
I think you misunderstand the usage of terminate method. Laravel actually call the terminate method, but the browser will not show the output of terminate. Because the response has been sent to browsers.
You can use this terminate method to test whether the call is successful.
public function terminate($request, $response)
{
file_put_contents(__DIR__ . '/1.txt', 'hello terminate');
}
By the way, I'm test your code, it always output:
Hi I am middleware
Hi I am index
I also wonder why you can get Shtting down...
I am trying to bind a class into the container via a middleware and it doesn't seem to work because the class that I am binding recieved 2 parameters in the constructor and they aren't recognised.
I am getting the following error message: "Unresolvable dependency resolving [Parameter #0 [ $base_url ]] in class App\Services\Factureaza".
Here is the middleware:
<?php
namespace App\Http\Middleware;
use App\Services\Factureaza;
use Closure;
class InitializeInvoiceProvider
{
public function handle($request, Closure $next)
{
app()->singleton(Factureaza::class, function () {
// get settings by calling a custom helper function
$settings = json_decode(get_setting('invoicing_provider_settings'), true);
$api_url = isset($settings['url']) ? $settings['url'] : null;
$api_key = isset($settings['key']) ? $settings['key'] : null;
return new Factureaza($api_url, $api_key);
});
return $next($request);
}
}
The Factureaza class looks like this:
<?php
namespace App\Services;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException;
class Factureaza
{
protected $client;
protected $base_url;
protected $api_key;
public function __construct($base_url, $api_key)
{
$this->client = new GuzzleClient([
'base_uri' => $base_url,
'verify' => false,
'auth' => [$api_key, 'x'],
]);
$this->base_url = $base_url;
$this->api_key = $api_key;
}
}
I am getting this error when I am trying to resolve the dependency in my controller:
<?php
class InvoicesController extends Controller
{
protected $api;
public function __construct()
{
$this->api = resolve('App\Services\Factureaza');
}
}
Your binding should be in a Service Provider. The middleware you have isn't ran until after that Controller has been instantiated. There is no singleton bound to the container at this point. The bind is too late in the life cycle to be able to be there for a controller that a route is being dispatched to.
Laravel instantiates the Controller before it runs the route middleware. It needs to do this to be able to gather the middleware that a controller can define in its constructor to build the middleware stack.
Update:
Some Possible work arounds (did not test) without refactoring:
1) Use method injection instead of trying to get an instance in the constructor:
public function show(Factureaza $factureaza, ...)
2) Use a closure middleware defined in the constructor of the controller to get an instance and assign it.
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->api = resolve(Factureaza::class);
return $next($request);
});
}
Hopefully the middleware that sets the information that the singleton needs has ran before this controller middleware.
3) Have a middleware set this api on the controller for you ... would require adding a method to the controllers to take this information.
You have access to the controller for the route as it has already be instantiated and assigned to the Route.
$request->route()->getController()->setApi(...);
I have a roles administrator, moderator and member in my laravel application. Application have fronted and backend sections. I want to allow access to backend section only for administrator and moderator. I create SuperUsersMiddleware:
<?php
namespace CMS\Http\Middleware;
use Closure;
class SuperUsersMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->user()->hasRole('administrator') || ! $request->user()->hasRole('moderator')) {
return redirect('/');
}
return $next($request);
}
}
Register in Kernel.php:
......
protected $routeMiddleware = [
'superusers' => \CMS\Http\Middleware\SuperUsersMiddleware::class,
'administrator' => \CMS\Http\Middleware\AdminMiddleware::class,
'moderator' => \CMS\Http\Middleware\ModeratorMiddleware::class,
'auth' => \CMS\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'guest' => \CMS\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
.....
and in my backend folder I create Controller.php (all other controllers in backend section extends this controller) and in __construct() function set middleware:
...
public function __construct()
{
$this->middleware('superusers');
}
...
But this doesn't work for me. I also create administrator and moderator middleware and it works separately but I needed both - together. How to do that? I tray:
public function __construct()
{
$this->middleware('administrator');
$this->middleware('moderator');
}
But this also can't work. What is a best practice for this situation?
First off I wouldn't apply any Middleware on your master Controller as then middleware would be applied to everything. You should do this on each individual controller like UserController.
You can apply as many middleware instances to a route/function as you want. I'm not aware of any limitations on that. So I'm not sure why you say this doesn't work:
public function __construct()
{
$this->middleware('administrator');
$this->middleware('moderator');
}
You can apply the different middleware to the routes that require the different levels. You can do this in your routes.php or in your Controllers. If you want to do it in your Controller like you are doing above you would have something like this:
public function __construct()
{
$this->middleware('auth'); //this applies to all actions
$this->middleware('administrator', ['only' => ['adminFunction', 'otherAdminFunction','bothCanAccess']]);
$this->middleware('moderator',['only' => ['moderatorFunction','bothCanAccess']);
}
public function adminfunction()
{
...
}
public function otherAdminfunction()
{
...
}
public function moderatorFunction()
{
...
}
public function bothCanAccess()
{
...
}
So first off the auth middleware will apply to all actions. This means a user has to be logged in to access any function in here. Then you can apply specific middleware to each function. If you need more info on this check out the documentation:
https://laravel.com/docs/5.2/controllers#controller-middleware
To do this in your router you would do something like this:
Route::get('/admin', ['middleware' => ['auth', 'administrator'],'uses'=>'Controller#adminFunction']);
So in this case it will apply the auth middleware first to make sure someone is logged in, then fire the administrator middleware and make sure the user is an admin.
Hopefully that helps.
Let's say I have an URL like this:
/city/nyc (display info about new york city)
and another like this:
/city/nyc/streets (display a list of Street of nyc)
I can bind them to a method like this:
Route::get('city/{city}', 'CityController#showCity');
Route::get('city/{city}/streets', 'CityController#showCityStreet');
The problem is that I need to execute some checks on the city (for example if {city} is present in the database) on both methods.
I could create a method and call them in both like this:
class CityController {
private function cityCommonCheck($city) {
// check
}
public function showCity($city) {
$this->cityCommonCheck($city);
// other logic
}
public function showCityStreet($city) {
$this->cityCommonCheck($city);
// other logic
}
}
Is there any better way?
Even though you think differently, I believe a middleware is the best solution for this.
First, use php artisan make:middleware CityCheckMiddleware to create a class in App/Http/Middleware. Then edit the method to do what your check is supposed to do and add a constructor to inject the Router
public function __construct(\Illuminate\Http\Routing\Router $router){
$this->route = $router;
}
public function handle($request, Closure $next)
{
$city = $this->route->input('city');
// do checking
return $next($request);
}
Define a shorthand key in App/Http/Kernel.php:
protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate',
// ...
'city_checker' => 'App\Http\Middleware\CityCheckerMiddleware',
];
Then, in your controller:
public function __construct()
{
$this->middleware('city_checker', ['only' => ['showCity', 'showCityStreet']]);
}
I think best way to do this, you can move common logic into a Model.So your code would like below.
class CityController {
public function showCity($city) {
City::cityCommonCheck($city);
}
public function showCityStreet($city) {
City::cityCommonCheck($city);
}
}
model class
class City{
public static function cityCommonCheck($city) {
//put here your logic
}
}
In this way you could invoke cityCommonCheck function from any controller.