one question about Symfony2 and the routes.
In my routing file i have this route:
offers_category:
pattern: /offers/{category}
defaults: { _controller: MyBundle:Offers:category}
In this way i can call any url and all of them will respond with an 200 (HTTP CODE).
The categories list is dynamic, inserted by an admin panel (created with Sonata) and saved on db.
I would like to check if the "category" exist and then respond with 200 or 404.
There is a way to tell to Symfony2 (in the route file) the dictionary available for the placeholder?
I think that i have to check inside my controller calling a query on db, but i hope to find a better or cleaned solution.
Thanks all
I found the solution!
Thanks to #lenybernard for the precious advice on his post.
With the #lenybernard solution i was able to configure a route with an indexed field like:
www.sitename.com/offers/1
but i needed a url like:
www.sitename.com/offers/travel
and to do that i used this method to mapping a label not indexed to an url:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use NameProject\MyBundle\Entity\Category;
/**
* #Route("/offers/{category}")
* #ParamConverter("type", class="NameProjectMyBundle:Category", options={"mapping": {"category": "name"}})
*/
...and everthing works!
There is a simple and nice way called ParamConverter to do but there is nothing to do in the routing file directly (furthermore this is not its role) and you're right, this is ControllerSide :
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use My\Bundle\MyBundle\Entity\Offers\Category;
class OffersController
{
/**
* #param Category $category
* #ParamConverter("category", class="MyBundle:Offers/category")
*/
public function showAction(Category $category)
{
...
}
}
As said in the documentation, several things happen under the hood:
The converter tries to get a MyBundle:Offers/category object from the
request attributes (request attributes comes from route placeholders
-- here id); If no Category object is found, a 404 Response is generated; If a Category object is found, a new category request attribute is defined
(accessible via $request->attributes->get('category')); As for other
request attributes, it is automatically injected in the controller
when present in the method signature. If you use type hinting as in
the example above, you can even omit the #ParamConverter annotation
altogether because it's automatic :
So you just have to cast the variable and the param converter will throw a 404 automatically, cool right ?
use My\Bundle\MyBundle\Entity\Offers\Category;
class OffersController
{
/**
* #param Category $category
*/
public function showAction(Category $category)
{
...
}
}
Related
1) There is this Controller Function I made:
/**
* #Route("/add", name="add" )
*/
public function add_question()
{
return $this->render("add.html.twig",[]);
}
2) When I go to http://127.0.0.1:8000/add Symfony returns this error:
The controller must return a response (null given). Did you forget to add a
return statement somewhere in your controller?
3) I have bunch of other Controllers that have the same structure and work correctly.
4) Whenever I want to add a new Controller, this error is being thrown.
5) I tried to return Response object - it doesn't work either.
EDIT:
my config/routes/annotations.yaml file:
controllers:
resource: ../../src/Controller/
type: annotation
Okay, I found the solution.
I have a few Controllers in my project.
One of them was located almost on top of the file and had following routing:
/**
* #Route("/{question_id}", name="questions" ) <--QUESTION ROUTE
*/
the logic of controller was like that:
function GenerateQuestionPage($question_id)
1find question in my database whose $id is equal to question_id.
2a. If you find such question then render proper twig template.
2b. If You don't find such question then you echo "No such question found";
Whenever I wanted to go to some page whose Routing was defined below the definition of the GenerateQuestionPage Controller, Symfony thought I was trying to use Question Route with
nonnumerical question_id. Then it was searching for such question in DB but obviously couldn't find so it wasn't returning Response object.
My solution was to relocate GenerateQuestionPage controller to the bottom of the file.
I'm building a shopping app using Laravel where each product's URL must be kept concise.
Instead of using the following permalink structure: (which is common, but unfavorable)
www.example.com/products/{product-slug}
I want to use this permalink structure:
www.example.com/{product-slug}
In order to accomplish this, I'm using an implicit route model binding in my routes file:
Route::get( '{product}', function ( App\Product $product ) {
return view( 'product' ); // this works, but is not what I want
});
And I am overriding the lookup behavior in my Product model:
class Product extends Model
{
public function getRouteKeyName()
{
return 'slug'; // use the 'product.slug' column for look ups within the database
}
}
Now, according to Laravel's documentation:
Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name.
(View Source)
So I know that Laravel will match the {product} variable to a product stored within my database, or return a 404 response if one is not found.
And this all makes sense to me...
However...
Each product page is unique, so after the route matches a {product}, that {product} object needs to be passed to a controller for further processing.
So how do I pass this route to a controller, if I want to keep my implicit model binding?
Point the route to a controller function.
This would be your route (I named the controller ProductController and pointed it to show function, but you can rename both to your liking):
Route::get( '{product}', 'ProductController#show');
And this would be in your ProductController:
public function show(Request $request, \App\Product $product)
{
// Do stuff with $product
// Return view and pass it the $product variable
return view('product', compact('product'));
}
To answer my own question, I think I've found a great solution that combines my initial approach and devk's response:
Credits to Arjun's Blog for the idea.
As it turns out, you can also perform implicit model binding within a controller by passing an Eloquent model as a dependency:
/* App/Http/Controllers/ProductController.php */
/**
* Get the Product object.
*
* #param App\Models\Product
*/
public function show( Product $product )
{
return view( 'product', compact( 'product' ) );
}
Even though we are now referencing the model using a controller, Laravel will still automatically resolve the model. In fact, this behavior is clearly defined within the documentation:
Laravel automatically resolves Eloquent models defined in routes or
controller actions whose type-hinted variable names match a route
segment name.
(View Source)
I must have missed those words when I read it the first time...
Now, in order to set up the {product-slug} route (in the way I wanted to), you must set up your model and route definitions like so:
/* App/Models/Product.php */
class Product extends Model
{
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'slug';
}
}
As mentioned earlier, overriding the getRouteKeyName() method will make Laravel search for a product using it's slug column in the database instead of its id column (which is the default).
/* routes/web.php */
Route::get( '{product}', 'ProductController#show' );
In our routes file, we still name our parameter {product} (instead of {product-slug}) because the name of the parameter must match the name of the Eloquent model.
Using this configuration, requests made on:
www.example.com/{product-slug}
will return a product page if the provided {product-slug} matches one stored inside the database. If a product is not found, a 404 Not Found response will be returned instead.
However, because we are binding this route to the base path /, this means that every URL requested by a client will be passed through this configuration.
To avoid this problem, make sure that your route definitions are in proper order within your routes file (from greatest to least precedence), and use validation when conflicts occur.
I want to have a url with a wildcard at the end site.com/{username} after trying to match url's like site.com/photos and site.com/blog. I'm using annotations and have two controllers.
I found an answer here Ordering of routes using annotations
But the folder structure is different in version 4 and in the answer they are using YAML while I'm using annotations, so I don't fully understand where to put what.
Video Controller
/**
* #Route("/videos", name="videos")
*/
public function index()
{
// Show video homepage
}
User Controller
/**
* #Route("/{username}", name="profile")
*/
public function index()
{
// Hello {username} !
}
In this order, site.com/videos reaches User controller instead of videos. Do I have to switch to manually putting all URL structures in yaml format or is there a way to set priority in annotations?
So far the only method I found was to create a controller starting with the letter "Z", and putting the action/route in there, that seems to run the route last.
The question you linked to is actually very relevant. You are asking just the same as the other guy:
But how can you do this if you define your routes as annotations in your controllers? Is there any way to specify the ordering of this routes in this case?
In Symfony 4, your config/routes.yaml is probably empty and config/routes/annotations.yaml probably like this:
controllers:
resource: ../../src/Controller/
type: annotation
You can replace the above YAML with this – notice that the order does matter:
Video:
resource: App\Controller\VideoController
type: annotation
User:
resource: App\Controller\UserController
type: annotation
Then site.com/videos reaches the video controller first.
You have to define your routes with yml/xml to be able to fully configure their order.
If you really want to use annotation you could add a prefix like user-:
/**
* #Route("/user-{username}", name="profile")
*/
public function index()
{
// Hello {username} !
}
I am working on a school project. while working on a schools detail page I am facing an issue with the URL. My client needs a clean URL to run AdWords. My school detail page URL: http://edlooker.com/schools/detail/4/Shiksha-Juniors-Ganapathy. But he needs it like http://edlooker.com/Shiksha-Juniors-Ganapathy. If anyone helps me out it will be helpful, thanks in advance.
You need to define this route after all routes in your web.php (if laravel 5.x) or in routes.php (if it is laravel 4.2).
Route::get('{school}','YourController#getIndex');
And your controller should be having getIndex method like this,
public function getIndex($school_name)
{
print_r($school_name);die; // This is just to print on page,
//otherwise you can write your logic or code to fetch school data and pass the data array to view from here.
}
This way, you don't need to use the database to get URL based on the URL segment and you can directly check for the school name in the database and after fetching the data from DB, you can pass it to the school details view. And it will serve your purpose.
Check Route Model Binding section in docs.
Customizing The Key Name
If you would like model binding to use a database column other than id when retrieving a given model class, you may override the getRouteKeyName method on the Eloquent model:
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'slug';
}
In this case, you will have to use one front controller for all requests and get data by slugs, for example:
public function show($slug)
{
$page = Page::where('slug', $slug)->first();
....
}
Your route could look like this:
Route::get('{slug}', 'FrontController#show');
Hey all I have a bit of a problem with root annotations in Symfony2.
I have two different controllers that call methods from the same URL positions /test.
Controller 1:
**
* #Route("/test", service="myProject.test.controller.art")
* #Cache(expires="+5 minutes", public=true)
*/
class BlogController
{
/**
* #Route("/{text}", defaults={"text" = null})
* #Route("/topic/{tag}", defaults={"tag" = null})
* #Method({"GET"})
*/
public function listAction(ArtQuery $query)
{
//.................
}
}
Controller 2:
**
* #Route("/test" , service="myProject.test.controller.sitemap"))
* #Cache(expires="+5 minutes", public=true)
*/
class SitemapController
{
/**
* #Route("/sitemap.xml/")
* #Method({"GET"})
*/
public function sitemapAction()
{
//..................
}
}
The problem is that the second Controller is never matched only if is add in my #route("/sitemap.xml/") but I realy want the route to be only #route("/sitemap.xml").
I think the problem is when i input the url /test/sitemap.xml Symfony treats sitemap.xml as /{text} variable route in first controller.
Can I make a exception so that first controller ends as soon as it hits sitemap.xml....?
I read something about requirements but dont quiet understand this concept
The router will use the first route that matches the path.
The only way to prioritize a route over another which could match is to ensure that the stricter requirements are check before the weaker ones.
Normally this would be accomplished by placing the sitemapAction method above listAction.
However since you have a controller for each of these, you will have to put the controllers in the correct order.
To do this you will need to add the controllers to the config individually like this:
app_sitemap:
resource: "#AppBundle/Controller/SitemapController.php"
type: annotation
prefix: /
app_blog:
resource: "#AppBundle/Controller/BlogController.php"
type: annotation
prefix: /
This way the controllers will be iterated in this order.
However it is better if you can give each route a unique path, perhaps:
#Route("/query/{text}", defaults={"text" = null})
according to documentation
Earlier Routes always Win
What this all means is that the order of the routes is very important.
If the blog_show route were placed above the blog route, the URL
/blog/2 would match blog_show instead of blog since the {slug}
parameter of blog_show has no requirements. By using proper ordering
and clever requirements, you can accomplish just about anything.
http://symfony.com/doc/current/book/routing.html
i suggest to use yml or xml file for routing
or you can make a requirement in your first route
/**
* #Route("/{text}", defaults={"text" = null}, requirements={"text" = "^(?!sitemap\.xml)$"})
* #Route("/topic/{tag}", defaults={"tag" = null})
* #Method({"GET"})
*/
public function listAction(ArtQuery $query)
{
//.................
}