0

To cut a long story short, assume I have a Register, a Provider and a Generator class.

In my Register, I have the addItem method that allows me to add items to a collection. The Provider's job is to access the Register's data and after some logic, provide items from its collection, basically we have the method, in the Provider:

function getItem( $handle )
{
    $items = $this->register->getItems();
    return $items[$handle]['name'];
}

...and there-in lies the issue, the ['name'], one line in another class' method dictates that the Register's addItem method must make sure that whatever is passed to it is (1) an array and (2) has the key name so that the function above will not have issues retrieving it.

How can I, in PHP, dictate implementation details like this?

Currently, to combat this, I just have a strong documentation which shows what object looks for what and so on, but how can I achieve this through code? I want the program to throw an error when somebody would be trying to pass an array without the key.


Yes, the objects & architecture are completely decoupled, proudly tested for SOLID and it's marvellous, yet I can't help but feel this is a very bad code smell: both me hard-coding like this and me requiring to have that implementation, in fact, I can 100% prove why hard-coding like this is bad:

Assume that I did depency injection right and my Provider requires a RegisterInterface as a dependency, in our case, the Register. Now, anyone that'd want to inject a different RegisterInterface could and it'd be valid, but - now they need to know, from some place else (not interfaces, nor code) that, in fact, the Provider requires the Register to build its data in a certain way, down to the array key level, anyonet hat would want to replace the Register, must also replace the Provider or at least make it work, when it should just simply work, the interface guarantees compatibility...or does it?

I think an interface in PHP, even with 7.0's return types is simply just a guider for composition but there's no interfaces to enforce certain implementation details.

3 Answers 3

2

Duck Typing

I'm not a PHP expert but in any programming language using attribute bags as objects, there always exist functions to ensure that a given attribute is defined, and how it is defined. Essentially all the basics you need to enforce Duck Typing.

Spaghetti Checks

This does leads to a distribution of such checks throughout the code base. Which is not just complex to manage but often results in code duplication.

This is obviously not a good state to be in and leads to the concept of a Contract.

Interface Contracts

Imagine a function (or object with some functions on it) which given an object can verify that all relevant attributes are defined, that they are of the correct types, and those types are correctly correlated. It can ensure that every function has the correct number of arguments, order, type, etc...

This is the same as verifying a house by looking at it and saying:

  • 1 door... check
  • n windows... check
  • on a street... check
  • it must be a house...

Protocol Contracts

We can go further and purposefully invoke "Readonly" methods to ensure that the object's state has not transitioned, or that other expectations are met. But this is getting awfully close to unit testing at run time.

The issue here is that these no-effect operations might still actually have an effect, the effect is simply not observable by the contract. Imagine for example a function that will throw an exception, this may be logged even though the contract simply discards the exception after verifying its expectations.

Kind of like walking up to the house and kicking a wall. If it isn't rotten or otherwise broken the foot reports that the pain is what was expected. But if it wasn't what you expected it to be then your foot is now stuck in the wall...

Offline Verification - Destructive Contracts

While not strictly about your runtime case - Contracts can be extended explicitly for test purposes.

This version actually needs a way to generate multiple instances of the object. Each being run through a series of scenarios to ensure that the higher level semantics implied by the contract are respected (eg: the item pushed onto the stack is the item popped off the stack).

There is no danger here, if the object misbehaves the test has failed by definition and nothing has happened in production.

It may be useful to supply these sorts of Protocol Contracts along with your framework. When the unexpected happens it is always useful to have a scenario laid out with exactly what should happen, and what didn't happen. The developer need only pass an example in to see what is wrong.

User Contracts

Sometimes you do not wish to dictate the entire object interface/protocol - but you still need to reasonably enforce expectations.

The User Contract to the rescue. This is a contract owned solely by this given function/object to describe how it wishes to interact with a given collaborator.

This approach is bottom up as it is not the collaborator dictating usage, but the using function/object itself.

A Protocol Lawyer in the Middle

  • Yes, they slow things down.
  • Yes, they get in the way.
  • Yes, they are more code.

They can enforce a protocol between a User and one or more collaborators.

In essence The Protocol Lawyer holds an understanding of the protocol that is to be followed. A simple example:

  • When you received one or more collaborators you would register them with the lawyer, and use whatever object the lawyer passes back.
  • When the user code makes a call, say ['name'], the lawyer checks its own idea of what is allowed, and verifies that this call is legal.
  • After verifying that the call is legal it dispatches the call to the desired collaborator.
  • The collaborator does its thing and ships back its response, which the lawyer dutifully verifies against its own ledger of acceptable responses.
  • Once the response has been validated the lawyer hands it back to the user.

If at any stage the lawyer detects anyone in the protocol having acted inappropriately they can decide what to do. An obvious strategy is to stop the interactions and notify some authority by throwing an exception, logging a message, or killing the process.

Other interventions are possible.

  • Correcting the output/request
  • Letting the transgression slide, but making a note of it.
  • Requesting some form of outside intervention.

It is even possible that there are several lawyers at play here, one introduced by each interested party, each verifying the interactions by their definition of the protocol going on.

This is perhaps not what you need, but it will ensure that the object behaves appropriately (or you know what happened and who to blame).

In Context

In this case you cannot possible ensure that every object complies with your expectations at runtime. However you can:

  • provide a Destructive/Protocol Contract as a testing/diagnostic tool for other developers
  • provide a lawyer to oversee that the protocol is executed correctly, or some sensible action is taken instead.
  • enforce a Destructive/Protocol Contract on known collaborators
  • Enforce either a full Interface Contract, or a User Contract on collaborators as they are passed in, or about to be used.
2
  • Ok, this makes sense and it's what I had in mind, but...code? :)
    – coolpasta
    Commented May 1, 2019 at 14:53
  • I'm not a PHP programmer... But the PHP manual has method_exists. There are other useful functions under related functions. As for Contracts the simplest is just function XYZContract(instance_to_check) { return (!method_exists(instance_to_check, "bob")); }. Specify as many checks as you want to describe the interface. As for more complex Contracts you may want to define a class with several functions under well known names for each form of verification. It might make sense to return a report object instead to detail the issues.
    – Kain0_0
    Commented May 2, 2019 at 0:27
2

If your program requires that every item has a name, then you should model the item as a class with a name property, instead of using an array. Then you can change the Register interface so that it is clear that it returns an Item (which has a name) and not just any random type.

class Item {
  public $name;
  // other properties
}

class Register {
  private $items;

  public function getItem($handle) : Item {
    return $this->items[$handle];
  }
}

class Provider {
  private $register;

  public function getItem($handle) {
    $item = $this->register->getItem($handle);
    return $item->name;
  }
}

Update: In the absence of language-supported return types (PHP 7), you'll have to enforce the "return type" manually at the call site. This is effectively the same thing, but since it is not part of the method signature, it's less obvious for implementors of Register (you'll just have to mention this requirement in the interface documentation).

class Provider {
  private $register;

  public function getItem($handle) {
    $item = $this->register->getItem($handle);
    if (!($item instanceof Item)) {
      throw new Exception("Register did not return an object of type Item");
    }
    return $item->name;
  }
}
2
  • This would be grand, but return types aren't allowed < PHP 7.0. Any other ideas?
    – coolpasta
    Commented May 1, 2019 at 14:51
  • @coolpasta: See updated answer.
    – casablanca
    Commented May 2, 2019 at 5:00
0

Maybe use some wrapper to enforce proper type

interface RegisterInterface
{  
    public function getItem();
}

class RegisterWrapper implements RegisterInterface
{
    private $register;    
    public function __construct(RegisterInterface $reg)
    {
        $this->register = $reg;
    }

    public function getItem()
    {
        $item = $this->register->getItem();
        if (! is_array($item)) // not necessary in PHP7 as you can declare array in interface
            throw new \Exception($this->register::class."::getItem() should return array");
        if (! array_key_exists('name', $item))
            throw new \Exception($this->register::class."::getItem() should return array with 'name' element");
        return $item;
    }
}

class Provider
{
    public function __construct(RegisterWrapper $register)
    {}
}

You can combine this with @casablanca answer to check instanceof Item, but this way if there are more classes using Register::getItem() then Provider, you didn't have to duplicate checking code.

And upgrade to PHP7 as soon as possible. Return types are enough reason.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.