full inheritance behaviour with Decorator in php - php

I'm not really used to design pattern generally, and I never used Decorator. I want an object which can have different behaviour according to the context. These behaviours are defined in different classes. I guess Decorator does the trick. But I need that each decorator can access to the same properties, and call children methods first, like with inheritance. So here what I've done:
abstract class Component{
/**
* Used to access last chain Decorator
*
* #var Decorator
*/
protected $this;
protected $prop1;//These properies have to be accessed in any decorators
protected $prop2;
protected $prop3;
//this method is used to share properties with the childrens
public function getAttributesReferencesArray() {
$attributes=[];
foreach($this as $attr=>&$val)
$attributes[$attr]=&$val;
return $attributes;
}
}
class Foo extends Component{
public function __construct() {
$this->prop1="initialized";
//...
}
public function method1() {//this method can be "overrided" and called here
//...
}
public function method2() {//this method call the overrided or not method1
//...
$this->this->method1();
//...
}
}
abstract class Decorator extends Component{
/**
* Used to access parent component
*
* #var Component
*/
protected $parent;
public function __construct(Component $parent) {
$attributes=$parent->getAttributesReferencesArray();
foreach($attributes as $attr=>&$val)
$this->{$attr}=&$val;
$this->parent=$parent;
$this->this=$this;
}
public function __call($method, $args) {
if(!$this->parent instanceof Decorator &&
!method_exists($this->parent, $method))
throw new Exception("Undefined method $method attempt.");
return call_user_func_array(array($this->parent, $method), $args);
}
}
class Bar extends Decorator{
//this method call the component method (I guess Decorator classical way)
public function method1(){
//...
$this->parent->method1();
$this->prop2="set in Bar";
}
}
class Baz extends Decorator{
public function method2(){//this method call the overrided or not method1
//...
$this->this->method1();
//...
}
}
Now we can "construct" the "inheritance" according to the context:
//...
$obj=new Foo();
if($context->useBar())
$obj=new Bar($obj);
if($context->somethingElse())
$obj=new Baz($obj);
and run the object with abstraction of behaviour:
$obj->method1();
//...
It does what I want, but:
there isn't anymore encapsulation
$this->parent is ugly
$this->this is ugly
What do you think about that?
How can I access decorator ("children") method another way
How can I share properties like if they where protected in an inherited context
Is it a bad usage of Decorator?
Is there some more elegant pattern that does the trick
parent and this attributes are a kind of reinventing the wheel isn't it?
A real world example: the coffee machine
abstract class CoffeeFactory{// Component
/**
* Used to access last chain Decorator
*
* #var Decorator
*/
protected $this;
/**
* Used to access user choices
*
* #var CoffeeMachine
*/
protected $coffeeMachine;
protected $water;//the water quantity in cl
protected $coffeePowder;
protected $isSpoon=FALSE;
protected $cup=[];
//this method is used to share properties with the childrens
public function getAttributesReferencesArray() {
$attributes=[];
foreach($this as $attr=>&$val)
$attributes[$attr]=&$val;
return $attributes;
}
}
class SimpleCoffeeFactory extends CoffeeFactory{//Foo
public function __construct(CoffeeMachine $coffeeMachine) {
$this->coffeeMachine=$coffeeMachine;
$this->water=$coffeeMachine->isEspresso()?10:20;
$this->coffeePowder=$coffeeMachine->isDouble()?2:1;
$this->water-=$this->coffeePowder;
$this->this=$this;
}
private function addCoffeePowder(){
$this->cup["coffeePowder"]=$this->coffeePowder;
}
private function addSpoon(){
if($this->isSpoon)
$this->cup["spoon"]=1;
}
public function isWaterHot($boilingWater){
return $this->getWaterTemperature($boilingWater)>90;
}
private function addWater() {
$boilingWater=$this->getWaterForBoiling($this->water);
while(!$this->this->isWaterHot($boilingWater))
$this->boilWater($boilingWater);
$this->cup["water"]=$boilingWater;
}
public function prepare() {
$this->addCoffeePowder();
$this->addSpoon();
}
public function getCup() {
$this->this->prepare();
$this->addWater();
return $this->cup;
}
}
abstract class Decorator extends CoffeeFactory{
/**
* Used to access parent component
*
* #var Component
*/
protected $parent;
public function __construct(Component $parent) {
$attributes=$parent->getAttributesReferencesArray();
foreach($attributes as $attr=>&$val)
$this->{$attr}=&$val;
$this->parent=$parent;
$this->this=$this;
}
public function __call($method, $args) {
if(!$this->parent instanceof Decorator &&
!method_exists($this->parent, $method))
throw new Exception("Undefined method $method attempt.");
return call_user_func_array(array($this->parent, $method), $args);
}
}
class SugarCoffeeFactory extends Decorator{
protected $sugar;
public function __construct(Component $parent) {
parent::__construct($parent);
$this->sugar=$this->coffeeMachine->howMuchSugar();
$this->water-=$this->sugar;
$this->isSpoon=TRUE;
}
public function prepare() {
$this->cup['sugar']=$this->sugar;
$this->parent->prepare();
}
}
class MilkCoffeeFactory extends Decorator{
protected $milk;
public function __construct(Component $parent) {
parent::__construct($parent);
$this->milk=$this->coffeeMachine->howMuchMilk();
$this->water-=$this->milk;
}
public function prepare() {
$this->parent->prepare();
$this->cup['milk']=$this->milk;
}
public function isWaterHot($boilingWater){
//The milk is added cold, so the more milk we have, the hotter water have to be.
return $this->getWaterTemperature($boilingWater)>90+$this->milk;
}
}
//Now we can "construct" the "inheritance" according to the coffee machine:
//...
$coffeeFactory=new SimpleCoffeeFactory($coffeeMachine);
if($coffeeMachine->wantSugar())
$coffeeFactory=new SugarCoffeeFactory($coffeeFactory);
if($coffeeMachine->wantMilk())
$coffeeFactory=new MilkCoffeeFactory($coffeeFactory);
//and get our cup with abstraction of behaviour:
$cupOfCoffee=$coffeeFactory->getCup();
//...

The Decorator pattern is not made to do internal changes in the base class (you call this one parent). What you are doing is bad usage of this pattern. The Decorators should only change the output of the functions instead of playing with variables.
One solution is to define getters and setters for your protected variables and to call them from the Decorator.
Another solution is what I prefer personally and that is splitting the behaviour which is dependent on the context and the base class:
class Component {
protected $behaviour;
function __construct() {
$this->behaviour = new StandardBehaviour();
}
function method1() {
$this->prop2 = $this->behaviour->getProp2Value();
}
function setBehaviour(Behaviour $behaviour) {
$this->behaviour = $behaviour;
}
}
abstract class Behaviour {
abstract function getProp2Value();
}
class StandardBehaviour extends Behaviour {
function getProp2Value() {
return 'set by bahaviour ';
}
}
class BarBehaviour extends StandardBehaviour {
function getProp2Value() {
return parent::getProp2Value().' Bar';
}
}
class BazBehaviour extends BarBehaviour {
function getProp2Value() {
return 'set in Baz';
}
}
Now we can use it like this:
$obj=new Foo();
if($context->useBar())
$obj->setBehaviour(new BarBehaviour);
if($context->somethingElse())
$obj->setBehaviour(new BazBehaviour);
I hope this answers your question!
EDIT after comments
I see your point that the behaviours replace each other instead of chaining. This is indeed a typical problem for the decorator class. However you really shouldn't change the original class in a decorator class. A decorator class only 'decorates' output of the original. Below a typical example of how the decorator pattern would be used in the real world scenario you mentioned:
interface ICoffeeFactory {
public function produceCoffee();
}
class SimpleCoffeeFactory implements ICoffeeFactory{
protected $water;//the water quantity in cl
public function __construct() {
$this->water=20;
}
protected function addCoffeePowder($cup){
$cup["coffeePowder"]=1;
return $cup;
}
protected function addWater($cup) {
$cup["water"]=$this->water;
return $cup;
}
public function produceCoffee() {
$cup = array();
$cup = $this->addCoffeePowder($cup);
$cup = $this->addSpoon($cup);
$cup = $this->addWater($cup);
return $cup;
}
}
class EspressoCoffeeFactory extends SimpleCoffeeFactory {
public function __construct() {
$this->water=5;
}
protected function addCoffeePowder($cup){
$cup["coffeePowder"]=3;
return $cup;
}
}
abstract class Decorator implements ICoffeeFactory {
function __construct(ICoffeeFactory $machine)
}
class SugarCoffee extends Decorator{
public function produceCoffee() {
$cup = $this->factory->produceCoffee();
if ($cup['water'] > 0)
$cup['water'] -= 1;
$cup['spoon'] = TRUE;
$cup['sugar'] += 1;
return $cup;
}
}
class MilkCoffee extends Decorator{
protected function produceCoffee() {
$cup = $this->factory->produceCoffee();
$cup['milk'] = 5;
return $cup;
}
}
//Now we can "construct" the "inheritance" according to the coffee machine:
//...
$coffee=new SimpleCoffeeFactory();
if($coffeeMachine->wantSugar())
$coffee=new SugarCoffee($coffee);
if($coffeeMachine->wantMilk())
$coffee=new MilkCoffee($coffee);
//and get our cup with abstraction of behaviour:
$cupOfCoffee=$coffee->produceCoffee();
//...

Still a bit incomplete but it does basically everything:
abstract Component class that everything extends.
abstract Decorator class that modifies classes extending Component.
It's a lot of code so here's the pastebin link:
[Old] http://pastebin.com/mz4WKEzD
[New] http://pastebin.com/i7xpYuLe
Components
Can extend one another
Can modify / add / remove properties
Can share properties with decorators
Decorators
Can attatch functions to components
Can modify / add / remove component properties
Example Input
$Sugar = 1;
$DoubleSugar = 1;
$Cofee = new SimpleCofee();
$Tea = new SimpleTea();
$Cofee->Produce();
$Tea->Produce();
print "\n============\n\n";
if($Sugar)
{
new SugarCube($Cofee);
$Cofee->Produce();
new SugarCube($Cofee);
$Cofee->Produce();
}
if($DoubleSugar)
{
new SugarCube($Tea);
$Tea->Produce();
new SugarCube($Tea);
$Tea->Produce();
}
OutPut
Making coffee....
Adding Water: 150
Making cofee: array (
'cofeee' => 25,
)
Making tea....
Adding Water: 150
Making tea: array (
'tea' => 25,
)
============
Making coffee....
Adding sugar: 1
Adding Water: 140
Making cofee: array (
'cofeee' => 25,
'Spoon' => 1,
)
Making coffee....
Adding sugar: 1
Adding sugar: 1
Adding Water: 120
Making cofee: array (
'cofeee' => 25,
'Spoon' => 1,
)
Making tea....
Adding sugar: 2
Adding Water: 130
Making tea: array (
'tea' => 25,
'Spoon' => 1,
)
Making tea....
Adding sugar: 2
Adding sugar: 2
Adding Water: 90
Making tea: array (
'tea' => 25,
'Spoon' => 1,
)
UPDATE
That was crazy but now children can overload parent functions. On top of that you can now use array interface $this['var'] to access shared properties. Hashes will be added automatically and transparently.
The only downside is that parents have to allow functions to be overloaded.
NEW Output
Making Cofee....
Adding Water: 150
Making Cofee: array (
'cofeee' => 25,
)
Making Tea....
Adding Water: 150
Making Tea: array (
'tea' => 25,
)
============
Making Cofee....
Adding sugar: 1
Adding Water: 140
Making Cofee: array (
'cofeee' => 25,
'Spoon' => 1,
)
Making Cofee....
Adding sugar: 1
Adding sugar: 1
Adding Water: 120
Making Cofee: array (
'cofeee' => 25,
'Spoon' => 1,
)
I have take over Produce!
But I'm a nice guy so I'll call my parent
Making Tea....
Adding sugar: 2
Adding Water: 130
Making Tea: array (
'tea' => 25,
'Spoon' => 1,
)
I have take over Produce!
But I'm a nice guy so I'll call my parent
Making Tea....
Adding sugar: 2
Adding sugar: 2
Adding Water: 90
Making Tea: array (
'tea' => 25,
'Spoon' => 1,
)
============
DoubleSugarCube::SuperChain(array (
0 => 'test',
))
SugarCube::SuperChain(array (
0 => 'DoubleSugarCube',
))
SimpleTea::SuperChain(array (
0 => 'SugarCube',
))
SimpleCofee::SuperChain(array (
0 => 'SimpleTea',
))
UPDATE
This is my final draft. I can't keep changing my solution bit by bit. If there is something wrong state it all in a list.
Removed callparent and put all functionality of it into parent::function
Children have access to parents properties.
Children can overload parent functions.
Overloading will start at base class all the way up till abstract class Decorator class. Afterwards the properties / methods are obtained from parent passed to constructor.
You said you liked your method of property sharing. So I haven't bothered to answer that.
I hope you'll accept the answer now. If not then I look forward to yours. I hope when you sort everything out you will share it with the rest of us.
Cheers

there is a solution to fit the coffeemachine problem
abstract class Coffee {
protected $cup = array();
public function getCup() {
return $this->cup;
}
}
class SimpleCoffee extends Coffee {
public function __construct() {
$this->cup['coffeePowder'] = 1;
$this->cup['water'] = 20;
$this->cup['spoon'] = FALSE;
}
}
abstract class Decorator extends Coffee {
private $_handler = null;
public function __construct($handler) {
$this->_handler = $handler;
$this->cup = $handler->cup;
}
}
class SugarCoffee extends Decorator {
public function __construct($handler) {
parent::__construct($handler);
$this->cup['water'] -= 1;
$this->cup['sugar'] = 1;
$this->cup['spoon'] = TRUE;
}
}
class MilkCoffee extends Decorator{
public function __construct($handler) {
parent::__construct($handler);
$this->cup['water'] -= 5;
$this->cup['milk'] = 5;
}
}
$wantSugar = TRUE;
$wantMilk = TRUE;
$coffee = new SimpleCoffee();
if($wantSugar)
$coffee = new SugarCoffee($coffee);
if($wantMilk)
$coffee = new MilkCoffee($coffee);
$cupOfCoffee = $coffee->getCup();
var_dump($cupOfCoffee);
and there is another real world example, I hope it can help you:
abstract class MessageBoardHandler {
public function __construct(){}
abstract public function filter($msg);
}
class MessageBoard extends MessageBoardHandler {
public function filter($msg) {
return "added in messageBoard|".$msg;
}
}
class MessageBoardDecorator extends MessageBoardHandler {
private $_handler = null;
public function __construct($handler) {
parent::__construct();
$this->_handler = $handler;
}
public function filter($msg) {
return $this->_handler->filter($msg);
}
}
class HtmlFilter extends MessageBoardDecorator {
public function __construct($handler) {
parent::__construct($handler);
}
public function filter($msg) {
return "added in html filter|".parent::filter($msg);
}
}
class SensitiveFilter extends MessageBoardDecorator {
public function __construct($handler) {
parent::__construct($handler);
}
public function filter($msg) {
return "added in sensitive filter|".parent::filter($msg);
}
}
$html = TRUE;
$sencitive = TRUE;
$obj = new MessageBoard();
if($html) {
$obj = new SensitiveFilter($obj);
}
if($sencitive) {
$obj = new HtmlFilter($obj);
}
echo $obj->filter("message");

Related

Php extend method while overriding it

Is it possible to extend a parent class method while overriding it ? For example:
class Foo {
public function edit() {
$item = [1,2];
return compact($item);
}
}
class Bar extends Foo {
public function edit() {
// !!! Here, is there any way I could import $item from parent class Foo?
$item2 = [3,4]; //Here, I added (extended the method with) some more variables
return compact($item, $item2); // Here I override the return of the parent method.
}
}
The issue is that I cannot edit the Foo class in any way as it is a vendor package.
I don't want to edit the vendor methods I need to extend them (add something more to their return function)
If you used array_merge() instead it will probably show the results better...
class Foo {
public function edit() {
$item = [1,2];
return $item;
}
}
class Bar extends Foo {
public function edit() {
$item = parent::edit(); // Call parent method and store returned value
$item2 = [3,4]; //Here, I added (extended the method with) some more variables
return array_merge($item, $item2); // Here I override the return of the parent method.
}
}
$a = new Bar();
print_r($a->edit());
This will output -
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
So the call to parent::edit() will return the array from the parent class and this will be added to the array from the second class function.
Update:
I can't test this, but hopefully this will give you what your after...
class Foo {
protected function getData() {
return [1,2];
}
public function edit() {
return return view('view-file', compact($this->getData()));
}
}
class Bar extends Foo {
protected function getData() {
$item = parent::edit();
$item2 = [3,4];
return array_merge($item, $item2);
}
}
This means that the only time you create the view is in the base class, all you do is add the extra information in the derived class.

PHP Serialization of Class that references itself

To give some context; this process was working prior to adding in the Serializable interface. The TestAction2 is not keeping a reference to itself after unserializing; and I have tried adding the Serializable to the Action class and telling it to serialize the $parent but still no difference. It is correctly unserializing the $method field though.
Class Action serializes the reference in $parent field when the serialization is being done on the class that is referencing it (class B), but not when it references itself. Something with serializing happening on Class A or B but not the Action class itself.
class A implements Serializable {
private static $label;
public function serialize() {
return serialize(self::$label);
}
public function unserialize($serialized) {
self::$label = unserialize($serialized);
}
}
class B extends A {
private $actions;
public function serialize() {
return serialize([
'actions' => $this->actions,
'parent' => parent::serialize()]);
}
public function unserialize($serialized) {
$data = unserialize($serialized);
$this->actions = $data['actions'];
parent::unserialize($data['parent']);
}
public function addAction($anAction) {
$this->actions[] = $anAction;
}
public function process() {
$this->addAction(new TestAction1($this, 'test1')); // WORKS FINE
$this->addAction(new TestAction2($this, 'test2')); // WORKS NOT!
}
}
class Action {
private $parent;
private $method;
public function __construct($cParent, $sMethod) {
$this->parent = $cParent;
}
}
class TestAction1 extends Action {
public function __construct($cParent, $sMethod) {
parent::__construct($cParent, $sMethod);
}
}
class TestAction2 extends Action {
private $maybeNeedLater;
public function __construct($cParent, $sMethod) {
$this->maybeNeedLater = $cParent;
parent::__construct($this, $sMethod); // pass $this instead
}
}
$testThis = new B();
$testThis->process();
$serialized = serialize($testThis);
$testThis = unserialize($serialized);
The Action class TestAction2 will have a null $parent field in the $actions field of $testThis
Your two different Action classes a bit of a red herring, because they are not the problem. Test2 just happens to use a reference other than $testThis; and many other reference would be wrong as well.
The real problem is that the order of serialization in your A/B classes is different from the order of unserialization. During serialization PHP encodes references with r (or R) and a number (r and R are different in many ways but the difference would just distract from the counting issue). In your example $needThis can be referenced with r:1;, A::label with r:2;, the actions array with r:3;, and so on. The $this reference to the TestAction2 object is r:8; (you can see this when you echo $serialized.)
When you call unserialize inside your unserialize function, there is no A::label (yet). Because of that all numbers greater than 1 will be off by one. r:8; now points to whatever is in maybeNeedLater.
Now things get a little complicated, because PHP normally does not create such strings. In PHP5 it seems to create some kind of error that is printed as NULL. In PHP7 parent will actually be a reference to $needThis.
Luckily there are a few easy ways to solve this:
Option 1:
Use a different format to encode your parent class:
class A implements Serializable {
private static $label;
public function serialize() {
return json_encode(self::$label);
}
public function unserialize($serialized) {
self::$label = json_decode($serialized);
}
}
Option 2:
Use double serialization in your child class and make sure the order fits:
class B extends A {
private $actions;
public function serialize() {
return json_encode([
'actions' => serialize($this->actions),
'parent' => parent::serialize()]);
}
public function unserialize($serialized) {
$data = json_decode($serialized, true);
$this->actions = unserialize($data['actions']);
parent::unserialize($data['parent']);
}
...

How to build static property that is aware of inheritance chain in PHP without boilerplate

I have been struggling to implement an OOP concept in PHP, and the solution is eluding me. I have an abstract base class whose constructor calls a series of setters in a dispatch table based on an associative array of argument data, as follows:
abstract class Base {
protected static $_SETTER_DISPATCH_MODEL = array();
public function __construct(array $data) {
foreach (static::$_SETTER_DISPATCH_MODEL as $dataKey => $method) {
if (array_key_exists($dataKey, $data)) {
if (method_exists($this, $method)) {
$this->$method($data[$dataKey]);
}
else {
// Handle error
}
}
}
}
}
What I would like to do is be able to define a method in my base class that builds the setter dispatch table by merging the subclass' dispatch table with that of each of its parents, so I would be able to do this:
class Foo extends Base {
protected static $_SETTER_DISPATCH_MODEL = array('foo' => 'setFoo');
}
class Bar extends Foo {
protected static $_SETTER_DISPATCH_MODEL = array('bar' => 'setBar');
// The base constructor should call setFoo() and setBar()
}
class Baz extends Bar {
protected static $_SETTER_DISPATCH_MODEL = array('baz' => 'setBaz');
// The base constructor should call setFoo(), setBar(), and setBaz()
}
I can't figure out a way to define this behavior in the base class and make it take into account everything that happens all the way down the inheritance chain. The only way I've been able to come up with to do what I'm trying to do is to create a second property that contains the setters the class wishes to add to the model, and add this boilerplate constructor to each subclass:
public function __construct(array $data) {
if (!array_key_exists(
key(self::$_SUPPLEMENTAL_SETTER_DISPATCH_MODEL),
static::$_SETTER_DISPATCH_MODEL
)) {
static::$_SETTER_DISPATCH_MODEL = array_merge(
static::$_SETTER_DISPATCH_MODEL,
self::$_SUPPLEMENTAL_SETTER_DISPATCH_MODEL
);
}
parent::__construct($data);
}
There has to be a better way to do this, right?

Changing object right before PHP serialises it

I have the following class tree:
class A /* Base class */
{
private/protected/public $state
}
class B extends A /* Auto generated class, not to be modified */
{
private $v
public function getV() { return $this->v; }
public function setV($val) { $this->v = $val; }
}
class C extends B { /* Custom code */ }
There is only one class A. There are multiple classes like class B, and all of those classes will have a subclass like C. Class B gets auto-generated and should not be modified.
I am storing objects of type(s) C in the session. What I want to do is to store some state information in every instance, just before PHP gets it serialised, and that will do something with it when it's unserialised. I want all this to be implemented in class A.
Considering, I need to use either __sleep() or Serializable interface. Using __sleep is out of the question, because of what the PHP manual says:
It is not possible for __sleep() to return names of private properties in parent classes. Doing this will result in an E_NOTICE level error. Instead you may use the Serializable interface.
Meaning that if I sleep an instance of class C, I'll loose the private variables declared in B. So I want to use Serializable, but for some reason, I simply can't get it to do what I want.
In essence, I would like the object to be serialised just as if I didn't implement any serialisation stuff myself, I just want to add information to $state right before it happens. I've tried covering all data with ReflectionObject->getProperties(), but I can't seem to find the right way to fetch and set the private values in class B to be serialised and unserialised.
How do I do this?
You can do this using the Reflection classes. You'll have to get the properties of the class itself and each of it's parent classes. Getting and setting the property values can be done using ReflectionProperty's getValue and setValue methods, combined with setAccessible to get access to private and protected properties. Combining those, I came up with the following code:
<?php
class A implements Serializable /* Base class */
{
protected $state;
public function serialize()
{
$this->state = "something";
return serialize($this->_getState());
}
public function unserialize($data)
{
$this->_setState(unserialize($data));
}
protected function _getState()
{
$reflClass = new ReflectionClass(get_class($this));
$values = array();
while ($reflClass != null)
{
foreach ($reflClass->getProperties() as $property)
{
if ($property->getDeclaringClass() == $reflClass)
{
$property->setAccessible(true);
$values[] = array($reflClass->getName(), $property->getName(), $property->getValue($this));
}
}
$reflClass = $reflClass->getParentClass();
}
return $values;
}
protected function _setState($values)
{
foreach ($values as $_)
{
list($className, $propertyName, $propertyValue) = $_;
$property = new ReflectionProperty($className, $propertyName);
$property->setAccessible(true);
$property->setValue($this, $propertyValue);
}
}
}
class B extends A /* Auto generated class, not to be modified */
{
private $v;
public function getV() { return $this->v; }
public function setV($val) { $this->v = $val; }
}
class C extends B { /* Custom code */ }
$instance = new C();
$instance->setV("value");
$s = serialize($instance);
$instance2 = unserialize($s);
var_dump($instance, $instance2);
Which seems to do what you want.

How can I implement extended interfaces?

I'm playing with some design patterns, and wanted to create an example using the SPL's observer pattern. Because it doesn't make sense to have observers and subjects be completely generic, I wanted to extend the interfaces to make them more specific to the application at hand. The problem is that when I run the code below, I get errors like "DataAccess::update() must be compatible with that of SplObserver::update()".
I know that I can make this code execute without errors by switching the method signatures to match those of the interfaces. My question is this: Why doesn't it allow children of the classes defined in the signatures? Below, ModelObserver is a SplObserver, and Model is a SplSubject. I would have expected this to work. Am I missing something?
FYI, I know I could use the explicit method signatures as defined in the interface and use the instanceof keyword in my code logic to achieve the same thing. I was just hoping to find a more elegant solution. Thanks!
<?php
interface ModelObserver extends SplObserver {
}
class DataAccess implements ModelObserver {
/*
* (non-PHPdoc) #see SplObserver::update()
*/
public function update(Model $subject) {
// TODO Auto-generated method stub
}
}
// Just a generic model for the example
class Model implements SplSubject {
private $_properties = array ();
private $_observers = array ();
/*
* generically handle properties you wouldn't want to do it quite like this
* for a real world scenario
*/
public function __get($name) {
return $this->_properties [$name];
}
public function __set($name, $value) {
$this->_properties [$name] = $value;
}
public function __call($method, $args) {
if (strpos ( $method, 'get' ) === 0) {
$name = lcfirst ( str_replace ( 'get', '', $method ) );
return $this->_properties [$name];
}
if (strpos ( $method, 'set' ) === 0) {
$name = lcfirst ( str_replace ( 'set', '', $method ) );
$this->_properties [$name] = $args [0];
return $this;
}
}
public function __toString() {
return print_r ( $this, true );
}
/*
* (non-PHPdoc) #see SplSubject::attach()
*/
public function attach(ModelObserver $observer) {
$this->_observers [] = $observer;
return $this;
}
/*
* (non-PHPdoc) #see SplSubject::detach()
*/
public function detach(ModelObserver $observer) {
if (in_array ( $observer, $this->_observers )) {
$f = function ($value) {
if ($value != $observer) {
return $value;
}
};
$observers = array_map ( $f, $this->_observers );
}
return $this;
}
/*
* (non-PHPdoc) #see SplSubject::notify()
*/
public function notify() {
foreach ($this->_observers as $observer) {
$observer->update($this);
}
}
}
$da = new DataAccess();
$model = new Model ();
$model->setName ( 'Joshua Kaiser' )->setAge ( 32 )->setOccupation ( 'Software Engineer' )
->attach($da);
echo $model;
Limiting DataAccess::update() to accept your child Model breaks the contract of this interface.
True, all Model objects are of class SplSubject , but not all SplSubject are of class Model. An interface is a contract guaranteeing that an implementing class it supports everything the interface supports.
Your code, if it worked would be limiting the DataAccess::update() method to only the Model sub class and not the wider parent class SplSubjects . You cannot narrow the scope of the parameter passed to method defined by an interface.
Let's say you added a property public $foo to the Model class. If it were allowed, you could in your DataAccess::update() method you uses that property $foo. Someone could come along and extend SplSubjects to a child OddModel which didn't have a $foo property. They could no longer pass the OddModel into your DataAccess::update() function--if they could it would break as no $foo property exists for the OddModel.
This is the whole idea behind interfaces, by implementing they you agree 100% to support what is defined by the interface. In this case your interface says:
if you implement me, you must accept every SplSubject or class that extends SplSubject
You're implementation of the interface attempts to break the contract.

Categories