Symfony Localized Routes - Optional Locale - symfony

I'd like the following urls to serve the appropriate actions:
/ - indexAction
/fr - indexAction
/foo - detailsAction (slug = foo)
/fr/foo - detailsAction (slug = foo)
I have added the following action methods:
/**
* #Route("/{_locale}", name="home", defaults={"_locale": ""}, requirements={"_locale": "fr|es"})
*/
public function indexAction() {
...
}
/**
* #Route("/{_locale}/{slug}", name="details", defaults={"_locale": ""}, requirements={"_locale": "fr|es"})
*/
public function detailsAction($slug) {
...
}
This works fine if I go to /, /fr and /fr/foo. However when I go to /foo it doesn't find a matching route. I'd appreciate it if someone could show me how to this.
Please note that ideally i'd like to achieve this without having to add multiple #Route annotations for a particular action method. That way I can use the UrlGenerator and point to the same name to produce the localized and none localized route whether I pass the _locale parameter or not.

I have managed to get this to work although my solution is slightly hacky. First I removed {_locale} part of the path, the defaults and requirements from my routes above.
Then when I created my routes I said:
$routes = new RouteCollection();
// Load the routes
...
$routes->addPrefix('/{_locale}', ['_locale' => ''], ['_locale' => '|fr|es']);
This automatically adds the localization bits (removed above) to the routes so it can easily be configured in one place. I changed RouteCollection to my own type with the following:
use Symfony\Component\Routing\RouteCollection as BaseRouteCollection;
class RouteCollection extends BaseRouteCollection {
public function addPrefix($prefix, array $defaults = [], array $requirements = []) {
foreach ($this->all() as $route) {
$route->setPath($prefix . rtrim($route->getPath(), '/'));
$route->addDefaults($defaults);
$route->addRequirements($requirements);
}
}
}
This makes sure the localized home page route doesn't end with a forward slash e.g. /fr/.
Finally I had to override the Route class with the following:
use ReflectionProperty;
use Symfony\Component\Routing\Route as BaseRoute;
class Route extends BaseRoute {
public function compile() {
// Call the parent method to get the compiled route
$compiledRoute = parent::compile();
// Override the regex property
$property = new ReflectionProperty($compiledRoute, 'regex');
$property->setAccessible(true);
$property->setValue($compiledRoute, str_replace('^/', '^/?', $compiledRoute->getRegex()));
return $compiledRoute;
}
}
This is particularly hacky but does save you from having to add a heap more code. All it does is replace the regular expression so that the first forward slash is optional which allows the /foo url to work. Note: You will have to make sure your RouteCollection is a collection of this class and not the Symfony Route class.
Hope this helps.

Related

API to get a content type against URL

I have a scenario where I need to perform some redirection of a not found url
http://localhost/drupal9/node/1/search
the word search is added though a plugin I am using and it is a front-end route not a backend so upon refreshing this url I get Not Found which totally makes sense what I need to do is remove the word search from the URL and redirect to,
http://localhost/drupal9/node/1/
as search is a common word and can be used in other content type I first need to check whether the URL is of my custom content type. let me show you a piece of implementation I already have.
function [module]_preprocess_page(&$variables) {
$query = \Drupal::entityQuery('node')
->condition('type', [module]);
$nids = $query->execute();
if(array_search(2,$nids)){
echo "yes";
}
}
so over here what I am doing is grabbing all the nodes with my content type and grabbing the Nid from URI and matching them and this does work but there is another problem with this.
In the page properties we have an option of ALias so if the user uses a custom alias, then I dont get the Nid in the URI anymore so this logic breaks,
the question may seem a bit tricky but the requirement is simple.I am looking for a unified solution to parse the URL into some drupal API and simply getting back the content type name.The Url may contain a custom alias or a Nid
You can create an EventSubscriber subscribing the event kernel.request to handle the case of URL <node URL>/search.
For detailed steps to create an EventSubscriber, you can see here.
And below is what you need to put in your EventSubscriber class:
RequestSubscriber.php
<?php
namespace Drupal\test\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class RequestSubscriber.
*/
class RequestSubscriber implements EventSubscriberInterface {
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents() {
return [
KernelEvents::REQUEST => 'onKernelRequest',
];
}
public function onKernelRequest($event) {
$uri = $event->getRequest()->getRequestUri(); // get URI
if (preg_match('/(.*)\/search$/', $uri, $matches)) { // check if URI has form '<something>/search'
$alias = $matches[1];
$path = \Drupal::service('path_alias.manager')->getPathByAlias($alias); // try to get URL from alias '<something>'
if (preg_match('/node\/(\d+)/', $path, $matches)) { // if it is a node URL
$node = \Drupal\node\Entity\Node::load($matches[1]);
$content_type = $node->getType();
//... some logic you need
}
}
}
}

Symfony3 get parameters from URL

I discovering symfony3, but im stuck at getting the parameters passed with a link from an action.
In my twig file, im redirecting the user to an action:
<td>go to</td>
And im my action, i'm trying to get the id with:
/**
* #Route("/AfficheDetail", name="esprit_park_affiche")
*/
public function afficheAction()
{
$id = $this->getParameter("id");
return $this->render("#EspritPark/Voiture/affiche.html.twig", array("id" => $id));
}
but each time i get: The parameter "id" must be defined.
like the getParameter isnt returning anything.
I even tried with:
$id = $this->get("request")->get("id");
but i get: You have requested a non-existent service "request". Did you mean one of these: "monolog.logger.request", "request_stack", "router.request_context", "data_collector.request"?
The getParameter() method from the base Controller class is looking up parameters from the service container.
I would make the parameters part of your route. You can then retrieve the values through the action method's parameters:
/**
* #Route("/AfficheDetail/{id}/{serie}/{dateMise}/{marque}", name="esprit_park_affiche")
*/
public function afficheAction($id, $serie, $dateMise, $marque)
{
// ...
}
If you do not add them to the route pattern, they will be accessible through the URL parameters (the current request will be injected automatically if you type hint an argument with the Request class):
public function afficheAction(Request $request)
{
$id = $request->query->get('id');
$serie = $request->query->get('serie');
$dateMise = $request->query->get('dateMise');
$marque = $request->query->get('marque');
// ...
}

How to generate symfony2 translations inside controller?

Symfony2 project. I'm using JMSTranslationsBundle.
Here is a fragment from function inside controller:
if ($user->isAccountConfirmed()) {
$this->toolbar->addInfo('user.account.confirmed');
}
How to generate translation for 'user.account.confirmed' in .xliff file? I mean, what code I should add to this function to be able to translate it?
Looking at the available extraction methods, it explains that there is no automatic extraction for your case available.
You will need to use trans (or any of the other methods explained) in your template or in the controller. Without this hint, the extractor will not be able to find your message. Personally I have used TranslationContainerInterface in one my projects.
With that you simply define a new method in your controller, which returns the "to-be-translated" strings:
<?php
// ...
use JMS\TranslationBundle\Translation\TranslationContainerInterface;
use JMS\TranslationBundle\Model\Message;
class AcmeController extends Controller implements TranslationContainerInterface
{
/**
* {#inheritdoc}
*/
static function getTranslationMessages()
{
return [
Message::create('user.account.confirmed')
];
}
}
An alternate solution would be to directly use the translater service. The call to this service should then again be visible to the extractor. E.g:
/** #var $translator \Symfony\Component\Translation\TranslatorInterface */
$translator = $this->get('translator');
if ($user->isAccountConfirmed()) {
$this->toolbar->addInfo(
$translator->trans('user.account.confirmed')
);
}

How can I dynamically set a parameter in Symfony2?

I'm trying to dynamically set a parameter in Symfony2 (that I cannot statically set in my parameters.yml file). My approach is to use an EventListener:
namespace Acme\AcmeBundle\EventListener;
use Symfony\Component\DependencyInjection\Container;
class AcmeListener
{
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function onKernelRequest()
{
// Dynamically fetch $bar
$bar = fetch('foobar');
// Set parameter
$this->container->setParameter('foo', $bar);
}
}
And my service definition in config.yml looks like this:
service:
kernel.listener.acme_listener:
class: Acme\AcmeBundle\EventListener\AcmeListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
arguments: [ '#service_container' ]
The problem is, I get an exception:
LogicException: Impossible to call set() on a frozen ParameterBag.
How can I work around this exception or do you see another way to dynamically set a parameter?
The container parameters rule is that:
You can only set a parameter before the container is compiled
How to remedy the problem depends on your needs with the premise that the container is not thought to have dynamic parameters.
create you custom dynamic "options" service and inject it in other services, in this way you can also manage your parameters in database (like wordpress wp_options), but i don't know a bundle that do this. For existing services (ex. mailer) you can use configurators.
invalidate the cache when parameters changes here an easy method so when you reload the page the container is rebuilt. If the parameters change frequently risks to reload the cache frequently and this becomes a problem if you have large loads.
if you choose the second option you need to set the parameters before it is filled in the container, so you can:
export in a custom yaml file loaded in app/config/config.yml when parameters changes before you clear the cache, in this way you can get the data from other services
load parameters in a bundle extension here the cookbook, in this way you can't access to other services to get the data, the extension can access only to the containerbuilder
I suggest, however, option 1 (options service and configurators) because (I repeat) the container is not thought to have dynamic parameters but it offers the ability to have custom dynamic service configurators that use data from any source.
I had the problem on a list of URLs starting by %base_url% when I wanted to do a failover system.
I finally replaced %base_url% by #base_url# and did a manual placeholders resolution in a service.
$tries = array (
$this->container->getParameter('base_url'),
$this->container->getParameter('base_url_failover'),
);
foreach ($tries as $try)
{
$url = str_replace('#base_url#', $try, $unresolvedUrl);
// whatever
}
I think we can add parameters as simple class functions to a service.
This is my controller function.
/**
* #Route("/products/checkService")
* #Template()
*/
public function checkServiceAction() {
$paypal = $this->get('Paypal');
return new Response($paypal->chkVal('dsdd'));
}
This is my service
<?php
namespace Eagle\ShopBundle\Services;
use Symfony\Component\HttpFoundation\Response;
class Paypal {
public function __construct($var) {
$this->paypalCongig = $var;
}
public function getVal() {
return $this->paypalCongig;
}
public function chkVal($var) {
return $var;
}
}

Symfony2: how to set url only with the slug [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Custom symfony routing based on doctrine
I have several url in my project related to each object type. Ex.
For products:
/product/product-1
/product/product-2
But now I created a controller for general info management. Ex. terms of use, about us ..etc.
I want urls that only contains the page slug.
/terms-use
/about-us
Not:
/page/terms-use
/page/about-us
How to define this in routing.yml?
Venu is right, Custom symfony routing based on doctrine provides a way to do this, the only complication comes if your slug parameter is built by adding slugs of parent pages such that the grandchild page has a slug like grandparent-slug/parent-slug/child-slug in which case an exception would be thrown.
We used routing to match slugs like the example I gave and it required some regex in the annotations:
/**
* #Route("{slug}", name="page_index", defaults={"slug" = false}, requirements={"slug" = "[0-9a-zA-Z\/\-]*"})
* #Template()
*/
public function indexAction($slug)
{
if ($slug !== false) {
$page = $this->findPage($slug);
The above requirements annotation means that the slug can be alphanumeric with forward slashes and hyphens. You then need to implement a method findPage($slug) that explodes the slug on forward slashes and finds the correct child page.
Because the route matches so many other routes, it is important to include this controller last in the routing.yml file and this action last of the public actions in the controller so that all other routes are matched first.
EDIT
This is the findPage method that we wrote to find a page from the above slug:
protected function findPage($slug_string, $first_page = false)
{
$slug_array = explode("/", $slug_string);
$slug = array_shift($slug_array);
$page = $this->em->getRepository("PagesBundle:Page")->getPageBySlug($slug, $this->site_id);
if (!$page) {
return false;
}
// if only the first matched page is required return it
if ($first_page) {
return $page;
}
// Otherwise loop through the slug array and match recursive
//children until slug array is empty or no match found
while (!empty($slug_array)) {
if ($page->getChildren()) {
$slug = array_shift($slug_array);
foreach ($page->getChildren() as $child_page) {
if ($child_page->getSlug() == $slug) {
$page = $child_page;
break;
}
}
} else {
return false;
}
}
return $page;
}

Resources