In a theoretical database access class, I found that there are quite a few helper functions that I use in the class, which have nothing to do the class's instance (and others, that could be manipulated to be unrelated to the class's instance using dependency injection).
For example, I have a function that gets a string between two other strings in a variable. I've been thinking of moving that to a String_Helper class, or something of the sort. This function has already been made static.
Also, I have a function that queries a database, query($sql)
. The connection details are provided by the instance, but I've been considering making it static, and using query($sql, $connection)
. Developers would then be able to call it statically and not need to instantiate the database class at all.
For me, the questions are:
Is it worth it to do something like this? Functions like the query function make me wonder if this is not just me trying to make everything as static as possible, without any real need to. Under what circumstances would you consider this useful?
I know static functions are harder to test, but if I make sure that their code is completely dependency free (or uses dependency injection where necessary), then they're just as easy to test as everything else, surely?
It isn't a concern at the moment, but if, in the future, I decided to extend the classes with the static functions, it would be impossible for me to make the current code use my extended functions. I've thought of Singletons, but the same problem arises: the code would be calling Singleton_Class::getInstance()
, and not My_Extended_Singleton_Class::getInstance()
. Dependency Injection seems to be the only way to solve this issue, but it might lead to a clunkier API, as every dependency has to be given to an object on __construct()
.
I have a container class, which holds certain pieces of information statically so that they can be accessed anywhere in the script (global scope). If I can't use static functions or singletons, a class that contained instances of different variables would be great. One could use for example Container::$objects['MyClass'] = $MyClass_object;
, and then the rest of the code could just access Container::$objects['MyClass']
. If I extended the MyClass class, I could use Container::$objects['MyClass'] = $MyExtendedClass_object;
, and the code that used Container::$objects['MyClass']
would use MyExtendedClass, rather than MyClass. This is by far the best way to do it, in my opinion, but I'd like to know what you think about it.
Ok, let me answer these one by one...
1. Is it worth doing something like this
Yes and no. Splitting out the helper functions into their own classes is a good idea. It keeps the "scope" of each of the classes rigidly defined, and you don't get creap. However, don't make a method static just because you can. The query method is there to make your life easier by managing the connection, so why would you want to lose that benefit?
2. They are harder to test
They are not harder to test. Static methods that depend on state are harder to test (that access static member variables or global variables). But static methods in general are just as easy to test as instance methods (in fact, they can be easier since you don't need to worry about instantiation).
3. Extending the classes
This is a valid concern. If you put String_Helper::foo()
in the class itself, you'll run into issues. But an option would be to set the name of the string helper as a class variable. So you could then do {$this->stringHelper}::foo()
(note, PHP 5.3 only). That way to override the class, all you need to do is change the string helper class in that instance. The Lithium
framework does this a lot...
4. Global Registry
I would stay away from this. You're basically just making every class a singleton without enforcing it. Testing will be a nightmare since you're now dependent on global scope. Instead, I'd create a registry object and pass it to classes via the constructor (Dependency Injection). You still accomplish the same thing since you have a store for the objects/classes, but you're no longer dependent on a global scope. This makes testing much easier.
In general
When you're looking at doing things like this, I like to stop when I hit questions like this. Stop and sit down and think *What actual problem am I trying to solve?". Enumerate the problem explicitly. Then pull our your supposed solutions and see if they actually solve them. If they do, then think about the future and if those solutions are really maintainable in the long run (Both from a bug fix standpoint, and with respect to feature additions). Only if you're happy with both of those answers should you even consider doing it. Oh, and also remember to keep it simple. Programming is not about making the most complex, most clever or most amazing solution. It's about making the simplest solution that solves the problem...
I hope that helps...
Good Luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With