Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static functions are bad - but what's the alternative?

Tags:

oop

php

static

yii2

In my example I'm using the PHP framework Yii2 but I think this applies to most OO languages.

I have an ActiveRecord base class which most of my business objects extend from e.g. Project.

At the moment if I want a Project instance I call

Project::findOne(['id' => $id]);

findOne is a static method of ActiveRecord (which is part of the Yii2 framework). So this is bad form because I can't easily mock/stub the return of this call when writing unit tests.

But what's the best way to get around this?

I could create a class CActiveRecord that inherits from ActiveRecord and wrap the static call in a non-static call and use that everywhere - but then I would have to instantiate a throw-away Project object in order to get the actual instance. What if the Project object needed some heavy config to be instantiated - I would be passing random nonsense into the constructor just to get an instance.

Summary: Simply changing statics to non-statics seems wrong - shouldn't I also move the functions somewhere else? If so, where?

like image 970
Force Hero Avatar asked Feb 09 '23 16:02

Force Hero


1 Answers

The issue with static calls is the hard coupling to a specific other piece of code. Just wrapping that in a "dynamic" call doesn't make this any better:

$c = new CProject;
$c->findOne(); // Calls Project::findOne()

That's pretty darn pointless. The issue is not the syntax of -> vs. ::, the issue is that this particular code references a specific other class and that you cannot easily exchange this class for something else. You're building rigid, hardcoded dependencies between your classes/objects, which makes it hard to take them apart, which makes your code hard to test, and which makes it harder to adapt code to different situations.

The alternative is dependency injection:

function foo(Project $project) {
    $p = $project->findOne();
}

This function is not coupled to any one specific Project class, but to a class which simply offers an interface akin to Project. In fact, Project could even be simply an interface. Which specific class and method is getting called here then is decided somewhere completely different, like your dependency injection container; or simply the caller of this code.

This makes it a lot easier to take this code apart and put it back together in different ways, as necessary for the situation at hand. That's not to say it can't work and that you should never use static calls at all, but you really need to be aware of what cross-dependencies you're establishing with every hardcoded class name, and whether that may or may not cause a problem down the line. For even moderately complex and/or growing software projects, it will almost certainly cause friction in some form or another eventually.

See How Not To Kill Your Testability Using Statics for a longer in-depth article.

like image 92
deceze Avatar answered Feb 11 '23 18:02

deceze