Articles taggés manipulation

symfony

Manipulation d’images sous Symfony2

0

Dans un article précédent, je montrais une bibliothèque permettant de manipuler des images d’une manière un peu particulière, mais à mon sens intéréssant. Ici, nous allons parler de la façon dont cette bibliothèque peut s’intégrer à Symfony2.

Je travaille actuellement sous Symfony2 et j’ai été amené à manipuler des images dans une application que je développe. Ayant déjà sous la main ma propre classe de gestion d’images, j’ai décidé de l’intégrer au framework à l’aide d’un Bundle. Comme vous pourrez le remarquer si vous jetez un oeil au projet git correspondant, l’intégration de la classe dans un bundle Symfony2 ne nécéssite pas beaucoup de code supplémentaire.

Installation

Commencez par placer le dépôt git dans vos dépendances et clonez-le. Ajoutez alors l’espace de nom Gregwar à votre app/autoloader.php :

'Gregwar' => __DIR__.'/../vendor/gregwar-image/bundle/',

Une fois fait, ajouter le bundle à votre app/AppKernel.php :

...
public function registerBundles()
{
$bundles = array(
...
new Gregwar\ImageBundle\GregwarImageBundle(),
...
);
...

Il ne vous reste plus qu’à ajouter à votre fichier de configuration (ex: app/config/config.yml) :

gregwar_image: ~

Puis, créez le dossier web/cache et donnez les permissions à apache d’écrire dedans

mkdir web/cache
chmod 777 web/cache

Si vous voulez utiliser un autre nom, précisez le dans la configuration:

gregwar_image:
   cache_dir: mes_fichies_caches

Services fournis

Ce Bundle Symfony2 vous fournit principalement deux choses:

  1. Un service image.handling permettant d’utiliser la classe Gregwar\Image avec l’injection de dépendance;
  2. Un helper twig, qui vous permet d’utiliser les fonctionnalités de Gregwar\Image directement dans vos templates

Je ne m’attarderais pas sur le premier point, vous l’aurez vite compris, vous pouvez écrire des lignes de la forme:

$this->get('image.handling')->open('linux.jpg')
    ->grayscale()
    ->rotate(12)
    ->save('out.jpg')

Dans vos contrôleurs ou dans n’importe quel classe ayant accès aux services, ce qui peut s’avérer pratique !

La partie intéréssante repose sur l’helper Twig, qui peut être très utile en tirant profit du dossier de cache. Le bundle enregistre la fonction twig image() qui fournit la même API qu’avec la classe Gregwar\Image. Un exemple:

<!-- template.html.twig -->

<img src="{{ image('linux.png').resize('30%').rotate(-12).jpeg }}" />

Le résultat sera alors l’adresse du fichier de cache hashé. Petit bonus: cet helper fait lui-même appel à l’helper asset() pour le chemin du fichier de cache.

Vous pouvez également profiter de l’API de la classe image:

linux.png a une largeur de {{ image('linux.png').width }} px

Dépôt Gregwar/ImageBundle sur Github

Manipulez vos images avec PHP

2

La classe Image présenté dans un article précédent a bien évolué depuis le temps. Elle est maintenant disponible dans un dépôt github dédié et permet de manipuler des images.

Comme l’explique le README, il est possible d’effectuer des actions sur vos images facilement, en utilisant le principe de chaînage de fonctions:

require_once('../lib/Gregwar/Image.php');

use Gregwar\Image;

// Redimensionnement dans une boîe de 100x100 et passage en négatif
Image::open('img/test.png')
    ->resize(100, 100)
    ->negate()
    ->save('out.jpg');

Ou encore, par exemple:

<!--?php require_once('../lib/Gregwar/Image.php'); use Gregwar\Image; // Fusion de deux images Image::open('img/test.png')     --->merge(Image::open('img/test2.jpg')->cropResize(100, 100))
    ->save('out.jpg');

Et même, dans le cas de l’utilisation du cache:

</pre>
<img src="<?php echo Image::open('img/test.png')     ->resize('26%')<br />    ->sepia()<br />    ->jpeg(); // Fichier de cache généré à la volée ?>" alt="" />
<pre>

Ce principe, implémenté à l’aide d’un simple « return $this; » en fin des fonctions, permet une bonne lisibilité du code et des opérations effectuées sur un objet.

L’optimisation des opérations

La particularité de cette classe et que lorsque vous apellez une méthode de manipulation sur un des objets image, aucun traitement n’est réellement effectué. En fait, la méthode et ses arguments sont stockés dans un tableau d’opérations à effectuer.

Si vous apellez save() explicitement, les opérations sont réellement appliquées, les resources nécéssaires ouvertes et traitées.

En revanche, si vous utilisez jpeg(), png() ou gif(), -c’est à dire que vous souhaitez utiliser une génération à la volée avec du cache- l’objet calculera alors un hash à partir de toutes les opérations demandées, du nom du type et de la date de modification du fichier d’image source. Ce hash est alors utilisé pour chercher un fichier dans le répertoire de cache (par exemple images/cache/1/4/a/d/2/98098ad29e294f.jpg) si ce fichier existe, la fonction retournera directement son nom, autrement elle lancera la compilation des opérations.

De cette façon, les opérations ne seront appliquées qu’au premier chargement de la page, la dehxuième fois, le calcul du hash suffira pour trouver le fichier de cache correspondant sans même ouvrir l’image source !

Dans les coulisses de la classe : la méta-programmation

Contrairement à la version présentée précédemment, les opérations qu’il est possible d’effectuer se sont multipliées. Il n’était plus envisageable d’écrire manuellement une fonction publique servant à placer l’appel de fonction dans le tableau des opérations et une fonction permettant de réellement effectuer l’opération. Il a fallu ruser pour éviter d’écrire du code extremement répetitif, et voici ce que ça donne:

public function __call($func, $args)
{
    $reflection = new \ReflectionClass(get_class($this));
    $methodName = '_'.$func;

    if ($reflection->hasMethod($methodName)) {
        $method = $reflection->getMethod($methodName);

        if ($method->getNumberOfRequiredParameters() > count($args))
            throw new \InvalidArgumentException(
                   'Not enough arguments given for '.$func);

        $this->addOperation($methodName, $args);
        return $this;
    }

    throw new \BadFunctionCallException('Invalid method: '.$func);
}

Cette technique fait appel à la fonction magique __call() et la la reflection. Le principe est simple: lorsqu’une fonction n’existe pas, __call() est apellée avec en argument le nom de la fonction et ses arguments. L’implémentation de __call vérifie alors que la méthode _$methode existe (le même nom, préfixé d’un _), si elle existe, elle vérifie que le nombre d’argument est bon et l’ajoute au tableau des opérations.

De ce fait, l’implémentation de negate() tient simplement en une ligne:

public function _negate()
{
    imagefilter($this->gd, IMG_FILTER_NEGATE);
}

Ces lignes suffisent pour faire marcher l’appel à ->negate() et son ajout au tableau d’opération.

Quand au rendu des effets, il tient en seulement trois lignes, à l’aide d’une boucle itérant sur les opérations et de l’utilisation de call_user_func_array, qui permet d’apeller une méthode au nom variable:

foreach ($this->operations as $operation) {
    call_user_func_array(array($this, $operation[0]), $operation[1]);
}

Contribuez

Si cette classe vous intéresse, n’hésitez pas à contribuer !

Rendez-vous sur https://github.com/Gregwar/Image pour forker le projet, n’hésitez également pas à proposez vos améliorations en faisant des pull requests ou en ouvrant des issues.

Le code est sous license MIT.

Haut de page