Skip to content

jonocode – a developer's blog

I moved! –>

Tag Archives: symfony

I love pretty URLs. I don’t like file name extensions unless they end with .html.

All versions of symfony save me from address bar ugliness, but only for the first application I create. Everything else is ass.php/backwards.

I’m going to give you two ways of beautifying your second, third, and forth applications so you can http://www.get.your/application/looking/sexy. Both methods involve creating a subdirectory.

Put it all in a subdirectory, and symlink your way out

So if you have an app named “admin,” you can do the following:

  1. Create a directory in your web folder called admin
  2. Copy your admin.php and admin_dev.php into this folder
  3. Copy the .htaccess file into this folder
  4. Rename admin.php to index.php
  5. Edit the file now named index.php, and change the require_once statement at the top so it’s an extra directory back
  6. Symbolically link the js, css, images, and uploads directories from within the admin directory to the directory above; so from your app directory: ln -s ../js

This is what I usually do in a Linux environment. However, I’m developing on a Windows box, and symbolic links don’t translate easily between Windows and Linux. So here’s the second option, without using symbolic links:

Put just the .htaccess in a subdirectory

  1. As before, create a subdirectory in your web folder for the admin.
  2. Copy the .htaccess file in, and at the very bottom of the file, change index.php to ../admin.php. It should read RewriteRule ^(.*)$ ../admin.php [QSA,L]
  3. Edit your routing.yml file for the admin app, and append /admin/ to all of your routes.

That should about cover it. Also remember to set no_script_name to on in each of your applications.


Alternative methods? Please leave comments.

Tags: ,

Here are all the country codes in YAML format for your downloading pleasure! You may place them in your app.yml (for symfony users) and call the list with sfConfig::get(‘app_countries’).

      CA:	Canada
      US:	United States of America
      AD: Andorra
      AE:	United Arab Emirates
      AF:	Afghanistan
      AG:	Antigua & Barbuda
      AI:	Anguilla
      AL:	Albania
      AM:	Armenia
      AN:	Netherlands Antilles
      AO:	Angola
      AQ:	Antarctica
      AR:	Argentina
      AS:	American Samoa
      AT:	Austria
      AU:	Australia
      AW:	Aruba
      AZ:	Azerbaijan
      BA:	Bosnia and Herzegovina
      BB:	Barbados
      BD:	Bangladesh
      BE:	Belgium
      BF:	Burkina Faso
      BG:	Bulgaria
      BH:	Bahrain
      BI:	Burundi
      BJ:	Benin
      BM:	Bermuda
      BN:	Brunei Darussalam
      BO:	Bolivia
      BR:	Brazil
      BS:	Bahama
      BT:	Bhutan
      BU:	Burma (no longer exists)
      BV:	Bouvet Island
      BW:	Botswana
      BY:	Belarus
      BZ:	Belize
      CC:	Cocos (Keeling) Islands
      CF:	Central African Republic
      CG:	Congo
      CH:	Switzerland
      CI:	Côte D'ivoire (Ivory Coast)
      CK:	Cook Iislands
      CL:	Chile
      CM:	Cameroon
      CN:	China
      CO:	Colombia
      CR:	Costa Rica
      CS:	Czechoslovakia (no longer exists)
      CU:	Cuba
      CV:	Cape Verde
      CX:	Christmas Island
      CY:	Cyprus
      CZ:	Czech Republic
      DD:	German Democratic Republic (no longer exists)
      DE:	Germany
      DJ:	Djibouti
      DK:	Denmark
      DM:	Dominica
      DO:	Dominican Republic
      DZ:	Algeria
      EC:	Ecuador
      EE:	Estonia
      EG:	Egypt
      EH:	Western Sahara
      ER:	Eritrea
      ES:	Spain
      ET:	Ethiopia
      FI:	Finland
      FJ:	Fiji
      FK:	Falkland Islands (Malvinas)
      FM:	Micronesia
      FO:	Faroe Islands
      FR:	France
      FX:	France, Metropolitan
      GA:	Gabon
      GB:	United Kingdom (Great Britain)
      GD:	Grenada
      GE:	Georgia
      GF:	French Guiana
      GH:	Ghana
      GI:	Gibraltar
      GL:	Greenland
      GM:	Gambia
      GN:	Guinea
      GP:	Guadeloupe
      GQ:	Equatorial Guinea
      GR:	Greece
      GS:	South Georgia and the South Sandwich Islands
      GT:	Guatemala
      GU:	Guam
      GW:	Guinea-Bissau
      GY:	Guyana
      HK:	Hong Kong
      HM:	Heard & McDonald Islands
      HN:	Honduras
      HR:	Croatia
      HT:	Haiti
      HU:	Hungary
      ID:	Indonesia
      IE:	Ireland
      IL:	Israel
      IN:	India
      IO:	British Indian Ocean Territory
      IQ:	Iraq
      IR:	Islamic Republic of Iran
      IS:	Iceland
      IT:	Italy
      JM:	Jamaica
      JO:	Jordan
      JP:	Japan
      KE:	Kenya
      KG:	Kyrgyzstan
      KH:	Cambodia
      KI:	Kiribati
      KM:	Comoros
      KN:	St. Kitts and Nevis
      KP:	Korea, Democratic People's Republic of
      KR:	Korea, Republic of
      KW:	Kuwait
      KY:	Cayman Islands
      KZ:	Kazakhstan
      LA:	Lao People's Democratic Republic
      LB:	Lebanon
      LC:	Saint Lucia
      LI:	Liechtenstein
      LK:	Sri Lanka
      LR:	Liberia
      LS:	Lesotho
      LT:	Lithuania
      LU:	Luxembourg
      LV:	Latvia
      LY:	Libyan Arab Jamahiriya
      MA:	Morocco
      MC:	Monaco
      MD:	Moldova, Republic of
      MG:	Madagascar
      MH:	Marshall Islands
      ML:	Mali
      MN:	Mongolia
      MM:	Myanmar
      MO:	Macau
      MP:	Northern Mariana Islands
      MQ:	Martinique
      MR:	Mauritania
      MS:	Monserrat
      MT:	Malta
      MU:	Mauritius
      MV:	Maldives
      MW:	Malawi
      MX:	Mexico
      MY:	Malaysia
      MZ:	Mozambique
      NA:	Namibia
      NC:	New Caledonia
      NE:	Niger
      NF:	Norfolk Island
      NG:	Nigeria
      NI:	Nicaragua
      NL:	Netherlands
      "NO":	Norway
      NP:	Nepal
      NR:	Nauru
      NT:	Neutral Zone (no longer exists)
      NU:	Niue
      NZ:	New Zealand
      OM:	Oman
      PA:	Panama
      PE:	Peru
      PF:	French Polynesia
      PG:	Papua New Guinea
      PH:	Philippines
      PK:	Pakistan
      PL:	Poland
      PM:	St. Pierre & Miquelon
      PN:	Pitcairn
      PR:	Puerto Rico
      PT:	Portugal
      PW:	Palau
      PY:	Paraguay
      QA:	Qatar
      RE:	Réunion
      RO:	Romania
      RU:	Russian Federation
      RW:	Rwanda
      SA:	Saudi Arabia
      SB:	Solomon Islands
      SC:	Seychelles
      SD:	Sudan
      SE:	Sweden
      SG:	Singapore
      SH:	St. Helena
      SI:	Slovenia
      SJ:	Svalbard & Jan Mayen Islands
      SK:	Slovakia
      SL:	Sierra Leone
      SM:	San Marino
      SN:	Senegal
      SO:	Somalia
      SR:	Suriname
      ST:	Sao Tome & Principe
      SU:	Union of Soviet Socialist Republics (no longer exists)
      SV:	El Salvador
      SY:	Syrian Arab Republic
      SZ:	Swaziland
      TC:	Turks & Caicos Islands
      TD:	Chad
      TF:	French Southern Territories
      TG:	Togo
      TH:	Thailand
      TJ:	Tajikistan
      TK:	Tokelau
      TM:	Turkmenistan
      TN:	Tunisia
      TO:	Tonga
      TP:	East Timor
      TR:	Turkey
      TT:	Trinidad & Tobago
      TV:	Tuvalu
      TW:	Taiwan, Province of China
      TZ:	Tanzania, United Republic of
      UA:	Ukraine
      UG:	Uganda
      UM:	United States Minor Outlying Islands
      UY:	Uruguay
      UZ:	Uzbekistan
      VA:	Vatican City State (Holy See)
      VC:	St. Vincent & the Grenadines
      VE:	Venezuela
      VG:	British Virgin Islands
      VI:	United States Virgin Islands
      VN:	Viet Nam
      VU:	Vanuatu
      WF:	Wallis & Futuna Islands
      WS:	Samoa
      YD:	Democratic Yemen (no longer exists)
      YE:	Yemen
      YT:	Mayotte
      YU:	Yugoslavia
      ZA:	South Africa
      ZM:	Zambia
      ZR:	Zaire
      ZW:	Zimbabwe
      ZZ:	Unknown or unspecified country

Tags: , ,

This post assumes you have some experience with Symfony 1.0.

At my work I needed to create several different versions of the same site. This mainly entailed changing background images to get a slightly different look and feel, and changing the content category, depending on a request parameter.

For this tutorial, I will use the company I work for as an example. Momentum IT Group has two distinct divisions: ELearning and Web. So let’s say I wanted to make slight design changes depending on which division I’m looking at.

We’ll start where all things Symfony begin: with the routes. Open up apps/frontend/config/routing.yml and enter the following:

  url:               /:division/:slug
  param:          { module: cms, action: display }
  requirements: { division: elearning|web }

I leave the cms module for you to implement. The display action would take a unique slug as a key to look up the page content from the database for display.

In this route, the division is required to be either elearning or web, and these two options will be used to display a different background design (and possibly select from a different content category in your cms module).

The next step is to implement the switch which determines which background to apply, if any. You can always write this code in your cms module, but you may have other modules other than the cms module which need to change design elements depending on which division the user is viewing. To handle this situation, you can create a filter.

A filter is applied once for each request. Start by opening apps/frontend/config/filters.yml and adding a new filter class:

rendering:    ~
web_debug: ~
security:     ~

# generally, you will want to insert your own filters here
  class:       cssFilter

cache:       ~
common:    ~
flash:         ~
execution:  ~

Create the filter class in apps/frontend/lib/cssFilter.class.php:


class cssFilter extends sfFilter
  public function execute($filterChain)
    if ( $this->isFirstCall())
      $response = $this->getContext()->getResponse();
      switch ( sfContext::getInstance()->getRequest()->getParameter('division') ) 
        case 'elearning':
          $response->addStylesheet('elearning', 'last');
        case 'web':
          $response->addStylesheet('web', 'last');

Make sure you place two new CSS files in your web/css directory.

For this example, they are elearning.css and web.css. They will be the last css files in your HTML header and will override your main.css where needed. So, you might override a background image like so:


body {
  background: url(../images/background.jpg) no-repeat top left;


body {
  background-image: url(../images/elearning.jpg);

Now if you direct your browser to [your_site]/elearning/[page_slug], you will have taken advantage of CSS cascading and Symfony filters to present a slightly different design depending on the context.

Tags: , , ,

With a query in its own function, you can write calling functions that:

a). Retrieve a collection for that type of query.

b). Retrieve one result for that type of query.

c). Extend that type of query with more conditions.

d). Pass the query to other objects that take a query as a parameter (eg. sfPager).

e). Distribute processing to the appropriate ORM classes.

For example, you have a database of classes and students.

If you need to get a classes’ students, you make a request to the classroom->getStudents() query function.

The classroom->getStudents() function adds its criteria (eg. = #), and then passes that criteria to the StudentTable->getStudents() query function.

In short, you reduce repetition.  You enhance maintainability.  Because each query has a single responsibility.

More info:

Tags: ,

So you’ve managed to integrate FCKEditor into Symfony for use with your administrative console.

Problem: FCKEditor has a file uploader for media files.  Images, documents, Flash files, that sort of thing.  It’s run off a PHP connector.

All that a hacker needs to do is enter a URL like so:


…and he or she is able to upload media files.

You’ve secured your application using security.yml, and perhaps the sfGuard plugin.  But what about that file uploader?  It isn’t launched by one of your controllers.  It has its own.

Solution: Turn the PHP connector in FCKEditor into a controller.

1.  Open up the PHP connector’s config.php at:


2.  Turn it into a Symfony controller.  Insert the following at the top of the file:

define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/../../../../../../..'));
define('SF_APP',         'backend');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);


if ( !sfContext::getInstance()-&gt;getUser()-&gt;isAuthenticated() ) {

Make sure you enter the correct SF_APP, and a relative path to SF_ROOT_DIR.

If the user isn’t authenticated, I simply exit.  The reason I don’t redirect to a nice “session timed out” page is because the PHP is called from FCKEditor’s Javascript.  If anyone has a more elegant solution, please comment!

Tags: , , ,

(Or a good PHP blog, or a good MySQL blog … )

This blog is only days old. I don’t have much authority in writing about what makes a good blog. So I’ll just highlight what I think a good technical blog should do.

1. Be simple, be clear and to the point.

Don’t write any filler. Most readers don’t have time to read it. If you have a lot of background to fill in, or a lot of technical information, make sure your content is well-summarized, easy to print, and available in PDF.

2. Write in simple English whenever possible.

For many developers, English is a second language. No one should have to consult the dictionary to read your high-level English.

3. Be an engineer. Or ask your readers to be engineers.

Developers are like engineers, and engineers like to solve problems. Post a problem and your solution. Ask for alternative solutions. Or post a problem and ask for solutions.

4. Be corrected.

Every time someone corrects you, they improve you. They improve your content, and they improve anyone who reads your content. They are taking time out of their day to do you a service.

I can’t tell you how many times I’ve searched Google for an answer to my problem, and either found the same question asked but not answered, or answered but have to pay to get it. (Experts Exchange! Gah!)

Every time you are corrected, or you correct someone else, you are building on a library of shared and free knowledge.

5. Be grateful.

Thank anyone who takes the time to help you improve. Even if they haven’t helped you solve your problem. Just by contributing they are letting you:

6. Be a community.

I think of all the blogs together as a giant discussion board, or a community network. When someone contributes to your blog, visit them in return and contribute something back.

Do you have any other ideas on how to improve blogs? Or on how to improve the development community in general?

Please comment!

Tags: , ,

While using Eclipse PDT, every time I tried to open a YAML file, the software would try to open the file externally.  Since I had no YAML editor installed, this would generate an error.

Now I could right click and choose Open With –> Text Editor.  But I wanted something a little better than a simple text editor, and I didn’t want the extra clicks.

That’s where Yedit came in, another great plugin for Eclipse.  The plugin can be found here:

It installs easily in Eclipse:

Help –> Software Updates –> Add Site


Select the plugin from the list under this site, and click Install…

Remember to restart Eclipse after you install, in order to see the update.  You should now be able to open YAML files in your project easily.

Tags: , , , ,