Skip to content

Instantly share code, notes, and snippets.

@mbijon
Created January 13, 2013 08:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbijon/fc7a656f97cbcca1ec1d to your computer and use it in GitHub Desktop.
Save mbijon/fc7a656f97cbcca1ec1d to your computer and use it in GitHub Desktop.
Body text of a WP.com-generated post notice with odd formatting from site/plugin formatting codes. Post is from Eric Mann's site and this is being sent to him to potentially fix plugins or talk to WP.com team.
Making Singletons Safe in PHP
by Eric
Last time, I argued in favor of the Singleton pattern in WordPress.  Singletons make sense in WordPress specifically for several reasons:
They live in the global scope without using the already abused/overused [cci]global[/cci] keyword
As a distributed application maintained by several hundred developers, they prevent problems that likely arise from others misusing your code
But one of the goals of object-oriented software development is to make your code reusable.  The Singleton pattern as I explained it last time isn't really applicable outside of WordPress.  Typically, Singletons are a really bad idea.  But rather than get hung up on the term and settle for the knee-jerk "it's bad, don't use it" conclusion, let's take a deeper look.  There's a reason Singletons exist; they solve a specific problem.  So rather than reject them out of hand, let's make a minor change to make them safe for general PHP use.
There were two major arguments against Singletons throughout the comments, Twitter posts, Y Combinator thread, and private emails that followed my last post. First, that Singletons introduce hidden dependencies in your code.  Second, that Singletons are nearly impossible to unit test.  Both of these arguments are valid, but I have counters to each.
Hidden Dependencies
Any parameters a function accepts are visible dependencies. But if the function requires something else to operate - i.e. an open database connection - that is referenced through a global variable rather than a parameter, that dependency is considered hidden. There is no way for a third-party to know about that functional dependency without actually viewing the implementation of the function. Hidden dependencies make it extremely difficult to code against an API because the API isn't the whole story.
As an application grows, it's easier and easier to take shortcuts to get things done.  WordPress, for example, [cci]global[/cci]izes many of its internal variables.  The entire post loop is set up specifically to abuse the fact that the [cci]$post[/cci] object is global:
if ( have_posts() ) : while ( have_posts() ) :
the_post(); // Populates global $post object
// Filter and echo $post->post_title from the global $post
the_title();
// Filter and echos $post->post_content from the global $post
the_content();
endwhile; endif;
To get around this hidden dependency, you inject it.  Rather than referencing the global post object inside the function (a hidden dependency that you can't easily override at runtime), you pass a post object when you call the function.  This post object can then be substituted with another at runtime and the code is none the wiser.
// So instead of this function
function do_something_to_post() {
global $post;
$post->post_content = apply_filters( 'modify_content', $post->post_content );
return $post;
}
// You'd use this function and pass $post with the call.
function do_something_to_post( $post ) {
$post->post_content = apply_filters( 'modify_content', $post->post_content );
return $post;
}
Your business logic shouldn't ever have to know where the post object comes from, just what it looks like.  Abstract that functionality away and you can provide any object you want - a post pulled from the database, one parsed from a document in the file system, or one hard-coded in a unit testing framework.
In Singletons
When developers use Singletons, they're often tempted to reference [cci]Singleton::get_instance()[/cci] directly (which is akin to invoking a global variable). Instead, code should accept a parameter and expect it to have a particular signature.  In strictly typed languages like C and C#, you'd do this by specifying an interface the object is required to implement.
Dynamically typed languages like PHP don't require that[ref]Omitting type references in PHP is often considered "duck typing." Basically, "if it walks like a duck and talks like a duck, it's a duck." So if the object passed in implements the methods you plan to use, it might as well be of the type you expected ... even if it's something else. A function can accept an array, but I can just as easily pass it an object that implements [cci]ArrayAccess[/cci] and the function doesn't know the difference.[/ref], but it's a good idea to get in the habit anyway.  It makes your intention clear to other developers and makes your code - and its dependencies - self-documenting. Instead of invoking our Singleton object inside the function, we pass in an object that implements the same interface as our Singleton:
function save_data( IDatabase $db_connection ) {
// The IDatabase interface specifies a persist() method. We don't
// care what the object that gets passed in is, so long as it
// conforms to the interface we expect to use.
$db_connection->persist();
}
$connection = DB::get_instance();
save_data( $connection );
The [cci]IDatabase[/cci] flag inside the function definition is what's called Type Hinting in PHP. It forces the passed parameter to be of the specified type - or for classes/interfaces to be a child or implementation of that type. It's not strictly required for dependency injection, but placing the type in the function call makes your intentions in the code crystal clear to the next developer who reads it.
Unit Testing
Every unit test should start from a clean slate.  Ideally, unit test should run independent of the database and filesystem.  This is actually my biggest problem with WordPress - since so many of its DB calls are hard-coded in the application, the unit test suite requires a database in order to run.  Bad form.
The problem with Singletons as I explained them in my last article is a global state - once they're instantiated, they stick around.  Anything you do to a Singleton in one test is persisted and visible in other tests.  This global state makes testing a mess, and my (lazy) solution was to introduce a [cci]::reset()[/cci] method that flushed the stored instance between tests.
There's a better way.
First, define the interface your Singleton object will implement. This isn't a strict requirement, but it's easy for other developers to look at an interface and grok the API you're exposing.
Second, define an abstract class that implements this interface. Don't actually include any abstract functionality (unless you really want to). The point here is to have the business logic of your Singleton encapsulated in a way that can't be instantiated directly.
Finally, define a Singleton class that extends the abstract business logic class. The Singleton wraps things up in a nice, can-only-be-instantiated-once wrapper.
interface IDemo {
function write( $file, $message );
// These methods will be used to demonstrate state
function increment( $step );
function get_counter();
}
abstract class Abstract_Demo implements IDemo {
protected $counter = 0;
public function write( $file, $message ) {
$fp = fopen( $file, 'a' );
fwrite( $fp, $message . "\r\n" );
fclose( $fp );
}
public function increment( $step ) {
$this->counter += $step;
}
public function get_counter() {
return $this->counter;
}
}
final class Singleton_Demo extends Abstract_Demo {
private static $instance = null;
private function __construct() {}
public static function get_instance() {
if ( null == self::$instance ) {
self::$instance = new self;
}
return self::$instance;
}
}
$logger = Singleton_Demo::get_instance();
$logger->write( 'file.txt', 'This writes out to a file.' );
The incredibly basic example above introduces an interface to document the signature of our class, an abstract class to implement the business logic, and a final class to instantiate that logic.  As an abstract class cannot be directly instantiated, there's no danger of anyone doing so without going through our Singleton.  In our live application, the Singleton prevents any other developers from accidentally making more than one copy of our class.
When it comes time to test, though, the unit test project can extend the class the same way the Singleton does, but with a public constructor.  Now, within our unit test suite, we can create a new instance of our class for every single test.
class Concrete_Demo extends Abstract_Demo {
public function __construct() {}
}
$logger = new Test_Demo();
$logger->write( 'file.txt', 'This also writes out.' );
Since our multiple-instance class implements the same interface as our Singleton, we can substitute it for our Singleton in whatever methods rely upon it.
Testing the business logic used by our Singleton is also fairly straight-forward using this concrete class. Below is an example set of 4 unit tests. The first two illustrate the conflict presented by testing Singletons - the state set up by the first test bleeds over into and corrupts the second. The later two illustrate using a concrete extension of our abstract class to allow multiple-instantiation within our test suite.
class Demo_Test extends PHPUnit_Framework_TestCase {
public function testSingletonDemo() {
$singleton = Singleton_Demo::get_instance();
$this->assertEquals( 0, $singleton->get_counter() );
$singleton->increment( 5 );
$this->assertEquals( 5, $singleton->get_counter() );
}
public function testSingletonDemoState() {
$singleton = Singleton_Demo::get_instance();
// A new test should start with a new state. But since we
// manipulated our singleton in the last test, it already
// has an internal state. This test will FAIL if run after
// the preceding test. This illustrates why most developers
// hate Singletons.
$this->assertEquals( 0, $singleton->get_counter() );
}
public function testConcreteDemo() {
$obj = new Concrete_Demo;
$this->assertEquals( 0, $obj->get_counter() );
$obj->increment( 5 );
$this->assertEquals( 5, $obj->get_counter() );
}
public function testConcreteDemoState() {
$obj = new Concrete_Demo;
// We're testing a new instance, so the internal counter
// is once again set to 0.
$this->assertEquals( 0, $obj->get_counter() );
}
}
// This class is included ONLY in the test suite. Not the live
// application. That way it's never accidentally used.
class Concrete_Demo extends Abstract_Demo {
public function __construct() {}
}
Conclusion
The point of using a Singleton is to:
Ensure only one instance of your class ever exists
Provide a global entry point or reference to that instance of the class
Yes, most people (including myself) disdain global variables.  But we use them all over the place.  Consider what a PHP web application would look like without [cci]$_GET[/cci] or [cci]$_COOKIE[/cci].  These are global variables that are baked in to the language, yet they slip our minds whenever we start the "all global variables are evil and developers who use them are too naive to know better" arguments.
Consider also the static [cci]Request[/cci] and [cci]Response[/cci] classes in C#.  They serve a similar purpose - only one instance of each exists, and they provide global entry points/references for the application.  You can create and use your own versions, but only through the behind-the-scenes [cci]HttpRequestBase[/cci] and [cci]HttpResponseBase[/cci] classes, which are both abstract serve a similar purpose as my abstract class above.
People hate Singletons not because Singletons are inherently bad, but because so many developers have used them too often and in improper scenarios.  If you take care in developing your application, you can use Singletons safely without introducing hidden dependencies and while still maintaining a properly unit-testable application.
Eric | January 7, 2013 at 12:00 pm | Tags: Class, Dependency Injection, Duck Typing, Global Variable, Method, Object-oriented Programming, Php, singleton, Singleton Pattern, Software Design Patterns, Unit Test, WordPress, Y Combinator | Categories: Technology | URL: http://wp.me/pKoof-1qg
Comment    See all comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment