Completed
Branch develop (9f43d2)
by Filipe
05:14
created

ObjectDefinition::assign()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 0
cp 0
cc 1
eloc 3
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * This file is part of slick/di package
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Di\Definition;
11
12
use Slick\Di\Definition\Object\DefinitionData;
13
use Slick\Di\Definition\Object\Resolver;
14
use Slick\Di\Definition\Object\ResolverAwareInterface;
15
use Slick\Di\Definition\Object\ResolverInterface;
16
use Slick\Di\Exception\ClassNotFoundException;
17
use Slick\Di\Exception\MethodNotFoundException;
18
use Slick\Di\Exception\PropertyNotFoundException;
19
20
/**
21
 * Object
22
 *
23
 * @package Slick\Di\Definition
24
 * @author  Filipe Silva <[email protected]>
25
 */
26
class ObjectDefinition extends AbstractDefinition implements
27
    FluentObjectDefinitionInterface,
28 8
    ResolverAwareInterface
29
{
30 8
    /**
31
     * @var DefinitionData
32
     */
33
    protected $definitionData;
34
35
    /**
36
     * @var ResolverInterface
37
     */
38
    protected $resolver;
39
40
    /**
41
     * @var \ReflectionClass
42
     */
43
    private $reflectionClass;
44
45
    /**
46
     * @var mixed
47
     */
48
    private $lastValue;
49
50
    /**
51
     * Creates an object definition
52
     *
53
     * @param string $className
54
     *
55
     * @throws ClassNotFoundException if the provided class name is from an
56
     *         undefined or inaccessible class.
57
     */
58
    public function __construct($className)
59
    {
60
        if (! class_exists($className)) {
61
            throw new ClassNotFoundException(
62
                "The class '{$className}' does not exists or it cannot be " .
63
                "loaded. Object definition therefore cannot be " .
64
                "created."
65
            );
66
        }
67
68
        $this->definitionData = new DefinitionData($className);
69
    }
70
71
    /**
72
     * Creates an object definition
73
     *
74
     * @param string $className
75
     *
76
     * @return FluentObjectDefinitionInterface
77
     *
78
     * @throws ClassNotFoundException if the provided class name is from an
79
     *         undefined or inaccessible class.
80
     */
81
    public static function create($className)
82
    {
83
        return new static($className);
84
    }
85
86
    /**
87
     * Resolves the definition into a scalar or object
88
     *
89
     * @return mixed
90
     */
91
    public function resolve()
92
    {
93
        return $this->getResolver()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Slick\Di\ContainerAwareInterface as the method resolve() does only exist in the following implementations of said interface: Slick\Di\Definition\AbstractDefinition, Slick\Di\Definition\Alias, Slick\Di\Definition\Factory, Slick\Di\Definition\ObjectDefinition, Slick\Di\Definition\Object\Resolver, Slick\Di\Definition\Value.

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...
94
            ->setContainer($this->getContainer())
95
            ->resolve($this->definitionData);
96
    }
97
98
    /**
99
     * Set the arguments for the last defined method call
100
     *
101
     * If no method call was defined yet it will set the constructor argument list
102
     *
103
     * @param array ...$arguments Arguments passed to object constructor
104
     *
105
     * @return $this|Object|self
106
     */
107
    public function with(...$arguments)
108
    {
109
110
        if (empty($this->definitionData->calls)) {
111
            $this->getReflectionClass()
112
                ->getMethod('withConstructorArgument')
113
                ->invokeArgs($this, $arguments);
114
            return $this;
115
        }
116
117
        $this->definitionData->updateLastMethod($arguments);
118
        return $this;
119
    }
120
121
    /**
122
     * Set the arguments used to create the object
123
     *
124
     * @param array ...$arguments
125
     *
126
     * @return self|Object
127
     */
128
    public function withConstructorArgument(...$arguments)
129
    {
130
        $this->definitionData->arguments = $arguments;
131
        return $this;
132
    }
133
134
    /**
135
     * Get the object resolver
136
     *
137
     * @return ResolverInterface
138
     */
139
    public function getResolver()
140
    {
141
        if (!$this->resolver) {
142
            $this->setResolver(new Resolver());
143
        }
144
        return $this->resolver;
145
    }
146
147
    /**
148
     * Set the object resolver
149
     *
150
     * @param ResolverInterface $resolver
151
     *
152
     * @return self|$this
153
     */
154
    public function setResolver(ResolverInterface $resolver)
155
    {
156
        $this->resolver = $resolver;
157
        return $this;
158
    }
159
160
    /**
161
     * Get the definition data
162
     *
163
     * @return DefinitionData
164
     */
165
    public function getDefinitionData()
166
    {
167
        return $this->definitionData;
168
    }
169
170
    /**
171
     * Define a method call in the freshly created object
172
     *
173
     * @param string $methodName The method name to call
174
     *
175
     * @return FluentObjectDefinitionInterface|self
176
     *
177
     * @throws MethodNotFoundException
178
     */
179
    public function call($methodName)
180
    {
181
        $this->getReflectionClass()
182
            ->getMethod('callMethod')
183
            ->invokeArgs($this, [$methodName]);
184
        return $this;
185
    }
186
187
    /**
188
     * Define a method call with provide call
189
     *
190
     * @param string $methodName
191
     * @param array ...$arguments
192
     *
193
     * @return self|ObjectDefinitionInterface
194
     *
195
     * @throws MethodNotFoundException
196
     */
197
    public function callMethod($methodName, ...$arguments)
198
    {
199
200
        if (! method_exists($this->definitionData->className, $methodName)) {
201
            throw new MethodNotFoundException(
202
                "The method '{$methodName}' is not defined in the class ".
203
                "{$this->definitionData->className}"
204
            );
205
        }
206
207
        $this->definitionData->addCall(
208
            DefinitionData::METHOD,
209
            $methodName,
210
            $arguments
211
        );
212
        return $this;
213
    }
214
215
    /**
216
     * Set the value that will be assigned to a property
217
     *
218
     * @param mixed $value
219
     *
220
     * @return self|Object
221
     */
222
    public function assign($value)
223
    {
224
        $this->lastValue = $value;
225
        return $this;
226
    }
227
228
    /**
229
     * Assign the last defined value to the provided property
230
     *
231
     * The value will be reset after its assigned.
232
     *
233
     * @param string $property
234
     *
235
     * @return self|Object
236
     */
237
    public function to($property)
238
    {
239
        if (! property_exists($this->definitionData->className, $property)) {
240
            throw new PropertyNotFoundException(
241
                "The class '{$this->definitionData->className}' has no " .
242
                "property called '{$property}'."
243
            );
244
        }
245
        $this->getReflectionClass()
246
            ->getMethod('assignProperty')
247
            ->invokeArgs($this, [$property, $this->lastValue]);
248
249
        $this->lastValue = null;
250
        return $this;
251
    }
252
253
    /**
254
     * Assigns a value to the property with provided name
255
     *
256
     * @param string $name
257
     * @param mixed  $value
258
     *
259
     * @return self|Object
260
     */
261
    public function assignProperty($name, $value)
262
    {
263
        $this->definitionData->addCall(
264
            DefinitionData::PROPERTY,
265
            $name,
266
            $value
267
        );
268
        return $this;
269
    }
270
271
    /**
272
     * Get the self reflection
273
     *
274
     * @return \ReflectionClass
275
     */
276
    protected function getReflectionClass()
277
    {
278
        if (!$this->reflectionClass) {
279
            $this->reflectionClass = new \ReflectionClass($this);
280
        }
281
        return $this->reflectionClass;
282
    }
283
}
284