Why do some PHP Developers <3 Static APIs?

Posted: 2012-12-31
Category: PHP

There are two kinds of PHP developers. Those who absolutely love static methods because they are easy to work with and those who think they are spawned by satan to test our devotion to proper programming practises.

This article is not intended to explain why statics are ok, I instead hope to use my experience with a few PHP frameworks - and the power of hindsight - to explain why some developers ignore best practises and use a whole bunch of statics.

Who loves statics?

Most people who have ever used CodeIgniter.

That means most Kohana developers, a bunch of you Laravel types and people who have moved away from using CodeIgniter to write their own stuff.

Why you might ask?

CodeIgniter's PHP 4 design obviously came before static methods were added into PHP 5.0. CodeIgniter uses a "super-global" instance which allows equal access to all loaded classes, which are all basically assigned to the controller, so they can be used throughout the system.

This means they can be accessed from any model method using a __get() which will look for that requested property using get_instance()->{$var}. Before that (obviously __get() wasn't around in PHP 4) they used to foreach through CI_Controller properties, then assign them to $this in the model.

In a library you have to call get_instance yourself (because libraries do not force you to inherit any class, so there is no way to hack in a __get().

Gross...

Yeah, this is some fairly crazy hackery to give you access to your code. Using statics can achieve the exact same functionality without the hackery.

Even the reasoning for the hackery didn't make much sense. "Oh good, I can access my session data in my model". AHHH! Why are you accessing your session data in your model? I will beat you with the bad-practise stick until you move it back out.

The "Solution"

The Kohana developers were the first to get serious hard-ons for statics, and thought they had fixed this by making changes like the following:

$this->input->get('foo');
// becomes
Input::get('foo');

This was like candy for many CI developers, who outright shunned CodeIgniter for it's PHP 4 global code and moved to Kohana to use its PHP 5 style global code. That's not much of an improvement overall, but at least it did not require multiple secret background hacks to make the logic work. Besides, less characters is always better, "right"?

Why is this bad?

Many PHP developers (especially those well versed in Symfony and Zend) will say "Dependency Injection, obviously!" but not many developers in the CodeIgniter community have any real experience with DI is as the framework makes it pretty difficult - so thats not an argument I can use to explain things to those guys.

Instead another argument, one which was very valid for FuelPHP - which while mostly using statics as an interface for instance logic still had issues with statics, especially when HMVC was involved.

This is all pseudo-code as I haven't used FuelPHP since about 1.1 and things have probably changed, but I know these general issues still exist.

class ControllerA extends Controller
{   
    public function action_foo()
    {
        echo Input::get('param');
    }
}

Fairly standard stuff. This method will output the value of ?bar= in the method.

What happens when we make a HMVC request to this method?

class ControllerB extends Controller
{   
    public function action_baz()
    {
        echo Input::get('param');
        echo " & ";
        echo Request::forge('controllera/foo?param=val1')->execute();
    }
}

If you call controllerb/baz in your browser then you'll see "val1" output, but if you call controllerb/baz?param=override then you're going to get both calls to the get method return the same value.

Relevance

Global code gives you no relevance, or scope might be a better word. In this example we would actually want to reference:

$this->request->input->get('param');

The Request object would contain a brand-new instance for each request, then input would again be another object instantiated for each request that contained only input data for that specific request. This is exactly how FuelPHP 2.0 plans to work, and solves the DI problem as well as the issues for HMVC.

But that syntax is gross!

You'll never hear that from a Symfony or Zend developer, but anyone using CodeIgniter will flip their shit at the thought of "going back to PHP 4".

Referencing code from $this to many seems like a PHP 4 approach, while static is PHP 5 and this is frustrating to try to get past.

$this should refer to the "current" object. Always.

Using $this to access all global code ever is terrible. Fact.

So, while $this->request->input->get() might look like it's even longer-form CodeIgniter syntax, really we're just sat in a controller. When the controller is instantiated an instance of new Request is assigned to it, and the constructor of Request gets an instance on Input too.

If you're sat in a model or other class, then accessing $this->request->input->foo() is not going to work, because $this is not a controller. Instead you would need to pass the request object into the method as an argument instead.

So… no static ever?

At this point there are just so many people tied to static love that it's hard to break them away. FuelPHP 2.0 and Laravel 4.0 are trying to cater for this by implementing a "Facade" layer of some description. See this writeup of how the Facade will work in Laravel 4.

That means, while you CAN access Input::get('foo') it's actually acting as a facade for instantiated logic in the background. That said, it still has all of the issues of global code, so if folks want to be lazy they can do it that way - then once they get bitten in the ass a few times trying to test their applications they can start to make the switch, without needing to totally use a new framework.

A great video by Taylor Otwell (creator or Laravel 4) outlines why and how you can replace static code with unit-testable instances via his DiC container.

Laravel 4 - IoC Controller Injection & Unit Testing from UserScape on Vimeo.

This hopefully shows off that static usage in Laravel is extremely optional, and while some modern-day frameworks certainly look like Kohana on the first glance they absolutely are not still doing things the same-old way.

Side/Sad Note

Right now I am converting PyroCMS from CodeIgniter to Laravel, and trying to take it straight from PHP 4 global code to perfect dependency injected code is absolute murder. So, an in-between step is being made to switch from using the CI loader to at least using PHP 5, PSR-2 autoloaded code, with a bunch of statics while we're still in CodeIgniter.

Then, switching from those statics to DiC code as highlight in the video will be made easy when we finally make the switch to Laravel.

Going from tightly-coupled CodeIgniter code to testable PSR-2 is an absolute mission, but the Pyro team are on it - and it's going to be epic.

Comments

Gravatar
Kevin Wood-friend

2012-12-31

Will PyroCMS be released a composer package? I see Lex is already on Packagist

Gravatar
Tony Dew

2012-12-31

Told you I smelled a blog post coming on. ;)

Gravatar
Trq

2012-12-31

[quote]Referencing code from $this to many seems like a PHP 4 approach, while static is PHP 5 and this is frustrating to try to get past.[/quote]

Seriously? There are people who use CI that actually think this? No wonder I've never been a fan of the project.

Gravatar

2012-12-31

Kevin: No, not EVERYTHING of PyroCMS will be released on Composer, but you will start to see more and more sections of code being broken off as components over time, yes. We'll either release bits as code ourselves, or replace core code with tried and tested packages from third-parties such as Symfony, Laravel, or whoever else did something cool that works well.

Tony: This is not a blog post about converting CI to PSR-2, but yeah I guess I covered it a little in the last block :p

Trq: Yes, so many f**king people.

Gravatar
Don Gilbert

2013-01-01

Great post Phil. I love the ease of use with statics, and since I haven't gotten too much into TDD yet, I may be able to get away with using them for a little while longer.

One question related to Taylor's video - in his HomeController __construct(), he is passing in a UserRepository - where is that being set and passed? Is that part of the magic of L4 using the App::bind() static method?

Gravatar
Michael

2013-01-01

Phil, why do you choose Laravel over FuelPHP ?

Gravatar

2013-01-01

Don: Exactly right, that is the magic of the bind() logic, with a little Reflection throw in no doubt.

Michael: I currently use Laravel 4 for a few projects because it is exactly what I would have liked FuelPHP 2.0 to be. The Fuel team moves a little slower than Taylor because Fuel was a part-time project for all of us, while Taylor works on Laravel full-time.

That means he made an epic framework which did things just how I wanted, so what is the point in trying to make another? FuelPHP 1.x and Laravel 3.x are incredibly old-school when compared to the new Composery way of doing things and I'll never go back.

Gravatar
Michael

2013-01-02

Yes, Taylor's commits frequency on laravel is very high ... For now, I'll stick with Fuel and I can't wait to see the 1.5 which will feature Composer
Sorry for trolling ;)

Gravatar
Xobb

2013-01-12

Not going to advocate the Kohana, but AFAIK they've left static usage of input. Using static classes only for helpers (and I suppose this is still cool if you have handy stateless functions). The request/response objects are first-class citizens there.

Anyway, your thoughts about static are so true. Currently working with the project with 1.5M SLOC with static all over the code (project started 10 years ago and didn't see the major refactoring since then). Thanks for a great article.

Gravatar
Nitin Reddy Katkam

2013-03-11

Static APIs seem like they are conserving resources by not creating multiple instances of objects, such as in HMVC requests. Under the hood, APIs may be accessing static resources through an __get, but there's a bit of uncertainty there about whether there's an object being created or an existing instance being re-used without delving into the source code.

Posting comments after three months has been disabled.