I recently started using a package called prophecy
which is a mocking library for PHP.
I was intrigued by the way the library was designed and how it was able to return the same object but with a different interface.
This made me think about how I could use this in my own code.
I decided to create a simple example of how this could be done.
I wanted to create an object that could be modified and then returned as a readonly object.
I created two interfaces, HumanModifierInterface
and HumanBeingInterface
.
HumanModifierInterface
is used to modify the object and HumanBeingInterface
is used to present the object as readonly.
I then created a class called HumanBuilder
that implements both interfaces.
The HumanBuilder
class has a static method called initialize
that returns an instance of the class.
Enough talk.. Here is the code:
<?php
interface HumanModifierInterface
{
public function identifyAs(string $type);
/** Any [magic](https://www.php.net/manual/en/language.oop5.magic.php) methods will return the same interface */
public function __call(): HumanModifierInterface;
/** Returns the same object but presents an interface that makes it readonly */
public function reveal(): HumanBeingInterface;
}
interface HumanBeingInterface
{
public function getIdentity();
public function getName();
public function getAttribute();
}
class HumanBuilder implements HumanModifierInterface, HumanBeingInterface
{
private string $identity;
private array $attributes;
public function __construct()
{
$this->attributes = [];
}
public function initialize(): HumanModifierInterface
{
return new self;
}
public function identifyAs(string $type): HumanModifierInterface
{
$this->identity = $type;
return $this;
}
/**
* Any magic methods will return the same interface
* In this example, This method will be used to set attributes
*/
public function __call($name, $arguments): HumanModifierInterface
{
// If it's a call method that starts with 'set', then set the attribute
if (strpos($name, 'set') === 0) {
$attribute = lcfirst(substr($name, 3));
$this->attributes[$attribute] = $arguments[0];
}
return $this;
}
public function getAttribute(string $name)
{
if (strpos($name, 'get') === 0) {
$attribute = lcfirst(substr($name, 3));
return $this->attributes[$attribute] = $arguments[0];
}
return $this;
}
public function reveal(): HumanBeingInterface
{
return $this;
}
public function getIdentity()
{
return $this->identity;
}
}
$humanCreator = (new HumanBuilder)->initialize()
->identifyAs('Human') // Returns a HumanModifierInterface
->setName('John Doe') // Returns a HumanModifierInterface
->setAge(25) // Returns a HumanModifierInterface
->reveal(); // Returns a HumanBeingInterface (readonly)
echo $humanCreator->getAttribute('name'); // John Doe
echo $humanCreator->getAttribute('age'); // 25
echo $humanCreator->getIdentity(); // Human
So, let's say we had a class (or service), Evolution
that required an instance of HumanBeingInterface
but we wanted to modify the object before passing it to the service.
We could do something like this:
class Evolution
{
private HumanBeingInterface $human;
public function __construct(HumanBeingInterface $human)
{
$this->human = $human;
}
public function hasEvolved(): bool
{
return $this->human->getAttribute('age') > 25;
}
}
$humanCreator = (new HumanBuilder)->initialize()
->identifyAs('Human') // Returns a HumanModifierInterface
->setName('John Doe') // Returns a HumanModifierInterface
->setAge(20); // Returns a HumanModifierInterface
$evolution = new Evolution($humanCreator->reveal());
echo $evolution->hasEvolved(); // false
$humanCreator->setAge(26);
echo $evolution->hasEvolved(); // true
And that's where the magic happens
This is a simple example of how you can use interfaces to present a readonly object. There are many other uses for this pattern. This example was inspired by a package called prophecy which is a mocking library for PHP.
I highly recommend it for mocking objects in your tests. I hope this helps you in your projects.
If this made sense to you, lemme know in the comments