Modernizing Serialized PHP Objects with class_alias()

Several weeks ago, a correspondent presented a legacy situation that I’ve never had to deal with. He was working his way through Modernizing Legacy Applications in PHP, and realized the codebase was storing serialized PHP objects in a database. He couldn’t refactor the class names without seriously breaking the application.

I was carefully moving my classes to a PSR-0/4 structure when I found this. The application saves the output of serialize($shoppingCart) in a BLOB column, and unserializes it later to recreate the ShoppingCart object. The serialized object string looks like this:

O:12:"ShoppingCart":17:{s:13:"\00Basket\00items";a:25:{...}; ...}

See how the class name is embedded in the serialized string? If I rename the ShopppingCart class to PSR-0/4 namespace, then the old class won’t be found when the application tries to unserialize() the serialized representation. How can I begin refactoring this without breaking the whole application?

Before I was able to reply, my correspondent ended up changing the serialization strategy to use JSON, which was a rather large change. It ended up well, but it turns out there is a less intrusive solution: class_alias().

If you’re in a serialized situation, and you need to change a class name, you can use a class_alias() to point the old class name to the new one. (Call class_alias() early in execution, probably before you register your autoloader.) You can then rename and move the class according to PSR-0/4 rules, and PHP will handle the rest for you.

For example, if you renamed ShoppingCart to Domain\Orders\Cart, you might do this:

class_alias('ShoppingCart', 'Domain\Orders\Cart');

Now when you call unserialize($shoppingCart) to create an object, PHP will create it as an instance of Domain\Orders\Cart instead of ShoppingCart. Re-serialized representations of the recreated object will retain the new class name, not the old one: O:18:"Domain\Orders\Cart":....

As soon as there are no more serialized representations using the old class name, you can remove the class_alias() call entirely.

MLAPHP Boot Camp!

You may have seen my introductory talk on steps toward modernizing a legacy application at Nashville PHP or at php[world]. That was the presentation that formed the first few chapters of Modernizing Legacy Applications in PHP.

The MLAPHP book has been a great help to a lot of people. But there are a lot of other developers who learn best by watching-and-listening first, instead of reading. They know it can be a great help to have someone talk them through the whole moderizing process, and see the steps being performed.

Usually you have to go to a conference for that sort of thing. You have to pay for the ticket, and for the hotel, and for travel, and meals, and everything else. Even if your employer helps with the money, there’s still the time involved: you have to be away for several days at a time.

Well, now you can have a conference-level training experience in the comfort of your own home, without the travel hassle and hotel expense. I have put together an online weekend “boot camp” where we go through the entire modernization process, from basic prerequisities at the beginning to setting up a DI container at the end. After that end-to-end study of the steps involved, you should be able to jump-start your own modernizing project much more easily.

The 8-hour camp will combine lecture and “live” coding with examples. In particular, there will be plenty of opportunity for you to get answers specific to your own modernizing situation, and to chat with other attendees.

The two-day camp will be on Sat 11 July and Sun 12 July. Each day will run from 12 noon to 4pm (US Central Daylight), with time for breaks built in. That makes it 10am-2pm for Pacific time zone attendees, and 1-5pm for Eastern, which should be convenient for almost everyone.

With your ticket to attend the boot camp, you get:

  • Two days of online boot camp, four hours each
  • A review of the entire modernization process from beginning to end
  • A bonus recording of the camp
  • For $399

This might be the only time I lead this kind of boot camp, so get your ticket while there are still seats available!

Relay: A PSR-7 Middleware Dispatcher

UPDATE (2015-06-09): Due to a conflict with the “pipeline” design pattern name, which this project does not implement, the project name has been changed from Pipeline to Relay. Links and naming have been updated inline, with the original text block quoted beneath.

In a fit of inspiration over the weekend, I extracted the middleware dispatcher from the Radar ADR core into its own standalone library: Relay. If you need to execute a queue of middleware, Relay is for you.

Relay uses a common PSR-7 middleware signature: function ($request, $response, $next) and return $response. That is what Slim 3 uses, along with Stratigility and Radar, and is modeled on Sencha Connect. Any callable/invokable with that signature will work with Relay.

As a standalone library, Relay is framework-independent, so you can use it anywhere. You could even build your own framework around it, picking a dotenv-loader of your choice, a DI container of your choice, and a middleware queue of your choice.

Speaking of DI containers, Relay is also container-independent. You can use any dependency-injection system you like to build your middleware queue and pass it to Relay. All you need to do is pass in a callable that converts each queue entry to an object or callable. Try it with PHP-DI, Pimple, Auryn, or even Aura.Di.

I have converted Radar to use Relay at its core, and soon I’ll be extracting two of the basic Radar middleware classes to standalone packages. When I do, they’ll be listed on the Relay wiki.

ORIGINAL RELEASE:

In a fit of inspiration over the weekend, I extracted the middleware dispatcher from the Radar ADR core into its own standalone library: Pipeline. If you need to execute a queue of middleware, Pipeline is for you.

Pipeline uses a common PSR-7 middleware signature: function ($request, $response, $next) and return $response. That is what Slim 3 uses, along with Stratigility and Radar, and is modeled on Sencha Connect. Any callable/invokable with that signature will work with Pipeline.

As a standalone library, Pipeline is framework-independent, so you can use it anywhere. You could even build your own framework around it, picking a dotenv-loader of your choice, a DI container of your choice, and a middleware queue of your choice.

Speaking of DI containers, Pipeline is also container-independent. You can use any dependency-injection system you like to build your middleware queue and pass it to Pipeline. All you need to do is pass in a callable that converts each queue entry to an object or callable. Try it with PHP-DI, Pimple, Auryn, or even Aura.Di.

I have converted Radar to use Pipeline at its core, and soon I’ll be extracting two of the basic Radar middleware classes to standalone packages. When I do, they’ll be listed on the Pipeline wiki.

Semantic Versioning and Public Interfaces

Adherence to Semantic Versioning is just The Right Thing To Do, but it turns out you have to be extra-careful when modifying public interfaces to maintain backwards compatibility. This is obvious on reflection, but I never thought about it beforehand. Thanks to Hari KT for pointing it out.

Why do you have to be extra-careful with interfaces and SemVer? To see it more clearly, let’s use a concrete class as an example.

class Foo
{
    public function bar() { ... }
    public function baz() { ... }
}

If we remove a public method, that’s clearly a BC break. If we add a non-optional parameter to an existing public method, that’s also clearly a BC break. Those kinds of changes will break any code already using the Foo class; that code will have to be modified to accommodate the changes in Foo.

However, if we add a new public method to the concrete class, that is not a BC break. Likewise, changing the signature of an existing method to add an optional parameter is not a BC break either. (UPDATE: See note below.) Code using the Foo class does not have to change to accommodate the new method or the new optional parameter.

But what happens with an interface?

interface FooInterface
{
    public function bar();
    public function baz();
}

Removing a method, or adding a non-optional parameter to an existing method, is the same as with a concrete class: it’s BC break.

However, unlike with a concrete class, adding methods is also a BC break. So is changing an existing method sigature to add an optional parameter. Existing code that implements FooInterface will no longer comply with the interface; that code will have to change to accommodate the new method or the new optional parameter.

Thus, interfaces are more susceptible to BC breaks than concrete classes. Once an interface is published as “stable”, I don’t see how it can be changed at all per the rules of SemVer (that is, unless you bump the major version). The only thing you can do to maintain BC is add an entirely new interface to the package while leaving the old one in place, perhaps even extending the old one if needed.

Again, it’s obvious in hindsight, but I did not have the foresight to anticipate it. Perhaps this realization will save you some trouble in the future.

p.s. On further examination, the interface constraints apply to abstract classes, too. Changes to an abstract mean that child classes will have to be modified as well, otherwise they won’t comply with the modified abstract.

UPDATE: Dave Marshall notes on Twitter that in the concrete class case, if you add an optional parameter to a method, and a user has extended and overridden that method, the extended class will break under PHP E_STRICT. He links to Symfony policy on the subject, which I will now have to read.