NodeDefinitionBuilder::setNodeClass()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
/*
3
 * This file is part of the Borobudur-Config package.
4
 *
5
 * (c) Hexacodelabs <http://hexacodelabs.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Borobudur\Config\Definition\Builder;
12
13
use Borobudur\Config\Exception\InvalidArgumentException;
14
15
/**
16
 * @author      Iqbal Maulana <[email protected]>
17
 * @created     8/10/15
18
 */
19
class NodeDefinitionBuilder implements DefinitionInterface
20
{
21
    /**
22
     * @var array
23
     */
24
    private static $nodeMapping = array();
25
26
    /**
27
     * @var DefinitionInterface|NodeDefinitionContainerInterface|null
28
     */
29
    protected $parent;
30
31
    /**
32
     * Constructor.
33
     */
34
    public function __construct()
35
    {
36
        self::$nodeMapping = array(
37
            'variable' => __NAMESPACE__ . '\\VariableDefinition',
38
            'scalar'   => __NAMESPACE__ . '\\ScalarDefinition',
39
            'integer'  => __NAMESPACE__ . '\\IntegerDefinition',
40
            'float'    => __NAMESPACE__ . '\\FloatDefinition',
41
            'boolean'  => __NAMESPACE__ . '\\BooleanDefinition',
42
            'enum'     => __NAMESPACE__ . '\\EnumDefinition',
43
            'array'    => __NAMESPACE__ . '\\ArrayDefinition',
44
        );
45
    }
46
47
    /**
48
     * Set node class.
49
     *
50
     * @param string $type
51
     * @param string $class
52
     */
53
    public static function setNodeClass($type, $class)
54
    {
55
        self::$nodeMapping[strtolower($type)] = $class;
56
    }
57
58
    /**
59
     * Get node class by type.
60
     *
61
     * @param string $type
62
     *
63
     * @return string
64
     */
65
    public static function getNodeClass($type)
66
    {
67
        $type = strtolower($type);
68
69
        if (!isset(self::$nodeMapping[$type])) {
70
            throw new InvalidArgumentException(sprintf('The node type "%s" is not defined.', $type));
71
        }
72
73
        $class = self::$nodeMapping[$type];
74
75
        if (!class_exists($class)) {
76
            throw new InvalidArgumentException(sprintf('Class "%s" is not defined.', $class));
77
        }
78
79
        return $class;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function setParent($parent = null)
86
    {
87
        if (null !== $parent
88
            && false === $parent instanceof DefinitionInterface
89
            && false === $parent instanceof NodeDefinitionContainerInterface
90
        ) {
91
            throw new InvalidArgumentException(sprintf(
92
                '"%s" should implement \Borobudur\Config\Definition\Builder\DefinitionInterface ' .
93
                'or \Borobudur\Config\Definition\Builder\NodeDefinitionContainerInterface',
94
                get_class($parent)
95
            ));
96
        }
97
98
        $this->parent = $parent;
99
100
        return $this;
101
    }
102
103
    /**
104
     * @return DefinitionInterface|NodeDefinitionContainerInterface|null
105
     */
106
    public function end()
107
    {
108
        return $this->parent;
109
    }
110
111
    /**
112
     * Append node to parent.
113
     *
114
     * @param NodeDefinitionInterface $node
115
     *
116
     * @return $this
117
     */
118
    public function append(NodeDefinitionInterface $node)
119
    {
120
        if ($node instanceof NodeDefinitionContainerInterface) {
121
            $builder = clone $this;
122
            $builder->setParent(null);
123
            $node->setBuilder($builder);
124
        }
125
126
        if (null !== $this->parent) {
127
            $this->parent->append($node);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Borobudur\Config\Definit...der\DefinitionInterface as the method append() does only exist in the following implementations of said interface: Borobudur\Config\Definit...Builder\ArrayDefinition, Borobudur\Config\Definit...r\NodeDefinitionBuilder.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
128
            $node->setParent($this);
129
        }
130
131
        return $this;
132
    }
133
134
    /**
135
     * Create variable definition.
136
     *
137
     * @param string $name
138
     *
139
     * @return VariableDefinition
140
     */
141
    public function variableNode($name)
142
    {
143
        return $this->node($name, 'variable');
144
    }
145
146
    /**
147
     * Create scalar definition.
148
     *
149
     * @param string $name
150
     *
151
     * @return ScalarDefinition
152
     */
153
    public function scalarNode($name)
154
    {
155
        return $this->node($name, 'scalar');
156
    }
157
158
    /**
159
     * Create boolean definition.
160
     *
161
     * @param string $name
162
     *
163
     * @return BooleanDefinition
164
     */
165
    public function booleanNode($name)
166
    {
167
        return $this->node($name, 'boolean');
168
    }
169
170
    /**
171
     * Create integer definition.
172
     *
173
     * @param string $name
174
     *
175
     * @return IntegerDefinition
176
     */
177
    public function integerNode($name)
178
    {
179
        return $this->node($name, 'integer');
180
    }
181
182
    /**
183
     * Create float definition.
184
     *
185
     * @param string $name
186
     *
187
     * @return FloatDefinition
188
     */
189
    public function floatNode($name)
190
    {
191
        return $this->node($name, 'float');
192
    }
193
194
    /**
195
     * Create enum definition.
196
     *
197
     * @param string $name
198
     * @param array  $values
199
     *
200
     * @return EnumDefinition
201
     */
202
    public function enumNode($name, array $values)
203
    {
204
        /** @var EnumDefinition $enum */
205
        $enum = $this->node($name, 'enum');
206
207
        return $enum->values($values);
208
    }
209
210
    /**
211
     * Create array definition.
212
     *
213
     * @param string $name
214
     *
215
     * @return ArrayDefinition
216
     */
217
    public function arrayNode($name)
218
    {
219
        return $this->node($name, 'array');
220
    }
221
222
    /**
223
     * Copy definition.
224
     *
225
     * @param string                  $name
226
     * @param NodeDefinitionInterface $template
227
     *
228
     * @return NodeDefinitionInterface
229
     */
230
    public function copyNode($name, NodeDefinitionInterface $template)
231
    {
232
        $node = clone $template;
233
        $node->setName($name);
234
        $this->append($node);
235
236
        return $node;
237
    }
238
239
    /**
240
     * Build node by type.
241
     *
242
     * @param string $name
243
     * @param string $type
244
     *
245
     * @return NodeDefinitionInterface
246
     */
247
    public function node($name, $type)
248
    {
249
        $class = self::getNodeClass($type);
250
        $node = new $class($name);
251
252
        $this->append($node);
253
254
        return $node;
255
    }
256
}
257