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:

[\d\w]+

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: "[\d\w]+" }

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

Secure Certificates and missing www

– 10:00 pm

Thanks to forgiving server-configurations it usually doesn’t matter whether you access a website via the fully qualified URL including “www” or without it - requests to http://codemassacre.com and www.codemassacre.com are taking you to the same place. After all you want to make it as easy as possible for people to access your website, so being forgiving is important.

However, as soon as you try to secure your site with help of a secure certificate and the corresponding https protocol, you need to pay closer attention, and, ultimately, make a choice as to which domain name the certificate applies to, because it will be either with www, or without.

Specifically, requesting https://some-domain.com for a site that only has a certificate for https://www.some-domain.com will cause your browser to raise a warning about an invalid certificate and block the site - certainly not a trust-invoking impression to give a potential customer! (unfortunately I am speaking first hand here, hehehe ;)

So, long story short, here’s an .htaccess entry that forces all requests to your site to have a www at the beginning:

RewriteEngine On
RewriteCond %{HTTP_HOST} ^some-domain.com
RewriteRule (.*) http://www.some-domain.com/$1 [R=301,L]

What’s happening here is that  we’re watching the HTTP_HOST server variable for anything that starts with “some-domain.com”. The ^ signifies the beginning of a string. If this condition is fulfilled, we rewrite any request (.*) to the fully qualified url, and we append whatever was matched by the (.*) part - which goes into the $1 variable. Lastly, we set the R=301 flag to tell the client browsers that this redirect is permanent. The L is for good taste to make sure this is the last rule applied to this request (no point in applying any other rules..).

Now requests to https://some-domain.com will be redirected to https://www.some-domain.com, the certificate matches, and the browser is happy - and so is whoever visited your site, because as we all know “ignorance is bliss” ;)


Filed under: Rnadom Sftuf — Tags: , , — by Richtermeister

Converting MS-SQL to MySql (from .bak file or otherwise)

March 17, 2009 – 12:52 pm

What to expect:

Using this methodology, you’ll be able to move table definitions and the data contained within those tables. However, you will NOT move Views, Stored Procedures, Triggers, etc.

Views arrive as simple tables, so be prepared to recreate them by hand. Procedures don’t arrive at all, so same “manuality” applies.

These are the steps to convert an MS-SQL database to MySql:

First, you will need access to a machine that runs MS-SQL, so if all you have is a .bak file, you will first need to restore a database from it within MS-SQL before you can covert it.

(Skip this step if your DB already up  and running).
If you don’t have MS-SQL running on your machine, download SQL Server Management Studio Express, a free tool from Microsoft that allows you to work with MS-SQL databases.
After installing, open the program and see if you can connect to your local machine. In my case I couldn’t becauseI didn’t have all the required services running.

To start the services, go to “Control Panel -> Administrative Tools -> Services” and look for 2 services: “SQL Server”, and “SQL Server Browser”. Start those. Now SQLSMSE should let you connect.

Restore DatabaseTo restore a database from a .bak file, simply right-click on the Databases item in the Object Explorer, select “Restore Database” and follow the directions. Voila, you should end up with a running MS-SQL database.

Now for the actual conversion to MySql:

You will need to have a MySql ODBC connector installed on your machine, which you can get from http://dev.mysql.com/downloads/connector/odbc/5.1.htm
(You don’t have to register to get the driver!)

Once installed, you need to add a new data source to your machine. Go to “Control Panel -> Administrative Tools -> Data Source” and under User DSN click “Add” and select the newly installed MySql ODBC driver. Then point this to the target MySql database (if it doesn’t yet exist, create it first).

Great! So now we have a source database, a target database, and both can transfer data via ODBC. All that’s left to do is fire up the Import / Export Data Wizard that comes with MS-SQL and select the appropriate input/output settings, and convert.


Filed under: Rnadom Sftuf — by Richtermeister

Problems Hosting Symfony 1.2 (!) sites on Godaddy?

– 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

IE 7 rejects my session for domains with “_”

December 9, 2008 – 4:38 pm

For this problem I want to bill Microsoft for an hour!
Here’s the gist: watch out when you use “_” in sub-domains.

Today I copied a website to a different subdomain on our dev-server, and I thought I add a “_bak” to the domain to tell things apart. All worked well, I thought, until my coworker tried to test the site using Internet Explorer 7 and found that she was unable to log in. No error either, just the same blank login form after submitting valid credentials.

Since all was working fine in Firefox, my first hunch smelled of cookies ;). And yup, IE rejected domain cookies from this sub-domain, and consequently session_start() generated a new session id for every request, effectively clearing the $_SESSION array which I was using for authentication.

Great Microsoft. Thanks for watching out for me. ,,|,,


Filed under: Rnadom Sftuf — by Richtermeister

static variables and caching

August 24, 2008 – 1:11 pm

If you’re working with classes a lot, you’ve probably come across the “static” keyword for properties and methods, enabling you to call these members straight on the class itself, without instantiating an object.


class UserPeer
{
protected static $userlist;

public static function getUserList()
{
return self::$userlist;
}
}
What’s a little less frequently encountered is a static variable inside a function (or a method for that matter). Say you have a function that returns something rather computing-intensive, but that’s called multiple times from different areas and you don’t want to re-compute every time, and you also don’t want to drag a global variable around.. then static is for you. Check this out:


function get_world_formula()
{
static $result;
if(!$result)
{
$result = //compute complex stuff here;
}
return $result;
}

Voila! A self-contained caching function. Obviously $result persists only for the duration of one request, but that’ll do just fine if it cuts down some CPU cycles. (It does add to the memory usage though, so be selective where you use it.) If you want to take this one step further, you can make caching optional by adding an extra parameter.


function get_world_formula($use_cache = true)
{
static $result;
if(!$result || !$use_cache)
{
$result = //compute complex stuff here;
}
return $result;
}

That’s it. Small but cute.

A word of OOP caution! I would not recommend using this approach in objects-methods, because this one static variable would be the same accross all object instances, so all objects would return the same data the first method call yielded, and this may not be appropriate for other objects. Use object properties instead to store chached results.


Filed under: Rnadom Sftuf — by Richtermeister

Problems Hosting Symfony sites on Godaddy?

July 24, 2008 – 5:11 pm

Please Note: There is an updated version of this post for Symfony 1.2

For all who are having problems getting a Symfony app to play nice with GoDaddy, here’s what you (most likely) need to tweak. First of all, I had to make the following entry in my settings.yml file:

path_info_key:          REDIRECT_URL

Then, and this was the real tricky part, I also needed to place a file called php5.ini at my site root, that contains the following line:

cgi.fix_pathinfo = 1

This tells the godaddy server to actually respect this front.php/somepage syntax. Otherwise this will throw a 404 error.

Hope this helps.


Filed under: Rnadom Sftuf — by Richtermeister

301 redirects - the tricky ones

June 14, 2008 – 9:54 pm

Here’s a tip for all who inherited or upgraded an existing site, and have to retain some crucial URLs. I generally hate dealing with this stuff, because it feels like polluting a nice new shiny project with legacy code that fulfills no other purpose than make your site work with outdated systems.. such as, err… Google! ;)
But then again, at the end of the day sites are only useful if you can find the information you’re looking for, and if the whole world is linking to your site already, we have to use redirects to make sure visitors continue to find the content they expect.

So on with it! Needless to say, the place to put redirects is the .htaccess file at your web root.

Now, for static content, redirects are straightforward - you simply write:

redirect 301 -from -to -flags (pseudo code)

for example:

redirect 301 /register.php /customer/join

Now, where it gets tricky is dynamic urls. Say you have an existing site with a product catalog, and your URLs look something like this:

/products/detail.php?product_id=31

While it is possible to redirect this via the same 301 redirect we used above, you would have to keep working with the same query string format, as a normal redirect would only appends it to the new url, resulting in, say, /newscript.php?product_id=31. However, what we really want to do here, is turn this legacy url into a nice sexy new-school url, such as /products/detail/31. In order to do that, we can’t just use redirects, we gotta do some url rewriting by defining a rewrite rule. Rewrite rules - nomen est omen - take a url and reformat it according to certain rules.

The resulting code is:

RewriteCond %{QUERY_STRING} product_id=([0-9]+) [NC]
RewriteRule ^products/detail\.php /products/detail/%1? [R=301, L]

What’s happening here? Basically, our rewrite condition checks a server variable for a pattern. Server variables that are made available by apache are accessed via %{VARIABLE_NAME_HERE}, and in this case the variable we want is QUERY_STRING, but different circumstances could call for others, such as HTTP_REFERER, REQUEST_METHOD, DOCUMENT_ROOT, etc.

For a great cheat sheet on all the variables and flags you can use, go to http://www.ilovejackdaniels.com/cheat-sheets/mod_rewrite-cheat-sheet. It’s one of the best cheat-sheets I’ve ever seen since college ;)

Next, the pattern we check QUERY_STRING for is product_id=([0-9]+), as product_id is the name of the GET parameter the site was using to determine which product to show. In this case we know that this id is numeric, so we’re using a pattern that matches 1 or more digits (+ for 1 or more). Only if this condition is met, the following rewrite rule applies. Also, the first matched pattern is stored in a temporary variable %1. For subsequent matches the variable name is %2, %3 etc..

The rewrite rule itself simply rewrites /products/details\.php into /products/detail, but we still need to stick the id into the url, so we append the previously matched pattern from the rewrite condition %1 and get /products/detail/%1?

Note that a period (.) has a special meaning in patterns, so you need to escape it via \ to say you mean a literal period.

Also note the flags we set on the condition and rule. The [NC] stands for “no case,” or “caSeInseNsitiVe.” Not required in this case, but you never know..

The R=301 tells the requesting browser to interpret this redirect as a 301 permanent redirect, and the L states that this is the Last rule that should be applied to this url. This may or may not apply to your case.. nothing prevents you from applying further rules below, in which case you need to remove the L.

That’s it, good rewriting!


Filed under: Rnadom Sftuf — 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

Symfony with php 5.0.1 bug - no “interface_exists()” function

March 2, 2008 – 2:00 pm

I came accross a bug in Symfony today when I installed a copy on our php 5.0.1 development server.

Turns out, php 5.0.1. doesn’t yet have the function “interface_exists()”, which was added in 5.0.2. Minor issue, but it IS being called as part of the Symfony bootstrapping procedure, so it crashes.

Luckily, in 5.0.1 the “class_exists()” method returns the same result, so putting the following lines into the app config.php file (or even into the symfony constants.php) should fix it:

if( !function_exists( 'interface_exists' ) ) {
function interface_exists( $name ) {
return class_exists( $name );
}
}

I say “should,” because in actuality this didn’t fix my problem (but it SHOULD, shouldn’t it? ;) ). So I tried simply returning false, and, well.. it works..

Let me know if you know why that is. For now I’m happy it works, and come next php version, I won’t have to worry about it..

Hope that helps someone out there who has the same problem.


Filed under: Symfony — by Richtermeister
Older Posts »

Powered by WordPress