complex routing with symfony - php

Looking for best practice of Symfony (4) routing action for my URL schema.
Let's say I'm detecting GEO IP and showing different language for web users even in URL.
So what best practice would be to develop routing?
link examples,
when a user comes from the USA:
example.com/static_link_in_en/additional_static_element_in_en/special_static_element_in_en/extra_static_attribute_in_en
example.com/product_name_in_en/additional_element_of_product_in_en/special_element_for_this_item_in_en/extra_attribute_in_en
when a user comes from France:
example.com/static_link_in_fr/additional_static_element_in_fr/special_static_element_in_fr/extra_static_attribute_in_fr
example.com/product_name_in_fr/additional_element_of_product_in_fr/special_element_for_this_item_in_fr/extra_attribute_in_fr
another, maybe better example:
example.com/tag - static route, generates a tag cloud in EN
example/tag/ - static route, but displays an error or 404 page, because we are not searching in the database with an empty string in EN
example.com/tag/red - searching in the database with keyword 'red'
example.com/tag/blue - exceptional keyword, we have reserved tag 'blue' so the script will not be searching in the database, the static route will be used in EN
the same actions just translated in the French language:
example.com/marque
example.com/marque/
example.com/marque/rouge
let's say URL deep level can be up to 10 elements, any suggestions?

A better policy is to include the locale in the URL. This is fully-supported by the routing system using the special _locale parameter.
# config/routes.yaml
contact:
path: /{_locale}/contact
controller: App\Controller\ContactController::index
requirements:
_locale: en|fr|de
When using the special _locale parameter in a route, the matched locale is automatically set on the Request and can be retrieved via the getLocale() method. In other words, if a user visits the URI /fr/contact, the locale fr will automatically be set as the locale for the current request.
You can now use the locale to create routes to other translated pages in your application.
Source : https://symfony.com/doc/current/translation/locale.html
Symfony doesn't support defining routes with different contents depending on the user language. In those cases, you can define multiple routes per controller, one for each supported language; or use any of the bundles created by the community to implement this feature, such as JMSI18nRoutingBundle and BeSimpleI18nRoutingBundle.
Source : https://symfony.com/doc/current/routing.html#translating-routes

Related

Multilingual Symfony2 website

I have a huge problem that I couldn't resolve in the last few days. I don't expect you to give me a final solution because I think the problem is not so simple.
So I have a huge Symfony2 application that was built with the idea to work only with one language. All routes are in their responsible controllers. Now I want to prefix all routes with the selected language so could have:
myapp.com/en/news
myapp.com/fr/news
etc...
And if it is possible the default language should not be shown in the route - if English is my default language I want only myapp.com/news. The problem is that I can't just go through all routes and make them work both with optional parameter about the language and also work without that parameter. If it is not possible I am ok en to be presented in the route too.
I tried the jmsi18nroutingbundle because many other people suggested it but when I setup the config.yml as it is said only some of the routes get prefix when I check them with app/console debug:router. And because of that I am either not configuring it well or it has some limitations.
My question is how do you handle multilingual websites and their routes - do you start with the idea about multilingual system from the beginning and create at least two routes for each action one with optional parameter about the language and one without that parameter or you have more global solution to handle all routes?

Symfony2 - What is the point of specifying your own route names?

Currently learning Symfony. Just wondering about route names, which the book doesn't really seem to say in advance what they are for.
As I understand it, #Route annotations are given a default name based on the bundle, controller and action name. So what's the point in specifying your own route names?
Is it good practice to either leave all routes to have a default name?
Is it better practice to specify your own name for every route?
What if two routes have the same name?
How am I expected to know if I'm using a route name which is already taken, perhaps in another bundle?
Route names are important because your code will often need to build url's. The route name is how you specify which url to construct. Names are also handy for listeners (such as authentication) which process specific url's.
Annotation generated default names are fine but tend to be long and could change on you.
Yes, I would give every route a custom name for convenience and readability. Plus, I don't use annotations. To me at least, storing routes in a central file make the code easier to maintain. It avoids the need to search through multiple controller files trying to determine which code handles which request.
Routes with the same name will replace any previously loaded route. It's useful if you wish to override a route from a third party bundle.
Namespace your route by using some form of your bundle name as a prefix.

Use cases for generated URLs in Symfony2?

Coming from a straight PHP and Drupal background, I am recently learning the Symfony2 framework. Currently I am in the routing chapter of the book. This is probably a simple question.
What are some real world use cases for why one would want to generate URLs in a Symfony app? I understand the code but I'm having a bit of trouble determining its practical applications.
I'm referring to this section should you need a refresher.
As always, thank you!
P.S. Symfony is amazing. :)
Basically, you need to generate a URL whenever you need to link to anywhere in your application.
Let's say that you have an application that needs to manage some users. This means that you will probably have URLs like /user/create, /user/edit/(user id) and /user/remove/(user id).
Whenever you display a link to edit a user you need to know on what URL you can find the page that allows you to edit a user. So you need to link to /user/edit/(user id). One solution would be to have this as a fixed link so that in your code you could just write
edit this user
But what if you want to change this URL scheme? Let's say someone is unhappy with the term "user", after all the humans managed by this system are not only users, they are actually "person"s! So now you need to change all the URLs containing "user". Probably there are quite a few places in your app where you have had to hardcode these URLs and now you will need to find and change all of them. Eugh.
But fear not, for Symfony routing comes to the rescue!
Instead of hardcoding these URLs, we can simply let the Symfony router generate them for us. This means that we first need to tell Symfony which routes we have, e.g. by adding the following YAML code to our routes config file:
user_edit:
path: /user/edit/{userId}
defaults: { _controller: AppBundle:User:edit }
requirements:
userId: \d+
This tells our application "Okay, whenever somebody requests a page that looks like /user/edit/{userId}, then you need to call the editAction method in our UserController class in the AppBundle namespace and you need to pass the userId as a parameter. Oh, and also you should only call the controller if userId is a valid integer with at least one number."
So this is how Symfony knows how to map URLs to controllers. But the goodness that comes along with it is that we can use this information for the reverse way as well.
Usually, in our application we do not really care about what the URL looks like for a certain action we want to perform. All we know is that when clicking a certain link, then the browser should jump to a page that allows me to edit a user. And since we just defined a route that takes us right there, we can have Symfony generate the correct URL to achieve just that.
So in your view you can now discard the hardcoded URL from earlier and instead replace it with a route generated by the Symfony router:
edit this user
Now whenever you need to change what the URL actually looks like all you need to do is edit your routing config and not a lot of separate views.
Because, imagine you want to change a given page URL and you've hardcoded it in 10 Twig templates. You will have to modify all these files. On the opposite, when using the routing component:
You would only have to change the URL where the route is defined.
The routing component "takes" care of the current environment you are using (dev, prod...)
Also note that is a bad practice to "switch environment", a typical issue is to hardcode an URL in a Javascript. In this case you can expose your Symfony2 routes in the Javascript by using a bundle like FOSJsRoutingBundle.
I almost immediately realized their use and now I feel silly. :) For those who stop by this question in the future:
Notes about Generating URLs:
Similar to the Drupal l() function, sometimes you need to generate links inside your application based on a variety of parameters.
You don't always want to hardcode your links in case you decide to change paths sometime down the line.
In summary: Think of this as an alternative to using straight anchor tags with href elements all over the app and, instead, keeping things dynamic.
Use case
In the project I'm working I use generateUrl to redirect the user
after creating, editing an entity.
For example after creating a Service entity, I redirect the user to the view
of the just created Service.
Controller
return $this->redirect($this->generateUrl('myentity_view', array('id'=> $id)));
Additional note
In twig files, you can use the path function which call the routing component and generate url with given route name and parameters.

PHP MVC Custom URI Without Controller Specified

What I am looking for is a way to implement MVC without needing the controller specified in the uri. The problem I am finding is that the only way I can have a simple url like example.com/blue-widget is if I first query the database to determine if the product table has the custom uri specified of "blue-widget" if it returns a row at all I can then route to the product controller. The problem with this method is that I have to query each table and then check if any rows are returned to determine which controller to route to. This feels like overkill. Especially if I end up needing more than a handful of controllers.
I would like to keep the traditional functionality of a typical MVC application so that the routing is controller/action/arguments. I simply want to add the feature so that the routing portion could determine what controller to use based on a custom-uri preferably without needing to query the database, but if that is the only option I will have to accept that and maybe minimize it to a single query that determines which table it is found in.
In short traditionally for me to access the product page of blue-widget I would need to have a url at minimum like this:
example.com/product/blue-widget
What I am looking for is a url like this:
example.com/blue-widget
but having some sort of logic to determine if the uri blue widget is a product, or a page, or a category etc without needing to create any kind of map of every possible custom uri.
This is a custom MVC implementation and not based on any particular framework.
There are two primary options as I see it:
have your router configured so that by default it matches classical /:controller/:action/:id structure, and if mo match is found, then it defaults to /:id pattern, with default values for :controller and :action segments.
have your router to primarily function using APCu cache, where each :id has a matching entry of controllers and action names associated with it. If the cache lookup fails, then it falls back standard pattern-matching as a backup.
Maybe this post is also useful: https://stackoverflow.com/a/19309893/727208

Is this the right way to structure MVC (understanding gathered from teresko's posts) + incorporating multi-language support into the framework?

DO I UNDERSTAND MVC CORRECTLY?
I've read many posts by the user teresko and I think I finally understand the real concept of modern MVC. I made a diagram of how I understand it to be, I'm hoping somebody can confirm this for me and that maybe this diagram will help some other users.
IS MY PLANNED DIRECTORY STRUCTURE "SMART"?
Also I'd like to get into folder structure. I'm not too sure that I plan on using a very smart/efficient way of structuring them. In my root directory I have 4 folders. The model folder contains 3 subdirectories called data_mappers, domain_objects and services. The presentation folder contains 2 subdirectories naturally called views and controllers. The 3rd folder in my root directory is one of the ones I'm less sure on called lib and contains another 3 sub-folders, scripts, themes, and templates (is this a good place for templates that will be called by view?). And the last folder which I have is called config (if anybody can think of a better name suggestions are welcomed). This is where I will place the router and base classes and any files of that type. I'm not just going for functionality with my framework, I want it to be technically sound, any suggestions/advise are welcome, I want to make sure my understanding up to this point is correct before I start making the index and router etc so I don't have to start over once again. Thanks
IS THIS A GOOD WAY TO GO ABOUT MULTI-LANGUAGE SUPPORT IN MVC?
(A little off topic, but I need to incorporate this into planning my MVC framework, it may be helpful to other users, and in order to answer the question clearly and concisely the structure of the website needs to be understood and it happens to be described in detail in the above)
My website must be available in English and French, both of which I happen to speak. I plan to create two more sub-folders within the templates folder, one called en and and another fr and simply place all of the English templates in en folder and translate them into French and place them in fr folder and set up the website so www.mywebsite.com/en/home shows the template in English and www.mywebsite.com/fr/home shows the template in French.
There will be a main language selection page that will redirect you to either en or fr. Then, I plan to store the 2 letter language code in a $_SESSION variable. The view will use this $_SESSION variable to pick which template to display. This is the way I had done it on the old version of my website. Is this a good way of accomplishing multi-language support in MVC or is there some better way?
What if I want to make the url also appear in French? If I put the view files inside more subfolders and instead of naming them like this: "/presentation/views/news/news.php" name them like this: "/pre
sentation/views/news_nouvelles/news.php" and then make the router (urls and routing is one of my weaknesses, but I will learn on my own ;) store everything before the "_" in a variable corresponding to the English url, and everything after corresponding to the French url. This is just a theory and I have not attempted what is in this last paragraph yet, the multi-language support is more for discussion than a question, although once again, it is relevant because a thorough understanding of my framework is needed to answer/discuss language support.
This really isn't all that related to MVC.
When you are implementing multilingual URLs, it's all about routing.
Routing
Basically, by the time your code gets to calling stuff on controller, you should have fully initialized request instance, that has recognized language from the URL and translated all the parts of input into something useful for creating instances.
$request = new Request($query);
$router = new Router;
$router->import('/path/to/routes.json');
$router->route( $request );
At this point you would have split the user's query (from example: '/presentation/view/dernières/nouvelles') into recognized segments, based on pattern that was recognized.
Lets take for example, that matted pattern was in config defined as:
/presentation[[/:action[/:filter]]/:resource]
And the produced result is:
resource >> 'nouvelles'
action >> 'view'
filter >> 'dernières'
Internationalization
The translations will usually be stored either in database or in some configuration file.
If your service factory is already initialized at this point, then you can dip into that for facilitating the interaction with this stored config:
$normalizer = new Normalizer( $serviceFactory->create('translation') );
$normalizer->adjust($request);
What you would collect from stored config by translating it would be
translation of the value that user provided in query
language in which that query fragment was written
title which corresponds to the fragment
The data that you get back would look something like:
translation | language | label
-------------------------------------
view | en | view
dernières | fr | latest
nouvelles | fr | news
Note: if you return also the queried value, then you can perform such operations with a single SQL query using .. WHERE translation IN ( <the list> ), which let you collect all in a single request.
These entries are used for two tasks:
actually translating the request so that the matched parameter will actually have classes/methods that correspond to them
for determining, which language user might expect
By the end of this you should have a translated ll the parameters in the Request instance and, based on some criteria, determined and added language parameter to that request.
Note: browser also sends some language-related information to you in the headers. Depending on your setup, you could user Accepted-Language header for figuring out which language user might prefer.
my 2 cents

Categories