1
votes

This code is ZF2, but this feels like it might be a more general OOP question because its about some confusion I have with interfaces. This example is a little long winded, but I wanted to use all of my actually code to show everything that I'm doing.

So here is the code snippet that is the source of my confusion:

// References
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Adapter\DbTable as DbTableAuthAdapter;

// Class definition
public function getAuthService()
{
if (! $this->authservice) {
   $dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
   $dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter, 'user','email','password', 'MD5(?)');
   $authService = new AuthenticationService();
   $authService->setAdapter($dbTableAuthAdapter);
   $this->authservice = $authService;
}
return $this->authservice;
}

public function processAction()
//
$this->getAuthService()->getAdapter()
   ->setIdentity($this->request->getPost('email'))
   ->setCredential($this->request->getPost('password'));

$result = $this->getAuthService()->authenticate();
if ($result->isValid()) {
   $this->getAuthService()->getStorage()->write($this->request->getPost('email'));
   return $this->redirect()->toRoute(NULL , array( 
      'controller' => 'login', 
      'action' => 'confirm' 
   ));
}

In the first method we construct a new Zend\Authentication\Adapter\DbTable with some params, pass it to an instance of Zend\Authentication\AuthenticationService and then return that instance of AuthenticationService.

In the next method we call that method ($this->getAuthService()) to get an instance of AuthenticationService, call the AuthenticationService's getAdapter() method and then start calling Zend\Authentication\Adapter\DbTable method on the object returned.

Here is what confuses me. Look at the definition getAdater(). It doesn't actually return an instance of Zend\Authentication\Adapter\DbTable it only returns an interface: Zend\Authentication\Adapter\AdapterInterface and this interface doesn't define any Zend\Authentication\Adapter\DbTable methods.

So if getAdapter() is only returning an interface how is it I am able to call Zend\Authentication\Adapter\DbTable methods on the object returned?

Sorry if this question is confusing to read I'm confused about what's happening here on a pretty fundamental level so its difficult for me to be more lucid.

3

3 Answers

1
votes

The definitions says that it can return any AdapterInterface (meaning any class that implements it) and not the AdapterInterface directly. As it is extended by DbTableAuthAdapter, you are able to use DbTable methods.

2
votes

Zend\Authentication\AuthenticationService is an example of how to respect SOLID principles with a Design by Contract approach and (arguably) a Strategy Pattern implementation (wow, that's a mouthful...).

The architecture of the class is a bit confusing, but I'll try to eviscerate it as an exercise for my understanding of design patterns. (Please, by all means correct me wherever I'm wrong).

The first point of Dipendency Inversion Principle in this case is that AuthenticationService should not depend on the implementation details of any low level authentication procedure, it rather should depend on an abstraction, from whom it only needs an authenticate() method encapsulating the strategy behaviour.
(oddly enough, the service also provide the same method by itself and it's where the adapter is actually consumed, being a wrapper around the adapter's method, but let's ignore that :-p).

So the service doesn't need to care about how the authentication is done, it just needs an outcome.
Enters the AdapterInterface contract, put in place as a strategy interface to define the requirements of the behaviour, that being just returning a Result instance from authenticate().

AuthenticationService can thus be considered the strategy context.

All this is done primarily to allow plugging in and out different AdapterInterface implementations, without having to change AuthenticationService itself, hence the Open/Closed Principle is also respected.

Since the AdapterInterface is an abstract dependency and it is made accessible with a getter, the getter must return an abstraction as well, it would make no sense to return anything different!

Yes, the dependency could probably be completely obfuscated, but having accessors helps with the unit testing (you may have to access mocked dependencies to simulate different behaviours), plus the current implementation of the service, as you can see within your own example, requires the service consumer (the Controller, in your case) to manually set some context on the adapter before calling the authentication procedure (and this actually breaks the Law of Demeter, but that's another story).

Now, as far as I understand the word 'adapter' has been a bit misused, because to my eyes this has nothing to do with the Adapter Pattern, which is for interoperation between incompatible interfaces. The various 'adapters' should not cause any concern about any incompatibility whatsoever, they are actually compatible with each other by providing a common behaviour enforced by their contract but encapsulated within themselves.

To lay aside any criticism, it may be worth noticing that the whole Zend\Authentication component is basically a port of the old ZF1 Zend_Auth.

1
votes

PHP is a loosely typed language, which means that you can call any methods on any object, no matter what type hints there are. If the method ultimately does not exist you'll get a runtime error. On the other hand, even in type safe languages functions can return any object that is a subtype of the documented type. This is a special case of the Liskov substitution principle.