Symfony2 et jQuery Datatable

Nous avons tous eu, un jour, le besoin d’afficher un tableau de données dans nos pages Web. Quand c’est un tableau de données figées et peu nombreuses, nous le réalisons à la main. Quand elles sont dynamiques, mais toujours peu nombreuses, nos outils de templating, comme Twig, sont amplement suffisants.
Par contre, quand les données sont dynamiques, très nombreuses, qu’il faut gérer une pagination et si en plus ce tableau doit proposer la manipulation de ces données, là ça commence à être vraiment complexe.

Datatable est un plugin pour jQuery qui permet de réaliser simplement des tableaux de données (aussi appelé datagrid) dynamiques et paginables, avec chargement des données asynchrones, qui trie par colonne, filtre des données affichées et/ou filtre côté serveur. Il propose de nombreuses fonctionnalités ce qui le rend vraiment indispensable. Lire la suite ›

Tagués avec : , ,
Publié dans Doctrine, PHP, Symfony2

Plan d’auto-formation à Symfony2 et les concepts associés

Voici un plan d’auto formation à Symfony2 et ses concepts associés. Cet article comporte beaucoup de liens vers différentes documentations qui vous permettrons d’appréhender simplement Symfony2 et le monde de la qualité de code de PHP.

Sommaire

Lire la suite ›

Tagués avec :
Publié dans Non classé, PHP, Symfony2

Extension Doctrine pour la fonction Match Against

Voici un tutorial Symfony2 pour ajouter la fonction Match Againts à Doctrine pour l’utiliser dans vos requêtes DQL.

Le serveur de base de données Mysql gère la fonction Match Againts uniquement avec le moteur MyISAM. Cela est vrais pour les versions inférieur à la 5.6. Dans la version 5.6 cette fonction sera aussi géré par le moteur innoDB. Ce tutorial est donc à adapter si vous bénéficier d’une version 5.6 ou plus.

Lorsque l’on créé une base de données avec Doctrine, il utilise par défaut le moteur innoDB car il gère les relations entre les tables. On va donc être obligé de s’adapter.

Dans ce tutorial, nous cherchons à faire une recherche Full Text sur certain champ d’une entité Fiche. Pour cela nous allons devoir coder un peu.
Dans un premier temps nous allons ajouter la fonction Match Againts à Doctrine (avec ses tests unitaires).
Nous devrons créer ensuite une classe qui servira uniquement à la recherche Full Text. C’est contraignant, mais si vous tenez à réduire le nombre de requête SQL émise par votre application, c’est un mal nécessaire.
Enfin nous ajouterons un écouteur pour synchroniser notre classe d’indexation avec notre fiche.
Lire la suite ›

Tagués avec : , ,
Publié dans Doctrine, PHP, Symfony2

Symfony 2 – Vérifier le role d’un utilisateur dans un Voter

Petit How-To sur comment vérifier si un utilisateur à bien le rôle nécessaire pour effectuer une action dans un Voter.

Avant tout, deux petits lien, l’un vers la documentation des Voter de Symfony 2 et le second sur le fonctionnement du Gestionnaire de décision d’accès (Access Decision Manager) .

Nous verrons ici comment tester si l’utilisateur en cours dispose bien d’un rôle en prenant en compte la hiérarchisation des rôles offert par Symfony 2.

 

Dans un premier temps, il faut ajouter dans le fichier de configuration de l’injection de dépendance, une dépendance au service “security.access.role_hierarchy_voter”.

Votre déclaration de Voter ressemblera à cela :

<service id="security.voter.categorie" class="MyBundle\Security\Authorization\Voter\CategorieVoter" public="false">
   <argument type="service" id="security.access.role_hierarchy_voter" />
   <tag name="security.voter" />
</service>

Dans la classe de votre Voter, il faut bien entendu ajouter un attribut et le constructeur pour récupérer le service.

namespace MyBundle\Security\Authorization\Voter;

use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class CategorieVoter implements VoterInterface
{
    /**
     * @var \Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter
     */
    private $roleVoter;

    public function __construct($roleVoter)
    {
            $this->roleVoter = $roleVoter;
    }
//...
}

L’utilisation est simple.

if($this->roleVoter->vote($token, $token->getUser(), array('ROLE_USER')) === VoterInterface::ACCESS_GRANTED) {
      // l’utilisateur à le role
}

Donc si votre utilisateur à un rôle admin, selon la hiérarchie des rôles, il à aussi un ROLE_USER. Donc le service renverra un VoterInterface::ACCESS_GRANTED !

Tagués avec : , ,
Publié dans PHP, Symfony2

Symfony – Internationalisation des dates pour Twig

Petit How To pour internationaliser l’affichage des dates dans Twig.

Dans le cas où l’on veut afficher des dates du type « 14 février 2032 » vous remarquerez que le filtre « date » avec les paramètres « d F Y » vous affichera la date en anglais.

Pour avoir nos date en français nous allons simplement installer l’extensions Intl de Twig qui ne fait pas partie du package par défaut.

Rendez-vous à l’adresse suivante : http://twig.sensiolabs.org/doc/extensions/intl.html
Sur cette page vous trouverez la commande pour installer l’extension Twig par Composer ou utilisez la commande suivante. Pensez juste à changer le numéro de version pour le dernier.

composer require twig/extensions ~1.2

Une fois ça de fait, vous avez fait la moitié du travail.

Ouvrez le fichier Resources/config/services.xml et ajoutez les lignes suivantes.
Ce paramétrage intègre l’extension à Twig.

    <services>
	< !-- [...] -->
        <service id="twig.extension.intl" class="Twig_Extensions_Extension_Intl">
            <tag name="twig.extension" />
        </service>
    </services>

Pour l’utilisation classique RTFM, et pour aller un peu plus loin (enfin pas trop loin), suivez le guide…

Donc voici quelques exemples :

Pour : 5 janvier

{{ post.published_at|localizeddate('none', 'none', null, null, 'd MMMM') }}

Pour : 5 janvier 1955

{{ post.published_at|localizeddate('none', 'none', null, null, 'd MMMM Y') }}

Pour : lundi 5 janvier 1955

{{ post.published_at|localizeddate('none', 'none', null, null, 'EEEE d MMMM Y') }}

Le formatage des dates se trouve à cette adresse : http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax

Publié dans Symfony2

Symfony 2 – Les Champs de choix booléen avec des radios buttons

Pour représenter un choix booléen (vrai, faux) dans un formulaire en utilisant le composant de formulaire fournit avec Symfony2, il est possible d’utiliser des checkbox.
Mais des fois on veut deux champs radios, pour bien faire comprendre à l’utilisateur qu’il à le choix entre « Oui » et « Non ».

Cependant, si ce champ est obligatoire et que notre utilisateur choisit « Non » il est possible qu’il voit apparaître un joli message d’erreur l’informant que la valeur saisie n’est pas correct.

Je vous donne ici une façon simple et rapide pour avoir un choix booléen avec des radios boutons.

Dans notre exemple, nous allons utiliser l’entité doctrine suivante.
Une entité avec une propriété « estOk » qui peut être soit à « true » soit à « false »

namespace Waldo\SomeBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity() 
 */
class Inscription
{
    /**
     * @ORM\Column(type="boolean")
     */
    protected $estOk;
    //[...]
}

Notre formType va se présenter comme ceci :

namespace Waldo\SomeBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class SomeForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
                ->add('estOk', 'choice', array(
                    'label' => 'Êtes vous OK ?',
                    'choices' => array(1 => 'Oui', 0 => 'Non'),
                    'expanded' => true,
                    'multiple' => false,
                    'required' => true
                ));
    }
}

La magie, vient du faite de définir que la valeur de « Oui » est à 1 et non à la constante TRUE. Idem pour « Non », il faut utiliser le chiffre 0 en lieu et place de la constante FALSE.

Dans le cas ou cela ne fonctionne toujours pas, voici la manière forte :

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
                ->add('estOk', 'choice', array(
                    'label' => 'Êtes vous OK ?',
                    'choices' => array(1 => 'Oui', 0 => 'Non'),
                    'expanded' => true,
                    'multiple' => false,
                    'required' => true
                ))
                ->addEventListener(\Symfony\Component\Form\FormEvents::PRE_SET_DATA, function (\Symfony\Component\Form\FormEvent $event) {
                    $data = $event->getData();
                    $data->setEstOk( $data->getEstOk() === false ? 0 : 1 );
                    $event->setData($data);
                })
     }
Tagués avec : , ,
Publié dans PHP, Symfony2

KnpMenuBundle le breadcrumb facile !

Dans un article précédent je vous expliquais comment faire un breadcrumb (file d’Ariane) qui fonctionne avec le KnpMenuBundle.
Malheureusement cet article est obsolète (depuis un moment en faite). Les mainteneurs du super KnpMenuBundle ont déprécié la méthode magique getBreadcrumbsArray.

Ne trouvant de solution, j’ai laissé traîner. Mais aujourd’hui je me suis bougé et j’ai pondu le CnertaBreadcrumpBundle !
Comme ça la prochaine fois que j’ai à faire un Breadcrump cela sera une partie de plaisir.

Sur la page github du projet vous trouverez comme l’installer.

Je vais juste vous montrer ici une implémentation plus détaillé que dans la documentation.

Ci-dessou la classe Builder classique au KnpMenuBundle :

namespace WaldoColorfullBundleMenu;

use KnpMenuFactoryInterface;
use SymfonyComponentDependencyInjectionContainerAware;

class Builder extends ContainerAware
{
    public function mainMenu(FactoryInterface $factory, array $options)
    {
        $menu = $factory->createItem('main');

        $menu->addChild('Entrée 1', array('route' => '_route_entre_1'))
                ->addChild('Entrée 1.1', array('route' => '_route_entre_1_1'))
                ->addChild('Entrée 1.1.1', array('route' => '_route_entre_1_1_1'));

        $menu->addChild('Entrée 2', array('route' => '_route_entre_1'))
                ->addChild('Entrée 2.1', array('route' => '_route_entre_2_1'))
                ->addChild('Entrée 2.1.1', array('route' => '_route_entre_2_1_1'));

        return $menu;
    }
}

Dans cette classe on y trouve uniquement notre menu. Ici pas besoin de hack comme dans l’article précédent. C’est le CnertaBreadcrumbBundle qui fait tout le travail.

Maintenant pour l’utiliser dans Twig, rien de plus simple :

{{ cnerta_breadcrumb_render('WaldoColorfullBundle:Builder:mainMenu') }}

C’est tout. Il vous suffit d’appeler le Helper “cnerta_breadcrumb_render” et de lui donner le chemin de votre menu. Comme avec le KnpMenuBundle. Trop facile ?

Voici une implémentation un peu plus poussée, dans le cas où l’on veux customiser directement dans son template le rendu.

{% set currentItem = cnerta_breadcrumb_get('WaldoColorfullBundle:Builder:mainMenu') %}

{% for item in currentItem %}
    {% if loop.index != 1 %}
        {% if loop.index > 2 %} > {% endif %}
        {% if not loop.last %}<a href="{{ item.uri }}">{{ item.label }}</a>
        {% else %}<span>{{ item.label }}</span>{% endif %}
    {% endif %}
{% endfor %}

Cependant, si vous voulez faire les chose bien et créer un template à part uniquement pour le breadcrump, c’est possible. Il suffit d’utiliser le helper “cnerta_breadcrumb_render” et de lui passer un nom de template (comme pour le KnpMenuBundle).

{{ cnerta_breadcrumb_render('WaldoColorfullBundle:Builder:mainMenu', {'template': 'MyWonderfullBundle:Breadcrumb:myBreadcrumb.html.twig'}) }}

Le code et l’architecture de ce bundle se base énormément sur le travail fait sur le KnpLabs.
Ce bundle n’est pas parfait et doit souffrir de bugs (inconnus à ce jour). Mais voila, il fait ce qui lui est demandé et c’est déjà pas mal !

Publié dans PHP, Symfony2

PHP Derrière un Proxy

Note : Dans cet article quand je parle de proxy, c’est un serveur filtrant les entrées/sorties d’un réseau sur le web. Pour qu’une machine (serveur ou poste client) puisse accéder au web et surfer, il faut que le proxy soit correctement configuré pour que toutes les connexions réseaux passent par lui.

Ne nous le cachons pas, PHP hébergé derrière un proxy c’est juste la merde. Il n’est pas possible d’effectuer une configuration de manière unique pour que toutes les applications PHP passe par un proxy. Il faut donc prendre en compte dans son développement cette problématique.

Quelques exemple pour éclairer mes propos :
SoapClient, SoapServer, CURL prennent les configurations de connexion au proxy en paramètres.
Les fonctions de la famille file_get_contents, peuvent prendre en paramètres les configurations proxy, ou être configurées de manière globale grâce au système de stream context.
Et il y a les fonctions qui ne se configurent pas du tout comme exif_imagetype.

La question qui se pose, comment se faciliter la vie et arrêter de se prendre la tête avec ce p****n de PROXY de M***E ?

On inspire profondément, puis on expire.

Si vous utilisez Symfony2, il ne vous reste plus qu’a installer le superbement magnifiquement génialissime BehindAProxyBundle (Oui il est de moi ;) ) et lire la doc.

Dans le cas contraire vous pouvez utiliser le code du ProxyService.php.

Que fait la classe ProxyService ?

Elle centralise les options de configurations de connexion au proxy de différentes fonctions.
On y retrouve ce qu’il faut pour des connexions SOAP, CURL, ou stream context.

Qu’en est-il pour les fonctions qui ne peuvent pas être configuré comme exif_imagetype ?

La solution palliative à ce problème est de charger le fichier distant sur le serveur dans un fichier temporaire. (Genre le mec, il se la pète avec une phrase super longue qui ne veux rien dire…)

Le plus simple pour comprendre c’est de coder tout ça.

Dans notre cas nous coderons en dure le stream context sans passer par des classes externes.

function loadFileTemporarily($url) {

// Notre stream Context
$context = array(
                'http' => array(
                    'proxy' => 'tcp://172.0.0.1:8080',
                    'request_fulluri' => true,
                )
            );
$cxContext = stream_context_create($context);

$tmpfile = file_get_contents($file, false , $cxContext);

               // Ecriture du contenu récupéré dans un fichier temporaire
            $tempStreamFile = tmpfile();
            fwrite($tempStreamFile, $tmpfile);
            fseek($tempStreamFile, 0);

    // Récupération d’information par rapport à ce fichier temporaire
    $streamMetadData = stream_get_meta_data($tempStreamFile);

    // On retourne l’uri
            return $streamMetadData['uri'];
}

echo "<pre>";
print_r(
exif_imagetype(
loadFileTemporarily( "http://perdu.com//une-image.jpg" )
)
);
echo "</pre>";

J’utilise cette méthode dans un fork de ImageBundle de Gregwar.

Comment configurer le stream contex de manière globale ?

Pour les fonctions PHP qui utilisent le système de stream context, il est possible de définir les paramètres de manière globale.

Pour cela on utilise la fonction stream_context_set_default. Cette fonction doit être placé dans un fichier de configuration (au même niveau que la connexion à une base de données).

Ci-dessous un exemple d’implémentation.

// Dans un fichier de configuration

$context = array(
                'http' => array(
                    'proxy' => 'tcp://127.0.0.1:8080',
                    'request_fulluri' => true,
                )
            );

stream_context_set_default($context);
Publié dans PHP, Symfony2

KnpMenuBundle Corrspondance avec le menu en cours

Le problème que concerne cet article est encore lié au KnpMenuBundle et fait suite à un précédent article sur le KnpMenuBundle et les fils d’Ariane.

Le problème que nous sommes nombreux à rencontrer, est que ce bundle ne fait pas la correspondance de route avec paramètre(s).

une route en Symfony2 peut être associé à un nom.
Par exemple si l’on utilise les annotations dans les controller, on peux avoir quelque chose comme ça :

class amazingController extends Controller {
/**
 * @Route("/a-funky-route" name="_a_funky_route")
 */
public function getHeadachesAction()
{
    //...
}

/**
 * @Route("/say-my-name/{myName}" name="_say_my_name", defaults={"myName"="Arthur Dent"})
 */
public function sayMyNameAction($myName)
{
    echo $myName; // <- ça c'est pas bien, c'est juste pour l'exemple ;)
}

Et si l’on configure notre Menu Builder comme ce-ci :

class Builder extends ContainerAware {
//...
 public function menu(FactoryInterface $factory, array $options) {

        $menu = $factory->createItem('menu');

            $menu->addChild('Menu 1', array('route' => '_a_funky_route'));
            $menu->addChild('Menu 2', array('route' => '_say_my_name', 'routeParameters' => array('myName' => null)));

        return $menu;
    }
}

Dans ce cas de configuration, KnpMenuBundle est incapable de faire correspondre une url http://site.fr/say-my-name/Pedro avec la route _say_my_name.

C’est là où l’on va ajouter un peu de magie dans tout ça !

En premier lieu, le code de @merk que l’on va un peux améliorer.

namespace WaldoMyFunTaStikBundleVoter;

use KnpMenuItemInterface;
use KnpMenuMatcherVoterVoterInterface;
use SymfonyComponentDependencyInjectionContainerInterface;

class RequestVoter implements VoterInterface {
   /**
     * @var SymfonyComponentDependencyInjectionContainerInterface
     */
    private $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * Checks whether an item is current.
     *
     * If the voter is not able to determine a result,
     * it should return null to let other voters do the job.
     *
     * @param ItemInterface $item
     * @return boolean|null
     */
    public function matchItem(ItemInterface $item)
    {

        /* @var $request SymfonyComponentHttpFoundationRequest */
        $request = $this->container->get('request');

        if ($item->getUri() === $request->getRequestUri()) {
            return true;
        }

        // C'est ici que l'on vérifie que la route en cours d'utilisation est bien la même
        // que celle contenu dans l'item que nous passe le KnpMenuBundle
        if ($item->getExtra('routes') !== null && in_array($request->attributes->get('_route'), $item->getExtra('routes'))) {
            return true;
        }

        return null;
    }
}

Ce bout de code Xml est à placer dans config/service.xml

<service id="waldo.voter.request" class="WaldoMyFunTaStikBundleVoterRequestVoter">
  <tag name="knp_menu.voter" />
  <argument type="service" id="service_container" />
</service>

Besoin de comprendre ?

$item->getExtra('routes') contient un tableau composé de route(s) définis dans le builder de menu : $menu->addChild('Menu 1', array('route' => '_a_funky_route'));.
Et $request->attributes->get('_route') donne le nom de la route qui correspond à l’url de la page que l’utilisateur a appelé.
On ne se base plus sur les URLs générés, comme le fait le KnpMenuBundle par défaut, mais sur le nom des routes qui ne change jamais, paramètres ou non.

Tagués avec :
Publié dans Symfony2, Symfony2 v2.1

KnpMenuBundle Comment faire un Breadcrumb

Cet article est obsolète, il existe une façon plus simple de faire un fil d’Ariane avec le Knp Menu Bundle.

Pas évident de trouver comment faire un fil d’Ariane ( ou breadcrumb ) avec le KnpMenuBundle

On va la faire courte.
Dans un premier temps on va coder dans le « Builder » de menu, puis on va faire un peu de Twig

Le Builder

namespace WaldoColorfullBundleMenu;

use KnpMenuFactoryInterface;
use SymfonyComponentDependencyInjectionContainerAware;

class Builder extends ContainerAware
{
    public function mainMenu(FactoryInterface $factory, array $options)
    {
        $menu = $factory->createItem('main');

        $menu->addChild('Entrée 1', array('route' => '_route_entre_1'))
                ->addChild('Entrée 1.1', array('route' => '_route_entre_1_1'))
                ->addChild('Entrée 1.1.1', array('route' => '_route_entre_1_1_1'));

        $menu->addChild('Entrée 2', array('route' => '_route_entre_1'))
                ->addChild('Entrée 2.1', array('route' => '_route_entre_2_1'))
                ->addChild('Entrée 2.1.1', array('route' => '_route_entre_2_1_1'));

        return $menu;
    }

    // Code pompé honteusement ici https://github.com/KnpLabs/KnpMenuBundle/issues/122#issuecomment-12149706
    public function myBreadCrumb(FactoryInterface $factory, array $options)
    {
        $menu = $this->leftExtranetMenu($factory, $options);

        $matcher = $this->container->get('knp_menu.matcher');
        $voter = $this->container->get('knp_menu.voter.router');
        $matcher->addVoter($voter);

        $treeIterator = new RecursiveIteratorIterator(
                        new KnpMenuIteratorRecursiveItemIterator(
                                new ArrayIterator(array($menu))
                        ),
                        RecursiveIteratorIterator::SELF_FIRST
        );

        $iterator = new KnpMenuIteratorCurrentItemFilterIterator($treeIterator, $matcher);

        // Set Current as an empty Item in order to avoid exceptions on knp_menu_get
        $current = new KnpMenuMenuItem('', $factory);

        foreach ($iterator as $item) {
            $item->setCurrent(true);
            $current = $item;
            break;
        }

        return $current;
    }

}

Le Twig

{% set currentItem = knp_menu_get('WaldoColorfullBundle:Builder:myBreadCrumb').getBreadcrumbsArray %}

{% for item in currentItem %}
    {% if loop.index != 1 %}
        {% if loop.index > 2 %} > {% endif %}
        {% if not loop.last %}<a href="{{ item.uri }}">{{ item.label }}</a>
        {% else %}<span>{{ item.label }}</span>{% endif %}
    {% endif %}
{% endfor %}
Tagués avec : , , ,
Publié dans Symfony2, Symfony2 v2.1