Some PHP, Generated with Python
Been looking at some code recently that provides a mechanism to localize a user interface for different human languages. The mechanism for doing so looks something like this (repeated many times over);
The global variable $lang is a giant associative array and is loaded before this function is called, depending on the users preferred language, perhaps something like these files, in terms of a PHP arrays;
'Name',
'btn_ok' => 'OK',
);
?>
'Nombre',
'btn_ok' => 'AUTORIZACION',
);
The code in question isn't an exception. I've seen many PHP applications handle localization in this way. In Internationalization and Localization with PHP the conclusion a "naive" reader might come to is this is the default best practice. Nothing wrong with the article but the necessity of simple example / short prose leaves other areas to the readers imagination.
The big problem with localization in this manner is that it's happening at runtime. Every page request resulted in the localized version of the page being generated dynamically - see here for a rough idea of why that's not so good.
Now it may be possible to cache the output HTML and store it as a static file but in a real example it's likely we'll have truly dynamic data mixed in there, such as something from a database, which makes caching tricky. Without caching the translation adds a significant baseline overhead to the generation of each page, which will increase the more complex the UI becomes.
Meanwhile there will be only a finite number of translations of the user interface, which will vary only when the UI itself changes (rarely - certainly not on a pre-request basis). So wouldn't it be better to have multiple versions of the drawNameForm() function, one per language e.g.;
And...
Of course who want's to manually maintain multiple versions of the same code?
For this specific problem solutions have already evolved, typically packaged as template engines. Jeff's file schemes implementation in WACT does "JIT" generation on PHP scripts, as you can see here. I believe (haven't looked) Smarty has similar functionality. Fine as long as you're happy with the template engine.
There are other, similar, categories of problem though, which have not been well solved yet. For example application configuration. Here's another suggestive snippet;
if ( $config['allow_bbcode'] ) {
if ( $user_sig != '' && $user_sig_bbcode_uid != '' ) {
$user_sig = ( $config['allow_bbcode'] ) ?
bbencode_second_pass($user_sig, $user_sig_bbcode_uid) :
preg_replace('/:[0-9a-z:]+]/si', ']', $user_sig);
}
if ( $bbcode_uid != '' ) {
$message = ( $board_config['allow_bbcode'] ) ?
bbencode_second_pass($message, $bbcode_uid) :
preg_replace('/:[0-9a-z:]+]/si', ']', $message);
}
}
}
The key line is;
if ( $config['allow_bbcode'] ) {
On every page request this condition (as well as many others) is being re-evaluated. Wouldn't it be better to simply eliminate the block of code, if $config['allow_bbcode'] is false? There have been attempts to build PHP installers, some very successful, designed for a single application (e.g eZ publish 3) with the most mature, generic attempt being Sandro's Zzoss Installer, which I've mentioned before here.
Around the point of writing installers in PHP is where I think things start to "fall apart", in general because PHP was designed specifically for web sites - it's not a general purpose solution. That's not to say "never" but rather, by taking other technologies into account, can life be made easier / PHP applications better?
Code generation has come up on this blog before. Another link from http://www.codegeneration.net/ since then, here;
PHP is amazing because it's so inexpensive to run. All you need is Apache, MySQL and PHP and you can run a web site or service.
...and add to that how easy it is to deploy a script - just drop it somewhere on your web server and away you go - something that opens the doors for "just in time" generation, for example.
Code generation often tends to be thought about in terms of building complete applications with some friendly "drag and drop", such as CodeCharge Studio. While that has it's place, it tends to be "all or nothing".
An interesting and alternative form of code generation, that I ran into today, is Aspect-Oriented PHP. It uses Java to do some basic parsing of a PHP script and merge with a second "aspect" script (see here to get a sense of what AOP is about). Right now it probably isn't a realistic proposition, in terms of performance but it's interesting because, first, they're using Java to do the work, which gives them a solid basis for dealing with things like multibyte characters or more complex parsing operations and because they're generating PHP on the fly.
Returning to the localization problem above, been looking at empy recently: "A powerful and robust templating system for Python". Some things that make empy attractive, so far, is it's intended for general purpose templating (not HTML specific), it's mature, the markup is very distinct from PHP and HTML (the only thing you really need to watch is the @ symbol) and it allows you to use Python itself as the template language, when needed.
An empy template to generate PHP from the drawNameForm() could look like this;
function drawNameForm() {
?>
The empy markup I've used is: @(name) and @(btn_ok) (empy has alot more than that but, for now, sticking with some basic variable references).
Viewing the output from this function in a browser results in the following HTML source;
In other words I can still use it as a working PHP script (in this example at least).
Meanwhile if I run it through a python script (which in turn invokes empy) like;
#!/usr/bin/python
# translate.py
# Load empy
import em
# Use a dictionary (like an associate PHP array)
# for sake of example. In practice use external files...
langs = {}
langs['en'] = {
'name': 'Name',
'btn_ok': 'OK',
}
langs['es'] = {
'name': 'Nombre',
'btn_ok': 'AUTORIZACION',
}
# Create an empy interpreter
interpreter = em.Interpreter()
# Load the template PHP script
tpl = open('ui.tpl.php').read()
# Loop through the available translations
for lang in langs:
# Give the interpreter a new file to write output to
interpreter.output = open('ui.'+lang+'.php','w')
# Reset for new parse (making it use the output file)
interpreter.reset()
# Populate the interpreters data space with the word list
# to write into the template
interpreter.globals = langs[lang]
# (Re-) Parse the template
interpreter.string(tpl)
interpreter.shutdown()
It spits out localized PHP scripts with filenames identifying the language e.g.;
I can now use it as part of the build process for my application, as it's being prepared for a release. At runtime the code (hand coded) which uses this script might look like;
Anyway - just musings and an obvious solution, if you're already doing it. What I can't say is how well this kind of approach scales to a large PHP codebase but, guessing, so long as the scripts involved remain simple, focusing on performing a single task, no real problems.
Some particular points about Python. By taking advantage of things like py2exe or py2app you could distibute executable installers. Throw in some wxPython and you've got a cross platform GUI (which looks like the real thing - uses native widgets) for installing.
Any stories?