[Symfony2][DoctrineExtension] Créer des slugs pour les caractéres non latin (Arabe particulièrement).

Le comportement Sluggable de la bibliothèque Doctrine extensions permet de créer facilement des URLs SEO-Friendly.
La mise en place du bundle est facile et ne nécessite pas beaucoup de configuration.

Dans le cas des url non-latin tel que l’arabe, le chinois, ou le japonais, le behavior Sluggable translittère les mots en latin, par exemple le mot « إقتصاد » sera translittéré en « qtsd » ce qui n’a aucun sens.

Dans cet article, nous allons voir comment installer et configurer le Bundle StofDoctrineExtensions pour bien créer des slugs pour les langues non-latin.

Installation du StofDoctrineExtensionsBundle

Le bundle StofDoctrineExtensionsBundle intègre la bibliothèque DoctrineExtensions .
L’installation se fait à travers le gestionnaire des dépendances composer :

composer require "doctrine/doctrine-fixtures-bundle:dev-master"
composer require "stof/doctrine-extensions-bundle:dev-master"

Par la suite, nous activons le bundle dans le fichier AppKernel.php

// app/AppKernel.php
// ...

public function registerBundles()
{
    $bundles = array(
        // ...
        new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
    );

    // ...
}

À la fin, nous ajoutons la configuration de bundle à la fin de notre fichier config.yml :

stof_doctrine_extensions:
    default_locale: en_US
    orm:
        default:
            sluggable:   true

Utilisation du comportement Sluggable

Dans notre exemple, nous allons créer un slug pour la propriété « name » de notre entity Post.

// src/Nm/CoreBundle/Entity/Post.php
// ...

use Gedmo\Mapping\Annotation as Gedmo;
// ...

class Post
{
    // ...

    /**
     * @Gedmo\Slug(fields={"name"}, updatable=false)
     * @ORM\Column(length=255, unique=true)
     */
    protected $slug;
}

Ce code permet de créer automatiquement des slugs sur la propriété « name ».
En ajoutant « updatable=false », cela indiquera que le slug ne sera jamais mis à jour une fois créée, même si nous changons la valeur de « name ».
C’est une bonne idée parce que le slug sera utilisé dans l’URL de l’événement, que nous ne voulons pas changer.

Par la suite nous mettons à jour notre base de données :

php app/console  doctrine:schema:update --force

Si vous avez des anciennes données vous allez vous trouver avec une erreur de contrainte d’intégrité, il faut donc vider la table de ces données et relancer la mise à jour, pour cela on peut supprimer toutes les tables et les recréer à nouveau à travers la console de symfony pour contourner ce problème :

php app/console doctrine:schema:drop
php app/console doctrine:schema:create

À ce niveau, l’installation et la configuration du comportement sluggable sont terminées. Vous pouvez faire les tests et voir que tout est fonctionnel.

Adaptation du comportement Sluggable pour les url non-latin

Comme je vous ai dit au début, le but de cet article est d’adapter ce behavior pour créer des slugs pour les langues non-latin.

Beaucoup de CMS, tel-que Drupal et WordPress possède un système de slugify puissant.
Pour notre cas, nous allons utiliser les fonctions utf8_uri_encode et slugify de wordpress.

Nous commencons par créer un service personnalisé et par la suite nous l’activons pour surcharger celui par défaut de DoctrineExtensions.

<!-- src/Nm/CoreBundle/Resources/config/services.xml -->
<service id="stof_doctrine_extensions.listener.sluggable" class="%stof_doctrine_extensions.listener.sluggable.class%" public="false">
<tag name="doctrine.event_subscriber" connection="default"/>
<call method="setAnnotationReader">
<argument type="service" id="annotation_reader" />
</call>
<call method="setTransliterator">
<argument type="collection">
<argument>Nm\CoreBundle\Service\Slugger</argument>
<argument>transliterate</argument>
</argument>
</call>
<call method="setUrlizer">
<argument type="collection">
<argument>Nm\CoreBundle\Service\Slugger</argument>
<argument>urlize</argument>
</argument>
</call>
</service>
view raw services.xml hosted with ❤ by GitHub
services:
stof_doctrine_extensions.listener.sluggable:
class: %stof_doctrine_extensions.listener.sluggable.class%
public: false
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [setAnnotationReader, ['@annotation_reader']]
- [setTransliterator, [[Nm\CoreBundle\Service\Slugger, transliterate]]]
- [setUrlizer, [[Nm\CoreBundle\Service\Slugger, urlize]]]
view raw services.yml hosted with ❤ by GitHub
<?php
//src/Nm/CoreBundle/Service/Slugger.php
namespace Nm\CoreBundle\Service;
use Gedmo\Sluggable\Util\Urlizer;
class Slugger
{
/**
* Disable the transliterate
*
* @param $text
* @param string $separator
* @return mixed
*/
public static function transliterate($text, $separator = '-')
{
$text = self::slugify($text, $separator);
return $text;
}
/**
* Slugify the given text
*
* @param $text
* @param string $separator
* @return string
*/
public static function urlize($text, $separator = '-')
{
$text = Urlizer::unaccent($text);
$text = self::slugify($text, $separator);
return $text;
}
/**
* Encode the Unicode values to be used in the URI.
*
* ported from wordpress
* @see https://core.trac.wordpress.org/browser/tags/3.9.1/src/wp-includes/formatting.php#L572
*
* @param string $utf8_string
* @param int $length Max length of the string
* @return string String with Unicode encoded for URI.
*/
private static function utf8_uri_encode($utf8_string, $length = 0)
{
$unicode = '';
$values = array();
$num_octets = 1;
$unicode_length = 0;
$string_length = strlen($utf8_string);
for ($i = 0; $i < $string_length; $i++) {
$value = ord($utf8_string[$i]);
if ($value < 128) {
if ($length && ( $unicode_length >= $length ))
break;
$unicode .= chr($value);
$unicode_length++;
} else {
if (count($values) == 0)
$num_octets = ( $value < 224 ) ? 2 : 3;
$values[] = $value;
if ($length && ( $unicode_length + ($num_octets * 3) ) > $length)
break;
if (count($values) == $num_octets) {
if ($num_octets == 3) {
$unicode .= '%' . dechex($values[0]) . '%' . dechex($values[1]) . '%' . dechex($values[2]);
$unicode_length += 9;
} else {
$unicode .= '%' . dechex($values[0]) . '%' . dechex($values[1]);
$unicode_length += 6;
}
$values = array();
$num_octets = 1;
}
}
}
return $unicode;
}
/**
* Make the string slug compatible
* ported from wordpress
*
* @see https://core.trac.wordpress.org/browser/tags/3.9.1/src/wp-includes/formatting.php#L1058
*
* @param $text
* @param $separator
* @return string
*/
private static function slugify($text, $separator)
{
$text = str_replace('%', '', $text);
if (Urlizer::seemsUtf8($text)) {
if (function_exists('mb_strtolower')) {
$text = mb_strtolower($text, 'UTF-8');
}
$text = self::utf8_uri_encode($text, 200);
} else {
$text = strtolower($text);
}
$text = str_replace('.', $separator, $text);
// Convert nbsp, ndash and mdash to hyphens
$text = str_replace(array('%c2%a0', '%e2%80%93', '%e2%80%94'), $separator, $text);
// Strip these characters entirely
$text = str_replace(array(
// iexcl and iquest
'%c2%a1', '%c2%bf',
// angle quotes
'%c2%ab', '%c2%bb', '%e2%80%b9', '%e2%80%ba',
// curly quotes
'%e2%80%98', '%e2%80%99', '%e2%80%9c', '%e2%80%9d',
'%e2%80%9a', '%e2%80%9b', '%e2%80%9e', '%e2%80%9f',
// copy, reg, deg, hellip and trade
'%c2%a9', '%c2%ae', '%c2%b0', '%e2%80%a6', '%e2%84%a2',
), '', $text);
$text = preg_replace('/[^%a-z0-9 _-]/', '', $text);
$text = preg_replace('/\s+/', $separator, $text);
$text = preg_replace('|-+|', $separator, $text);
$text = trim($text, $separator);
$text = urldecode($text);
return $text;
}
}
view raw Slugger.php hosted with ❤ by GitHub

Maintenant vous pouvez slugify votre url facilement en gardant votre langue par défaut. Dans le cas de l’Arabe le mot « مال و أعمال » sera « مال-و-أعمال ».

12 réflexions sur “[Symfony2][DoctrineExtension] Créer des slugs pour les caractéres non latin (Arabe particulièrement).

  1. Hi Rmed19,

    I am exactly in that case! I would like to test your service, but i have a services.yml… Is it possible for you to adapt it from xml to yml? I’m not very good in that.

    Sorry for my english 🙂

    Cheers,
    David

      • Merci énormément Mohammed pour le code en yml, ton code fonctionne parfaitement, c’est magique quand tu vois apparaître ton premier slug chinois 😀 !
        Je n’ai pas eu à galérer car je suis très vite tombé sur ta page quand j’ai découvert ce problème, sinon je sens que le mandarin n’aurait plus figuré à la liste des langues de mon site.
        Encore une fois 10.000 merci !!!
        Tu as du y passer du temps ?

    • Salut David,
      Tu peux essayer avec cette configuration :
      stof_doctrine_extensions.listener.sluggable :
      class: « %stof_doctrine_extensions.listener.sluggable.class% »
      public: false
      tags:
      – { name: doctrine.event_subscriber, connection: default }
      calls:
      – [ setAnnotationReader, [ @annotation_reader ] ]
      – [ setTransliterator, [ Nm\CoreBundle\Service\Slugger, transliterate] ]
      – [ setUrlizer, [ Nm\CoreBundle\Service\Slugger, urlize ] ]

      • Salut Mohammed,
        MERCI pour ta réponse super rapide :). Je viens de mettre ta config en place et j’ai une erreur : « ParseException: Unable to parse at line 54 (near « – [ setAnnotationReader, [ @annotation_reader ] ] ») ». Je n’ai pas de tabulation, j’ai fait quelques recherches sur la doc de symfony pour voir comment fonctionnaientt les calls, ils ont l’air d’être bon dans ta config… Une idée ?

  2. Bonsoir,

    Votre script fonctionne avec symfony2 et orm mais lorsque j’utilise le meme code avec un autre projet sous symfony 3 avec odm ca ne marche pas, le slug reste vide quelques soit la langue. Pouvez-vous m’aider SVP ?

Répondre à redayoub Annuler la réponse.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.