Showing posts with label Symfony 2. Show all posts
Showing posts with label Symfony 2. Show all posts

Thursday, December 11, 2014

Symfony site localization based on domain name

Problem: I want to localize my site based on the domain name.

Symfony strongly suggests that you use paths (like '/en' or '/fr') after the domain name to determine what the locale should be. This is ideal for a site with only one domain name, but for a site that has a different one for each localization, it's unnecessary. You should be able to determine the language based on the domain.

Solution: Use an event listener.

With an event listener, you can catch the request, parse the domain name, and set the locale appropriately. For this blog's purposes, let's say that a site has www.endomain.com for its English site and www.frdomain for its French site.

Create this folder/file in your bundle: EventListener/LocaleListener.php. Inside, put this:

namespace My\CustomBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;


class LocaleListener
{
  public function setLocale(GetResponseEvent $event)
  {
    if (strstr(strtolower($_SERVER['HTTP_HOST']), strtolower('frdomain')))
    {
      $request = $event->getRequest();
      $request->setLocale('fr');
    }

  }
}

If the HTTP_HOST contains the string 'frdomain', then set the locale to 'fr'.

Now register the listener in your bundle's services.yml file (it should be in Resources/config/). Inside, put this:

services:
    my_custom.language.kernel_request_listener:
        class: My\CustomBundle\EventListener\LocaleListener
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: setLocale }

Now every time a request is sent to Symfony, this listener runs first and sets the locale to 'fr' if it detects 'frdomain' in HTTP_HOST. Otherwise, it keeps the default locale (in this case, it's en).

That should work! Happy coding!

Friday, November 8, 2013

Using Symfony's routing.yml file for different host names

I like extensions, but when they crap out on you, you're kind of a sitting duck. So rather than rely on them, I figured I might as well learn to handle multiple domain names the extension-free way. Did you know this is now available in Symfony? How to match a route based on the host.
I needed a language toggle for my website, so that when a user is on the English site, the link to the French is available, and vice versa. Between routing.yml, parameters.yml, and config.yml, I was able to create a decent (but still "wordy") toggle for the pages I needed. (And as I type this, I realize that the correct way might be annotations instead. I'll look into that in a bit.)
Step 1: parameters.yml (in app/config/)
This is the only file that will contain the domain names in English and French. Ergo, all other config files will pull from this file. Specify keys like “domain_locale_en” and “domain_locale_fr” to hold the domain names of your English and French sites.
domain_locale_en:    www.programmingnotestoself.com
domain_locale_fr:    www.notesdeprogrammationalauto.com 
Step 2: config.yml (in app/config/)
This is the file where you’ll make the domain names available to the Twig (template) files. Modify the twig section.
twig:
    # ...
        globals:  
            domain_en:    %domain_locale_en%
            domain_fr:    %domain_locale_fr%
If you ever need to print out the domain in a link, for example, you can just do this:
< a href="%domain_en%">English blog< /a>
(If you try to include "http://" in parameters.yml, you’re going to get a surprise. At least, I did… I ended up in the default Symfony welcome page.)
Step 3: routing.yml (in app/config/)
This is the part that I’m sure can use a little more polishing, so if you have suggestions, please let me know. But, anyway, given that I now have two domain names for the root, I can code routing.yml like this:
en_root:
  path:     /
  host:     %domain_locale_en%
  defaults: { _controller: BlogPostsBundle:Default:index }

fr_root:
  path:     /
  host:     %domain_locale_fr%
  defaults: { _controller: BlogPostsBundle:Default:index }
So, yes, whether it detects www.programmingnotestoself.com or www.notesdeprogrammationalauto.com, it’ll go to the same controller. Ugly, no?
The not-so-fun part
Because of this hack, all the other pages and routes in my site need to be registered twice in the routing file. For example, www.programmingnotestoself.com/help and www.notesdeprogrammationalauto.com/aide lead to the same page, but Symfony needs to know that. This is how they look like in my routing.yml file:
_help:
    pattern:   /help
    host:      %domain_locale_en%
    defaults:  { _controller: BlogPostsBundle:Default:help }

_aide:
    pattern:   /aide
    host:      %domain_locale_fr%
    defaults:  { _controller: BlogPostsBundle:Default:help }
Notice host:? This restricts the path to that particular domain name. So www.programmingnotestoself.com/aide is totally legit, but try www.programmingnotestoself.com/aide and it’ll fail. But if you take out host:, you can totally do a crossover (the latter link). As for what locale will be served up, it’ll be whatever the domain name’s locale is.
The cute part
I didn’t know you could force Twig to translate a string to a particular locale!
Assume that the controller for this page passed on variable $lang as either 'fr' or 'en' and $lang_name as 'English' or 'Français':
{% if lang == 'en' %}
    < a href="http://{{domain_fr}}/{{ 'help'|trans(locale='fr')}}>{{lang_name}}< /a>
{% else %}
    < a href="http://{{domain_en}}/{{ 'help'|trans(locale='en')}}>{{lang_name}}< /a>
{% endif %}
If you’re on the English site, force 'help' to be translated to the French string, and do similar if you’re on the French site.
Happy coding!

Tuesday, September 17, 2013

Symfony 2 symblog tutorial errors

It seems that the Symfony 2 tutorial ("Symblog") is a little out of date. While going through it, I stumbled on some unexpected behaviour. There were exceptions getting thrown when the tutorial made no mention of them. This could be due to several behaviours being deprecated since 2.1; I was using 2.3.4.

So without further ado...

The errors and their fixes as you work through the tutorial

1) When creating a controller method for the Contact page, $form->bind($this->bindRequest($request) resulted in:

FatalErrorException: Error: Call to undefined method Symfony\Component\Form\Form::bindRequest() in /path/to/root/src/Blogger/BlogBundle/Controller/PageController.php line 31

I poked around at code that I already knew worked and found this alternative:
$form->bind($this->getRequest());

2) While editing src/Blogger/BlogBundle/Entity/EnquiryType.php, adding this line $metadata->addPropertyConstraint('body', new MaxLength(50)); in static function loadValidatorMetadata(ClassMetadata $metadata) resulted in an exception:

FatalErrorException: Error: Class 'Symfony\Component\Validator\Constraints\MaxLength' not found in /path/to/root/src/Blogger/BlogBundle/Entity/Enquiry.php line xx

I read elsewhere that MaxLength and MinLength are deprecated since Symfony 2.1, so you're better off declaring the above line as
$metadata->addPropertyConstraint('subject', new Length(array('max' => 50)));

Just make sure to add this to the top of the class: use Symfony\Component\Validator\Constraints\Length;

3)When told to add the following lines to src/Blogger/BlogBundle/Resources/config/config.yml

parameters: blogger_blog.comments.latest_comment_limit: 10
You will instead get an error along the lines of blogger_blog.comments.latest_comment_limit must be defined. Now, the solution is to either do this tutorial (http://symfony.com/doc/current/cookbook/bundles/extension.html), or do the shortcut, which I did (because lazy).

4) While trying to enable Assetic for BloggerBlogBundle, you are instructed to put it in app/config/config.yml. However, it doesn't take effect. Instead all sidebar.css-ing disappeared. This is because you should have put BloggerBlogBundle in app/config/config_dev.yml.

5) Yui Compressor jar file not found - Make sure that 1) you've put the jar file in app/Resources/java and that 2) the name of that jar file is the same as in config_dev.yml.

If there will be more, I will update this post. Happy coding!