You can deprecate *anything*
Monday, December 20, 2010 at 11:31PM I've been reading through this very interesting list of feature requests for PHP, and one of the reasons of denial that the PHP team keep giving to some of these crucial ideas is that they "wouldn't be backward compatible". Now, this may be total conjecture, but my honest, educated opinion is that it is possible to make anything backward compatible. Aside from obscure circumstances, it's generally easy. In fact, I'd go as far as to say that deprecating code is one of the easiest things programmers can do.
Let's take a few bits of code and build a few examples to let me illustrate my point. Assume we're building some sort of CMS or other application that has a community surrounding it. This could be pretty much any open-source project, it really doesn't matter. Now, version 1 of our application has an emailing class that looks something like this:
class Email {
public function send_email($email) {
$this->_verify_email_format($email);
$headers = $this->_build_headers($email);
return mail($email['to'], $email['subject'], $email['message'], $headers);
}
protected function _verify_email_format($email) {
foreach (array('to', 'subject', 'message', 'from') as $field) {
if (!isset($email[$field]) || empty($email[$field])) {
die("Error: the '$field' field is required");
}
}
}
protected function _build_headers($email) {
$headers = array();
foreach (array('to', 'subject', 'message') as $field) {
unset($email[$field]);
}
foreach ($email as $key => $value) {
$headers[] = $key . ": " . $value;
}
return implode("\r\n", $headers);
}
}
Version 1 of our application uses this code absolutely fine, and all the add-ons created for our wonderful system can utilise this too. However, one of our developers comes up with a better idea for an email class. We all agree that his class has a better API, better syntax and will make for much cleaner and concise code. His class looks like this:
class Email {
public $to = '';
public $from = '';
public $subject = '';
public $message = '';
public $cc = '';
public $bcc = '';
public $reply_to = '';
public $additional_headers = array();
public $sent = FALSE;
public function __construct($config = array()) {
foreach ($config as $key => $value) {
if (isset($this->$key)) {
$this->$key = $value;
} else {
$this->additional_headers[] = $key . ": " . $value;
}
}
}
public function send() {
$this->_check_required_fields();
$headers = $this->_build_headers();
$this->sent = mail($this->to, $this->subject, $this->message, $headers);
}
protected function _check_required_fields() {
foreach (array('to', 'subject', 'message', 'from') as $field) {
if (!isset($this->$field) || empty($this->$field)) {
die("Error: the '$field' field is required");
}
}
}
protected function _build_headers() {
$headers = array();
$headers[] = ($this->cc) ? 'Cc: ' . $this->cc : '';
$headers[] = ($this->bcc) ? 'Bcc: ' . $this->bcc : '';
$headers[] = ($this->reply_to) ? 'Reply-to: ' . $this->reply_to : '';
$headers = array_merge($additional_headers, $headers);
return implode("\r\n", $headers);
}
}
I think we can all agree that the latter, although slightly more verbose, is a much, much nicer alternative. Now, we're all happy and preparing to go and swap out the old class with the new class, when a solitary add-on developer, browsing through the development forum, alone at night with an all-encompassing feeling of sequestration, cries out:
All my add-ons that use this class are going to break. Anybody else using this class in their development projects are going to have to rewrite big portions of their applications to get this to work. You can't just swap out the classes, they've got to be backward compatible!
Oh, maverick developer, we can't let you down! We'll have to find a better solution to this problem. A solution that allows new add-ons to use the new class and older add-ons to continue using the old class in peace, without disturbing them. This way, since we're busy, we won't have to redevelop the core of the app either, if we can make the code backward compatible, we'll be able to leave the old system in place and convert it when we have more time.
But how would you convert an API that looks like this...
$email = array(
'to' => 'sjobs@apple.com',
'from' => 'jamie@jamierumbelow.net',
'subject' => 'iPhone 5',
'message' => "Steve, I am bored with being the lead developer on the iPhone 5.",
'reply-to' => 'jamie@jamierumbelow.net',
'cc' => 'jive@apple.com'
);
$sender = new Email();
if ($sender->send_email($email)) {
die('Email sent!');
}
...to something that looks like this...
$email = new Email();
$email->to = 'sjobs@apple.com';
$email->from = 'jamie@jamierumbelow.net';
$email->subject = 'iPhone 5';
$email->message = "Steve, I am bored with being the lead developer on the iPhone 5.";
$email->reply_to = 'jamie@jamierumbelow.net';
$email->cc = 'jive@apple.com';
$email->send();
if ($email->sent) {
die('Email sent!');
}
...while managing to retain backward compatibility? Well, it's actually incredibly easy. Simply take your new Email class and add a quick compatibility layer. We want to re-implement the send_email() to mimic the old API but use the new emailing system. It needs to take exactly the same format array and return a boolean response. This way it'll still work for the time being.
public function send_email($email) {
foreach ($email as $key => $value) {
if (in_array($key, array('to', 'from', 'subject', 'message', 'cc', 'bcc'))) {
$this->$key = $value;
} else {
$this->additional_headers[] = $key . ": " . $value;
}
}
$this->send();
return $this->sent;
}
Hey, take a look at that. That was very easy. We now have a perfectly backward compatible API. Both examples of code above will work flawlessly with the new system. And we got that from an extra 11 lines of code. Not too shabby.
The majority of the time when you're deprecating something, you intend to replace it with something else that does the same job. This means that there is a usually a way of mapping the functionality across with only a little effort, because you've got that one-to-one situation. If you're removing something for good and don't intend on replacing it, mark it as deprecated and tell developers that you're doing so. Leave it in for a few versions, and then after a while, remove it.
The fact remains that deprecating functionality is just not that difficult, and you really shouldn't miss out on improving whatever code you've got in place because you're worried about backward compatibility. There are always ways around these sorts of problems.
deprecation,
features,
php,
programming 