AbstractCondition::__call()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 2
dl 0
loc 21
rs 9.2728
c 0
b 0
f 0
1
<?php
2
3
namespace Net\Bazzline\Component\Requirement;
4
5
use InvalidArgumentException;
6
use SplObjectStorage;
7
8
/**
9
 * Class ConditionAbstract
10
 *
11
 * @package Net\Bazzline\Component\Requirement
12
 * @author stev leibelt <[email protected]>
13
 * @since 2013-06-25
14
 */
15
abstract class AbstractCondition implements ConditionInterface
16
{
17
    /**
18
     * @var bool
19
     * @author stev leibelt <[email protected]>
20
     * @since 2013-09-29
21
     */
22
    private $isDisabled;
23
24
    /**
25
     * @var \SplObjectStorage|IsMetInterface[]
26
     * @author stev leibelt <[email protected]>
27
     * @since 2013-06-25
28
     */
29
    protected $items;
30
31
    /**
32
     * @var array
33
     * @author stev leibelt <[email protected]>
34
     * @since 2013-07-20
35
     */
36
    private $methodNamesPerItem;
37
38
    /**
39
     * @var bool
40
     * @author stev leibelt <[email protected]>
41
     * @since 2013-09-29
42
     */
43
    private $returnValueIfIsDisabled;
44
45
    /**
46
     * Constructor of the class
47
     */
48
    public function __construct()
49
    {
50
        $this->isDisabled = false;
51
        $this->items = new SplObjectStorage();
52
        $this->methodNamesPerItem = array();
53
        $this->setReturnValueIfIsDisabledToTrue();
54
    }
55
56
    /**
57
     * {$inheritdoc}
58
     */
59
    public function addItem(IsMetInterface $item)
60
    {
61
        $this->items->attach($item);
62
63
        return $this;
64
    }
65
66
    /**
67
     * Magic call method to keep this class as generic as possible.
68
     *
69
     * @param string $methodName - name of the method
70
     * @param mixed $arguments - value
71
     * @return $this
72
     * @throws \InvalidArgumentException
73
     * @author sleibelt
74
     * @since 2013-06-25
75
     */
76
    public function __call($methodName, $arguments)
77
    {
78
        if (count($arguments) != 1) {
79
            throw new InvalidArgumentException(
80
                'Only one argument value should be provided.'
81
            );
82
        }
83
        $value = current($arguments);
84
85
        foreach ($this->items as $item) {
86
            if ($item instanceof ConditionInterface) {
87
                $item->$methodName($value);
88
            } else {
89
                if ($this->itemSupportsMethodCall($item, $methodName)) {
90
                    $item->$methodName($value);
91
                }
92
            }
93
        }
94
95
        return $this;
96
    }
97
98
    /**
99
     * {$inheritdoc}
100
     */
101
    public function getItems()
102
    {
103
        $items = array();
104
105
        foreach ($this->items as $item) {
106
            $items[] = $item;
107
        }
108
109
        return $items;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $items; (array) is incompatible with the return type declared by the interface Net\Bazzline\Component\R...tionInterface::getItems of type null|Net\Bazzline\Compon...rement\IsMetInterface[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
110
    }
111
112
    /**
113
     * @return $this
114
     * @author stev leibelt <[email protected]>
115
     * @since 2013-09-29
116
     */
117
    public function disable()
118
    {
119
        $this->isDisabled = true;
120
121
        return $this;
122
    }
123
124
    /**
125
     * @return bool
126
     * @author stev leibelt <[email protected]>
127
     * @since 2013-09-29
128
     */
129
    public function isDisabled()
130
    {
131
        return $this->isDisabled;
132
    }
133
134
    /**
135
     * Checks if item implements method name
136
     *
137
     * @param IsMetInterface $item
138
     * @param string $methodName
139
     * @return bool
140
     * @author stev leibelt <[email protected]>
141
     * @since 2013-07-20
142
     */
143
    protected function itemSupportsMethodCall(IsMetInterface $item, $methodName)
144
    {
145
        $hash = spl_object_hash($item);
146
147
        if (empty($this->methodNamesPerItem)
148
            || !isset($this->methodNamesPerItem[$hash])) {
149
            $itemMethods = array_flip(get_class_methods($item));
150
            $this->methodNamesPerItem[$hash] = $itemMethods;
151
        }
152
153
        return (isset($this->methodNamesPerItem[$hash][$methodName]));
154
    }
155
156
    /**
157
     * @return bool
158
     * @author stev leibelt <[email protected]>
159
     * @since 2013-09-29
160
     */
161
    protected function getReturnValueIfIsDisabled()
162
    {
163
        return $this->isDisabled;
164
    }
165
166
    /**
167
     * @author stev leibelt <[email protected]>
168
     * @since 2013-09-29
169
     */
170
    protected function setReturnValueIfIsDisabledToFalse()
171
    {
172
        $this->returnValueIfIsDisabled = false;
173
    }
174
175
    /**
176
     * @author stev leibelt <[email protected]>
177
     * @since 2013-09-29
178
     */
179
    protected function setReturnValueIfIsDisabledToTrue()
180
    {
181
        $this->returnValueIfIsDisabled = true;
182
    }
183
}
184