Quelques nouvelles...

2 gravatar Par Grégoire Marchal - 14/08/2012

Ca fait disons quelque temps que je n'avais pas écrit de billet sur ce blog. J'avais mes raisons, voici une liste non-exhaustive de mes excuses, ordonnées aléatoirement :

  • J'avais beaucoup de travail
  • Je n'étais plus très motivé pour écrire à propos de symfony 1, sachant qu'il est en fin de vie
  • Je n'avais pas l'inspiration
  • Les tâches que j'effectuais ne méritaient pas d'être bloguées
  • Il fait beau dehors (ça c'est pas vrai...)
  • C'était les JO (je sais, ça n'a pas duré 1 an et demi...)

Bref, il y avait toujours une bonne raison ! Mais désormais l'erreur est réparée. Et les choses vont changer maintenant, j'espère ! Tout d'abord parce que je me mets (enfin) à Symfony2 : je travaille actuellement sur la réécriture de ce blog, afin de m’affranchir de wordpress !  Je découvre donc plein de nouvelles choses, je galère un peu, je cherche et je trouve des solutions, et je crée des pull requests même ! J'espère donc que je vais avoir des choses passionnantes à raconter, et que je me motiverai pour vous en faire part.

Stay tuned!

Un post validator pour gérer les dépendances entre les champs

0 gravatar Par Grégoire Marchal - 18/04/2011

Aujourd'hui, je partage un post validator qui permet de définir des dépendances entre des champs. Il permet par exemple de définir que le champ postal_code n'est requis que si le champ country vaut FR.

Voici donc cette classe :

/**
 * sfValidatorSchemaDependency allows to define dependency between fields
 */
class sfValidatorSchemaDependency extends sfValidatorSchema
{
  
  /**
   * Constructor.
   *
   * Available options:
   *
   *  * affected_field:  The field that is required or not
   *  * expected_values: A key/value array that represents the fields and their value
   *                     that must be matched to set the affected_field as required
   *
   * @param string $affected_field   The field that is required or not
   * @param array  $expected_values  A key/value array that represents the fields and their value
   *                                 that must be matched to set the affected_field as required
   * @param array  $options          An array of options
   * @param array  $messages         An array of error messages
   *
   * @see sfValidatorBase
   */
  public function __construct($affected_field, $expected_values, $options = array(), $messages = array())
  {
    $this->addOption('affected_field', $affected_field);
    $this->addOption('expected_values', $expected_values);

    parent::__construct(null, $options, $messages);
  }

  /**
   * @see sfValidatorBase
   */
  protected function doClean($values)
  {
    if (null === $values)
    {
      $values = array();
    }

    if (!is_array($values))
    {
      throw new InvalidArgumentException('You must pass an array parameter to the clean() method');
    }

    $affected_field = isset($values[$this->getOption('affected_field')]) ? $values[$this->getOption('affected_field')] : null;
    
    $bAllValuesMatched = true;
    foreach ($this->getOption('expected_values') as $field => $value)
    {
      // if a field has not the expected value
      if (!isset($values[$field]) || $values[$field] != $value)
      {
        $bAllValuesMatched = false;
        break;
      }
    }

    // if every field has the expected value, and affected_field not defined
    if ($bAllValuesMatched && !$affected_field)
    {
      // "affected_field is required" error
      throw new sfValidatorErrorSchema($this, array(
        $this->getOption('affected_field') => new sfValidatorError($this, 'required')
      ));
    }

    return $values;
  }
}

Et voici comment l'utiliser dans le cas de l'exemple de notre introduction :

  /**
   * @see sfForm::configure()
   */
  public function configure()
  {
    // ...
    $this->mergePostValidator(new sfValidatorSchemaDependency('postal_code', array('country' => 'FR')));
  }
Ainsi, lors de la validation du formulaire, si le champ country vaut FR et si le champ postal_code est vide, une sfValidatorError de type required va être liée au champ postal_code. Notez qu'il est possible de définir plusieurs dépendances, en passant plusieurs éléments dans le tableau en second paramètre du constructeur de sfValidatorSchemaDependency.

Créer un panel pour la Web Debug Toolbar utilisant l'Event Dispatcher

1 gravatar Par Grégoire Marchal - 09/02/2011

Allez, un petit tutoriel aujourd'hui. On va créer un panel pour la WDT (Web Debug Toolbar) qui va afficher la liste des requêtes WebService SOAP (mais vous pouvez l'adapter à ce que vous voulez) exécutées sur la page, à la manière des panels Doctrine/Propel (dont je me suis inspirés) qui affichent les requêtes SQL. Son contenu sera alimenté par des événements gérés par l'Event Dispatcher.

Pour la création du squelette du panel sfWebDebugPanelSoapClient, je vous laisse suivre le tutoriel officiel, qui est très bien fait. Une fois que c'est fait, on va remplir ce panel en utilisant l'Event Dispatcher. Pour cela, on va commencer par générer les événements ; ça se passe donc dans mon cas dans la class MySoapClient, la classe qui gère les choses que je veux logger.

Je commence par y créer la méthode qui crée et dispatche l'événement :

  /**
   * Dispatch a 'soapclient.log' event (used by web debug panel)
   *
   * @param SoapCommand $oCommand  The executed command
   * @param int         $iDuration The duration of the soap call
   */
  protected function dispatchEvent($oCommand, $iDuration)
  {
    $this->context->getEventDispatcher()->notify(new sfEvent($this, 'soapclient.log', array(
      'command'  => $oCommand,
      'duration' => $iDuration,
    )));
  }

Comme vous le voyez, je crée un événement de type "soapclient.log" portant sur l'objet courant ($this), et je fournis au passage des paramètres informatifs que j'afficherai dans mon panel un peu plus tard... Puis j'utilise l'Event Dispatcher pour notifier les objets qui écoutent les événements "soapclient.log" qu'il y en a un nouveau !

Il faut ensuite appeler cette méthode au moment opportun, dans mon cas, immédiatement après l'appel au WebService.

    $iTime = microtime(true);
    // do your stuff...
    $this->dispatchEvent($oCommand, microtime(true) - $iTime);

Retournons maintenant dans la classe panel sfWebDebugPanelSoapClient, afin justement de lui dire d'écouter ce type d'événements. Pour cela, on va surcharger son constructeur afin de connecter une méthode à cet événement.

  protected $aEvents = array();

  public function __construct(sfWebDebug $webDebug)
  {
    parent::__construct($webDebug);
    $this->webDebug->getEventDispatcher()->connect('soapclient.log', array($this, 'listenForSoapClientLogs'));
  }

  /**
   * Listens to soapclient.log event and record them
   *
   * @param sfEvent $event
   */
  public function listenForSoapClientLogs(sfEvent $event)
  {
    $this->aEvents[] = $event;
  }

Dans le constructeur, on demande à l'Event Dispatcher d'appeler la méthode "listenForSoapClientLogs" de notre classe dès qu'il reçoit un événement de type "soapclient.log". Dans cette méthode, on stocke juste chaque événement reçu dans un tableau, propriété de notre classe.

On a désormais toutes les billes. Il n'y a plus qu'à afficher le tout dans notre panel.

  /**
   * Get the html content of the panel
   *
   * @return string $html
   */
  public function getPanelContent()
  {
    return '
    <div id="sfWebDebugSoapClientLogs">
      <ol>'.implode("\n", $this->getSoapClientLogs()).'</ol>
    </div>
    ';
  }

  /**
   * Retrieves events as html
   *
   * @return string
   */
  protected function getSoapClientLogs()
  {
    $aRet = array();
    $i = 1;
    foreach ($this->aEvents as $oEvent)
    {
      $aParams = $oEvent->getParameters();

      $oClient = $oEvent->getSubject();
      $oCommand = $aParams['command'];

      $sHtml = '
        <li>
          ' . get_class($oCommand) . ' (' . round(($aParams['duration'] * 1000), 2) . ' ms)
          ...
        </li>
      ';

      $aRet[] = $sHtml;
      $i++;
    }
    return $aRet;
  }

Et voilà ! Le tour est joué ! On a un joli panel pour débugger dans mon cas un client SOAP, mais je suis sûr que vous en ferez plein de trucs utiles !

Soap Debug Panel