13 Jul 2010For one of the tests I'm currently writing, I needed to assert that my method was correctly returning an error if the connection to the database was impossible.
I couldn't find an easy way to change datasource credentials once the app was initiated, so I decided to manually update the ConnectionManager
inner values.
Here's how I did it :
// Getting the datasource cache in the ConnectionManager object
$connectionManagerInstance = ConnectionManager::getInstance();
$databaseConfig = &$connectionManagerInstance->_dataSources;
// Saving the initial setting for reverting it later
$_defaultConfig = $databaseConfig['default'];
// Changing the password so the credentials will fail
$databaseConfig['default']->config['password'].= 'pass';
// Getting the updated datasource
$connect = ConnectionManager::getDataSource('default');
// Error handling when connection unavailable
[...]
// And reverting the settings back
$databaseConfig['default'] = $_defaultConfig;
This proved really useful when testing to simulate a database server error.
05 Jul 2010The easiest way to display a date in a given format is to use a combination of strftime()
and strtotime()
.
Sometimes, you also need your date to be displayed in a specific language. For example, for a french website, I needed a date to be displayed as "mardi 03 août 2010" instead of "Wednesday, August 3rd".
Setting the locale
To achieve that, you just have to tell PHP which locale to use when displaying date with setlocale(LC_TIME, $locale)
.
The value of $locale
is OS dependent, though. For example, on a linux server, you have to set fr_FR
while it is fr
or even french
on Windows.
Fortunatly, you can pass an array of locales to setlocale
(, and the system will use the first one it can find. You just have to pass an array containing all possible values and you'll be good to go.
I wrote a little method that use the L10n
object that comes with cakePHP to automate the creation of such an array. Just feed him a 3-letter language code and it will return an array of the most common locale names.
function getLocales($lang) {
// Loading the L10n object
App::import('L10n');
$l10n = new L10n();
// Iso2 lang code
$iso2 = $l10n->map($lang);
$catalog = $l10n->catalog($lang);
$locales = array(
$iso2.'_'.strtoupper($iso2).'.'.strtoupper(str_replace('-', '', $catalog['charset'])), // fr_FR.UTF8
$iso2.'_'.strtoupper($iso2), // fr_FR
$catalog['locale'], // fre
$catalog['localeFallback'], // fre
$iso2 // fr
);
return $locales;
}
You may note that I set in first position a locale with the mention of the encoding. This is only used on Linux machines, Windows does not handle that. That's a pity, but I'll show you how to correctly make it work underWwindows.
As a side note, setlocale
will not return false
if the locale is not found, it will just fail to load it.
Displaying date in UTF8
If your app is in UTF8 (and it should be !) you may run into problem when trying to display a simple strftime("%B", strtotime($date))
on Windows.
%B
translate to the current month name. For a month like Août (August) the funny û will not get correctly displayed, because Windows does its locale translation in its native encoding.
You'll need to manually encode it in utf8, but if you do so on a linux machine, as the result is already encoded in utf8 you may end in double encoding the same string, resulting in others display errors.
Note also that if your format string itself contains utf8 encoded characters (like%A %d %B %Y à %Hh%M
), encoding it in utf8 again will also result in wrong characters displayed.
What I've done is creating a simple method in an helper that will take care of encoding the result if needed.
function time($format, $date = null) {
// On Windows, we will force the utf8 encoding of the date
if (DIRECTORY_SEPARATOR == '\\') {
return utf8_encode(strftime(utf8_decode($format), strtotime($date)));
}
// On linux, this is already taken care of by setlocale()
return strftime($format, strtotime($date));
}
This way, we make sure that the date is correctly displayed in utf8, no matter the OS, even if you already supply utf8 characters in the format string.
30 Jun 2010One pattern I use when writing new method is to put conditions that may end the script early on top. Like "stop the method if the arguments are not right" or "stop the action if the user is not loggued in".
It allow me to avoid having nested if
/else
statement that became unreadable.
Felix from Debuggable wrote something about that a while ago.
This is a pretty easy pattern to follow when writing methods, but can be quite harder to achieve if you need to stick inside a main method. You just want to go "out" of the if
statement, to continue the script, but not end the method.
Break to the rescue
You can't use the break
keyword in an if
statement like you would in a loop. It just throws an error.
But, you can define a simple do {} while (false)
loop and use the break goodness inside it.
do {
if (empty($data)) break;
$this->create($data);
if (!$this->validates()) break;
$this->save();
} while (false);
This helped me some times, hope it can help someone else.
28 Jun 2010Gradients are one of the new cool stuff CSS3 brought with it. Like the others cool things, it still suffer from a partial implementation and vendor-specific properties.
It also isn't correctly parsed by CSSTidy. Here I'll show you how to patch your CSSTidy to make it eat gradients correctly.
Quick and dirty patch
First, you'll need to edit the huge parse()
method in csstidy.ph
p. You'll have to add a condition to explictly tell CSSTidy not to discard -webkit- gradient
and -moz-linear-gradient
.
Just open your csstidy.php
file, find the parse()
method and locate the case 'instr'
in the huge switch
statement.
if (!($this->str_char === ')' && in_array($string{$i}, $GLOBALS['csstidy']['whitespace']) && !$this->str_in_str)) {
$this->cur_string .= $temp_add;
} **else {**
** if ($this->sub_value=="-webkit-gradient" || $this->sub_value=="-moz-linear-gradient") {**
** $this->cur_string.=' ';**
** }**
**}**
In bold, the else
part to add. This will make sure your webkit and firefox gradient rules will get processed correctly.
I don't really understand WHY it work, but it does. The parse()
method is a huge uncommented mess, it is quite difficult to understand it. There must be a better way, a more generic one than specifying some properties, but I didn't manage to come with anything better than that.
Fortunatly, the next part is cleaner.
Telling CSSTidy which properties not to merge
If you write a css like the following, only the latest (color:white
) rule will get through CSSTidy.
body {
color:red;
color:white;
}
That's logical, because CSSTidy will remove any unused CSS declaration. Unfortunatly, this is not what we want, because we need to declare several background:
rules, one for Webkit, and one for Firefox.
By looking at CSSTidy source code, we can find that it contain a quick fix to allow the cursor:
property to be defined several time (to cope with the old cursor:pointer
/ cursor:hand
issue).
I just extended this quick fix to work for other properties as well, and even managed to allow them to be passed as a config value.
Defining the config value
First, open the csstidy.php
file, and around line 310 you should find a list of default config values. Just add the following :
$this->settings['multiple_properties'] = array('cursor', 'background');
This will define the default list of properties that are allowed to be defined several times in a css rule.
Next, we'll edit the set_cfg()
method to allow the passing of array values. Just replace the else statement with this one :
else if(isset($this->settings[$setting]) && $value !== '') {
// Merging array settings
if (is_array($value) && is_array($this->settings[$setting])) {
$this->settings[$setting] = array_merge($this->settings[$setting], $value);
} else {
// Setting classic setting
$this->settings[$setting] = $value;
}
if ($setting === 'template') {
$this->_load_template($this->settings['template']);
}
return true;
}
You can now pass a list of properties to be added to the existing list by calling ->set_cfg('multiple_properties', array('property1', 'property2'));
Now, find the css_add_property(
) method, and around line 1066, change theif (strtolower($property) == 'cursor')
if statement to this more generic one :
if (in_array($property, $this->get_cfg('multiple_properties')))
And now, in csstidy_print.php
, find the_print()
method, and replace the case PROPERTY
block with this (more concise) one :
case PROPERTY:
// Converting back multiple properties
$multipleProperties = $this->parser->get_cfg('multiple_properties');
foreach($multipleProperties as $property) {
$propertyLength = strlen($property);
if (substr($token[1], 0, $propertyLength)==$property) $token[1] = $property;
}
// Applying correct casing
$caseProperties = $this->parser->get_cfg('case_properties');
if ($caseProperties==2) $token[1] = strtoupper($token[1]);
if ($caseProperties==1) $token[1] = strtolower($token[1]);
$out .= $template[4] . $this->_htmlsp($token[1], $plain) . ':' . $template[5];
break;
And that's it
You now can have gradients compressed with CSSTidy. Well sort of, because this is just a quick and dirty patch, as I'm not the creator of CSSTidy.
This could surely be improved in a less hacky way, for example by compressing the color code used in the gradients...
25 Jun 2010On July 21st will be held an informal meeting in Paris for all the web developers that have an interest in web performance.
Eric Daspet and Stoyan Stefanov will be there and host a talk about performance impact on sales and some more technical less known facts.
The first part will be in French while Stoyan's will be in English. If you have the chance to be in Paris in July, do not miss this event.
I will, of course, be there. Eric Daspet is the french "gourou" on web performances, and Stoyan Stefanov is one of the main author of the Smushit plugin/websit.
Web performance is a really interesting subject where there is so much to learn and so much to try.