parse_url pour de vrai

Pour ceux qui ne la connaissent pas, la fonction parse_url « analyse une URL et retourne un tableau associatif contenant tous les éléments qui y sont présents » (dixit sa documentation). Ce que la documentation dit moins c'est que la fonction s'endort un peu en cours de route en ce qui concerne la requête (les trucs après le point d'interrogation). Ainsi, lorsque l'on utilise la fonction avec l'URL – totalement bidon, mais intéressante – http://www.weirdog.com/blog/?tag=XML&chars=%E9%E9%E0%E0 on se retrouve avec le tableau suivant:

Array
(
    [scheme] => http
    [host] => www.weirdog.com
    [path] => /blog/
    [query] => tag=XML&chars=%E9%E9%E0%E0
)

Bon, c'est sympa, mais quid de query ? Ce serait chouette de l'avoir aussi sous forme de tableau, parce que là elle ne ressemble pas à grand chose.

Malheureusement, papa PHP n'a pas pensé à ajouter une petite option pour cela, alors comme d'habitude il va falloir être inventif (ce qui n'est pas mal non plus).

La relève

Je vous propose donc wd_parse_url(), une petite fonction qui fera exactement ça : tout mettre sous forme de tableau. Et comme si de rien n'était, elle décode aussi les arguments de la requête en prenant bien soin d'encoder les caractères dans le jeu de caractère de votre choix (ce que ne fait même pas urldecode() soit dit en passant).

<?php

function wd_parse_url($url$charset='UTF-8')
{
    $url = parse_url($url);
    
    #
    # is the query key is defined, we need to create a table
    # from its parameters and values
    #
    
    if (isset($url['query']))
    {
        #
        # split query parts 'a=1', 'b=2'...
        #
        
        $query = $url['query'];
        
        #
        # select separator
        #
        
        $separator = strpos($query'&amp;') ? '&amp;' : '&';
                
        #
        # transform query parts into a lovely [key => value] array
        #
        
        $parts = explode($separator$query);
        $query = array();
        
        foreach ($parts as $part)
        {
            $av = explode('='$part);
            
            #
            # if the argument's value is an empty string we discard the argument
            #
            
            if ($av[1] === '')
            {
                continue;
            }
            
            $value = urldecode($av[1]);
            
            #
            # encode the value to the final charset
            #
            
            if ($charset)
            {
                $value = mb_convert_encoding($value$charset);
            }

            $query[$av[0]] = $value;
        }
        
        $url['query'] = $query;
    }
    
    return $url;
}

Résultat

Pour un résultat toujours plus blanc :

Array
(
    [scheme] => http
    [host] => www.weirdog.com
    [path] => /blog/
    [query] => Array
        (
            [tag] => XML
            [chars] => ééàà
        )

)

Séparateur

Si tu as fais attention au code, ami développeur, tu verras que j'essaie de choisir au mieux le séparateur pour les arguments de la requête. On pourrait se dire que dans une URL belle et valide, le caractère & est échappé en amp;. Le problème c'est que ce n'est vrai que dans un fichier HTML. Lorsque tu lis les URL depuis le tableau associatif $_SERVER, les échappements ce sont échappés, ce qui est bien normal.

Ainsi, pour ne pas te compliquer la vie (ni la mienne), si &amp; est présent il est utilisé comme séparateur, sinon c'est & qui est utilisé.

Si ça te plait pas, tu peux toujours modifier la fonction d'un geste rageur.
Sinon c'est aussi bien.

Pourquoi ne pas utiliser parse_str ?

Ordre de pensée :

  • « Ben je savais même pas que ça existait. »
  • « Pourquoi ils ont pas appelé ça parse_query ? »
  • « A ben ça marche même pas bien ! »

Tout comme tant d'autres fonctions, parse_str ne supporte pas l'utf-8.

Un test

  1. Créez une page HTML avec un encodage en UTF-8
  2. Utilisez la fonction proposée par naholyr
  3. Copiez le code suivant dans votre page :
<?php

$a = wd_parse_url($_SERVER['REQUEST_URI']);

echo '<pre>RESULT: ' . print_r($atrue) . '</pre>';
  1. Ajoutez ?test=ééàà à l'adresse de votre page

Et vous obtiendrez :

RESULT: Array
(
    [path] => /wd/wdpublisher/test.php
    [query] => Array
        (
            [pouic] => ????
        )
)

Encore une fois le manque de support de l'utf-8 se fait cruellement sentir. De plus parse_str ajoute de vilains guillemets magiques, même si l'on s'en ai débarrassé plus tôt. Merci pour ta remarque Naholyr, mais je préfère laisser cette fonction de côté.

Laisser un commentaire

6 commentaires

naholyr
naholyr

Je proposerais plutôt cette implémentation :

<?php

function wd_parse_url($url$charset='UTF-8')
{
  $url = parse_url($url);
  $url['query'] = isset($url['query']) ? parse_str($url['query']) : null;
}
Olivier
Olivier

Attention ! parse_str ne retourne pas de valeur. Une implémentation correcte serait plutôt :

<?php

function wd_parse_url($url)
{
    $url = parse_url($url);

    if (isset($url['query']))
    {
        parse_str($url['query']$url['query']);
    }  
  
    return $url;
}

En réponse à ton commentaire, j'ai ajouté dans mon billet pourquoi parse_str n'est pas viable.

naholyr
naholyr

En effet, parse_str semble avoir un vilain bug : il utf8_encode() dans tous les cas :( même si la query_string est déjà envoyée en UTF-8. Il faut donc corriger ça en effet.

<?php

function wd_utf8_decode($value) {
        if (is_array($value)) {
                return array_map('wd_utf8_decode'$value);
        } else {
                return utf8_decode($value);
        }
}

function wd_parse_url($url) {
        $url = parse_url($url);
        if (isset($url['query'])) {
                $encoding = mb_detect_encoding(urldecode($url['query']));
                parse_str($url['query']$url['query']);
                if ($encoding == 'UTF-8') { // Fix double-encoding from parse_str
                        $url['query'] = wd_utf8_decode($url['query']);
                }
        }

        return $url;
}

Pour les magic quotes, ça pourrait se régler au même endroit.

Je viens de m'apercevoir du post sur PHPFrance, et je dirais comme Hubert que dans la mesure du possible il vaut mieux travailler avec l'existant plutôt que de faire des parseurs à la main ;)

clem
clem

chez moi avec ta fonction ça donne :

Array
(
    [path] => /test/
    [query] => Array
        (
            [test] => ééà à 
        )

)

par contre avec le parse_str :

Array
(
    [path] => /test/
    [query] => Array
        (
            [test] => ééàà
        )

)

OSX – Firefox 4 – PHP 5.3.6

je te propose la petit modif suivante :

<?php

#
# encode the value to the final charset
#
$encoding = mb_detect_encoding($value'UTF-8,ISO-8859-1'true);
            
if ($charset !== $encoding)
{
    $value = mb_convert_encoding($value$charset$encoding);
}

ça marchera même si on mixe de l'utf-8 et l'ascii dans la requete :

?utf8=%C3%A9%C3%A9%C3%A9&ascii=%E9%E9%E0%E0

je te fait un PR sur le git ^^

Olivier
Olivier

C'est un peu une erreur de jeunesse cette fonction, je ne m'en sers même plus. En tout cas, pour ceux que ça intéresse vous voyez ce qu'il vous reste à faire. Merci Clément !

Clem
Clem

Erreur je sais pas, y'a souvent des bugs sur ces fonctions, et c'est vrai que parse_str encode direct en UTF-8, c'est pas forcement pratique