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> |
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]]] |
<?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; | |
} | |
} |
Maintenant vous pouvez slugify votre url facilement en gardant votre langue par défaut. Dans le cas de l’Arabe le mot « مال و أعمال » sera « مال-و-أعمال ».
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
Je ne sais pas pourquoi j’ai parlé en anglais :s. Peut être parce que j’étais sur stackoverflow avant…
Salut David,
Je viens d’ajouter à l’article la version YML de fichier de service
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 ?
Non pas beaucoup 🙂
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 ?
Bonjour, merci pour le tuto. J’ai suivi les mêmes étapes mais ça marche pas, pouvez-vous m’aider ? Merci d’avance.
C’est quoi exactement le problème?
Bonjour, j’ai suivi tout à la lettre mais le slug généré est toujours converti en latin. Pouvez-vous m’aider SVP ?
C’est bon ça marche, merci bcp.
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 ?