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

Symfony2 – Envoie d’emails par la ligne de commande

Logo du Framework Symfony2Lorsque l’on essaye d’envoyer un email en utilisant la ligne de commande (cmd) offerte par Symfony2, on se heurte à un petit problème.

Dans le fichier de configuration « config.yml » est défini pour swiftmailer ce paramètre : spool: { type: memory }

Cela signifie que tout les mails envoyé sont mis dans un spool mémoire. Dans le cas d’un envoie depuis un controller, l’email est placé dans le spool et envoyé une fois que le controller à terminé (par le biais des event), cela évite que la page mette trop de temps à ce charger.

Dans le cas d’un déclenchement par la cmd, il n’y a pas d’événement qui déclenche l’envoie d’email. Il faut donc le faire à la main !

namespace waldoUnBundleCommand;

use SymfonyBundleFrameworkBundleCommandContainerAwareCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;

class CronCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
                ->setname('uranie:cron')
                ->setDescription('Exécute les crons');
    }

    public function execute(InputInterface $input, OutputInterface $output)
    {
        $message = Swift_Message::newInstance()
            ->setSubject('Coucou Email')
            ->setFrom('send@example.com')
            ->setTo('recipient@example.com')
            ->setBody("un super message");
        $this->getContainer()->get('mailer')->send($message);

	 // Du fait qu'il y est un spool de mail en mémoire définit dans le fichier de config et que ce spool est uber pratique dans le cadre des controller
        // On force l'envoie des mails du spool avec le code-ci dessous
        /* @var $mailer Swift_Mailer */
        $mailer = $this->getContainer()->get('mailer');

        $transport = $mailer->getTransport();
        if ($transport instanceof Swift_Transport_SpoolTransport) {
            $spool = $transport->getSpool();
            $sent = $spool->flushQueue($this->getContainer()->get('swiftmailer.transport.real'));
        }
    }

Rien de bien compliqué en somme.

Publié dans PHP, Symfony2, Symfony2 v2.1

Doctrine2 – Truncate Table dans DataFixture

Un vrais Truncate Table avec doctrine dans les DataFixture

Doctrine étant parfois un peu quiche… Lorsqu’on lui demande de faire un truncate table lors de l’exécution de la commande de datafixture, doctrine est tout simplement incapable de réaliser cette action car il existe des relations entre les tables.

Donc voici une méthode à utiliser dans vos dataFixtures qui permet de faire un trucate table qui fonctionne (dans mon cas en tout cas…).
Les deux seules choses à paramétrer sont l’emplacement de vos Entity et le début de leur namespace.


namespace WaldoWonderlandBundleDataFixturesORM;

use DoctrineCommonPersistenceObjectManager;
use DoctrineCommonDataFixturesFixtureInterface;
use DoctrineCommonDataFixturesOrderedFixtureInterface;
use SymfonyComponentDependencyInjectionContainerAware;
use SymfonyComponentFinderFinder;

/**
 * Purge de la base de données
 * Purge/Vide les données de la base de données
 * La base étant géré par le moteur InnoDB qui prend en compte les relations entre les table, Doctrine est dans l'incapacité à effectuer cette tâche
 *
 *
 * @uses $ php app/console doctrine:fixtures:load --append
 */
class Load00TruncateTable extends ContainerAware implements FixtureInterface, OrderedFixtureInterface
{

    private $tableToTruncateList = array();
    private $connection;

    //A voir pour être utilisé dans le CronBundle
    public function load(ObjectManager $manager)
    {

        $this->connection = $manager->getConnection();

        $this->connection->executeUpdate("SET FOREIGN_KEY_CHECKS=0;");

        /* @var $classMetadata DoctrineORMMappingClassMetadata */
        foreach ($manager->getMetadataFactory()->getAllMetadata() as $classMetadata) {

            $this->truncateTable($classMetadata->getTableName());

            foreach ($classMetadata->getAssociationMappings() as $field) {
                if (isset($field['joinTable']) && isset($field['joinTable']['name'])) {
                    $this->truncateTable($field['joinTable']['name']);
                }
            }
        }

        $this->connection->executeUpdate("SET FOREIGN_KEY_CHECKS=1;");
    }

    private function truncateTable($tableName)
    {
        if (!in_array($tableName, $this->tableToTruncateList)) {
            $this->connection->executeUpdate($this->connection->getDatabasePlatform()->getTruncateTableSQL($tableName));
            $this->tableToTruncateList[] = $tableName;
        }
    }

    /**
     * Définit dans quel ordre les fixtures doivent être lancé
     */
    public function getOrder()
    {
        return 1; // the order in which fixtures will be loaded
    }

}

C’est classe, non?

Publié dans Doctrine, PHP, Symfony2

Symfony2 – Rafraîchir un token utilisateur depuis un controller

Voici une méthode qui permet de rafraîchir un token utilisateur.Logo du Framework Symfony2
Rappel, on accède dans un contrôleur au token grâce à la méthode :

$this->get('security.context')->getToken()->getUser()

Je suis pas sûr que ce soit la meilleur façon de procéder, mais dans l’urgence ça a le mérite de fonctionner…

use SymfonyComponentHttpKernelEventGetResponseEvent;
use SymfonyComponentHttpKernelHttpKernelInterface;

[…]

$contextListnerClass = $this->container->getParameter("security.context_listener.class");
$t = $this->get('security.firewall.map.context.secured_area')->getContext();
foreach ($t[0] as $v) {
    if ($v instanceof $contextListnerClass) {
        $v->handle(new GetResponseEvent($this->get('kernel'), $this->getRequest(), HttpKernelInterface::MASTER_REQUEST));
    }
}

Je vous ais livré le code sans explication, car je ne sais pas vraiment quoi dire…
Donc à utiliser avec précaution.

Publié dans PHP, Symfony2, Symfony2 v2.1

Relation ManyToMany des entités Doctrine

Logo DoctrineSuite du tutoriel sur la création des relations entre entités pour leur persistance en base de données avec Doctrine.

Pour les précédent tutoriels :

Dans cette partie nous traiterons de la relation ManyToMany.

relation ManyToMany Lire la suite ›

Publié dans Doctrine, PHP, Symfony2

Relation OneToMany et ManyToOne des entités Doctrine

Logo DoctrineSuite du tutoriel sur la création des relations entre entités pour leur persistance en base de données avec Doctrine.

Pour les précédent tutoriels :

Dans cette partie nous traiterons de OneToMany et sa réciproque ManyToOne

Shchéma merise d'une relation OneToMany ManyToOneUne région possède zéro ou plusieurs départements.
Un département appartient à une et une seul région. Lire la suite ›

Publié dans Doctrine, PHP, Symfony2