WdBlog

09 mai

URL & Résilience Posté dans PHP Pas de commentaire

Après trois changements d'algorithme de création d'URL pour les articles de mon blog, il fallait bien que ça arrive : il y a plein de liens erronés qui trainent maintenant sur internet et donc dans Google, ce qui est assez nul en terme de référencement, ainsi que pour le chaland qui tombe sur une page toute nulle, avec pour contenu un peu encourageant « Article inconnu ».

Alors oui, c'est triste et c'est con, mais impossible de renoncer à la forme actuelle de mes URL : une URL comme pour de vrai, sans numéro d'identification.

Après avoir réfléchi très fort (aïe), j'ai trouvé une solution pas trop mal, même si elle peut être couteuse en terme de performance à cause de l'algorithme choisi. Mais enfin, mon CMS est super malin alors c'est pas très grave :-)

L'idée

C'est arrivé avec le morceau d'URL supprimer_les_accents_des_caracteres_accentues_en_php.html, qui est maintenant supprimer-les-accents-des-caracteres-accentues-php.html. Les séparateurs ne sont plus les mêmes et les mots qui font moins de trois caractères sont maintenant supprimés (les nombres n'étant pas affectés).

Je me suis dit : « c'est dommage, c'est presque les mêmes » et paf j'ai pensé aux fonctions similar_text() et levenshtein() de PHP qui permettent de calculer selon la fonction, les similitudes ou les différences entre deux chaines de caractères.

J'ai choisi d'utiliser la fonction similar_text(), même si la documentation dit que sa copine est plus rapide, les résultats de similar_text() sont vraiment stupéfiant (j'en rajoute à peine pour le suspense).

Le code

Voici le morceau de code qui me sauve la mise.

Si vous voulez l'utiliser il faudra l'adapter un bout parce que les fonctions liées à la base de donnée sont celles de mon CMS, mais sinon en gros ça devrait le faire.

<?php

#
# fetch the title and the id of all the articles
#

$entries = $module->select(array(self::ID, self::TITLE));

if (empty($entries))
{
    return l('There is no article in the database');
}

#
# normalize the title we are looking for,
# discarding words smaller than 4 characters, while preserving
# numbers
#

$title = wd_normalize($url_title);
$title = wd_discard_substr_by_length($title4);

#
# the closest we are to the title, the smallest the return value of
# levenshtein() is, thus we have to start with a big score
#

$max = 0;

foreach ($entries as $entry)
{
    #
    # normalize the title to compare, the same way we did
    # for the title to find
    #

    $compare = wd_normalize($entry[self::TITLE]);
    $compare = wd_discard_substr_by_length($compare4);
    
    #
    # compare string
    #
    
    similar_text($title$compare$p);
    
    #
    # log result
    #
    
    printf('"%s" ?= [id:%02d] "%s" == %.2f%%<br />'$title$entry[self::ID]$compare$p);

    if ($p > $max)
    {
        #
        # we have found a better match, we save its id
        #
    
        $id = $entry[self::ID];
                                
        if ($p > 90)
        {
            #
            # huge match, we can break the loop
            #

            break;
        }
        
        $max = $p;
    }
}

?>

Le résultat

Bon, j'ai un peu compliqué la mauvaise URL en utilisant supmer_les_acents_en_php.html pour voir l'efficacité du machin, mais voilà ce que ça donne :

"supmer-acents" ?= [id:01] "naissance-application" == 35.29%
"supmer-acents" ?= [id:02] "3-indispensables-pour-developpeur" == 21.74%
"supmer-acents" ?= [id:03] "30" == 0.00%
"supmer-acents" ?= [id:04] "guide-graphiste-independant" == 25.00%
"supmer-acents" ?= [id:05] "result-type-should-either" == 26.32%
"supmer-acents" ?= [id:06] "vide" == 11.76%
"supmer-acents" ?= [id:07] "portrait-aurore" == 35.71%
"supmer-acents" ?= [id:08] "markdown-textile-textmark" == 15.79%
"supmer-acents" ?= [id:09] "creer-nuage-mots" == 48.28%
"supmer-acents" ?= [id:10] "comment-creer-logo-wdblog" == 21.05%
"supmer-acents" ?= [id:11] "support-mise-valeur-code-html" == 47.62%
"supmer-acents" ?= [id:12] "formater-dates-avec-legerete" == 39.02%
"supmer-acents" ?= [id:13] "initiation-variables" == 6.06%
"supmer-acents" ?= [id:14] "marqueurs-controle" == 32.26%
"supmer-acents" ?= [id:15] "supprimer-accents-caracteres-accentues" == 50.98%
"supmer-acents" ?= [id:16] "trier-cles-accentuees-tableau-associatif" == 33.96%
"supmer-acents" ?= [id:17] "bloc-pagination-classe" == 11.43%
"supmer-acents" ?= [id:18] "vilains-guillemets-magiques" == 30.00%
"supmer-acents" ?= [id:19] "anti-spam-fastoche" == 25.81%
"supmer-acents" ?= [id:21] "previsions-meteo" == 27.59%
"supmer-acents" ?= [id:22] "dessiner-damier-photoshop" == 42.11%
"supmer-acents" ?= [id:23] "classe-cinq-methodes-pour-creer-miniatures" == 29.09%

50.98% de ressemblance c'est pas le pied mais ça suffit, surtout avec une URL aussi abimée et vilaine.

Chouette !

08 mai

Une classe et sept méthodes pour créer des miniatures Posté dans PHP Pas de commentaire

Les miniatures c'est chouette, mais les faire avec Photoshop c'est déjà moins chouette. Surtout quand on gère un album web dans lequel de nombreuses personnes peuvent laisser des images. Il est possible de les redimensionner par l'intermédiaire de la fonction imagecopyresampled() mais on ne peut pas dire que l'on soit étouffé sous une pléthore d'options de redimensionnement et de cadrage.

Je vous propose donc la méthode resize() de ma classe WdImage et quelques exemples de rendu pour vous mettre l'eau à la bouche.

Sept méthodes de redimensionnement

Les exemples suivant utilisent trois image aux formats bien différents : un carré (300 × 300px), un rectangle horizontal (400 × 200px) et un rectangle vertical (200 × 400px). Elles sont ensuite redimensionnées en 100 × 200px.

Voilà ce que cela donne en fonction des options de redimensionnement choisies :

WdImage::RESIZE_SCALE_MAX

Redimensionne l'image pour quelle tienne dans l'espace cible. Cela donne des miniatures ayant toutes la même largeur et la même hauteur, avec un fond visible.

resize-scale-max

WdImage::RESIZE_SCALE_MIN

Redimensionne l'image pour que tout l'espace cible soit rempli. L'image est cadrée horizontalement et verticalement. Cela donne des miniatures ayant toutes la même largeur et la même hauteur, avec un maximum de l'image source visible.

resize-scale-min

WdImage::RESIZE_FIXED_HEIGHT

L'image est redimensionnée par rapport à la hauteur cible. La largeur est calculée proportionnellement. Cela donne des miniatures qui ont toutes la même hauteur, mais pas forcément la même largeur.

resize-fixed-height

WdImage::RESIZE_FIXED_HEIGHT_CROPPED

L'image est redimensionnée par rapport à la hauteur cible. Si la largeur de l'image est plus importante que celle de la cible, l'image est cadrée. Cela donne des miniatures qui ont toutes la même hauteur, mais pas forcément la même largeur.

resize-fixed-height-cropped

WdImage::RESIZE_FIXED_WIDTH

L'image est redimensionnée par rapport à la largeur cible. La hauteur de l'image est calculée proportionnellement. Cela donne des miniatures qui ont toutes la même largeur, mais pas forcément la même hauteur.

resize-fixed-width

WdImage::RESIZE_FIXED_WIDTH_CROPPED

L'image est redimensionnée par rapport à la largeur cible. Si la hauteur de l'image est plus importante que celle de la cible, l'image est cadrée. Cela donne des miniatures qui ont toutes la même largeur, mais pas forcément la même hauteur.

resize-fixed-width-cropped

WdImage::RESIZE_SURFACE

L'image est redimensionnée pour que sa surface égale celle de la cible. Cela donne des miniatures de largeurs et de hauteurs différentes mais qui occupent toutes la même surface en terme de pixels.

resize-surface

Un exemple d'utilisation

<?php

$thumbnail = WdImage::resize
(
    $source,
            
    $target_width,
    $target_height,
            
    WdImage::RESIZE_SCALE_MAX,

    'fill_callback'
);

?>

Comme vous pouvez le voir dans l'exemple ci-dessus, il est possible de définir la fonction qui sera appelée pour remplir la zone cible avant de redimensionner l'image source. J'en profite pour utiliser la méthode drawGrid() que je vous avez présenté précédemment.

<?php

function fill_callback($image$w$h)
{
    WdImage::drawGrid
    (
        $image00$w - 1$h - 1
    );
}

?>

Une méthode bien méritée

Voici donc la méthode resize() extraite de la class WdImage :

<?php

class WdImage
{
    const RESIZE_SCALE_MAX = 'scale-max';
    const RESIZE_SCALE_MIN = 'scale-min';
    const RESIZE_FIXED_HEIGHT = 'fixed-height';
    const RESIZE_FIXED_HEIGHT_CROPPED = 'fixed-height-cropped';
    const RESIZE_FIXED_WIDTH = 'fixed-width';
    const RESIZE_FIXED_WIDTH_CROPPED = 'fixed-width-cropped';
    const RESIZE_SURFACE = 'surface';
    
    static public function resize($source&$t_w&$t_h$method$fill_callback=NULL)
    {
        #
        # source dimensions
        #
        
        $s_x = 0;
        $s_y = 0;
        $s_w = imagesx($source);
        $s_h = imagesy($source);
        
        #
        # destination dimensions
        #

        $d_x = 0;
        $d_y = 0;
        $d_w = $t_w;
        $d_h = $t_h;
        
        #
        # select scale method
        #
        
        switch ($method)
        {
            case self::RESIZE_SCALE_MAX:
            default:
            {
                #
                # scale-max
                #
                # Resize the image so that it all fits in the target space.
                # This will result in thumbnails equals in width and height, with a possible
                # background visible.
                #
                
                $s_r = $s_w / $s_h;
                $d_r = $d_w / $d_h;
                
                $r = $s_r > $d_r ? $s_w / $d_w : $s_h / $d_h;
    
                $d_w = round($s_w / $r);
                $d_h = round($s_h / $r);
            }
            break;
    
            case self::RESIZE_SCALE_MIN:
            {
                #
                # scale - min
                #
                # Resize the image so that the whole target space is filled. The image is cropped
                # to the target width and height.
                # This will result in thumbnails equals in width and height, with the maximum of
                # information visible.
                #
                
                $s_r = $s_w / $s_h;
                $d_r = $d_w / $d_h;
                
                if ($s_r > $d_r)
                {
                    $r = $s_h / $d_h;
                    $s_x += round(($s_w - $t_w * $r) / 2);
                }
                else
                {
                    $r = $s_w / $d_w;                   
                    $s_y += round(($s_h - $t_h * $r) / 2);
                }
                
                $d_w = round($s_w / $r);
                $d_h = round($s_h / $r);
            }
            break;

            case self::RESIZE_FIXED_HEIGHT:
            {
                #
                # fixed-height
                #
                # The image is resized to match the target height.
                # The image width is resized accordingly.
                # This will result in thumbnails equals in height, but not in width.
                #
                
                $r = $s_h / $d_h;

                $d_w = round($s_w / $r);
                
                if ($s_w > $s_h)
                {
                    $d_h = round($s_h / $r);                    
                }

                $t_w = $d_w;
            }
            break;
            
            case self::RESIZE_FIXED_HEIGHT_CROPPED:
            {
                #
                # fixed-height-cropped
                #
                # The image is resized to match the target height.
                # If the image width is larger than the target width, the image is cropped.
                # This will result in thumbnails equals in height, but not in width.
                #
                
                $r = $s_h / $d_h;

                $d_w = round($s_w / $r);
                
                if ($s_w > $s_h)
                {
                    $d_h = round($s_h / $r);                    
                }
                else
                {
                    $t_w = $d_w;
                }               

                #
                # crop image
                #

                if ($s_w > $t_w * $r)
                {
                    $s_x += round(($s_w - $t_w * $r) / 2);
                }
            }
            break;

            case self::RESIZE_FIXED_WIDTH:
            {
                #
                # fixed-width
                #
                # The image is resized to match the target width.
                # The image height resized accordingly.
                # This will result in thumbnails equals in width, but not in height.
                #
                
                $r = $s_w / $d_w;

                $d_h = round($s_h / $r);
    
                if ($s_w < $s_h)
                {
                    $d_w = round($s_w / $r);                    
                }
                
                $t_h = $d_h;
            }
            break;

            case self::RESIZE_FIXED_WIDTH_CROPPED:
            {
                #
                # fixed-width-cropped
                #
                # The image is resized to match the target width.
                # If the image height is taller than the target height, the image is cropped.
                # This will result in thumbnails equals in width, but not in height.
                #
                
                $r = $s_w / $d_w;

                $d_h = round($s_h / $r);
    
                if ($s_w > $s_h)
                {
                    $t_h = $d_h;
                }
                else
                {
                    $d_w = round($s_w / $r);                    
                }
                
                #
                # crop image
                #

                if ($s_h > $t_h * $r)
                {
                    $s_y += round(($s_h - $t_h * $r) / 2);
                }
            }
            break;
        
            case self::RESIZE_SURFACE:
            {
                #
                # surface
                #
                # The image is resized so to its surface matches the target surface.
                # this will result in thumbnails with different width and height, but with
                # the same amount of pixels.
                #
                
                $r = sqrt(($s_w * $s_h) / ($t_w * $t_h));
                
                $d_w = round($s_w / $r);
                $d_h = round($s_h / $r);
                
                $t_w = $d_w;
                $t_h = $d_h;
    
            }
            break;
        }
        
        #
        # center destination image result
        #
        
        if ($t_h > $d_h)
        {
            $d_y = round(($t_h - $d_h) / 2);
        }
        
        if ($t_w > $d_w)
        {
            $d_x = round(($t_w - $d_w) / 2);
        }
        
        #
        # create destination image
        #
        
        $destination = imagecreatetruecolor($t_w$t_h);

        #
        # call user's function to fill the area
        #
        
        if ($fill_callback)
        {
            call_user_func($fill_callback$destination$t_w$t_h);
        }

        #
        # now we resize and paste our image
        #
        
        imagecopyresampled
        (
            $destination,
            $source,
            
            $d_x$d_y$s_x$s_y,
            $d_w$d_h$s_w$s_h
        );
        
        return $destination;
    }
}

?>

08 mai

Dessiner un damier à la Photoshop Posté dans PHP 1 commentaire

C'est assez ennuyeux, lorsque l'on redimensionne des images avec la bibliothèque GD, de découvrir nos images transparentes sur un fond noir. C'est d'autant plus ennuyeux quand l'image en question est elle même en noir, comme par exemple du texte sur un fond transparent. Du coup on y voit plus rien et c'est tout nul.

Pour votre bonheur de créateur de miniatures, voici une fonction tirée de ma classe WdImage qui vous permettra de dessiner des damiers comme sous Photoshop.

Je sais, c'est chouette.

Quelques exemples

Dans les exemples suivants, $image est une ressource image créée avec la fonction imagecreatetruecolor(). $w représente sa largeur et $h sa hauteur.

Un damier de base

Avec le code suivant, nous allons créer un damier tout simple :

<?php

WdImage::drawGrid($image00$w - 1$h - 1);

?>

drawGrid 1

Une moitié pour mettre en valeur la différence

Puisque nous pouvons définir les coordonnées du damier, autant faire un petit test pour voir la différence :

<?php

WdImage::drawGrid($image$w / 2 - 10$w - 1$h - 1);

?>

drawGrid 2

Taille sans importance

Par défaut, la taille du damier est fixée à medium, ce qui correspond à des cases de 8 pixels de côté. En utilisant le paramètre size vous pouvez définir la taille du damier. Pour se faire, vous pouvez utiliser une des valeurs prédéfinies : small, medium ou large qui correspondent aux tailles 4, 8 et 16. Vous pouvez bien sûr spécifier la taille que vous désirer avec un entier : 2, 32, 64…

<?php

WdImage::drawGrid($image00$w - 1$h - 1'large');

?>

drawGrid 3

Des couleurs funkys

Les couleurs par défaut du damier ne sont pas toujours adaptées pour mettre en valeur une image. Le paramètre color1 vous permet de changer la première couleur du damier, ou d'utiliser un des schémas de couleurs pré-définis. Ce sont les même que sous Photoshop, à savoir : light, medium, dark, red, orange, green, blue et purple.

<?php

WdImage::drawGrid($image00$w - 1$h - 1'medium''dark');

?>

drawGrid 4

Enfin, le paramètre color2 vous permet de modifier la seconde couleur utilisée pour dessiner le damier :

<?php

WdImage::drawGrid($image00$w - 1$h - 1'medium'0xFF00FF0x00FF00);

?>

drawGrid 5

La méthode en question

Voici donc un extrait de la classe WdImage, contenant la méthode drawGrid() ainsi que la méthode allocateColor() qui est utilisée pour allouer les couleurs.

À noter que les couleurs peuvent être spécifiées en hexadécimal 0xFFFFFF ou sous forme de tableau array(255, 255, 255). Les définissions CSS sont normalement supportées mais on été désactivées pour les besoins de la démonstration. La classe WdCSS ou sa méthode decodeColor() feront sans doute l'objet d'un prochain article.

<?php

class WdImage
{
    public static function drawGrid($image$x1$y1$x2$y2$size=4$color1=0xFFFFFF$color2=0xCCCCCC)
    {
        #
        # resolve size
        #
            
        if (is_string($size))
        {
            static $sizes = array
            (
                'none' => 0,
                'small' => 4,
                'medium' => 8,
                'large' => 16
            );
            
            if (isset($sizes[$size]))
            {
                $size = $sizes[$size];
            }
            else
            {
                $size = 4;
            }
        }

        #
        # resolve colors
        #
        
        if (is_string($color1))
        {
            static $color_schemes = array
            (
                'light' => array(0xffffff0xcccccc),
                'medium' => array(0x9999990x666666),
                'dark' => array(0x3333330x666666),
                'red' => array(0xffffff0xffcccc),
                'orange' => array(0xffffff0xffd8bd),
                'green' => array(0xffffff0xcce4cc),
                'blue' => array(0xffffff0xcce0f8),
                'purple' => array(0xffffff0xdcccf8)
            );
            
            if (isset($color_schemes[$color1]))
            {
                $scheme = $color_schemes[$color1];
            
                $color1 = $scheme[0];
                $color2 = $scheme[1];
            }
        }
        
        #
        # allocate colors
        #
        
        $c1 = self::allocateColor($image$color1);
        $c2 = self::allocateColor($image$color2);
        
        #
        # draw grid
        #
        
        if ($size)
        {
            #
            # draw grid, line by line, square by square
            #
            
            for ($j = $y1$b = 0 ; $j < $y2 ; $j += $size$b++)
            {
                for ($i = $x1$a = 0 ; $i < $x2 ; $i += $size$a++)
                {
                    imagefilledrectangle
                    (
                        $image,
                        
                        $i$j,
                        max($i + $size$x2),
                        max($j + $size$y2),
                        
                        ($a % 2) ? $c1 : $c2
                    );
                }
                
                #
                # in order to have a nice pattern,
                # after each line is drawn we switch color 1 and color 2
                #
    
                $t = $c1;
                $c1 = $c2;
                $c2 = $t;
            }
        }
        else
        {
            #
            # grid size is 0, we simply draw a rectangle with color 1
            #
        
            imagefilledrectangle($image$x1$y1$x2$y2$c1);
        }
    }
    
    public static function allocateColor($image$color)
    {
        #
        # use WdCSS class to decode CSS colors definition (#FFF, rgb(), white)
        #

// if (is_string($color))
// {
// $color = WdCSS::decodeColor($color);
// }

        if (is_array($color))
        {
            return imagecolorallocate
            (
                $image$color[0]$color[1]$color[2]
            );
        }
        else if (is_numeric($color))
        {
            return imagecolorallocate
            (
                $image($color & 0xFF0000) >> 16($color & 0x00FF00) >> 8($color & 0x0000FF)
            );
        }
        else
        {
            return imagecolorallocate
            (
                $image128128128
            );
        }
    }
}

?>

20 avr

parse_url pour de vrai Posté dans PHP 3 commentaires

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>';

    ?>
  4. 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é.

19 avr

PHP, XML & Prévisions Météo Posté dans PHP Pas de commentaire

On ne sait plus trop comment s'habiller ces jours-ci. Un jour on se ballade poils au vent, caressé par un soleil complice. Le lendemain on rentre du boulot en vélo, arrosé par une pluie moqueuse. Des conditions climatiques perplexes, idéales pour présenter ma dernière classe : WdWeather.

Présentation

Utilisant les services pratiques et connus de weather.com, WdWeather permet d'obtenir les prévisions météorologiques jusqu'à 10 jours (limite imposée par le service). Les informations sont aussi nombreuses que variées. Ainsi pour chaque jour on obtient, en autre, les informations suivantes :

  • Les heures de lever et de coucher du soleil
  • Les températures les plus basse et les plus hautes

Et pour chaque période de la journée (Jour / Nuit) :

  • Le numéro du pictogramme associé au temps.
  • Une description du temps en anglais, par exemple : Mostly Sunny.
  • La force et la direction du vent.
  • Les précipitations.
  • Le taux d'humidité.

Bref, il y a de quoi faire.
Voyons justement comment tout ça marche.

Comment ça marche ?

C'est vraiment très simple. Le principe est le suivant:

  1. On demande le code de la ville dont on souhaite connaitre les prévisions météo.
  2. On demande les prévisions météo pour les jours à venir.
  3. On affiche le résultat.

C'est tellement fastoche que je me suis presque endormi en écrivant les étapes.

Le Code

<?php

class WdWeather
{
    var $markup_contents = '([^<]+)';
    
    function getCityCode($search)
    {
        #
        # we create a nice looking URL
        #
        
        $search = explode(','$search);
        $search = array_map('urlencode'$search);
        $search = implode(',+'$search);
        
        #
        # ask the service about the location
        #
    
        $xml = file_get_contents
        (
            'http://xoap.weather.com/search/search?where=' . $search
        );
        
        #
        # try to get the first result
        #
        
        if (!preg_match('#loc id="([^"]+)#'$xml$matches))
        {
            return;
        }
        
        return $matches[1];
    }

    function getWeather($city_code$days)
    {
        $xml = file_get_contents
        (
            'http://xoap.weather.com/weather/local/' . $city_code .
            '?cc=*&unit=s&dayf=' . $days
        );
        
        if (!$xml)
        {
            return;
        }
        
        return $this->parseDays($xml);
    }
    
    function parseDays($xml)
    {
        $days = array();
        
        #
        # split xml by days
        #

        $parts = preg_split('#<day d=[^>]+>#'$xml);

        array_shift($parts);

        $mc = $this->markup_contents;

        foreach ($parts as $xml)
        {
            $days[] = $this->parseDay($xml);
        }
        
        return $days;
    }

    function parseDay($xml)
    {
        $mc = $this->markup_contents;
    
        preg_match
        (
            '#' .
            
            '<hi>' . $mc . '</hi>\s*' .
            '<low>' . $mc . '</low>\s*' .
            '<sunr>' . $mc . '</sunr>\s*' .
            '<suns>' . $mc . '</suns>\s*' .
            '<part p="d">(.*)</part>\s*' .
            '<part p="n">(.*)</part>' .
            
            '#ms',
            
            $xml$matches
        );
        
        array_shift($matches);
                    
        #
        # parse day periods (day / night)
        #
        
        $matches[4] = $this->parseDayPeriod($matches[4]);
        $matches[5] = $this->parseDayPeriod($matches[5]);
        
        #
        #
        #
        
        return array_combine
        (
            array('hi''low''sunr''suns''day''night')$matches
        );
    }
    
    function parseDayPeriod($xml)
    {
        $mc