I'm using Laravel 5.3. I have a bunch of urls that I'd like to handle with a single route, to multiple controllers.
e.g.
GET /admin/foo => FooController#index
GET /admin/foo/edit/1 => FooController#edit($id)
GET /admin/bar => BarController#index
GET /admin/bar/edit/1 => BarController#item($id)
GET /admin/baz => BazController#index
GET /admin/baz/edit/1 => BazController#item($id)
etc.
I want to be able to detect if the controller exists, and if not throw a 404 or route to a default controller (which may throw a 404).
Below is what I've got so far, but I'm not sure what I'm doing. Shouldn't I be instantiating the controller using the service container? I don't think I should be hardcoding namespaces like this. And my handling of the id parameter is sketchy. Perhaps I should have two routes for these two patterns or something?
Route::get('/admin/{entityType}/{action?}/{id?}', function ($entityType, $action = 'index', $id = null) {
$controllerClass = 'App\Http\Controllers\\' . ucfirst($entityType) . 'Controller';
$controller = new $controllerClass;
$route = app(\Illuminate\Routing\Route::class);
$container = app(\Illuminate\Container\Container::class);
return (new Illuminate\Routing\ControllerDispatcher($container))->dispatch($route, $controller, $action);
abort(404);
});
I'd recommend you to define a route for every controller explicitly. This is the best way to build a maintainable app.
Also, if using one route and one method is an option (with right architecure it is) use one route:
Route::get('/admin/{entityType}/{action?}/{id?}', 'Controller#method');
And one entry point:
public function method($entity, $action = null, $id = null)
{
// Handle request here.
https://laravel.com/docs/5.3/routing#parameters-optional-parameters
Related
I have a route and I need to know a controller that would be used for it.
I know how to find a controller for the current route:
Illuminate\Support\Facades\Route::currentRouteAction();
But how can I do the same for other routes?
Route Facade is the answer. It can return Illuminate\Routing\RouteCollection object.
and then you can get Illuminate\Routing\Route object by route name.
Every Route triggers multiple actions such as middleware and controller methods. So we need only the controller.
use Illuminate\Support\Facades\Route as RouteFacade;
/*#var $route Illuminate\Routing\Route*/
$name = 'admin.reports.my-report.get-filters'; // sample route name
$route = RouteFacade::getRoutes()->getByName($name);
$controllerAction = $route->action['controller'];
$controller = explode('#', $controllerAction)[0];
logger($controller);
P.S.
In cases like that - remember to make a unit test for this functionality to be sure it works as you upgrade your laravel.
I'm building a website using Codeigniter and I really like how in the MVC pattern URLs are used to reference controller methods. It seems very logical and intuitive however, I seem to be running in an array of issues with this very pattern!
So I am building an events website and currently I'm passing everything through one main Site controller, passing a number of parameters:
public function index($page = NULL, $city = NULL, $type_venue = NULL, $slug = NULL)
{
// if the page argument is empty show the homepage
if( ! ($page))
{
$page = 'home';
}
// create an array for passing to the views
$data = array(
'title_city' => $city,
'title_type_venue' => str_replace('-', ' ', $type_venue),
'locations' => $this->locations_model->load(),
'events' => $this->events_model->load($city, $type_venue, $slug),
'venues' => $this->venues_model->load($city, $slug)
);
// construct the page layout with the following views
$this->load->view('partials/head', $data);
$this->load->view('partials/header', $data);
$this->load->view('content/'.$page, $data);
$this->load->view('partials/footer');
}
This works fine, in that it loads content for the following URLs:
site.com/events/bristol/open-mic/city-varieties/another-incredible-event
site.com/events/bristol/open-mic/city-varieties/
site.com/events/bristol/open-mic/
site.com/events/bristol/
However if I want to pass anything else through this controller that isn't an event, i.e. register/user, I have to write a specific route for this!
Worth noting my routing is:
$route['(:any)'] = 'site/index/$1';
I could write separate controllers for each entity, i.e. events, venues, cities but each one would look largely like the above (correct?) in that each would need the parameters to get the data.
My question is - what is the best practice approach for developing long query strings like this? Is a single controller correct? It doesn't feel like it, but then multiple controllers would violate DRY, just because they all need so much similar data. Any help appreciated!
Avoid putting everything into a single controller; even further, in each controller, avoid putting everything into a single index function.
There is no need to write specific controllers for each function in Codeigniter - suggest you read that part again in the manual. Most of your routing will be done automatically for you if you follow the normal guidelines.
The more you try to use a single controller or function, the more you will have to add untestable, unmanageable, unscalable conditional code later.
I'm attempting to create dynamic routing in Laravel for my controllers - I know this can be done in Kohana, but I've been unsuccessful trying to get it working with Laravel.
This is what I have right now:
Route::get('/{controller}/{action?}/{id?}'...
So I would like to call controller/method($id) with that.
Ideally this is what I would like to do:
Route::get('/{controller}/{action?}/{id?}', $controller . '#' . $action);
And have it dynamically call $controller::$action.
I've tried doing this:
Route::get('/{controller}/{action?}/{id?}', function($controller, $action = null, $id = null)
{
$controller = new $controller();
$controller->$action();
});
But I get an error message: Class Controller does not exist.
So it appears that Laravel is not including all the necessary files when the controller extends the BaseController.
If I use $controller::$action() it tells me I can't call a non-static function statically.
Any ideas for how to make this work?
You can auto register all controllers in one fell swoop:
Route::controller( Controller::detect() );
If you're using Laravel 4 (as your tag implies), you can't use Controller::detect() anymore. You'll have to manually register all the controllers you want to use.
After reading that Laravel doesn’t support this anymore, I came up with this solution:
$uri = $_SERVER['REQUEST_URI'];
$results = array();
preg_match('#^\/(\w+)?\/?(\w+)?\/?(\w+)?\/?#', $_SERVER['REQUEST_URI'], $results);
// set the default controller to landing
$controller = (empty($results[1])) ? 'landing' : $results[1];
// set the default method to index
$method = (empty($results[2])) ? 'index' : $results[2];
Route::get('{controller?}/{action?}/{id?}', $controller . '#' . $method);
// now we just need to catch and process the error if no controller#method exists.
I would like to know if there is a possibility in Laravel 4 to actually call function of controller based on parameter given. For example if I have route like :
'auth/{action}
then is there a way to call controller action based on 'action' param ? In Kohana I could write something like:
'auth/<action>' -> defaults (controller=>'UserController',action=>'<action>'
Well not exacly like that but you know what I mean :) Anyway if there is no chance to do that then do I have to split my route to single routes ?
Sounds like you just need to route to the controller with auth being the base URI.
Route::controller('auth', 'AuthController');
This controller (AuthController) now expects your methods to be prefixed with the HTTP verb they should respond to. You can also use the getIndex method to respond to the base URI, which in this case is auth.
An example controller might look something like this:
class AuthController extends Controller {
public function getIndex()
{
return 'Index page'; // Responds to localhost/auth
}
public function getLogin()
{
return 'Login page'; // Responds to localhost/auth/login
}
}
There is one thing you should be aware of. If you do Route::controller('/', 'HomeController'); then it should be LAST. Any routes after it will not get called because of Laravel automatically adding a "missing method" route that will catch anything that isn't matched by a routable method on the controller.
More on RESTful controllers at the official documentation.
I haven't tested this exact code, but I used something similar and it worked:
Route::any('auth/{action}', function($action){
$controller = new UserController();
$controller->$action();
});
You might find you also need to handle parameters, like this:
Route::any('auth/{action}/{param}', function($action, $param){
$controller = new UserController();
$controller->$action($param);
});
You could even tweak it to cover all your controllers:
Route::any('{controller}/{action?}/{param?}', function($controller,$action='index',$param=null)
{
$controller = str_replace(' ', '', ucwords(str_replace('-', ' ', $controller))).'Controller';
$controller = new $controller;
$action = lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $action))));
return $controller->$action($param);
});
If you wanted to have an index action with a parameter, that wouldn't work, but otherwise, it seems to work well enough. It's also not going to handle a second parameter, if you were wanting to do that.
Lots of ways you could extend this idea.
Jason's answer is more correct, (matches the docs, cleaner code, etc.) but if you didn't want to think about HTTP methods, or you wanted a sort of master route to handle nearly every request, this is an option.
In Laravel 3, you could call a controller using the Controller::call method, like so:
Controller::call('api.items#index', $params);
I looked through the Controller class in L4 and found this method which seems to replace the older method: callAction(). Though it isn't a static method and I couldn't get it to work. Probably not the right way to do it?
How can I do this in Laravel 4?
You may use IoC.
Try this:
App::make($controller)->{$action}();
Eg:
App::make('HomeController')->getIndex();
and you may also give params:
App::make('HomeController')->getIndex($params);
If I understand right, you are trying to build an API-centric application and want to access the API internally in your web application to avoid making an additional HTTP request (e.g. with cURL). Is that correct?
You could do the following:
$request = Request::create('api/items', 'GET', $params);
return Route::dispatch($request)->getContent();
Notice that, instead of specifying the controller#method destination, you'll need to use the uri route that you'd normally use to access the API externally.
Even better, you can now specify the HTTP verb the request should respond to.
Like Neto said you can user:
App::make('HomeController')->getIndex($params);
But to send for instance a POST with extra data you could use "merge" method before:
$input = array('extra_field1' => 'value1', 'extra_field2' => 'value2');
Input::merge($input);
return App:make('HomeController')->someMethodInController();
It works for me!
bye
This is not the best way, but you can create a function to do that:
function call($controller, $action, $parameters = array())
{
$app = app();
$controller = $app->make($controller);
return $controller->callAction($app, $app['router'], $action, $parameters);
}
Route::get('/test', function($var = null) use ($params)
{
return call('TestController', 'index', array($params));
});
Laurent's solution works (though you need a leading / and the $params you pass to Request::create are GET params, and not those handled by Laravel (gotta put them after api/items/ in the example).
I can't believe there isn't an easier way to do this though (not that it's hard, but it looks kinda hackish to me). Basically, Laravel 4 doesn't provide an easy way to map a route to a controller using a callback function? Seriously? This is the most common thing in the world...
I had to do this on one of my projects:
Route::controller('players', 'PlayerController');
Route::get('player/{id}{rest?}', function($id)
{
$request = Request::create('/players/view/' . $id, 'GET');
return Route::dispatch($request)->getContent();
})
->where('id', '\d+');
Hope I'm missing something obvious.
$request = Request::create('common_slider', 'GET', $parameters);
return Controller::getRouter()->dispatch($request)->getContent();
For laravel 5.1
It's an Old question. But maybe is usefull. Is there another way.
In your controller: You can declare the function as public static
public static function functioNAME(params)
{
....
}
And then in the Routes file or in the View:
ControllerClassName::functionNAME(params);