Prendre un Café

Le weblog de Nicolas Perriault

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

mercredi 24 décembre 2008

Announcing the Guitar Tune Of The Day Weblog

I must admit it, I am a guitarist. Not a good one, but I like playing the instrument, and listening to good guitar players. So I’ve just opened a little blog, the Guitar Tune Of The Day blog which aims to present a song I like, every day if I can.

Oh, and happy end of year people.

lundi 1 décembre 2008

Meet Joe Beet

Sorry for the lame title of this blog post, but I’m happy enough to announce the immediate availability of both symfony 1.2 and Jobeet, the new symfony advent calendar!

Jobeet It is a set of 24 tutorials, published day-by-day between December 1st and Christmas. Each tutorial is meant to last one hour, and will be the occasion to see the ongoing development of a web application with symfony, from A to Z.

So don’t hesitate to start reading day one of this great tutorial :-)

Edit: I forgot to mention the simultaneous release of the symfony and Doctrine book :)

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:

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:

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:

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:

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
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
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 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:

<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 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
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
// ...
  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:

<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

mercredi 17 septembre 2008

Request For Comments: sending emails in symfony 1.2

There should be a standard way for sending emails in upcoming symfony 1.2, and I’m in charge of implementing it, using the excellent SwiftMailer library. So I’ve created a dedicated RFC page on the symfony wiki just to get your opinions on the Right Way It Should Be Done®.

If you’re interested, there’s also a dedicated thread on the devs mailing list.

Edit: Swift integration didn’t make it for symfony 1.2, and here are my main reasons:

  • I didn’t get enough time
  • Swift is strangely coded from an architecture point of view: you must override the SMTP connection class to be able to send a mail using PHP mail() native function ; so it’s hard to extend or tweak it easily
  • I didn’t get enough time
  • Swift looked like a dead project at the time symfony 1.2 was released
  • I didn’t get enough time

To be honnest I’d like to see it integrated for symfony 1.3, but maybe with another lib… Stay tuned.

dimanche 14 septembre 2008

Back from the camp!

I’m back from the symfony camp 2008. It’s been a really great pleasure to meet the community[1], there are so many smart people using symfony all around the world, it’s just amazing.

I gave a one day training session on symfony 1.1 to eleven attendees the day before, I think it was somewhat interesting even if it may have been too short to see every important aspects of the framework…

The conferences were also great, and some slides are already available:

Stefan has also taken some shots of the event.

Last and as a side note, as of now all my symfony related posts will be written in English, as it’s the main language used by the community.

Notes

[1] even if the weather was… well, dutch wet

jeudi 8 mai 2008

No, really, symfony doesn't scale!

Well, err... Really?

jeudi 24 janvier 2008

Symfonians, three days after launch

Looks like people seems to appreciate the service provided by Symfonians.net. At least, they use it ! Here's some stats, three days after the site has been launched :

I've just added some more cumulative filtering features on people, applications and jobs lists, so now you can :

  • filter people available for hire, from a given country
  • filter applications from a given country, filter only opensource ones [3]
  • filter job offers proposed by a company, in a country and eventually accepting teleworking [4]

I've also slighly microformatted some detailled views like company and person pages, using hCard.

By the way, I want to mention that if your cocain is Django, you might use the DjangoPeople service, very nicely done and proven useful. Makes me think I must push on some geolocalization features on Symfonians... Stay tuned.

Edit: We got (roughly alpha) maps.

Notes

[1] Too bad I had some mail blacklist problem :/ Stay tuned for a blog post on this issue.

[2] I had two, one of them is about the fact I play the guitar :D

[3] Four opensource applications on five are french \o/

[4] Play, say Hi!

dimanche 20 janvier 2008

The Symfonians Project

It's been a long time that I and some people I know were looking after a place on the web to publish a Symfony developer profile, post a job offer, share contacts and/or project opportunities. There's been some initiatives here and here, but nothing has really convinced me so far. So I've decided to write something on my own.

The name of the project is Symfonians. It's available in a beta state and only in english language for now.

There must exist a lot of bugs, so feel free to report them if you use the service :)

Ah, last but not least, I imagine that the site won't display nice on IE6 (maybe on IE7 too), but for now I don't care as I don't have much time to loose on this kind of fascinating CSS hacking challenges :p

- page 1 de 2