How to write Open-Source in PHP 3: Deprecating Code

Humans, world and PHP Frameworks constantly evolve - their code functionality changes. Class or method is renamed, method has 1 new argument or new class is decoupled.

In Symfony world you probably know about Backward Compatibility Promise. It prevents from unexpected and frustrating BC breaks and helps users to upgrade gradually thanks to deprecation messages.

In this post I will show you how to work with deprecation messages.

This technique is quite rare to see (apart PHP frameworks). It's very simple to add to your open-source code workflow though - let me convince you.

Why Write Deprecation Messages?

If you DON'T

If you DO

To explain last point a bit more: if you write your message in a way, that some parser would be able to understand it, it would be able to refactor other code accordingly.

  1. Read

    SomeClass::oldMethod => SomeClass::newMethod
  2. Run

    bin/refactor app src
  3. Enjoy new code!

That was the future, now back to the present.

Today's topic: Changed Method Name

Let's take real example from real code - a class from Nette\Utils 2.4.

What we need to know?

Before this change you used:

$html = Html::el('div');
$html->add('<strong>I am brand new!</strong>');

And after this change you will use:

$html = Html::el('div');
$html->addHtml('<strong>I am brand new!</strong>');

This is the snippet from the Nette\Util\Html class we are interested in:

namespace Nette\Utils;

class Html
{
    public function add(...)
    {
        // ...
    }
}

So how to inform all ends users about this?

You can choose from 2 ways to write deprecations messages, based on your preference.

1. A @deprecate annotation

namespace Nette\Utils;

class Html
{
    /**
     * @deprecated
     */
    public function add(...)
    {
        $this->addHtml(...);
    }

    public function addHtml(...)
    {
        // ...
    }
}

This is the least you can do. But you could do better, right?

/**
 * @deprecated Method add() is deprecated.
 */
public function add(...)

Should I delete all those methods calls in my code?

/**
 * @deprecated Method add() is deprecated, use addHtml() instead.
 */
public function add(...)

A-ha, that's better!


I Have 1 Question for you: What happens when programmer runs $html->add(...) method now?

...

Well, exactly... nothing. Annotations have no influence on code run, so it will work and programmer won't notice anything.


Luckily, there is option that will actually inform about the deprecation.

2. A trigger_error()

A trigger_error() is native PHP function, that can inform user about changes in the code.

With the 2nd argument is level of these messages - there is special constant E_USER_DEPRECATED destined for this case.

namespace Nette\Utils;

class Html
{
    public function add(...)
    {
        # we already know how to write useful mesagges
        trigger_error('Method add() is deprecated, use addHtml() instead.', E_USER_DEPRECATED);

        $this->addHtml(...);
    })

    public function addHtml(...)
    {
        // ...
    }
}

You can see it used in similar way in the original code.

What happens when programmer runs $html->add(...) method with this type of deprecation?

2 things:

In case he or she is not ready for upgrade, it can be disabled in application bootstrap file:

error_reporting(~E_USER_DEPRECATED);

Source (Czech only)

I said...

It's very simple to add to your open-source code workflow...

...and this is it!

That was Symfony's Backward Compatibility Promise in a nutshell.

Happy coding!



What do you think?