Elegant Asset Handling - my first Symfony Plugin “teUrlToAssetPlugin”

August 25, 2009 – 10:17 am

Most websites I work on these days have some sort of header image on almost every page - an optional “banner” if you will, that adds to the general feel of the current section of the site. Since most of these sites as also managed through a CMS setup (symfony, sfDynamicCMS + custom stuff) it is a requirement to be able to adjust these images on a per-page level, even though most pages share the same image within their section..
In short, we often have a need for flexible customization without putting the user through the nightmare of picking a picture through the admin interface every time they want to create a page.

The solution we came up with is rather simple, yet so elegant that we found it to work in all our sites - we use a storage convention based on the page URL.

For example, let’s take a page with the following url:

http://www.somesite.com/sports/outdoors/hiking

where “sports” and “outdoors” are categories, and “hiking” is a specific activity.

The required flexibility now is that the we wanted to be able to have a base-banner for the “sports” section, be able to (optionally) override this for each sub-category, and for each specific activity. As you can see, mapping the url to a directory in the webroot and working backwards would yield just that nice cascade.

Say for example we needed a banner on said page, we would want to look for the following images (in this order):

"/uploads/banner/sports/outdoors/hiking.jpg"
"/uploads/banner/sports/outdoors.jpg"
"/uploads/banner/sports.jpg"
"/uploads/banner.jpg"

Whichever picture is found first, wins.

We add the “banner” directory to be able to pull the same trick for other assets.. maybe a product manual, or a particular flash file for the footer? Never limit your options.. ;)

Now, the code that does this cascading lookup based on url is fairly trivial, but I wanted the whole thing to be very intuitive and simple for our front-end guys, so that they can easily use this functionality from within templates. Turns out this is a perfect case for the ArrayAccess interface, which allows you to access objects via array syntax.

The resulting template code looks as simple as:

$image = $assets["banner"];

and you we even let you specify the file type via an optional extension (defaults to JPG):

$image = $assets["banner.png"];
$document = $assets["manual.pdf"];
$audio = $assets["music.swf"];

As you can see, this setup makes it very easy to distribute general and specific assets throughout your site and administering the files via a wysiwyg or standalone asset manager - without cluttering up the admin area.

Hope you’ll find this as time-saving and elegant as I find it.

Have a great day,
Daniel


Filed under: Symfony — Tags: , — by Richtermeister

PHP Error: Trying to clone an uncloneable object of class ReflectionClass

August 11, 2009 – 7:35 am

I’ve recently run into this error when deploying symfony applications:

“Trying to clone an uncloneable object of class ReflectionClass”.

What’s causing this is a relatively common setting in php.ini, called “zend.ze1_compatibility_mode”, which enables php4 compatibility mode in php5. Specifically it affects a couple of object handling conventions (empty objects cast to FALSE, and - oh this is my favorite - objects are passed by value instead of by reference.. can you imagine how much fun debugging your php5 application would be if this setting didn’t actually save you by breaking things altogether?) Needless to say some hosts like to keep this turned on to me “more compatible” with the past. Also needless to say I hate some hosts.

The way around it is either changing the php.ini setting to:

zend.ze1_compatibility_mode=Off

or disabling it at runtime, before the application bootstraps, via:

ini_set("zend.ze1_compatibility_mode", "off");

Hope it helps someone.


Filed under: Rnadom Sftuf — Tags: — by Richtermeister

Symfony 1.2 Admin with custom primary key

April 27, 2009 – 10:40 pm

Playing with the Symfony 1.2 Admin Generator I noticed that “out-of-the-box” it only likes to play with integer primary keys (ideally based on auto-increment columns I assume). Now, there are cases where you want to use primary keys that aren’t neccessarily numeric, for example countries or languages, where you could use the corresponding ISO codes as id.

When trying this with the new admin generator, all looks fine initially (”list” action works, “new” action works), but when you try to edit a record, which happens automatically after creating one, you will get a “404 Page not found” error. Looking at the logs you’ll see that Symfony interprets the non-numeric id as action name, and matches it with the :module/:action route. Of course this action doesn’t exist, resulting in above-mentioned error.

What’s going on here? Turns out, the culprit is the object routing class that the admin generator uses for generated modules. In my case the responsible class is called “sfPropelRouteCollection”, and it seems to expect the id column to be numeric, judging by it’s default setting for the requirements:

d+

In above expression the d is regulatory speak for “a numeric character”, and the + means “one or more”. Luckily for us, all it takes is changing this requirement setting to make the generated modules work with non-numeric primary keys as well! The pattern that worked best for me is this:

[dw]+

Translated this means “either a numeric character or a letter character”, and “one or more” of those. Depending if you have special characters in your primary key (you really shouldn’t), you may have to massage this pattern a little, but take care to watch for “/” and “.” since those are used by Symfony’s routing to tokenize the url request.

The place to put this updated requirements is the routing.yml file, where it would look somewhat like this:

customer:
  class: sfPropelRouteCollection
  options:
    model:               Countries
    module:             countries
    prefix_path:    countries
    column:              id
    with_wildcard_routes: true
  requirements:  { id: "[dw]+" }

Watch out that the requirements entry is on the same level as class and options.

Needless to say I was very relieved when I found that this is less of a bug than a conventions issue, and that the admin generator didn’t completely break down on such a trivial requirement.

Happy custom Pk’ing!


Filed under: Symfony — Tags: — by Richtermeister

Problems Hosting Symfony 1.2 (!) sites on Godaddy?

March 17, 2009 – 9:58 am

If you’re trying to deploy a Symfony 1.2 site on Godaddy, you may run into some issues that are not remedied by my previous post (which was limited to Symfony 1.0).

The main error you might get looks something like:
Empty module and/or action after parsing the URL “/frontend_dev.php” (/).

To remedy this, you need to edit the factories.yml and add a parameter to the request params, like so:

all:
  request:
    param:
      path_info_key: REQUEST_URI

(Do not confuse this with the “routing” settings that are already in the file. The “request” settings are commented out in the bottom, so you may not see them right away, and the code above just changing the default path_info_key from PATH_INFO to REQUEST_URI. Just add it below the “routing” settings, on the same indentation level.)

After this is done things may still not work. What I found is that Godaddy adds the script filename to the beginning of the $_SERVER["REQUEST_URI"] variable. This is different from most other hosts I’ve encountered, and it confuses symfony. And me.

So here’s a hack around that. In the /config/ProjectConfiguration.class.php add the following command to the top of the file:

$_SERVER["REQUEST_URI"] = str_replace($_SERVER["SCRIPT_NAME"], "", $_SERVER["REQUEST_URI"]);

All this does is remove the script name from the REQUEST_URI before symfony bootstraps. Simple but effective.

That should do the trick,
happy shared hosting!

Addendum:
Hang from Flying Bug blog just posted some other interesting godaddy tweaks and corrected an indentation error in my original post. Much appreciated - go check out his blog as well: http://xiehang.com/blog/2009/07/03/symfony-and-godaddy/


Filed under: Symfony — Tags: , — by Richtermeister

Symfony - Returning content in default language, no matter what.

March 10, 2008 – 10:28 am

If you have even worked on an international site using Symfony, you’ll be familiar with the ease you can switch between languages on a model level. I’m loving this, because you don’t have to worry about it in the controller or the view, meaning you can easily add i18n dimensions to pretty much any application in one central place ( the propel builder classes ).

That being said, I found the default behavior insufficient for sites that don’t have the resources to translate absolutely everything. In many cases, our clients focus their translation efforts on the most important product lines, press releases, or event postings, and are happy to default back to English for everything else.
In some cases this means translating only selected fields, and this is where it gets tricky, because Symfony will only retrieve i18n content for the current culture, so leaving fields blank in a translation won’t prompt it to retrieve the default language instead (so far), so what we need here is a way to make every i18n accessor method smart enough to grab default content if the translation returns nothing.

Enter the propel model builder classes. Thanks to the pattern-al™ ;) foresight of the Symfony creators we have a central access point to all auto generated accessor methods via the sfObjectBuilder class in your Symfony/lib/addon/propel/builder directory, which we can extend and overwrite to meet our requirements.

As a result every i18n accessor method now tests whether there is a string to return, and, if not, the object switches its culture to default and queries again, switching back afterwards.

Now, I have to qualify that what I did here might not be the prettiest solution, and I’m not quite comfortable with the amount of parent code that I had to copy into the child class to make the overridden functions work, but it works, and if anybody feels like cleaning it up, be my guest and send me a copy.

A word about performance

The overall approach I took is clearly geared towards development speed, not neccessarily runtime efficiency, so if you output a long list of i18n objects, odds are that you’ll fire off a query for each individual one. Functionality-wise that works, and speed is actually surprisingly fast, but that’s considering a rather small database and your particular server might puke. Haven’t tested on large system, but I would believe caching would alleviate this.
Also, I would imagine that propel 1.3 could alleviate this via object cache, where we could run one query to retrieve all applicable i18n objects before we output the list, and all future references to i18n content would come from cache. Haven’t tried this yet.

How to use this:

Download and unzip this SFi18nFallbackObjectBuilder class into the Symfony/lib/addon/propel/builder directory and edit the config/propel.ini file to use this file for propel.builder.object.class (instead of sfObjectBuilder).

Then all you should have to do is rebuild the model and activate it in the configuration by adding

use_fallback: true

to your i18n.yml configuration (for all or just some environments). I added this configuration switch, so that you can turn this off for admin areas, where you would otherwise not have empty fields for translations.

That’s it, have fun, and let me know how I could improved this somehow.


Filed under: Symfony — Tags: , , — by Richtermeister

Powered by WordPress