Php : une boucle qui incarne une des pires horreurs possibles

Voici une horreur qu’on peut trouver, en plein milieu du code sur lequel on m’a demandé de travailler.
Si vous faites mieux dans la série « la pire horreur en Php », n’hésitez pas à en donner un exemple en commentaire…

for ($i=0; $i<count($tab); $i++)
{
    if ($tab[key($tab)]==true) {
        $montableau2[][key($tab)]='code';
    }
    next($tab);
}

Php : challenge ASCII art

J’ai fait un pari. Petit challenge à réaliser : faire un script (peu importe le langage) qui prend en paramètre un fichier image, et qui en sort de l’ASCII art.
Il faut qu’il soit rapidement paramétrable sur la taille des fontes.

Je l’ai fait « théorique » en 59 minutes, mais entièrement fonctionnel en une heure trente. J’ai fait deux scripts : un qui génère le fichier des fontes, et le principal qui lit le fichier image des fontes, et le fichier image en paramètre, et sort de l’ASCII art.

Exemple de résultat (si si c’est bien du texte, vous pouvez le sélectionner et copier coller en tant que texte !) :

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBMBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB¨ MBBBBBBMM0BBB
BBBBBBBBBBBBBBBBBBBBBBBBBP'`BB  ,BBBP¨    MBB
BBBBBBBBBBBBBBBBBBBBBBBBB&  %BMBPBBL  ,a  ¨BB
BBBBBBBBBBBBBBBBBBBBBBBBBB  jBB  MBBaBBP   BB
BBBBBBBBBBBBBBBBBBBBBBBBPM   BB  ¨BBB^  ,  0B
BBBBBBBBBBBBBBBBBBBBBBP`     BBb  BB'  mB  jB
BBBBBBB@P'BBBBBB@M@BBB   mB  $BK  0B  jBP  ¨B
BBBBBBBK  BBBP^`   MBB  dBB  MBB  jB   ¨ L,aB
B^¨ 'BBB  %BB  ,a   BB  &BBL  BB   Bh  ,WBBBB
B    ¨BB  jBB,mBM^  $B  'BBF  BBL  BBBBBBBBBB
BL    `BL  BBBP¨    jB   ^^   %B&mBBBBBBBBBBB
B&  w  ¨&  BBP  wB   B&    wamBBBBBBBBBBBBBBB
BB  %w     MBL  BB¨  BBBwamBBBBBBBBBBBBBBBBBB
BB  MBw    ¨BK  ¨`, ,&BBBBBBBBBBBBBBBBBBBBBBB
BBL  BBN    BB,  aBBBBBBBBBBBBBBBBBBBBBBBBBBB
BB&  BBBh   0BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBB  MBBBhwmBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBB  OBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

Ne regardez pas la suite si vous voulez essayer de le faire pour vous !

<?php
function usage_et_exit($message = '', $exit_error = -1) {
    global $argv;
    if ($message != '') {
        echo $message."\n";
    }
    echo $argv[0]." {image gif reference} {image noir et blanc jpg}\n";
    exit($exit_error);
}

function noir_ou_blanc($r, $g, $b) {
    /* http://stackoverflow.com/questions/254388/how-do-you-convert-an-image-to-black-and-white-in-php */
    if (((0.299*$r) + (0.587*$g) + (0.114*$b))> 0x7F) {
        return 0xFFFFFF;
    } else {
        return 0x000000;
    }
}

function cherche_meilleure_correspondance(
    $img_src, $x, $y, $img_fonts, $font_size, $tab_base
) {
    $meilleur = -1;
    $retour = '';
    foreach ($tab_base as $idx => $ch) {
        $match = 0;
        for ($t_y=0; $t_y<$font_size; $t_y++) {
            for ($t_x=0; $t_x<$font_size; $t_x++) {
                if (imagecolorat($img_src, $t_x+$x, $t_y+$y)
                    != imagecolorat(
                        $img_fonts,
                        ($idx*$font_size) + $t_x, $t_y
                    )
                ) {
                    $match++;
                }
            }
        }
        if ($match>$meilleur) {
            $retour = $ch;
            $meilleur = $match;
        }
    }
    return $retour;
}

function convert_img_noir_et_blanc($filename, $img)
{
    list($width, $height, $type, $attr) = getimagesize($filename);
    /* Conversion en noir et blanc */
    for ($y=0; $y<$height; $y++) {
        for ($x=0; $x<$width; $x++) {
            $rgb = imagecolorat($img, $x, $y);
            $r = ($rgb >> 16) & 0xFF;
            $g = ($rgb >> 8) & 0xFF;
            $b = $rgb & 0xFF;
            $nb = noir_ou_blanc($r, $g, $b);
            imagesetpixel($img, $x, $y, $nb);
        }
    }
}

$tab_base = array(
    '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
    'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
    'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
    '5', '6', '7', '8', '9', '&', '\'', ',', ';', ':', '?', '.', '/', '!',
    '%', '^', '¨', '$', '~', '#', '(', ')', '[', ']', '|', '-', '`', ' '
);
$font_size = 14;
$min_width  = 400;
$min_height = 400;

if ($argc!=3) {
    usage_et_exit("Arguments invalides", -1);
}

$img_tmp = imagecreatefromstring(file_get_contents($argv[1]));
$width = imagesx($img_tmp);
$height = imagesy($img_tmp);
$img_fonts = imagecreatetruecolor($width, $height);
imagecopy($img_fonts, $img_tmp, 0, 0, 0, 0, $width, $height);
imagedestroy($img_tmp);
unset($img_tmp);
if ($img_fonts===false) {
    usage_et_exit("Erreur chargement de ".$argv[1], -1);
}

/* Conversion en noir et blanc des fontes */
convert_img_noir_et_blanc($argv[1], $img_fonts);
imagepng($img_fonts, './resultat_fonts.png', 0);

list($width, $height, $type, $attr) = getimagesize($argv[2]);
if ($width<$min_width) {
    usage_et_exit("Largeur trop petite de ".$argv[1].", taille min = ".$width_min, -1);
}
if ($width<$min_height) {
    usage_et_exit("Hauteur trop petite de ".$argv[1].", taille min = ".$width_min, -1);
}

$img_src = imagecreatefromjpeg($argv[2]);
if ($img_src===false) {
    usage_et_exit("Erreur chargement de ".$argv[2], -1);
}
convert_img_noir_et_blanc($argv[2], $img_src);
/* Ok on a l'image en noir et blanc. Parcourir toute l'image */
imagepng($img_src, './resultat_noir_et_blanc.png', 0);

$y = 0;
$max_result = 0;
while (($y + $font_size) < $height) {
    $x = 0;
    while (($x + $font_size) < $width) {
        echo cherche_meilleure_correspondance(
            $img_src, $x, $y, $img_fonts, $font_size, $tab_base
        );
        $x += $font_size;
    }
    echo "\n";
    $y += $font_size;
}
echo "Done.\n";
?>

Et le fichier qui génère les fontes (22 minutes de perdues à le faire…) :

<?php
$tab_base = array(
    '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
    'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
    'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
    '5', '6', '7', '8', '9', '&', '\'', ',', ';', ':', '?', '.', '/', '!',
    '%', '^', '¨', '$', '~', '#', '(', ')', '[', ']', '|', '-', '`', ' '
);
$x=0;
$font_size = 14;
$width = count($tab_base)*$font_size;
echo 'width='.var_export($width,true)."\n";
$height = $font_size*4;
$img = imagecreatetruecolor($width, $height);
if ($img===false) {
    throw new Exception("Erreur");
}
imagefilledrectangle($img, 0, 0, $width, $height, 0xFFFFFF);
foreach ($tab_base as $idx => $char) {
    $ok = imagettftext(
        $img,
        $font_size, 0,
        $idx * $font_size, $font_size,
        0x000000,
        './consola.ttf', $char
    );
}
imagegif($img, './test.gif');
echo "Done.\n";
?>

PHPExcel : comment générer des cellules avec des styles différents

PHPExcel est un outil pour génerer des fichier Excel en Php.

Il y a beaucoup d’exemples, et tout est bien censé fonctionner. Comme les méthodes sont très parlantes et que c’est bien écrit, j’ai voulu faire un code qui crée plusieurs cellules avec des styles différents.

Ce qui n’est pas expliqué dans la documentation c’est ce qui suit : les « pages » Excel (« tabsheets ») ont des groupes de styles, et chaque cellule référence un de ces styles (sachant qu’il y a un style par défaut). Donc ce que j’ai fait était faux : j’ai essayé d’appliquer directement un style à une cellule, puis un autre style à une autre cellule. Le code exécuté ne correspondait pas du tout à ce que je pensais. Le code faisait ceci : je recherche le style à appliquer parmi tous les styles qui sont recensés dans le « tabsheet » courant. Comme le style n’existait pas, alors il remplaçait le style par défaut du « tabsheet » courant par celui que je demandais à appliquer. Donc comme toutes les cellules référençaient le même style, elles avaient toutes les mêmes propriétés, et évidemment, la dernière « application de style » modifiait en conséquence toutes les cellules. La solution est simple, mais elle n’est pas documentée, et de plus, la fonction à appeler (« duplicateStyle() ») n’est pas très parlante, contrairement à la totalité de l’outil PhpExcel (ce qui est d’autant plus déroutant).

Voici mon code, et je vous explique après pourquoi ça ne fonctionnait pas, et ce qu’il faut faire pour que cela fonctionne.

(1) Je crée deux tableaux différents, de styles différents :

$styleParDefaut = array(
    'font' => array(
        'name'   => 'Bernard MT Condensed',
        'size'   => 10,
        'italic' => false,
        'bold'   => false,
    ),
    'alignment' => array(
        'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_LEFT,
        'wraptext' => false
    )
);
$styleDonneesTelechargeesLe = array(
    'font' => array(
        'name'   => 'Arial',
        'size'   => 10,
        'italic' => true,
        'bold'   => false,
    ),
    'alignment' => array(
        'horizontal' => PHPExcel_Style_Alignment::HORIZONTAL_LEFT,
        'wraptext' => false
    )
);

(2) Puis je tente de les assigner, un par cellule, comme ça je pensais obtenir deux cellules avec deux styles différents :

function setText($sheet, $colonne, $ligne, $style, $text)
{
    $cell->setValue($text);
    $cell = $sheet->getCellByColumnAndRow($colonne, $ligne);
    $cell->getStyle()->applyFromArray($style);
    $cell->setValue($text);
}

Et le code par cellule :

setText(
    $sheet,
    $colonne, $ligne + 3,
    $styleParDefaut,
    'Texte 1'
);
setText(
    $sheet,
    $colonne, $ligne + 5,
    $styleDonneesTelechargeesLe,
    'Texte 2'
);

Eh bien ça ne fonctionne pas du tout comme cela. En lisant le code, il faut générer dynamiquement les style, et les passer via duplicateStyle().

Donc :

(1) Créer les styles :

$styleParDefaut = new PHPExcel_Style();
$styleParDefaut->applyFromArray(array(.. même chose que précedemment ..));
$styleDonneesTelechargeesLe = new PHPExcel_Style();
$styleDonneesTelechargeesLe->applyFromArray(array(.. même chose que précedemment ..));

(2) Les réutiliser via duplicateStyle()

$cell = $sheet->getCellByColumnAndRow($colonne, $ligne);
$sheet->duplicateStyle($style, $cell->getCoordinate());
$cell->setValue($text);

Symfony: The form’s view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is…

J’ai eu l’erreur qui est le titre de mon post.

Pourtant, j’avais tout fait correctement. J’avais mis en place une collection ManyToMany simple : un partenaire peut être relié à une ou plusieurs personnes et une personne peut être reliée à un ou plusieurs partenaires.

Sur symfony 2.1, tout fonctionnait correctement, et j’ai fait la mise à jour sur symfony 2.3 (pour rester à la pointe 😉 ) mais là une erreur apparaissait :

« The form’s view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class HQF\Bundle\PizzasBundle\Entity\Personne. You can avoid this error by setting the « data_class » option to « HQF\Bundle\PizzasBundle\Entity\Personne » or by adding a view transformer that transforms an instance of class HQF\Bundle\PizzasBundle\Entity\Personne to scalar, array or an instance of \ArrayAccess. »

Après un bon moment de recherche, j’ai trouvé ma solution ici.

Dans mon bundle j’avais mon fichier « Bundle/Form/Type/PartenaireType.php » dans lequel j’avais déclaré tout comme il fallait :

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('nom',                    'text');
        $builder->add('site_actif',             'checkbox');
        $builder->add('afficher_img_produits',  'checkbox');
        $builder->add('commande_en_ligne',      'checkbox');
        $builder->add('livraison',              'checkbox');
        $builder->add('livraison_delai',        'integer');
        $builder->add(
            'personnes',
            'collection',
            array(
                'type' => new PersonneType(),
            )
        );
    }

Tout était parfait, et fonctionnait sous Symfony 2.1. Mais lors de la mise à jour, ça ne fonctionnait plus. Il manquait cela : préciser à nouveau que la PersonneType était liée à un type Personne :

'options' => array('data_class' => 'HQF\Bundle\PizzasBundle\Entity\Personne')

Donc dans mon code ça donne ça :

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('nom',                    'text');
        $builder->add('site_actif',             'checkbox');
        $builder->add('afficher_img_produits',  'checkbox');
        $builder->add('commande_en_ligne',      'checkbox');
        $builder->add('livraison',              'checkbox');
        $builder->add('livraison_delai',        'integer');
        $builder->add(
            'personnes',
            'collection',
            array(
                'type' => new PersonneType(),
                'options' => array(
                    'data_class' => 'HQF\Bundle\PizzasBundle\Entity\Personne'
                ),
            )
        );
    }

Un grand merci à Stackoverflow, sinon j’aurais mis bien plus de temps à m’en sortir, je pense !

Qwant : un google beaucoup plus sympa

Je ne veux pas faire de publicité parce que ce sont des Français, mais c’est la première fois depuis des années que je vois un moteur de recherche que je trouve plus agréable que google.

Si si, essayez le : http://www.qwant.com/

La page de présentation est super sympa et super moderne, et s’affiche vite et bien ::

Qwant main page

Et la page détail est aussi super sympa :

Qwant detail page

Essayez le, il ne manque plus que quelques résultats de recherche et c’est bon !

Bon j’espère que chez bnpparibas, ils vont tout de même ouvrir le pare feu un peu plus loin que google.fr… Enfin c’était un message personnel 😉

jQuery et la ré organisation de div / cartes

Si jamais vous voulez faire plein de petits « cartes », ou « article », voire des photos, et que vous vouliez qu’ils soient automatiquement réorganisés, voici ce qu’il vous faut :

Ce plugin en JavaScript pour jQuery est absolument génial : http://masonry.desandro.com/. Essayez de redimensionner les fenêtres et vous verrez comment tout ça est réorganisé.

Css inutilisé : comment supprimer toute information inutile – how to

Si jamais vous avez un site qui utilise plein d’outils et que vous pensez que beaucoup de votre CSS n’est pas utilisé, alors allez jeter un coup d’oeil ici : http://unused-css.com/.

C’est un site qui aide pour dire quels sont tous les styles CSS déclarés, et lesquels ne sont pas utilisés.
Extrêmement pratique.

Notez tout de même que si jamais vous construisez dynamiquement des objets DOM (notamment en jQuery) qui utilisent un CSS particulier, et que ce dernier n’est pas utilisé ailleurs, alors je pense que ça trompe le vérificateur et qu’il pense que ce CSS n’est jamais utilisé.

Symfony 2 : mon site de démonstration est sorti : livrepizzas

Mon site de démonstration pour l’utilisation du framework Symfony 2 est sorti.

http://www.livrepizzas.fr

Ce n’est qu’un site de démonstration, il est loin d’être parfait !

  • Bien sûr il est optimisé pour google et pour les mobiles et tablettes.
  • Bien sûr il est compatible w3c.
  • Bien sûr il est moderne et utilise du HTML5.
  • Bien sûr il est en responsive design.
  • Bien sûr c’est facile à faire. 🙂

Si vous avez des commentaires ou suggestions à faire, n’hésitez pas !

Php, AJAX et problèmes de cache sur iOS : comment les résoudre

J’ai expliqué qu’il fallait mettre, avant de faire le tout dernier ordre de sortie echo json_encode($resultat_final); les headers JSON.

header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: application/json');

On m’a demandé à quoi servaient les entête de cache. J’ai expliqué que ça servait à s’assurer que les interrogations AJAX se feraient toujours (notez le futur), c’est à dire à demander au navigateur de ne rien garder en cache.

Comme toujours, ma mémoire me fait défaut mais mon expérience reste, et je savais qu’il faut éviter tout risque de problème cache.
J’ai retrouvé une explication concrète du problème : certaines versions du navigateur Safari sur iOS6 ne réinterrogent pas les appels AJAX.

Il suffit donc de faire deux choses :

  • Soit modifier la configuration Apache (c’est pour ça que j’ai consacré quelques heures sur les hôtes virtuels et leur configuration) pour qu’il y ait ces entêtes par défaut :
    Header set Cache-Control "no-cache"
  • Soit faire ma solution directement en Php (mais ça implique de le faire dans tous les retours AJAX – ce qui ne gêne en rien pour les petits projets)
  • Soit préciser en JavaScript de modifier le header :
    $.ajaxSetup({
        type: 'POST',
        headers: { "cache-control": "no-cache" }
    });

Toutes les solutions sont prises via le site de questions/réponses de reférence : en Anglais ici.