Search code examples
phplithiumapi-versioning

Versioning a PHP Lithium API


Goal

I've been tasked with versioning a pretty large PHP Lithium API.

The end result I'm looking for is to use namespacing to separate the versions.

For example, normally a lithium route that looked like this:

Router::connect('/{:controller}/{:action}/{:id:\d+}');

Could have the following URL, map to the following PHP call:

http://www.fungames.com/game/view/2
app\controllers\Game::View($gameId)

But I would like to create the following mapping:

Router::connect('/{:version}/{:controller}/{:action}/{:id:\d+}');

Such that the following two calls could be made:

http://www.fungames.com/v1/game/view/2
app\controllers\v1\Game::View($gameId)

http://www.fungames.com/v2/game/view/2
app\controllers\v2\Game::View($gameId)

Problem

Unfortunately, the Lithium documentation doesn't make much mention of API versioning. There's a brief mention here as an example of continuation routes. But that approach would required making if statements in my controllers to version my API, and I personally consider that a poor approach.


TLDR

What is the best way to achieved namespaced API versioning when using the PHP Lithium Framework?


Solution

  • API versioning is a really broad topic, and while Li3 gives you all the necessary tools to implement any approach you choose, the actual implementation is up to you. Having said that, the routing layer is fairly sophisticated and will get you very, very far. See this gist on routing configuration examples.

    Combining this with Request::get() which allows you to match on any request parameters including headers (you can pass get()-compatible values as $params in Router::connect()see examples here), and Request::detect(), which allows you to implement custom matching logic, you can send any configuration of values down to the controller/dispatch layer. This allows you to segment your versions by differently namespaced controllers (with fallback to the default), prefixed actions, or pass a namespace/class path for a different model.

    Yet another approach, and one that I would recommend in the general case, is implementing a set of transaction managers/mappers for any endpoints that need to be versioned, and using them as an intermediary between your controller and your model, then use routing to switch between them.

    This approach is made easier if you evolve your API using, i.e. content types, since you can choose a mapper based on that. This is also nice since version numbers are strongly discouraged by REST's creator.

    Anyway, hope that helps.