Skip to content

jonocode – a developer's blog

I moved! –>

Category Archives: Symfony 1.2

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

Symfony has automated the process of creating a CRUD application, and even has built-in CSRF protection.

However, for a site with multiple users, the generated CRUD code just won’t do.

In my schema, each row of a model is owned by a user, according to their user_id. In the dashboard application I am building, each user has a list of programs.

They can create, read, update and delete each program that they own.

But the generated actions would allow users access to other user’s programs, simply by changing the id in URLs such as /program/edit/id/1.

My solution? Store the user_id of the currently logged in user in myUser.class.php. Since the myUser class has ownership of the id of the currently logged in user, I thought it was appropriate to implement convenience getters and setters for the dashboard here.

So my list action would contain:

$this->programs = $this->getUser()->getPrograms();

The myUser::getPrograms() method is implemented like so:

public function getPrograms()
  return Doctrine::getTable('Program')->fetchByOwner($this->getId());

public function getProgram($pk)
  $program = Doctrine::getTable('Program')->find($pk);
  return $this->hasOwnershipOf($program) ? $program : false;

public function hasOwnershipOf($row)
  if ( isset($row['user_id']) )
    if ( $row['user_id'] == $this->getId() )
      return true;
  return false;

Adding the myUser::fetchByOwner() method leaves the model functionality open to being called from another administrative application other than the dashboard. You could use a Doctrine magic method here, however I would prefer to return programs in alphabetical order (or perhaps allow for other options to be passed in using a Doctrine_Query as a second parameter).

For myUser::getProgram($pk), I simply use the ::find() magic method in doctrine to retrieve the object by primary key, as usual, and then return it only if it matches the user_id of the logged-in user.

Writing for multiple users is common place for web-based applications. However, it’s something I don’t usually deal with at work where I write simple, single-user back ends for editing website content.

Is there a more elegant way of handling user ownership of database content? Perhaps by writing a doctrine behavior? How do you usually go about it?

Please comment.

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.

    identifier: string(255)
    background_color: string(255)

Add a fixture and load it up:


    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:


  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.


  public function preExecute()

  public function executeRender()
    $this->stylesheet = Doctrine::getTable('DynamicCSS')

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.


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


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:


  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.

Tags: ,

Here is something to watch out for. If you define global behaviors in your schema, and then define table-level behaviors, the global behavior will be removed (unless you declare it again on the table level).

Here is a quick example:

actAs: [Timestampable]

      fields: [name]
    name: string(255)
    user_id: integer(4)
      foreignType: many
      type: one
      local: user_id
      foreign: id

For a short while I couldn’t figure out why the created_at and updated_at fields weren’t showing up in the Program table. Then I realized that it was removed at the table level, and I had to add it in again.

The official documentation does not mention this:

Hopefully this will save you 15 minutes of scratching your head.

Here is more information on Symfony 1.2 and doctrine:

This post has moved to:

Please update your links.