Mirmo Dynamics

Si tu kiffes pas reunoi, t'écoutes pas et puis c'est tout.

To content | To menu | To search

Keyword - symfony

Entries feed - Comments feed

Wednesday 1 July 2009

Help Symfony: fix one bug per day and win some gifts !

Today is the first day of the "1 day / 1 ticket" Symfony event.

Starting from today, each ticket you close (or help closing) from this list by either submitting a patch, submitting unit tests, etc will earn you points. At the end of the month, gifts are attributed to people with the most points.

This is a fun and exciting way to help your (hopefuly) favorite opensource project ;)

For more details on rules, please see the official page.

Wednesday 6 May 2009

Speaking at Symfony Live 09

For those not following me on twitter (shame on you), I will be speaking about unit and functional testing in Symfony (with a tiny bit of continuous integration too) at Symfony Live 2009. I will not be alone since my workmate Hugo Hamon will be under the spotlights with me. Also, the talk will be given in french.

Hope to see you there :)

Monday 30 March 2009

Customizing actions in symfony's admin generator

A quick tip about symfony's admin generator: if you want to have a custom action, with the following generator.yml bit for example (actions prefixed with an underscore are builtin admin-gen actions):

form:
  actions:
    _delete:  ~
    _cancel:  ~
    _save:    ~
    _save_and_add: ~
    custom: ~

You can totally customize the link generated for this action (to add javascript for example) using the generator helper in your module's lib/ directory:

public function linkToCustom($object, $params)
{
  return '<a href="#" onclick="console.log(\'woohoo\'); return false;">log woohoo</a>';
}

You get passed the current object as an argument, and whatever parameters you passed in the generator.yml:

    custom: { label: 'WOOHOO' }
public function linkToCustom($object, $params)
{
  return '<a href="'.url_for($this->getUrlForAction('custom')).'" onclick="console.log(\'woohoo\'); return false;">'.__($params['label']).'</a>';
}

Also, as you can see that standard helpers are available too.

Friday 20 March 2009

Symfony Live les 11 et 12 Juin 2009

Sensio Labs organise les 11 et 12 juin l'événement Symfony Live. Voilà tout est presque dit, cliquouillez le lien pour plus d'infos.

Symfony Live Flyer

Wednesday 18 March 2009

Automatically cd to a symfony project's root in vim

I made a little vimscript to automatically cd to the project root of a symfony project. It makes it easier for me to use the ctags :-)

"-------------------------------------------------------------------------------
"  Description: Finds and cd to the symfony root of the project
"    Copyright: Copyright (C) 2009 Geoffrey Bachelet
"   Maintainer: Geoffrey Bachelet
"      Version: 1.0
"-------------------------------------------------------------------------------

if exists('find_symfony_root_loaded')
  finish
endif

let find_symfony_root_loaded = 1

if ! exists('find_symfony_root_symfony_executable')
  let find_symfony_root_symfony_executable = 'symfony'
endif

" root detection when opening a new vim seems to work
" only if these two events are bound. not sure why.
autocmd BufWinEnter,BufRead * call FindSymfonyRoot()

function FindSymfonyRoot()
  let l:cwd = GetAbsoluteDirname(@%)
  let l:symfony_root = findfile(g:find_symfony_root_symfony_executable, l:cwd.';')
  let l:symfony_root = GetAbsoluteDirname(l:symfony_root)
  if strlen(l:symfony_root) != 0
    execute 'cd '.l:symfony_root
  endif
endfunction

function GetAbsoluteDirname(path)
  let l:path = a:path
  " gets the dirname
  if !isdirectory(l:path)
    let l:path = strpart(l:path, 0, strridx(l:path, '/'))
  endif

  " makes it absolute
  if match(l:path, '/') != 0
    let l:path = getcwd().'/'.l:path
  endif

  return l:path
endfunction

note for later: add vimscript support to geshi

Or see the highlighted version on gist.

Thursday 12 March 2009

How to render a partial in an action with symfony 1.1+

public function executeFoobar()
{
  return $this->renderPartial('my_partial');
}

This is an attempt to take over the first place on google for the query how to render partial action symfony.

Tuesday 10 March 2009

Using the timestamp data type in doctrine fixtures

Please note that to be valid, you have to enclose the value within single quotes. For example, say you have the following (totally useless) schema:

Foobar:
  columns:
    published_at: { type: timestamp }

And you want to create fixtures for this table. You might go this way (please also note that symfony allows php in fixtures files):

Foobar:
  foobar_1:
    published_at: <?php echo time(); ?>

Which won't work, since the Doctrine_Validator_Timestamp expects a date in the Y-m-d H:i:s format. So maybe you'll try this one:

    published_at: <?php echo date('Y-m-d H:i:s'); ?>

Which still doesn't work, since the value gets converted to an unix timestamp.

The right way is (note the enclosing single quotes):

   published_at: '<?php echo date('Y-m-d H:i:s'); ?>'

Yay o//

This behavior is explained in this ticket might help.

Wednesday 25 February 2009

symfony api opensearch plugin

In case you didn't notice, as of today (well, yesterday really) the symfony's api documentation features an opensearch plugin with autocompletion, that you can use straight from the comfort of your favorite browser (given that it supports the opensearch standard, which is the case for at least firefox and IE, although you won't get autocomplete in this last case).

More informations on the symfony blog.

Monday 23 February 2009

symfony: use your own View class

The view class name in symfony is determined per-module, it means that you need to use a module-level configuration setting via the module.yml configuration file. Say you want to use the myView class to handle your view in the default module of your frontend application, create the file sf_root_dir/apps/frontend/config/module.yml and put the following in it:

default:
  view_class:  my

Symfony will add the View suffix for you. Of course, you have to take care of making this class available to the framework. The sf_root_dir/apps/frontend/lib/myView.class.php file would be a good place for this.

As often with sfConfig, you could have put the module.yml config file in sf_root_dir/config/ to make it global to your whole project.

PS: extending the sfPHPView class could be a good idea to get you started too ;)

Thursday 22 January 2009

Multiple domains for one symfony project, the basics, config file version

When I published my post about using a single symfony application to serve multiple domains using a database to store the site-specific data, there was some people to complain about the extra query you get on each request. Right. Using a (cached) config file instead is not that hard as long as you use it only to match a domain name against a numeric id. Storing more site-specific data is a bit more problematic, but we will solve this in a later post.

And because I'm lazy, I won't re-write the entire article, just highlight the differences between the two methods, so you might as well read the database version if you did not already.

Now, first thing, the config file. We will create a simple project-wide config file: /config/app.yml:

all:
  paris.carshop: 1
  auckland.carshop: 2

Then we detect the site and add a generic sfConfig entry for it. This is done, as before, in /config/ProjectConfiguration.class.php. We will only change the detectSite() method to take advantage of the cached config information:

public function detectSite(sfEvent $event)
  {
    sfConfig::add(array('site_id' => sfConfig::get('app_'.$_SERVER['HTTP_HOST'])));
  }

Easy heh ?

Now we just have to adapt the model classes to use this config entry. The CarTable::createQuery() method will look like:

/**
 * Creates a query, adding the site criteria automatically
 *
 * @return Doctrine_Query
 * @see Doctrine_Table::createQuery()
 */
 
public function createQuery($alias = '')
{
  $query = parent::createQuery($alias);
  $query->where('site_id = ?', sfConfig::get('site_id'));
  
  return $query;
}

The Car::save() method:

/**
 * Automatically populates the site_id field if necessary
 * 
 * @see sfDoctrineRecord::save()
 */
 
public function save(Doctrine_Connection $conn = null)
{
  if (empty($this->site_id))
  {
    $this->setSiteId(sfConfig::get('site_id'));
  }
 
  return parent::save();
}

And you're all set ! For the record, here are the other modified files from the last post:

config/doctrine/schema.yml:

Car:
  columns:
    site_id:      { type: integer }
    name:         { type: string(255) }
    description:  { type: clob }
    image:        { type: string(255) }

data/fixtures/ now contains only the Car fixtures:

Car:
  car_1:
    site_id: 1
    name: Peugeot 307
    description: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  car_2:
    site_id: 1
    name: Renault Laguna
    description: Maecenas tortor nunc, aliquam et, ultrices id, ornare consectetur, mauris.
  car_3:
    site_id: 2
    name: Subaru Impreza
    description: Ut accumsan diam et orci. Sed sit amet neque ac diam rutrum iaculis.

Monday 29 December 2008

Multiple domains for one symfony project, the basics

So let's say you run a car franchise, and you have several shops that you want to be able to manage through a web application. The application would be same for each shop, except you'd have to manage different sets of data and have specific assets for each shop. Fear not, this is very easy to achieve using symfony (well, it's quite easy with any framework I guess, but we will be using symfony :p)

Sample project

At the beginning, there were generate:project:

$ mkdir carshop
$ cd carshop
$ symfony generate:project carshop
$ symfony generate:app frontend
$ symfony generate:module frontend default

We will also need a simple database schema for the sake of examples. We will be using Doctrine, so let's fill in our config/doctrine/schema.yml with a very simple schema:

detect_relations: true
 
Car:
  columns:
    site_id:      { type: integer }
    name:         { type: string(255) }
    description:  { type: clob }
    image:        { type: string(255) }
 
Site:
  columns:
    name:         { type: string(255) }
    domain:       { type: string(255), unique: true }
    main:           { type: boolean, default: false }

Also, let's not forget to enable Doctrine in our project in config/ProjectConfiguration.class.php, replace the line:

$this->enableAllPluginsExcept('sfDoctrinePlugin');

with:

$this->enableAllPluginsExcept('sfPropelPlugin');

and to configure the database:

$ symfony configure:database --name=doctrine --class=sfDoctrineDatabase "mysql:host=localhost;dbname=carshop" root

Oh, we shall need some fixtures too, let's put them in data/fixtures/01_sites.yml:

Site:
  paris:
    name: Paris
    domain: paris.carshop
  auckland:
    name: Auckland
    domain: auckland.carshop

and data/fixtures/02_cars.yml:

Car:
  car_1:
    Site: paris
    name: Peugeot 307
    description: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  car_2:
    Site: paris
    name: Renault Laguna
    description: Maecenas tortor nunc, aliquam et, ultrices id, ornare consectetur, mauris.
  car_3:
    Site: auckland
    name: Subaru Impreza
    description: Ut accumsan diam et orci. Sed sit amet neque ac diam rutrum iaculis.

And now that everything is ready, let's create the tables and load the fixtures:

$ symfony doctrine:build-all-load

Vhosts

The second thing we will need is a set of vhost with differents domains all pointing to our newly generated project. It's quite easy to do, and I will assume you run some kind of linux or unix here (although it's similar under windows if I remember well). Let's start with creating fake domain names, you just have to add the following line to your /etc/hosts:

127.0.0.1 auckland.carshop paris.carshop

And the following to your apache vhost:

ServerName paris.carshop
ServerAlias *.carshop

Save and quit, reload apache, and you're all set.

Detecting the current site

First thing we need to know if we want to handle site-specific data, is which site we're currently in. This can easily be done in the project configuration. Since we will need database access, we will hook up on the context.load_factories event. So, on with coding, open your config/ProjectConfiguration.class.php and add the following line to the setup() method:

$this->dispatcher->connect('context.load_factories', array($this, 'detectSite'));

This will hook the detectSite() method to the event context.load_factories. We now need to add the detectSite() method to the ProjectConfiguration class:

/**
 * Detects the current site based on the url
 * Fallback to the main site (main = 1 in database) if we can't find a suitable entry
 *
 * @param sfEvent $event
 */
 
public function detectSite(sfEvent $event)
{
  $request = sfContext::getInstance()->getRequest();
  $domain = $request->getHost();
 
  $siteTable = Doctrine::getTable('Site');
 
  if (false !== $site = $siteTable->retrieveByDomain($domain))
  {
    $siteTable->setCurrent($site);
  }
  else
  {
    $siteTable->setCurrent($siteTable->retrieveMain());
  }
}

Nothing very hard here. We get the HTTP host from the request, then try to fetch a corresponding Site from the database, fallbacking to the main site if necessary. You may have noticed that we added three methods to the Site model class: retrieveByDomain(), retrieveMain() and setCurrent(). Your lib/model/doctrine/SiteTable.class.php should look like this now:

<?php
/**
 * This class has been auto-generated by the Doctrine ORM Framework
 */
class SiteTable extends Doctrine_Table
{
 
  /**
   * Holds the current site
   * @var Site
   */
 
  protected $current;
 
  /**
   * Retrieve a Site by its domain
   *
   * @param string $domain
   * @return Site or false if no site is found
   */
 
  public function retrieveByDomain($domain)
  {
    return $this->createQuery()->where('domain = ?', $domain)->fetchOne();
  }
 
  /**
   * Retrieve the main site
   *
   * @return Site or false if no site is found
   */
 
  public function retrieveMain()
  {
    return $this->createQuery()->where('main = 1')->fetchOne();
  }
  
 
  /**
   * Sets the current site
   *
   * @param Site $site
   */
 
  public function setCurrent(Site $site)
  {
    $this->current = $site;
  }
 
}

While we're there, let's add a getCurrent() method as well:

/**
 * Gets the current site
 *
 * @return Site
 */
 
public function getCurrent()
{
  return $this->current;
}

Handling domain-specific logic and data

Ok, so now that we know where we are, let's make the Car model aware of the current site. Open lib/model/doctrine/CarTable.class.php and add the createQuery() method:

/**
 * Creates a query, adding the site criteria automatically
 *
 * @return Doctrine_Query
 * @see Doctrine_Table::createQuery()
 */
 
public function createQuery($alias = '')
{
  $query = parent::createQuery($alias);
  $query->where('site_id = ?', Doctrine::getTable('Site')->getCurrent()->getId());
  
  return $query;
}

This is the method used internally by Doctrine_Table to create queries related to the Car model, so now, all find methods fired from the Car model will only fetch cars from the current site. We need to take care of this at save() time too, in lib/model/doctrine/Car.class.php:

/**
 * Automatically populates the site_id field if necessary
 * 
 * @see sfDoctrineRecord::save()
 */
 
public function save(Doctrine_Connection $conn = null)
{
  if (empty($this->site_id))
  {
    $this->setSite(Doctrine::getTable('Site')->getCurrent());
  }
 
  return parent::save();
}

Handling domain specific assets

On with the assets. We are going to use our good friend mod_rewrite to handle this. First, let's create a specific directory for each domain we're going to manage:

$ mkdir -p web/perhost/{paris,auckland}.carshop/{css,images,js,uploads}

Also, for testing purpose, let's create some fake js file:

$ echo 'paris.carshop' > web/perhost/paris.carshop/js/foo.js
$ echo 'auckland.carshop' > web/perhost/auckland.carshop/js/foo.js
$ echo 'fallback' > web/js/fallback.js

Then add some voodoo magic in your web/.htaccess, before the we skip all files with .something rules generated by symfony:

RewriteCond /home/ash/projects/carshop/trunk/web/perhost/%{HTTP_HOST}%{REQUEST_FILENAME} -f
RewriteRule (.*) /perhost/%{HTTP_HOST}/$1 [L]

Please note that %{REQUEST_FILENAME} includes the starting /, so that we don't need to add one after %{HTTP_HOST}.

Now check that you have mod_rewrite enabled (for example check that /etc/apache2/mods-enable/rewrite.load exists). If it's not enabled it, do it now:

$ sudo a2enmod rewrite
$ sudo /etc/init.d/apache2 reload

You can test the following urls to see if everything is going well, the results should be pretty obvious:

See you next time !

Now you have a working multi-domain architecture for your symfony project, but there are still a couple of things we could (and will) discuss:

  • uploading assets (from forms for example)
  • having a different layout for each site
  • having specific css files for each site (eg: paris.css and auckland.css)
  • cache problematic for shared assets

All these points will be addressed in the next part of this tutorial, but not before 2009 has came !

Wednesday 17 December 2008

sfConsolePlugin, a console plugin for symfony

I've just release the first beta of my first symfony plugin: sfConsolePlugin. This plugins provides a PHP shell, with tab-completion, history support, and symfony capabilities. To install and use it, you need symfony 1.2 and the readline extension. From your project root:

$ ./symfony plugin:install sfConsolePlugin --release=0.0.2
$ ./symfony console:run

see the README file for more info.

Sunday 2 November 2008

Passing arbitrary parameters to a route in symfony

Parameters are an important parts of routes in symfony, and in any other routing framework for that matter, and everyone knows how to add mandatory or optional parameters to a route. But did you know you can also silently pass arbitrary parameters to a route from the routing definitions ? This may not be a very clear description, so let's take an example. Say you have a tabbed navigation system, and you want to know in your template which tab is the current one. In a perfect world, you could assume one tab corresponds to a module and check this module, but let's say that one tab can contain multiples modules. How are you doing now ? Quite simply actually, by passing a tab parameter to your route.

foo_route:
  url:       /foo
  param: { tab: foobar }

bar_route:
  url:       /bar
  param: { tab: foobar }

Now you can access the tab parameter in your layout:

<?php $currentTab = $sf_params->get('tab'); ?>

Easy heh ? Of course this kind of parameters is not limited to tell which tab we're currently in, you can, for example, bypass the limitation of a system that requires a particular file to be in a particular location (not that using mod_rewrite would not be more efficient, I just wanted one more example):

foobar_xml:
  url:       /a/stupid/url/that/you/cant/change/foobar.xml
  param: { module: default, action: static, file: "/assets/foobar.xml", mimetype: "text/xml" }

Here we simply pass the file and mimetype parameters to an action that'll take care to serve the right file.

Handy ! But please note that you can't pass such parameters in sfPropelRouteCollection for now (yup, this a bug, and there's a ticket already).

Wednesday 15 October 2008

Now working at Sensio Labs

A quick post to tell (for those not knowing already) that I now work at Sensio Labs, creators of the symfony framework. Expect some symfony stuff to be posted regularly \o/

For those wondering, yes that means I'm actually dropping any previously started ZF related development for good, I'll post something soon on why I don't like ZF anymore :-)

Wednesday 15 October 2008

A specific stylesheet for each module

So this is the first post of the newly opened symfony category of this blog, and I want to make things clear right now: you (most likely) won't find (yet) any protip or high level symfony tutorials here, as I'm still in the process of learning symfony. The good news though is that I'm currently assigned an 1.2-DEV project, so you may get some insight at what's up in the dev branch of the framework (especially regarding sfForm) :-)

If your are looking for more complete material on the subject, please redirect yourself to the official website (where you can find the documentation and a very interesting blog) or Fabien's blog.

That said, I think it could be interesting to post all the little things I learn everyday that make development with symfony easier for the everyday php developper that you might be if you made it this far into this post ;-)

Soooo, let's begin the show, with some yaml magic. Yaml I said ? Yaml I said. For those not knowing yet, yaml is the format of choice for symfony's configurations file. So what's the point between configuration files and stylesheets ? Let's say you've got a symfony application (say, frontend), and you'd like a particular module (say, news) in this application to have its own stylesheet in addition of the defaults stylesheets you defined already. Very simple, you start by creating the adequate configuration file:

cd apps/frontend/modules/news/
mkdir config
vi config/view.yml

All you have to do know is declare the stylesheet:

all:
  stylesheets: [news]

And that's all, no helper call in the layout, your news.css will automagically be appended to the <head> of your generated html. You can also declare multiples css, or control which actions get a particular css, and even specify to which media they apply:

all:
  stylesheets: [news, news_print: { media: print }]
list:
  stylesheets: [list]

Handy, heh.

But that's not all ! If you're not that much into yaml, you can use a view helper, directly into your template:

<?php use_stylesheet('news'); ?>

Or even add it from the controller:

<?php $this->getResponse()->addStylesheet('news'); ?>

Tuesday 14 October 2008

Il est bon ton café gringo

Un petit post rapide pour dire (pour ceux qui ne savaient pas déjà) que je bosse désormais chez Sensio Labs, les créateurs de symfony, depuis une bonne grosse semaine maintenant. J'aurai donc bientot plein de tips de noob à vous raconter sur symfony \o/

Monday 21 January 2008

Symfonians, un site pour les maestros en herbe

Les utilisateurs du framework symfony ont désormais leur cafet' dédiée pour aller prendre un ptit café ! Pour résumer, symfonians se propose de réunir la petite communauté symfony autour de fonctionnalités aussi conviviales qu'utiles, telles que la création d'un profile développeur symfony, des offres d'emploi, ce genre de choses quoi. Le projet est chapoté par NiKo, ce qui au moins est un gage de bonne qualité.