For my first article, I thought I’d share something I’ve been cooking up at my day job. My only hope is that someone, somewhere finds this useful.

Dependency injection is a great way to decouple components and make your application more flexible and testable. But for many web apps, a full-on dependency injection mechanism can add a lot of complexity and be more difficult to maintain than it is really worth. Another way to decouple components is to use a Service Locator. It is simpler than dependency injection and provides the same benefits. But there is a trade-off.

The main trade-off between the two schemes is that dependency injection, while being more complex, does not require the dependent object to hold a reference to the injector. It is completely ignorant of how its dependencies are filled. Using a service locator requires the dependent object to reference the locator to acquire its dependencies, and so is coupled to it. But, this is often a worthwhile trade-off.

The service locator presented here is essentially a generic Registry that makes sure the object it returns is of the correct type. The first thing you need to do to use the locator is tell it to bind an abstract class or interface to a concrete implementation. Let’s say we have a service called EmployeeService that fetches employee records from a database and is defined something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface iEmployeeService {
    public function GetAllEmployees();
    public function GetEmployeeById($id);
}
 
class EmployeeService implements iEmployeeService {
    public function GetAllEmployees() {
        // query all records from database
    }
 
    public function GetEmployeeById($id) {
        // query database by Id
    }
}

Now, let’s say we have two different clients for this service. One is local and runs within the same application as the service itself. For this client, we can bind iEmployeeService directly to EmployeeService in our client application.

1
Locator::Bind('iEmployeeService', 'EmployeeService');

And when the client needs to access the service, instead of instantiating it directly, we use the locator to retrieve the object. So instead of

1
$employeeService = new EmployeeService;

we write

1
$employeeService = Locator::GetInstance('iEmployeeService');

The locator looks up the iEmployeeService interface, creates an instance of the EmployeeService class that it is bound to and returns it to the client, who doesn’t care what the locator returns, as long as it implements iEmployeeService.

Now, we also have another client that is remote and communicates to our services through SOAP. We can then write a Proxy or Plugin class that implements the iEmployeeService interface but, instead of performing the action itself, sends a message to the remote service and returns the response.

1
2
3
4
5
6
7
8
9
class EmployeeServiceSOAPProxy implements iEmployeeService {
    public function GetAllEmployees() {
        // send SOAP request and return reply
    }
 
    public function GetEmployeeById($id) {
        // send SOAP request and return reply
    }
}

Our remote client code is the same as our local code, except the Locator binds iEmployeeService to a different class.

1
Locator::Bind('iEmployeeService', 'EmployeeServiceSOAPProxy');

And our client goes merrily along its way, not knowing the difference. Pretty simple, eh?

So what have we gained from all this? We have decoupled our client (aka Presentation Layer) from our services and that’s a Good Thing(TM). It means easier testing of our client because we can test it separately from the services it depends on. It also gives us greater flexibility in how our client and services interact: local, remote; to our client it doesn’t matter.

On the downside, we now have a reference to Locator everywhere in the client. But this one reference replaces many and if we decide to change the client application to use ShinyNewEmployeeService instead of plain old EmployeeService, there is a single place we must make that change. Sounds like a win to me.

Download Locator class