Create a dynamic CSS module in Symfony 1.2 fast

I’m sure there are a number of ways you can handle this. There are also tons of modifications you can do to make it more useful, or better adapted to your particular needs. This post will quickly illustrate one way to create a dynamic stylesheet in Symfony 1.2.

I just finished writing a module that allows my users to change their background color. So I thought I’d share a similar way of doing this with an example.

Create a new doctrine model. Since we’re keeping things simple, our style sheet will only have one editable element: the body tag’s background-image.

DynamicCSS:
  columns:
    identifier: string(255)
    background_color: string(255)

Add a fixture and load it up:

/data/fixtures/fixtures.yml

DynamicCSS:
  black:
    identifier: "main"
    background_color: "#000000"
$ symfony doctrine:build-all-reload

The identifier will be the stylesheet’s name. You’ll see it in the route, which you’ll add now:

/apps/frontend/config/routing.yml

dynamiccss:
  url: /dynamiccss/:identifier.css
  param: { module: dynamiccss, action: render }

Create a new module:

$ symfony init-module frontend dynamiccss

This module will be used to render a dynamic style sheet. You’ll want to make sure it doesn’t render the layout, and that it returns a content type of ‘text/css’. So edit the actions file and add a preExecute() method. Of course, you’ll also need a method to fetch the style sheet and render it.

/apps/frontend/modules/dynamiccss/actions/actions.class.php

...
  public function preExecute()
  {
    $this->setLayout(false);
    $this->getResponse()->setContentType('text/css');
  }

  public function executeRender()
  {
    $this->stylesheet = Doctrine::getTable('DynamicCSS')
      ->getByIdentifier($request->getParameter('identifier'));
    $this->forward404Unless($this->stylesheet);
  }
...

Next we’ll add the template, which is at this point is very simplistic. You’ll need to have that ::getByIdentifier() method implemented in the DynamicCSSTable class, as well.

/apps/frontend/modules/dynamiccss/templates/renderSuccess.php

body {
  background-color: <?php echo $stylesheet->getBackgroundColor() ?>;
}

/lib/model/doctrine/DynamicCSSTable.class.php

...
public static function getByIdentifier($identifier)
{
  return $this->createQuery('c')
    ->addWhere('c.identifier = ?', $identifier)->fetchOne();
}
...

Now you can add your dynamic style sheet to the view. Here is an example:

/apps/frontend/config/view.yml

default:
  ...
  stylesheets: [/dynamiccss/main.css: { raw_name: true }]
  ...

That “raw_name” option allows you to override the typical way of creating an asset’s file name.

Now whenever someone edits the background color for the “main” style sheet, from the database, the background color can change.

You’ll still need to add an interface for editing the background color (or however many other options you add). That is up to you.

Complaints? Pointers? Thought of a better way?

Please post.

5 Comments

  1. Posted August 12, 2009 at 8:59 am | Permalink

    Hello,

    The sf routings system has a native support for different formats and mime-types. Css in one of them.

    For all the formats (except html), the has_layout option is set to false, and the correct content type is returned. So half of the work is already done !

    Here is the list of dealed formats :

    formats:
    txt: text/plain
    js: [application/javascript, application/x-javascript, text/javascript]
    css: text/css
    json: [application/json, application/x-json]
    xml: [text/xml, application/xml, application/x-xml]
    rdf: application/rdf+xml
    atom: application/atom+xml

    You can find them in factories.yml
    http://www.symfony-project.org/reference/1_2/en/05-Factories#chapter_05_sub_formats

    If your dynamic options are stored in database, you probably should use a doctrine route.
    For instance, if the user can choose some color for his account :

    account_stylesheet:
    url: /account/:id/stylesheet.css
    class: sfDoctrineRoute
    options: { model: Account, type: object }
    param: { module: account, action: stylesheet, sf_format: css }
    requirements:
    sf_method: ‘get’

    In apps/frontend/modules/account/actions/actions.class.php :
    public function executeStylesheet(sfWebRequest $request)
    {
    $this->account = $this->getRoute()->getObject();
    }

    In apps/frontend/modules/account/templates/stylesheetSuccess.php :
    body {
    background-color: background_color ?>;
    }

    If like in this exemple, the stylesheet path has some dynamic variable, you should call it from an action or better, from a template, like the layout.php one.

    ‘account_stylesheet’, ’sf_subject’ => $account), true)) ?>

    If you want to learn more about the sf_format in routes, there’s an old post about it on the sf blog : http://www.symfony-project.org/blog/2008/06/09/how-to-create-an-optimized-version-of-your-website-for-the-iphone-in-symfony-1-1

    And you should definitively read how Jobeet deals with atom feeds : http://www.symfony-project.org/jobeet/1_2/Doctrine/en/15

    As usual, my English sucks and to be honest, as I’ve not tested the code I wrote in this comment, it probably has small errors to fix. But the way is good.

    Hope it helps you. Last advise : when you developp with symfony, stay focus on the routes, they will suggest you pretty often how to work.

    • jjmontgo
      Posted August 12, 2009 at 1:53 pm | Permalink

      Thank you Eric.

      I had looked through the Jobeet day for web services for more info on sf_format, and didn’t find it very useful here unless I needed to provide the same feed of data in multiple formats. I’ll give that blog post a look-through, and the Day on atom feeds.

      I also didn’t know that the text/css was built in.

      Anyway, thanks for the code. I’ll probably take your suggestions (and others) and use them to revise this post in a week or so. I didn’t realize it would get so many hits.

  2. Jérôme Texier
    Posted August 12, 2009 at 10:46 am | Permalink

    Actually you don’t need to write a method like DynamicCSSTable::getByIdentifier because Doctrine natively provides finder methods for every fields of your table.
    Something like Doctrine::getTable(‘DynamicCSS’)->findOneByIdentifier($identifier) should work.

    More info here :
    http://www.symfony-project.org/doctrine/1_2/en/06-Working-With-Data#chapter_06_sub_finders

    • jjmontgo
      Posted August 12, 2009 at 11:45 am | Permalink

      That’s awesome. I had no idea – I’ve been through the Jobeet tutorial, and the short books on Doctrine and the form framework. I really need to look at the updated book, or go through the Doctrine documentation. I’m going to start replacing a few needless findByField methods in my work. Thank you!

  3. Posted August 17, 2009 at 9:10 pm | Permalink

    Hello,

    Good article, there is also a great plugin recently released, its a port of LESS (http://lesscss.org/) in PHP:
    http://www.symfony-project.org/plugins/sgLESSPlugin

    Enjoy.


Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*