PHP 5.3 Is Out: Should You Upgrade Your LAMP App Yet?
PHP 5.3.0 landed on June 30, and it’s a big one. The release announcement leads with namespaces, closures and late static binding, which are the features people have been asking about for years. It also quietly moves a lot of old PHP habits from “discouraged” to “warns you every time”, and that second part is what decides whether your app upgrade is a quiet afternoon or a noisy week.
So here’s how I’d think about it if you run a LAMP app you care about: what’s genuinely new, what will break or start complaining, and how to find out safely before your users do.
The new stuff worth caring about
Namespaces are the headline. If you’ve ever named a class Blog_Model_Post_Comment purely so it wouldn’t collide with some library’s Comment, this is the fix. You declare namespace Blog\Model; at the top of a file, and code elsewhere refers to \Blog\Model\Comment. Yes, the separator is a backslash. It looks odd for about a day.
Closures are the one I expect to change everyday code the most. Until now, a callback for usort() or preg_replace_callback() meant a named function defined somewhere else, or create_function() with code inside a string. Now you can write the function inline, and pull in variables from the surrounding scope with use. The anonymous functions page in the manual covers the details.
<?php
$minLength = 4;
$words = array('php', 'apache', 'mysql', 'linux');
$long = array_filter($words, function ($w) use ($minLength) {
return strlen($w) >= $minLength;
});
Late static binding fixes an annoyance that anyone who has tried to build an ActiveRecord style base class knows well. Inside a static method, self:: always means the class where the method was written, not the subclass you actually called it on. PHP 5.3 adds static::, which refers to the called class, along with get_called_class(). So Post::find() inherited from a Model base class can finally know it’s a Post.
There are smaller niceties too. __DIR__ gives you the directory of the current file, so all those dirname(__FILE__) calls in your include paths can get shorter. The ?: shortcut returns the left side if it’s truthy and the right side otherwise. There’s also a garbage collector for circular references, mysqlnd as an optional native replacement for libmysql, and new bundled extensions including phar, intl, fileinfo and sqlite3.
None of that requires you to change anything, though. The question for most of us is what happens to code that already works on 5.2.
What breaks, and what just gets loud
PHP’s own upgrade notes for 5.3 and the manual’s 5.2 to 5.3 migration guide cover this in detail, and the pain splits into real breaks and deprecations.
The real breaks are mostly edge cases, but they’re the kind that hide in older code. namespace and goto are now reserved words, so a function or class with either name is a parse error. Closure is now a reserved class name. The array functions usort(), uasort(), uksort(), natsort(), natcasesort(), array_flip() and array_unique() no longer accept objects. The magic methods __get(), __set(), __isset(), __unset() and __call() have to be public and can’t be static, and __toString() can’t take arguments. Functions that expect a by-reference argument but get a plain value now emit a warning and set those parameters to NULL instead of carrying on. The unified parameter parsing means many internal functions return NULL when you pass them the wrong type.
The release announcement also lists a few extensions that are gone from the core distribution. mhash is out (ext/hash has a compatibility layer), msql, pspell and sybase are dropped, and ming, fbsql, ncurses and fdf moved to PECL. If you still have zend.ze1_compatibility_mode switched on somewhere, PHP 5.3 raises an E_ERROR at startup. And if you build against the new mysqlnd library, it can’t log in to MySQL accounts that still use the old pre 4.1 short password hashes.
Then there’s the loud part. PHP 5.3 adds a new error level, E_DEPRECATED, and it’s included in E_ALL. The whole ereg family (ereg(), eregi(), ereg_replace(), split() and friends) now raises it, as do session_register() and its siblings, call time pass-by-reference, and the old PHP 4 idiom $obj =& new Foo(). If register_globals, magic_quotes_gpc, magic_quotes_runtime, safe_mode or register_long_arrays are turned on, you get a deprecation notice at startup. None of this stops your code from running today. It’s PHP telling you these things are scheduled for removal, and if your app sets error_reporting(E_ALL) with display_errors on, your visitors will be reading those notices too.
For ereg, the fix is usually mechanical: switch to the PCRE functions like preg_match() and add delimiters around the pattern. For register_globals and magic quotes, the deprecation is overdue. If your app still leans on either one, register_globals especially, that’s a security problem worth fixing whether or not you upgrade.
The timezone warning everyone is about to meet
This one deserves its own section because it’ll hit apps that don’t use any deprecated feature at all. When PHP has to guess the timezone because you never told it, the date functions complain with “It is not safe to rely on the system’s timezone settings”. In 5.2 that message was raised as E_STRICT, which E_ALL doesn’t include, so almost nobody saw it. In the 5.3.0 source it’s an E_WARNING, which E_ALL does include, so a lot of apps will start printing it as soon as they call date() or strtotime().
The fix is one line in php.ini, set to a real identifier from the timezone list. The date.timezone setting is the default every date function uses:
date.timezone = "America/Chicago"
If you can’t edit php.ini, calling date_default_timezone_set() early in your bootstrap does the same job. Note that both of the new sample config files PHP 5.3 ships, php.ini-development and php.ini-production, leave date.timezone commented out, so copying one into place won’t save you.
Those two files replace the old sample php.ini files, and the difference between them matters here. The production one sets error_reporting = E_ALL & ~E_DEPRECATED with display_errors = Off, and the development one uses E_ALL | E_STRICT with errors displayed. That’s roughly the setup I’d want anyway: quiet in production, noisy on the box where you’re testing.
Test it on a staging box first
Please don’t do this upgrade on the live server. Build a staging machine (a spare VM is fine) that matches production apart from PHP, put 5.3.0 on it, and restore a copy of your database and your code.
Before you even load a page, it’s worth searching your code for the obvious offenders so you know what you’re in for:
grep -rnE "ereg|(^|[^_[:alnum:]])spliti?\(|session_(un)?register|=[[:space:]]*&[[:space:]]*new" --include='*.php' .
find . -name '*.php' -exec php -l {} \; | grep -v '^No syntax errors'
Run that lint with the 5.3 binary, because reserved word collisions only show up as parse errors. Then use the development ini with log_errors on, click through the app (and run your tests if you have them), and read the error log instead of just looking at pages. Anything logged as E_DEPRECATED is a to-do list for later. Warnings, fatal errors and NULLs where you expected arrays are the things to fix before go-live.
Don’t forget the parts of the stack that aren’t your code. PHP 5.3 bumps the Zend module API number, so every compiled extension has to be rebuilt for it, and that includes opcode caches. The newest stable APC on PECL is still 3.0.19 from May 2008, more than a year older than PHP 5.3.0, so check that whichever cache and loaders you rely on actually work on the staging box before you plan a date. The same goes for frameworks and CMSes: check their own notes for 5.3 support before assuming.
Why your host and your distro aren’t there yet
If you’re on shared hosting, you probably can’t upgrade this month even if you want to. A host running hundreds of customer apps on one PHP build would be switching everyone’s sites into a release that deprecates things a lot of those sites depend on, so I’d expect most to wait, test, and maybe offer 5.3 as an opt-in down the line. Ask your host about their plans rather than guessing.
Distro packages are behind too, and that’s by design. Debian 5.0 lenny ships PHP 5.2.6. Ubuntu 9.04 ships 5.2.6 as well, 8.04 LTS is on 5.2.4, and the in-development 9.10 was updated to 5.2.10 at the end of June. Red Hat Enterprise Linux 5 is further back still, as its April security update for PHP shows: it’s patching 5.1.6. Stable distros backport security fixes into the version they shipped rather than jumping to a new minor release, so getting 5.3 on those systems today means compiling it yourself or trusting a third party repository.
So, should you upgrade now?
For a new project where you control the server, I’d start on 5.3 today. I think namespaces and closures will shape how PHP libraries get written from here on, and there’s no old code to break.
For an existing production app, I wouldn’t rush. The 5.2 branch is still getting releases (5.2.10 came out about two weeks before 5.3.0), and a .0 release of something this large is exactly when early bugs get found. Scan the full 5.3.0 changelog, get a staging box running now, set date.timezone, and start clearing out ereg and the other deprecated calls, because that work pays off on 5.2 too. When your staging logs are clean and your extensions have caught up, the upgrade itself should be the boring part.