Ce blog — désormais archivé — est en lecture seule. Pour continuer à lire mes tribulations, rendez-vous sur le blog d'Akei, ma société.

Prendre un Café

L'espace d'expression de Nicolas Perriault

Aller au contenu | Aller au menu | Aller à la recherche

Keyword - php

Fil des billets

vendredi 17 juillet 2009

Installing php 5.3, apache2 and MySQL 5 on OS X using Macports

I wanted to test latest php 5.3 on my OSX box. So here’s a quick reminder on how to proceed to a full setup of apache2, mysql5 and php 5.3 using Macports.

First, get a fresh copy of the Macports installer for OSX. Install macports, then in a shell prompt, type:

$ sudo port install mysql5 +server
$ sudo port install php5 +apache2 +debug +pear +sqlite +mysql5 

You have now the time to have lunch, dinner, sex or to watch an episode of Derrick[1].

To enable the php module for apache:

$ cd /opt/local/apache2/modules
$ sudo /opt/local/apache2/bin/apxs -a -e -n "php5" libphp5.so

Then, add this line in the /opt/local/apache2/conf/httpd.conf file:

Include conf/extras-conf/*.conf

Copy one of the standard php.ini files proposed by the default installation:

$ sudo cp /opt/local/etc/php5/php.ini-development /opt/local/etc/php5/php.ini

To start apache automatically at system startup:

$ sudo launchctl load -w /Library/LaunchDaemons/org.macports.apache2.plist

Or manually:

$ sudo /opt/local/apache2/bin/apachectl start

To initialize, configure and start MySQL automatically:

$ sudo -u mysql mysql_install_db5
$ sudo /opt/local/bin/mysql_secure_installation5
$ sudo /opt/local/etc/LaunchDaemons/org.macports.mysql5/mysql5.wrapper start
$ sudo launchctl load -w /Library/LaunchDaemons/org.macports.mysql5.plist

Now launch your browser at http://localhost/: you’re done. Have some rest.

Notes

[1] Don’t try to do all that stuff at the same time, result cannot be guaranteed

jeudi 16 juillet 2009

Little Symfony Forms Tricks

Hey, it’s been a long time I didn’t blog something clever here on Symfony, let me try to remedy this.

I’ve just stumbled upon this blog post about the use of sfValidatorCallback, which is quite cool because by using this particular validator you can virtually employ any kind of php callable to validate something. But as the author warned, it can be problematic to tie the symfony form validation framework to your model classes.

Personnaly, I rather prefer to declare a method in the form class itself to validate some value without boring myself writing each time a new sfValidatorBase derived class[1] :

 php
<?php
class myForm extends sfForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'name' => new sfWidgetFormInput(),
    ));
    
    $this->setValidators(array(
      'name' => new sfValidatorCallback(array('callback' => array($this, 'validateChuckNorris'))),
    ));
  }
  
  public function validateChuckNorris(sfValidatorBase $validator, $value)
  {
    // you can't validate chuck norris, but chuck norris can invalidate you
    if ('Chuck Norris' === $value)
    {
      throw new sfValidatorError($validator, 'invalid');
    }
  }
}

Of course, if you got the exact same need in another form, you should create a dedicated validator class. If you want custom error messages and options, you’ll have to create a dedicated class as well. But for simple and casual needs, this is just enough.

The neat thing with the sfValidatorCallback validator is you can even validate form schema values (say, an array containing all the values bound to the form) the same way, eg. using a post validator. Let’s see an example reusing the form shown previously:

 php
<?php
class myForm extends sfForm
{
  static protected $choices = array(
    'none' => 'No emails', 
    'commercials' => 'Commercial emails', 
    'news' => 'Annoucement emails', 
    'alerts' => 'Alert emails',
  );

  public function configure()
  {
    $this->setWidgets(array(
      //...
      'email' => new sfWidgetFormInput(),
      'opt_in' => new sfWidgetFormSelectRadio(array('choices' => self::$choices)),
    ));
    
    $this->setValidators(array(
      //...
      'email' => new sfValidatorEmail(array('required' => false)),
      'opt_in' => new sfValidatorChoice(array('choices' => array_keys(self::$choices))),
    ));
    
    $this->validatorSchema->setPostValidator(new sfValidatorCallback(array(
      'callback' => array($this, 'validateSchema'),
    )));
  }
  
  // We want the user to provide his email if he choosed to receive stuff by email
  public function validateSchema(sfValidatorBase $validator, array $values)
  {
    if ($values['opt_in'] !== 'none' && !$values['email'])
    {
      throw new sfValidatorErrorSchema($validator, array(
        'email' => new sfValidatorError($validator, 'required'),
      ));
    }
  }
}

As a cool side effect, it will also make testing your form validation very easy, because you just have to test the callable method of your form.

Here’s another trick I stole to Kris on a project we worked together on lately, using a formatter callback to alter the presentation of a widget. Here’s an example showing how to get rid of the unordered list displaying a collection of checkboxes by default, by inlining them instead:

 php
<?php
class myForm extends sfForm
{
  static protected $choices = array(
    'none' => 'No emails', 
    'commercials' => 'Commercial emails', 
    'news' => 'Annoucement emails', 
    'alerts' => 'Alert emails',
  );

  public function configure()
  {
    $this->setWidgets(array(
      //...
      'opt_in' => new sfWidgetFormSelectRadio(array(
        'choices' => self::$choices,
        'formatter' => array($this, 'formatInline'),
      )),
    ));
    
    $this->setValidators(array(
      //...
      'opt_in' => new sfValidatorChoice(array('choices' => array_keys(self::$choices))),
    ));
  }
  
  public function formatInline($widget, $inputs)
  {
    $formatted = array();
    
    foreach ($inputs as $input)
    {
      $formatted[] = $input['input'].' '.$input['label'];
    }

    return join(' ', $formatted);
  }
}

There are tons of little tricks like these which make the life of a developer using the forms framework easier, I’ll try to share them with you progressively.

Notes

[1] All my examples are using latest Symfony 1.2.

mardi 2 juin 2009

Utiliser les fonctions GeoIP de PHP sous Ubuntu

Mon Dieu, les billets techniques refleuriraient-ils au printemps ? Si vous désirez récupérer des informations géographiques à partir de l’adresse IP (ou du hostname) d’un utilisateur, vous pouvez utiliser les fonctions fournies par l’extension PECL GeoIP.

Voici la procédure d’installation sur une Ubuntu 8.04:

$ sudo -s
# apt-get install build-essential php5-dev php5-cli libgeoip-dev libgeoip1 php-pear
# pecl install geoip

Si toiut s’est bien passé :

# echo "extension=geoip.so" >> /etc/php5/cli/php.ini

Si vous utilisez Apache comme serveur :

# echo "extension=geoip.so" >> /etc/php5/apache2/php.ini

Il faut également installer la base GeoIPCity de Maxmind :

# wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
# gunzip GeoLiteCity.dat.gz
# mv GeoLiteCity.dat /usr/share/GeoIP/GeoIPCity.dat

Vous pouvez maintenant tester l’extension avec une ligne du genre[1] :

$ echo "<?php var_dump(geoip_record_by_name('209.202.168.**'));"|php

Ça donne ici :

array(11) {  ["continent_code"]=>
  string(2) "NA"
  ["country_code"]=>
  string(2) "US"
  ["country_code3"]=>
  string(3) "USA"
  ["country_name"]=>
  string(13) "United States"
  ["region"]=>
  string(2) "NC"
  ["city"]=>
  string(4) "Cary"
  ["postal_code"]=>
  string(5) "27511"
  ["latitude"]=>
  float(35.7********)
  ["longitude"]=>
  float(-78.7*******)
  ["dma_code"]=>
  int(560)
  ["area_code"]=>
  int(919)
}

Enjoy.

Notes

[1] J’ai volontairement masqué certaines informations pour d’évidentes raisons de confidentialité.

vendredi 31 octobre 2008

Let's Play with Symfony 1.2 and Doctrine

It’s been quite a long time I didn’t give a go to Doctrine, so as it’s gonna be bundled by default in with the upcoming 1.2 release of symfony, I thought it was a good occasion to play with it.

So let’s checkout the 1.2 SVN branch of symfony and create a test project with a main application[1]:

$ mkdir sf12test && cd sf12test
$ mkdir -p lib/vendor
$ svn co http://svn.symfony-project.com/branches/1.2 lib/vendor/symfony
$ php lib/vendor/symfony/data/bin/symfony generate:project sf12test
$ ln -s ../lib/vendor/symfony/data/web/sf web/sf
$ ./symfony generate:app main

Create a webserver vhost pointing to the web folder of the project directory. I’ve already explained plenty of times how to achieve this step.

Now, let’s enable the sfDoctrinePlugin and disable the Propel one by editing the setup() method of the config/ProjectConfiguration.class.php file:

 php
  public function setup()
  {
    $this->disablePlugins('sfPropelPlugin');
    $this->enablePlugins('sfDoctrinePlugin');
  }

You can list the available tasks running this simple command:

$ ./symfony list doctrine

Managing the Database Schema

First, configure your config/databases.yml file to set the database connection parameters. If you want to quick test Doctrine, use a local SQLite db, like this:

 yaml
all:
  doctrine:
    class:    sfDoctrineDatabase
    param:
      dsn:    sqlite://<?php echo dirname(__FILE__).'/../data/data.db' ?>

We’re going to make a very simple weblog application, so let’s configure our database schema. We can do it in YAML[2], so fire up your favorite editor/IDE and edit a brand new config/doctrine/schema.yml:

 yaml
BlogPost:
  actAs:
    Sluggable:
      fields:       [title]
    Timestampable:
  columns:
    title:          string(255)
    body:           clob
    author:         string(255)

BlogComment:
  actAs:            [Timestampable]
  columns:
    blog_post_id:   integer
    author:         string(255)
    email:          string(255)
    content:        clob
  relations:
    BlogPost:
      class:        BlogPost
      local:        blog_post_id
      foreign:      id
      foreignType:  many
      type:         one

Note that Doctrine offers several pretty cool features including native behaviors (timestampable and slugable are used here).

Now, create a data/fixtures folder and put a data.yml file in, containing some test data in YAML format:

 yaml
BlogPost:
  p1:
    title: My first post
    body: |
      This is cool.
    author: NiKo
    created_at: "<?php echo date('Y-m-d H:i:s', time() - 86400) ?>"
  p2:
    title: My second post
    body: |
      This is still cool.
    author: NiKo
    created_at: "<?php echo date('Y-m-d H:i:s', time() - 7200) ?>"
  p3:
    title: Third post
    body: |
      Is this one cool?
    author: Roger Hanin
    created_at: "<?php echo date('Y-m-d H:i:s') ?>"

BlogComment:
  c1:
    BlogPost: p3
    author: John
    email: john@doe.com
    content: Hey, you're right there.
    created_at: "<?php echo date('Y-m-d H:i:s', time() - 86400) ?>"
  c2:
    BlogPost: p3
    author: Paul
    email: paul@doe.com
    content: Nope, he's not.
    created_at: "<?php echo date('Y-m-d H:i:s') ?>"

Okay, now run the command below to generate the needed files, create the database and fill it with the data fixtures:

$ ./symfony doctrine:build-all-load

We can run several DQL queries in command line to check if everything is fine. DQL is very powerful, and compatible with a lot of RDBMS. You’ll find more information on DQL on the doctrine website.

For example, to find all blog posts:

$ ./symfony doctrine:dql "From BlogPost p"
found 3 results
-
  id: '21'
  title: 'My first post'
  body: "This is cool.\n"
  author: NiKo
  slug: my-first-post
  created_at: '2008-10-29 15:14:25'
  updated_at: '2008-10-30 15:14:25'
-
  id: '22'
  title: 'My second post'
  body: "This is still cool.\n"
  author: NiKo
  slug: my-second-post
  created_at: '2008-10-30 13:14:25'
  updated_at: '2008-10-30 15:14:25'
-
  id: '23'
  title: 'Third post'
  body: "Is this one cool?\n"
  author: 'Roger Hanin'
  slug: third-post
  created_at: '2008-10-30 15:14:25'
  updated_at: '2008-10-30 15:14:25'

Another example, to find informations about the blog post with slug third-post and its associated comments:

$ ./symfony doctrine:dql "Select p.title, p.author, c.author, c.content From BlogPost p, p.BlogComment c Where p.slug = 'third-post' Group by c.id"
found 3 results
-
  id: '23'
  title: 'Third post'
  author: 'Roger Hanin'
  BlogComment: [{ id: '15', author: John, content: 'Hey, you''re right there.' }, { id: '16', author: Paul, content: 'Nope, he''s not.' }]

Put the Query Logic in the Model

The Model part of any MVC architecture must contains the business data and associated logic. In other words, these data and logic should never be handled anywhere else, to decouple your components at max. So we’ll add some query methods in the lib/model/doctrine/BlogPostTable.class.php file, which represents our blog_post table and available operations on it:

 php
<?php
class BlogPostTable extends Doctrine_Table
{
  public function getAll()
  {
    return Doctrine_Query::create()->
      select('p.title, p.slug, p.body, p.author, p.created_at, count(c.id) numcomments')->
      from('BlogPost p, p.BlogComment c')->
      orderBy('p.created_at DESC')->
      groupBy('p.id')->
      execute();
  }

  public function getOneBySlug($slug)
  {
    $posts = Doctrine_Query::create()->
      from('BlogPost p')->
      leftJoin('p.BlogComment c')->
      where('p.slug = ?')->
      orderBy('c.created_at ASC')->
      limit(1)->
      execute(array($slug));

    return isset($posts[0]) ? $posts[0] : null;
  }
}

A Weblog is About Web Interface, uh?

Okay, let’s add pretty controllers and templates to give some life to our blog. First, generate a post module in the main app:

$ ./symfony generate:module main post

Then, edit the apps/main/modules/post/actions/actions.class.php file:

 php
<?php
class postActions extends sfActions
{
  public function executeIndex($request)
  {
    $this->posts = Doctrine::getTable('BlogPost')->getAll();
  }
  
  public function executeShow($request)
  {
    $this->post = Doctrine::getTable('BlogPost')->getOneBySlug($slug = $request->getParameter('slug'));
    $this->forward404Unless($this->post, 'No post with slug=' . $slug);
    $this->comments = $this->post->getBlogComment();
  }
}

We should have display templates too. The first one will show the posts list, in apps/main/modules/post/templates/indexSuccess.php:

 php
<?php foreach ($posts as $post): ?>
  <?php include_partial('post/post', array('post' => $post, 'numComments' => $post->getNumcomments())) ?>
  <hr/>
<?php endforeach; ?>

Note that we must create the _post partial template, in apps/main/modules/post/templates/_post.php:

 php
<h2><?php echo link_to($post->getTitle(), 'post/show?slug='.$post->getSlug()) ?></h2>
<p>
  <small>Posted by <?php echo $post->getAuthor() ?> on <?php echo $post->getCreatedAt() ?>
  <?php if (isset($numComments)): ?>
    - <?php echo $numComments ?> comments
  <?php endif; ?>
  </small>
</p>
<?php echo $post->getBody(ESC_RAW) ?>

The other main template will display one post and its comments, in apps/main/modules/post/templates/showSuccess.php:

 php
<?php include_partial('post/post', array('post' => $post)) ?>

<h2>Comments</h2>
<?php if (!count($comments)): ?>
  <p>No comment yet.</p>
<?php else: ?>
<?php foreach ($comments as $comment): ?>
  <p><small>By <?php echo $comment->getAuthor() ?> on <?php echo $comment->getCreatedAt() ?></small></p>
  <blockquote><?php echo $comment->getContent() ?></blockquote>
<?php endforeach; ?>
<?php endif; ?>

That’s it. A rough but functional weblog if you lauch your browser to yourhost/main_dev.php/post/index:

step2.png

And if you click a post title:

step1.png

Good News, the Forms Framework Works with Doctrine Too

Symfony 1.1 introduced the new forms framework, and good news, Doctrine can take part of it. So maybe you’ve already noticed it, we have form classes generated already, in the lib/form/doctrine folder of the project.

So let’s add a neat commenting system to our blog, by first editing the lib/form/doctrine/BlogCommentForm.class.php file:

 php
<?php
class BlogCommentForm extends BaseBlogCommentForm
{
  public function configure()
  {
    unset($this['id'], $this['created_at'], $this['updated_at']);
    
    $this->widgetSchema['blog_post_id'] = new sfWidgetFormInputHidden();
    
    $this->validatorSchema['author']  = new sfValidatorString(array('min_length' => 3));
    $this->validatorSchema['email']   = new sfValidatorEmail();
    $this->validatorSchema['content'] = new sfValidatorString(array('min_length' => 5));
  }
}

Now, use the form in the executeShow() method of our controller:

 php
<?php
// ...
  public function executeShow($request)
  {
    $this->post = Doctrine::getTable('BlogPost')->getOneBySlug($slug = $request->getParameter('slug'));
    $this->forward404Unless($this->post, 'No post with slug=' . $slug);
    $this->comments = $this->post->getBlogComment();
    
    $comment = new BlogComment();
    $comment->setBlogPost($this->post);
    $this->form = new BlogCommentForm($comment);
    
    if ($request->isMethod('post') && $this->form->bindAndSave($request->getParameter('blog_comment')))
    {
      $this->redirect('post/show?slug='.$this->post->getSlug());
    }
  }

And in the showSuccess.php template, we’ll append the form display:

 php
<h3>Add a comment</h3>

<?php echo $form->renderFormTag(url_for('post/show?slug='.$post->getSlug())) ?>
  <table>
    <?php echo $form ?>
    <tr>
      <td></td><td><input type="submit"/></td>
    </tr>
  </table>
</form>

We’ve now a pretty commeting system added to our blog, thanks to all the goodness provided by symfony and Doctrine:

step3.png

Conclusion

The time when everyone choosed Propel because it was more stable than Doctrine seems to be over. Doctrine is robust, and performs quite well on my box. Furthermore, it handles complex relationships and dynamic object hydratation natively and better than Propel. Doctrine is also very well integrated into symfony, certainly because Jonathan Wage - the Doctrine lead developer - now works for Sensio, creator and main sponsor of symfony.

Notes

[1] Note that Windows users should replace calls to ./symfony by php symfony.

[2] If you hate YAML, you can still write Doctrine table definition classes in raw PHP by hand

jeudi 30 octobre 2008

Utiliser Memcached avec PHP sous Mac OS X

Ayant récemment eu besoin de travailler sur une application utilisant memcached, j’ai du l’installer sur ma machine perso tournant sous Mac OS X. Pour mémoire, memcached est un système de stockage distribué de paires clé/valeur en mémoire vive, très rapide et performant. Cela peut s’avérer un outil de choix pour faire monter en charge une architecture, par exemple en ajoutant des frontaux web et en utilisant memcached comme espace partagé de stockage des données de session utilisateur. On peut également imaginer d’y stocker les résultats de traitements complexes, des templates compilés, des jeux de résultats SQL, etc.

J’ai trouvé un excellent tutoriel d’installation de memcache pour OS X pour cela, que je vous invite à suivre pour mettre en œuvre les exemples ci-après. Une fois l’installation effectuée, vous pouvez lancer le démon memcached avec cette ligne de commande :

$ sudo memcached -d -u nobody -m 128 127.0.0.1 -p 11211

Notez que cette dernière ligne de commande lance le démon memcached sous l’utlisateur nobody, en local sur le port 11211 et alloue 128 Mo de mémoire vive au service de stockage.

Exemple d’utilisation en PHP

Le tutoriel couvre également l’installation de l’extension PECL memcache, fournissant une API particulièrement simple et efficace à PHP pour utiliser le service.

Exemple d’utilisation basique :

 php
<?php
$m = new Memcache;
$m->connect('localhost', 11211) or die ("Could not connect");
$m->set('toto', 'tata');
echo $m->get('toto'); // tata

Pour utiliser memcached comme système de stockage des sessions, PHP dispose d’un gestionnaire de sessions memcache qu’il suffit d’activer par configuration dans votre fichier php.ini. Il suffit de remplacer la valeur :

session.save_handler = files

Par ces deux lignes, en adaptant au besoin les valeurs de connexion au démon :

session.save_handler = memcache
session.save_path="tcp://127.0.0.1:11211?persistent=1&weight=1&timeout=1&retry_interval=15"

Attention cependant, en cas de coupure du service memcached, toutes les données de sessions actives seront perdues.

vendredi 25 juillet 2008

Partager la session utilisateur entre Flash/Flex et symfony avec AmfPHP

Pour les besoins d'un projet récent, j'ai eu besoin de valider la possibilité de gérer l'authentification et l'accès à la session symfony (côté serveur) depuis une interface générée par Adobe Flex (en Flash, donc côté client).

Pour cela, j'ai utilisé la librairie AmfPHP en version 1.9beta2, certes pas très récente mais suffisament fonctionnelle pour satisfaire à ce besoin précis. Voyons comment ça se passe concrètement. L'avantage de la démonstration ci-dessous est qu'elle ne nécessite pas d'installer Flex puisque AmfPHP fournit un navigateur de services (browser) qui nous suffira pour valider notre concept.

Installation du plugin sfGuard

Je pars du principe que tout le monde a une installation de symfony 1.1 nanti d'une application main, ainsi qu'un projet et un virtual host apache fonctionnels pointant sur local.mademo.org. Si ce n'est pas le cas, voila de quoi vous mettre à jour.

On commence par installer le plugin sfGuard, qui se chargera de la persistance des droits et permissions utilisateurs en base de données, et fournira les utilitaires d'authentification et de manipulation de la session côté serveur :

$ ./symfony plugin:install sfGuardPlugin
$ ./symfony propel-build-all
$ ./symfony cc

On charge quelques données de test dans notre base de données nouvellement mise à jour :

$ mkdir data/fixtures 
$ cp plugins/sfGuardPlugin/data/fixtures.yml.sample data/fixtures/fixtures.yml
$ ./symfony propel:data-load main

Ce jeu de données de test nous fournit par défaut un compte admin (mot de passe admin) qui nous servira à tester notre service d'authentification.

Ensuite, il nous faut modifier notre classe apps/main/lib/myUser.php gérant la session utilisateur afin qu'elle étende désormais la classe sfGuardSecurityUser, fournie par le plugin sfGuard :

 php
<?php
// Fichier apps/main/lib/myUser.php
class myUser extends sfGuardSecurityUser
{
}

Installation et configuration d'AmfPHP

Nous allons installer la librairie AmfPHP dans le sous-répertoire web/ de notre projet[1], et aménager quelque peu notre arborescence pour accueillir les services AmfPHP :

$ cd /path/to/project
$ svn export https://amfphp.svn.sourceforge.net/svnroot/amfphp/tags/1.9beta2 web/amfphp
$ mkdir lib/amfphp-services
$ mv web/amfphp/services/amfphp lib/amfphp-services/

Ceci fait, nous allons éditer plusieurs fichiers d'amfphp afin de l'adapter à notre environnement symfony. Tout d'abord, commençons par éditer la valeur de la variable $servicesPath dans le fichier web/amfphp/globals.php :

 php
<?php
// ...
$servicesPath = dirname(__FILE__).'/../../lib/amfphp-services/';

Enfin, il nous faut "patcher"[2] le fichier web/amfphp/core/amf/app/Filters.php, qui initialise la session PHP sans définir le nom de la session. Ici, nous utiliserons le nom de la session symfony par défaut, "symfony" (ligne 105 du fichier) :

 php
102     //Fix for godaddy not allowing ini_get
103     $sessionName = "PHPSESSID";
104   }
105   session_name('symfony');
106   session_start();
107   $session_id = session_id();

Création d'un service permettant le partage de la session utilisateur

Voila, nous pouvons maintenant créer un service de gestion de l'authentification, que nous nommerons pompeusement UserSessionService et que nous enregistrerons dans le fichier lib/amfphp-services/UserSessionService.php :

 php
<?php
require_once dirname(__FILE__).'/../../config/ProjectConfiguration.class.php';

/**
 * This class tests the symfony session within an AmfPHP context
 *
 */
class UserSessionService
{
  /**
   * Symfony context
   * @var sfContext
   */
  protected $context = null;
  
  /**
   * Symfony session
   * @var sfGuardSecurityUser
   */
  protected $user = null;
  
  /**
   * Public constructor
   *
   */
  public function __construct()
  {
    $configuration = ProjectConfiguration::getApplicationConfiguration('main', 'dev', true);
    $this->context = sfContext::createInstance($configuration);
    $this->user = $this->context->getUser();
  }
  
  /**
   * Checks wheter user is authenticated or not
   *
   * @return boolean
   */
  public function isAuthenticated()
  {
    return $this->getUser()->isAuthenticated();
  }
  
  /**
   * Authenticates user
   *
   * @param  string  $username
   * @param  string  $password
   * @return boolean True if user has been successfully authenticated
   */
  public function login($username, $password)
  {
    if ($this->isAuthenticated())
    {
      return true;
    }
    
    $user = sfGuardUserPeer::retrieveByUsername($username);
    
    if (!is_null($user) && $user->checkPassword($password))
    {
      $this->getUser()->signIn($user);
      return true;
    }
    
    return false;
  }
  
  /**
   * Signs out a user 
   *
   */
  public function logout()
  {
    return $this->getUser()->signOut();
  }
  
  /**
   * Retrieves the current symfony context
   *
   * @return sfContext
   */
  protected function getContext()
  {
    return $this->context;
  }
  
  /**
   * Retrieves the current symfony user session
   *
   * @return sfGuardSecurityUser
   */
  protected function getUser()
  {
    return $this->user;
  }
}

Pour tester notre service, utilisons le navigateur de service proposé par AmfPHP. Pour cela, il faut lancer un navigateur sur http://local.mademo.org/amfphp/browser/index.html :

Naviagateur de services AmfPHP

Via cette interface, elle même réalisée en Flex, on peut tester les méthodes publiques définies dans notre service, manipuler les arguments, et constater que nous arrivons à nous authentifier et que nous accédons bien à la même session utilisateur que dans symfony : login, logout et test du statut d'authentification.

En conclusion

On pourrait aller beaucoup plus loin dans cet exemple, en proposant par exemple une classe proxy en ActionScript 3 représentant un utilisateur du système (dans notre cas, une instance de la classe sfGuardUser), cette dernière reproduisant tout ou partie de ses méthodes et propriétés, et donc d'utiliser l'ORM Propel directement depuis Flash... Je vous laisse faire vos tests si le coeur vous en dit.

D'autre part, même si la librairie AmfPHP semble un peu passée au niveau architecture, elle reste néanmoins très efficace pour publier des services PHP dans Flash au travers du protocole AMF. J'ai eu vent d'autres librairies comme WebORB ou SabreAMF, mais je ne sais pas vraiment ce qu'elles valent... Des avis dans l'assistance ?

Notes

[1] Du coup, on expose certains scripts AmfPHP, mais la librairie n'est malheureusement que prévue pour fonctionner en ce sens...

[2] Oui, c'est terriblement crade, je ne comprend d'ailleurs pas qu'AmfPHP n'aie pas prévu ce cas de figure...

mercredi 23 juillet 2008

Mes conventions de codage...

... sont celles des projets sur lesquels je me greffe. C'est en effet pour moi une forme de respect que d'appliquer les standards de codage partagés par une communauté (ou une équipe) de développeurs : ainsi, on maximise les chances de se comprendre et on minimise les coûteuses phases de communication entre geeks introvertis[1] :p

En effet, rien de plus pénible que de reprendre le code de quelqu'un qui a pris des libertés avec des conventions établies à ce niveau, l'apothéose étant obtenue avec ce genre de code :

 php
<?php
  class Ma_superClasse {

    function dire_coucou ( $popol) {
  echo 'coucou ' . $popol   . ' !' ;
}
     function DireAuRevoir($Popol )
{ print "Au revoir $Popol !";
     }
  }

Je force bien évidemment ici le trait, mais tout le monde est déjà tombé sur ce genre de code illisible, qui multiplie par 10 votre temps d'intervention sur ce dernier et divise par 1000 votre passion pour la TMA.

Bien entendu, il peut arriver de produire du code sur un projet ne nécessitant l'utilisation d'aucune brique logicielle existante. Auquel cas vous pouvez librement appliquer vos propres standards de codage, l'important étant ici qu'ils soient cohérents et constamment appliqués. S'il peuvent être ceux d'un projet open source existant reconnu, cela augmentera la sympathie potentielle à votre égard de futurs intervenants sur votre code ;)

Je noterai quand même en vrac quelques bonnes pratiques générales globalement reconnues et appréciées :

  • être explicite,
  • indenter son code,
  • documenter son code,
  • à choisir entre les deux, privilégier la lisibilité à la concision,
  • utiliser des noms de variables, de classes, de méthodes, de fonctions et d'arguments parlants,
  • utiliser des noms anglophones,
  • utiliser des motifs de conception connus.

Personnellement, j'ai mes petites préférences et tout comme Oncle Tom - qui m'a gentiment refilé cette chaîne[2] - j'ai tendance à appliquer les standards de codage de symfony, que je trouve homogènes et cohérents. Mais ce sont là bien évidemment essentiellement des questions de goûts et de couleurs.

Notes

[1] Voire les trolls genre les tabulations ça pue, vive l'indentation à trois espaces...

[2] Salopard, ça va se payer ! ;-)

dimanche 20 juillet 2008

Debug PHP facile avec Firefox, Firebug et FirePHP

Tous ceux qui ont déjà eux à batailler avec du code javascript connaissent certainement la fabuleuse extension Firebug pour Firefox. L'outil propose une console permettant d'examiner l'environnement d'exécution javascript mais aussi HTML et CSS de n'importe quelle page web.

FirePHP est une autre extension qui a pour but de proposer le même service mais pour le langage PHP. L'extension repose elle-même sur Firebug et propose, une fois installée, l'affichage dans la console des messages de debug émis depuis vos scripts PHP :

Démo FirePHP

Une fois l'extension Firefox installée, pour pouvoir envoyer un message de log dans la console depuis vos scripts, il faut utiliser une librairie spécifique PHP fournie téléchargeable depuis la page d'accueil du projet FirePHP. Cette librairie très simple est d'ailleurs documentée ici. Une fois l'archive récupérée, décompressez-la et appelez FirePHP de cette façon depuis un script PHP standard :

 php
require_once '/path/to/firephp/lib/FirePHPCore/FirePHP.class.php';

$f = FirePHP::getInstance(true);
$f->fb('Hello FirePHP console', FirePHP::INFO);
$f->fb(array('hello' => 'how are you?'));
$f->fb(array('hello' => array('how', 'are', 'you')));
$f->fb(array('foo', 'bar'), 'Results', FirePHP::WARN);

$o = new stdClass();
$o->foo = 'foofoo';
$o->bar = 'barbar';

$f->fb($o);

Pour envoyer les informations de debug à la console, la librairie PHP envoie les données sérialisées au format JSON dans un entête HTTP personnalisé dédié (X-FirePHP-Data). Ainsi, aucune interférence n'est possible avec vos scripts existants, la seule condition étant bien entendu de ne pas lancer la sortie standard PHP avant que ces entêtes aient été envoyés.

Données JSON passées dans un entête dédié

En bref, un outil génialement simple et efficace.

- page 2 de 10 -