Laravel routing functionality allows you to name a resource and name a controller to go with it. I am new to Laravel and would like to know if anyone knows how to extend the resources method in the route class provided.
Basically say I have: (which works fine)
/invoices
But say I want:
/invoices/status/unpaid
How is this achievable?
To see the basics of what I am doing check:
http://laravel.com/docs/controllers#resource-controllers
Resource controllers tie you into a specific URLs, such as:
GET|POST /invoices
GET|PUT /invoices/{$id}
GET /invoices/create
and so on as documented.
Since, by convention, GET /invoices is used to list all invoices, you may want to add some filtering on that:
/invoices?status=unpaid - which you can then use in code
<?php
class InvoiceController extends BaseController {
public function index()
{
$status = Input::get('status');
// Continue with logic, pagination, etc
}
}
If you don't want to use filtering via a query string, in your case, you may be able to do something like:
// routes.php
Route::group(array('prefix' => 'invoice'), function()
{
Route::get('status/unpaid', 'InvoiceController#filter');
});
Route::resource('invoice', 'InvoiceController');
That might work as the order routes are created matter. The first route that matches will be the one used to fulfill the request.
Related
I am developing an application in the Laravel 5.2 which must have a friendly URL-s. This is not problem with the regular way, where the {slug} wildcard is handled by a controller but I want to make it in a different way.
For now I have only two controllers:
ProductsController#show to show details product
CategoriesController#show to show selected category with listing products which are assigned to it
And routes:
Route::get('product/{product}', 'ProductsCategory#show')->name('product.show');
Route::get('category/{category}', 'CategoriesController#show')->name('category.show');
So when I want to echo a just use route('product.show', compact('product'))
Nothing special until I want to handle different URL-s which are fetched from a database. I thought it will be possible to make an another routes which are assigned to existing and when I use a route(...) helper it will be handled automatically. But it is not. So for example I have a new URL:
domain.com/new-url-for-product.html
so by route it should be assigned to the regular 'product.show' route with some ID, which is handled by route model binder for {product} wildcard. The same for route(..) helper it should print friendly URL-s.
I don't know if my strategy is good. How do you handle with the similar problems?
of course route will handle this automatically. have a look into this. I am just giving you example, you have to set this code as per your need.
route('product.show', 'product'=>'new-url-for-product.html');
will generate this
domain.com/new-url-for-product.html
route('product.show', 'product'=>'new-url2-for-product.html');
will generate this URL
domain.com/new-url2-for-product.html
and so on, and then you have to handle all this in your controller method.
eg: your controller method for this route is ProductsCategory#show which is
public function show($product){
if($product == 'new-url-for-product.html'){
//perform this
}
if($product == 'new-url2-for-product.html'){
//perform this
}
}
this just an example, you have to map this according to your need
Edited Here is the example I tested it
Route::get('/product/{product}.html', ['as'=>'product.show', 'uses'=>'ProductsCategory#show']);
In Laravel, we can get route name from current URL via this:
Route::currentRouteName()
But, how can we get the route name from a specific given URL?
Thank you.
A very easy way to do it Laravel 5.2
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1'))->getName()
It outputs my Route name like this slug.posts.show
Update: For method like POST, PUT or DELETE you can do like this
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST'))->getName()//reference https://github.com/symfony/http-foundation/blob/master/Request.php#L309
Also when you run app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST')) this will return Illuminate\Routing\Route instance where you can call multiple useful public methods like getAction, getValidators etc. Check the source https://github.com/illuminate/routing/blob/master/Route.php for more details.
None of the solutions above worked for me.
This is the correct way to match a route with the URI:
$url = 'url-to-match/some-parameter';
$route = collect(\Route::getRoutes())->first(function($route) use($url){
return $route->matches(request()->create($url));
});
The other solutions perform bindings to the container and can screw up your routes...
I don't think this can be done with out-of-the-box Laravel. Also remember that not all routes in Laravel are named, so you probably want to retrieve the route object, not the route name.
One possible solution would be to extend the default \Iluminate\Routing\Router class and add a public method to your custom class that uses the protected Router::findRoute(Request $request) method.
A simplified example:
class MyRouter extends \Illuminate\Routing\Router {
public function resolveRouteFromUrl($url) {
return $this->findRoute(\Illuminate\Http\Request::create($url));
}
}
This should return the route that matches the URL you specified, but I haven't actually tested this.
Note that if you want this new custom router to replace the built-in one, you will likely have to also create a new ServiceProvider to register your new class into the IoC container instead of the default one.
You could adapt the ServiceProvider in the code below to your needs:
https://github.com/jasonlewis/enhanced-router
Otherwise if you just want to manually instantiate your custom router in your code as needed, you'd have to do something like:
$myRouter = new MyRouter(new \Illuminate\Events\Dispatcher());
$route = $myRouter->resolveRouteFromUrl('/your/url/here');
It can be done without extending the default \Iluminate\Routing\Router class.
Route::dispatchToRoute(Request::create('/your/url/here'));
$route = Route::currentRouteName();
If you call Route::currentRouteName() after dispatchToRoute call, it will return current route name of dispatched request.
As I am new to the laravel 4 after spending some few months in Codeigniter, I went through the lots of tutorials about laravel and one thing I want to be clear is what is the actual difference between Routes and Controller in laravel, because we can create and generate view in both controller and routes too. Will anyone explain me in brief when to use routes and Controller in laravel? Because in other Framework we need routes to specify some particular URL's within the apps and Controller were used to do some real tasks but in laravel I didnt get the main concept of Routes except the routing mechanism?
In Laravel, you can totally skip controllers and do the task of performing business logic and generating the view in the routes.
E.g I have a link b2.com/getUsers so in routes.php I can write:
Route::get('/getUsers',function()
{
$users=User::all(); //select * from users
return View::make('allUsers')->with('users',$users);
}
So, here to serve the request b2.com/getUsers, we didn't use controller at all and you can very well do this for handling all requests in your application, both get and post.
But then, if your application is large and have 500+ url's with complex business logic then imagine putting everything in one routes.php. It will totally make it criminally messy and whole purpose of architecture will be defeated. Hence what we usually do is, reserve routes.php for routing only and write all business logic (along with generation of views inside controllers)
So the same example can be solved as:
To handle link: b2.com/getUsers, in routes.php
Route::get('/getUsers', array('before' => 'auth', 'uses' => 'MyController#getUsers'));
MyController has the method getUsers defined like this:
public function getUsers()
{
$users=User::all(); //select * from users
return View::make('allUsers')->with('users',$users);
}
I usually create a controller for related activities e.g for login/signup/logout. I create AuthController and all the links related to those activities are routed to AuthController through routes.php.
The fact that you can get views or do a lot of things in Routes::any() is against MVC and separation of logic.
In Route::get("admin", function(){}), you indeed have a fast access to your route callback, which otherwise in a standard fashion must just be bound to controller. But Laravel allows you to do your job there in a closure (function(){}), instead of binding it to a controller. Anyway, it lets you, but you'd better avoid it. In Route::get() you only should go with your 'routing' and nothing more.
There is no reason for you to use callbacks in Route unless for testing or some trivial requests. So, better to avoid this:
Route::get("admin", function(){
return View::make("admin_index");
});
And rather go with this:
Route::controller("admin", "AdminController");
And in your AdminController.php :
// I mean create a file named AdminController.php in controllers directory under app.
class AdminController extends Controller
{
function getIndex()
{
return View::make("admin_index");
}
}
Read more about Route::controller and restful controllers.
Some Notes:
Having the ability to add closures in your Routes allows you to make
complex decisions on Routes and have a powerful routing system.
These callbacks let you add conditions to your route.
Having Controller separated from you Routes makes you application
more extensible, less confusing and makes other coders more
comfortable in future.
It allows you to focus better on your problem and finding solution,
this physical separation is very important. Having View::make()
inside your Route stirs all problems into each other and makes up a
confusion for the coder.
Let's see what we have in both cases:
In CodeIgniter, a route is just pointing your request to a specific method of your controller:
$route['blog/joe'] = "blogs/users/34";
Here when you visit application.com/blog/joe, you will invoke the controller BlogsController, call the method users() and pass 34 as the first parameter. Nothing else. As they put it, a route in CI is just a one-to-one relationship between a URL string and its corresponding controller class/method.
Now, in Laravel, you have a lot of possibilities:
You can directly return a simple response
You can return a view
You can point the request to a specific controller and a method
You can write some logic in a closure and then decide what you want to do
You can add some additional functionality to them, like attaching filters, checking parameters upon a regex, give them separate names, etc., but this is the main functionality.
What's the reason for being able to do so much stuff? It gives you the power to use them in any way you need. Examples:
Need a small website, rendering static HTML? Use them like this:
Route::get('/', function()
{
return View::make('greeting');
});
Need a bigger application using the traditional MVC pattern? Use like this:
Route::get('user/{id}', 'UserController#showProfile');
Need a RESTful approach? No problem. This will generate routes for all the CRUD methods:
Route::resource('photo', 'PhotoController');
Need something quick and dirty to handle a specific Ajax request? Keep it simple:
Route::post('foo/bar', function()
{
return 'Hello World';
});
TL;DR: For very simple things without or with very little logic, use them instead of controllers. Otherwise, always stick to the MVC principles and route to your controllers, so that they're the ones who do the actual work.
In Laravel, we can get route name from current URL via this:
Route::currentRouteName()
But, how can we get the route name from a specific given URL?
Thank you.
A very easy way to do it Laravel 5.2
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1'))->getName()
It outputs my Route name like this slug.posts.show
Update: For method like POST, PUT or DELETE you can do like this
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST'))->getName()//reference https://github.com/symfony/http-foundation/blob/master/Request.php#L309
Also when you run app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST')) this will return Illuminate\Routing\Route instance where you can call multiple useful public methods like getAction, getValidators etc. Check the source https://github.com/illuminate/routing/blob/master/Route.php for more details.
None of the solutions above worked for me.
This is the correct way to match a route with the URI:
$url = 'url-to-match/some-parameter';
$route = collect(\Route::getRoutes())->first(function($route) use($url){
return $route->matches(request()->create($url));
});
The other solutions perform bindings to the container and can screw up your routes...
I don't think this can be done with out-of-the-box Laravel. Also remember that not all routes in Laravel are named, so you probably want to retrieve the route object, not the route name.
One possible solution would be to extend the default \Iluminate\Routing\Router class and add a public method to your custom class that uses the protected Router::findRoute(Request $request) method.
A simplified example:
class MyRouter extends \Illuminate\Routing\Router {
public function resolveRouteFromUrl($url) {
return $this->findRoute(\Illuminate\Http\Request::create($url));
}
}
This should return the route that matches the URL you specified, but I haven't actually tested this.
Note that if you want this new custom router to replace the built-in one, you will likely have to also create a new ServiceProvider to register your new class into the IoC container instead of the default one.
You could adapt the ServiceProvider in the code below to your needs:
https://github.com/jasonlewis/enhanced-router
Otherwise if you just want to manually instantiate your custom router in your code as needed, you'd have to do something like:
$myRouter = new MyRouter(new \Illuminate\Events\Dispatcher());
$route = $myRouter->resolveRouteFromUrl('/your/url/here');
It can be done without extending the default \Iluminate\Routing\Router class.
Route::dispatchToRoute(Request::create('/your/url/here'));
$route = Route::currentRouteName();
If you call Route::currentRouteName() after dispatchToRoute call, it will return current route name of dispatched request.
So i have checked out
PHP - Routing with Parameters in Laravel
and
Laravel 4 mandatory parameters error
However using what is said - I cannot seem to make a simple routing possible unless im not understanding how filter/get/parameters works.
So what I would like to do is have a route a URL of /display/2
where display is an action and the 2 is an id but I would like to restrict it to numbers only.
I thought
Route::get('displayproduct/(:num)','SiteController#display');
Route::get('/', 'SiteController#index');
class SiteController extends BaseController {
public function index()
{
return "i'm with index";
}
public function display($id)
{
return $id;
}
}
The problem is that it throws a 404
if i use
Route::get('displayproduct/{id}','SiteController#display');
it will pass the parameter however the URL can be display/ABC and it will pass the parameter.
I would like to restrict it to numbers only.
I also don't want it to be restful because index I would ideally would like to mix this controller with different actions.
Assuming you're using Laravel 4 you can't use (:num), you need to use a regular expression to filter.
Route::get('displayproduct/{id}','SiteController#display')->where('id', '[0-9]+');
You may also define global route patterns
Route::pattern('id', '\d+');
How/When this is helpful?
Suppose you have multiple Routes that require a parameter (lets say id):
Route::get('displayproduct/{id}','SiteController#display');
Route::get('editproduct/{id}','SiteController#edit');
And you know that in all cases an id has to be a digit(s).
Then simply setting a constrain on ALL id parameter across all routes is possible using Route patterns
Route::pattern('id', '\d+');
Doing the above will make sure that all Routes that accept id as a parameter will apply the constrain that id needs to be a digit(s).