Le but n'est pas de présenter le composant serializer de Symfony2 , la documentation officielle est là pour ça. Il existe egalement un présentation assez complète faite par Hugo Hamon lors d'un Symfony Live.
L'idée est de montrer un exemple d'utilisation, et par celui-ci, montrer quelques astuces pour modifier le résultat. J'ai du farfouiller le code du composant pour trouver ces infos alors je me dis que cela pourra peut être interesser quelqu'un d'autre.
Nous allons donc créer un sitemap dynamique pour un siteweb. Les morceaux de code proviennent d'un site en Symfony2 mais cela pourrait fonctionner de la même manière avec uniquement le composant.
Nous avons une route /sitemap qui match une action dans le controller sitemapAction.
Nous avons une class Url qui contient les données pour le sitemap:
class Url { /** * Url complète de la page * * @var string */ protected $loc; /** * Date de dernière modification de la page (format Y-m-d) * * @var string */ protected $lastmod; /** * Priorité de la page * * @var float */ protected $priority; //GETTERS et SETTERS }
Je ne détaille pas la récupération des objets Url car cela n'a aucun intérêt pour l'exemple.
public function sitemapAction() { //initialisation du serializer $encoders = array(new XmlEncoder(), new JsonEncoder()); $normalizers = array(new GetSetMethodNormalizer()); $serializer = new Serializer($normalizers, $encoders); //recuperation des objets Url dans la variable $urls // $urls -> tableau indexé d'objets de type Url $response = new Response(); $response->setContent($serializer->serialize($urls, 'xml')); $response->headers->set('Content-Type', 'application/xml'); return $response; }
On obtient le résultat suivant:
<response> <item key="0"> <loc>http://url1</loc> <lastmod>2013-03-10</lastmod> <priority>0.8</priority> </item> <item key="1"> <loc>http://url2</loc> <lastmod>2013-03-10</lastmod> <priority>0.8</priority> </item> <item key="2"> <loc>http://url3</loc> <lastmod>2013-03-10</lastmod> <priority>0.5</priority> </item> </response>
Il y a deux problèmes pour respecter la normalisation des fichiers sitemap:
- la balise principale est par défaut "response" alors qu'on veut "urlset"
- les différentes urls ne sont pas dans une balise "url" mais "item" avec un attribut "key" qui nous interesse pas.
Première fouille dans le code, et plus précisement dans l'encoder XML: Symfony\Component\Serializer\Encoder\XmlEncoder:
class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, DecoderInterface, NormalizationAwareInterface { private $dom; private $format; private $rootNodeName = 'response'; /** * Construct new XmlEncoder and allow to change the root node element name. * * @param string $rootNodeName */ public function __construct($rootNodeName = 'response') { $this->rootNodeName = $rootNodeName; }
Facile à trouver, il suffit de passer un paramètre dans le constructeur:
//extrait de la méthode sitemapAction du controller $encoders = array(new XmlEncoder('urlset'), new JsonEncoder());
Notre xml devient donc:
<urlset> <item key="0"> <loc>http://url1</loc> <lastmod>2013-03-10</lastmod> <priority>0.8</priority> </item> <item key="1"> <loc>http://url2</loc> <lastmod>2013-03-10</lastmod> <priority>0.8</priority> </item> <item key="2"> <loc>http://url3</loc> <lastmod>2013-03-10</lastmod> <priority>0.5</priority> </item> </urlset>
Toujours dans la même classe, en regardant la construction du xml de plus près, voici ce que l'on trouve:
/** * Parse the data and convert it to DOMElements * * @param DOMNode $parentNode * @param array|object $data data * @param string $xmlRootNodeName * * @return Boolean * * @throws UnexpectedValueException */ private function buildXml($parentNode, $data, $xmlRootNodeName = null) { $append = true; if (is_array($data) || $data instanceof \Traversable) { foreach ($data as $key => $data) { //Ah this is the magic @ attribute types. if (0 === strpos($key, "@") && is_scalar($data) && $this->isElementNameValid($attributeName = substr($key, 1))) { $parentNode->setAttribute($attributeName, $data); } elseif ($key === '#') { $append = $this->selectNodeType($parentNode, $data); } elseif (is_array($data) && false === is_numeric($key)) { /** * Is this array fully numeric keys? */ if (ctype_digit(implode('', array_keys($data)))) { /** * Create nodes to append to $parentNode based on the $key of this array * Produces <xml><item>0</item><item>1</item></xml> * From array("item" => array(0,1)); */ foreach ($data as $subData) { $append = $this->appendNode($parentNode, $subData, $key); } } else { $append = $this->appendNode($parentNode, $data, $key); } } elseif (is_numeric($key) || !$this->isElementNameValid($key)) { $append = $this->appendNode($parentNode, $data, "item", $key); } else { $append = $this->appendNode($parentNode, $data, $key); } } return $append; }
Au milieu de ce code par forcement très lisible, un commentaire :
/**
* Create nodes to append to $parentNode based on the $key of this array
* Produces <xml><item>0</item><item>1</item></xml>
* From array("item" => array(0,1));
*/
Donc, tout ce qu'il nous faut c'est mettre notre tableau $urls dans un autre tableau avec comme clé 'url'.
Notre action devient donc:
public function sitemapAction() { //initialisation du serializer $encoders = array(new XmlEncoder('urlset'), new JsonEncoder()); $normalizers = array(new GetSetMethodNormalizer()); $serializer = new Serializer($normalizers, $encoders); //recuperation des objets Url dans la variable $urls // $urls -> tableau indexé d'objets de type Url $map['url'] = $urls; $response = new Response(); $response->setContent($serializer->serialize($map, 'xml')); $response->headers->set('Content-Type', 'application/xml'); return $response; }
Et notre sitemap est créé:
<urlset> <url> <loc>http://url1</loc> <lastmod>2013-03-10</lastmod> <priority>0.8</priority> </url> <url> <loc>http://url2</loc> <lastmod>2013-03-10</lastmod> <priority>0.8</priority> </url> <url> <loc>http://url3</loc> <lastmod>2013-03-10</lastmod> <priority>0.5</priority> </url> </urlset>
Ceci est un exemple assez simple au final.
Mais l'idée était de montrer qu'on peut faire des petits trucs sympas assez facilement avec le composant Serializer de Symfony2 et que, très souvent, il suffit de fouiller et de lire un peu le code source pour résoudre la plupart des problèmes.
Il y a 4 commentaires.
Mikl 14 juin 2013 Kevin 11 avr. 2013 Muriel 10 avr. 2013 Pascal 9 avr. 2013L'ajout de commentaire est temporairement désactivé à cause du spam.