Completed
Push — master ( 65a7e4...c8ba84 )
by Simone
02:04
created

MagicResource::__callStatic()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 2
crap 4
1
<?php
2
3
/**
4
 * This file is part of sensorario/resources repository
5
 *
6
 * (c) Simone Gentili <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sensorario\Resources;
13
14
use Sensorario\Resources\Configurator;
15
use Sensorario\Resources\Exceptions\FactoryMethodException;
16
use Sensorario\Resources\Validators\ResourcesValidator;
17
18
/**
19
 * @method array defaults() returns resource's default values
20
 * @method array rules() returns resource's rules
21
 */
22
abstract class MagicResource
23
{
24
    protected $properties = [];
25
26
    protected $validator;
27
28
    private static $methodWhiteList = [
29
        'box',
30
        'allowedValues'
31
    ];
32
33 3
    public function __call($functionName, $arguments)
34
    {
35 3
        $propertyName = strtolower($functionName);
36
37 3
        if ($this->hasProperty($propertyName)) {
38 1
            return $this->get($propertyName);
39
        }
40
41 2
        if (isset($this->defaults()[$propertyName])) {
42 1
            return $this->defaults()[$propertyName];
43
        }
44
45 1
        throw new \Sensorario\Resources\Exceptions\UndefinedMethodException(
46 1
            'Method `' . get_class($this)
47 1
            . '::' . $functionName 
48 1
            . '()` is not yet implemented'
49
        );
50
    }
51
52
    abstract public function applyConfiguration(Configurator $configurator);
53
54 39
    public function __construct(
55
        array $properties,
56
        ResourcesValidator $validator,
57
        Configurator $configuration = null
58
    ) {
59 39
        $this->properties    = $properties;
60 39
        $this->validator     = $validator;
61 39
        $this->configuration = $configuration;
0 ignored issues
show
Bug introduced by
The property configuration does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
62
63 39
        if ($configuration) {
64 21
            $this->applyConfiguration(
65 21
                $configuration
66
            );
67
        }
68
69 39
        $this->init();
70 24
    }
71
72 39
    private function init()
73
    {
74 39
        $this->ensurePropertiesConsistency();
75 38
        $this->validate();
76 24
    }
77
78 39
    public static function __callStatic($methodName, array $args)
79
    {
80 39
        $isMethodAllowed = in_array($methodName, self::$methodWhiteList);
81 39
        $configuration = null;
82
83 39
        if (isset($args[1]) && 'Sensorario\Resources\Configurator' == get_class($args[1])) {
84 21
            $configuration = new Configurator($args[1]->resourceName(), $args[1]->container());
85
        }
86
87 39
        if ($isMethodAllowed) {
88 38
            return new static($args[0] ?? [], new ResourcesValidator(), $configuration);
89
        }
90
91 1
        throw new FactoryMethodException('Invalid factory method ' . '`' . $methodName . '`');
92
    }
93
94 31
    final public function hasProperty($propertyName)
95
    {
96
        return isset(
97 31
            $this->properties[$propertyName]
98
        );
99
    }
100
101 1
    final public function set($propertyName, $propertValue) 
102
    {
103 1
        $this->properties[$propertyName] = $propertValue;
104 1
    }
105
106 24
    final public function get($propertyName)
107
    {
108 24
        $this->ensurePropertyNameIsNotEmpty($propertyName);
109
110 23
        if ($this->hasNotProperty($propertyName)) {
111 3
            if ($prop = $this->defaults()[$propertyName] ?? false) {
112 2
                return $prop;
113
            }
114
115 1
            throw new \Sensorario\Resources\Exceptions\NoValuesException(
116
                'No value nor method `'
117 1
                . $propertyName
118 1
                . '` found in this resource'
119
            );
120
        }
121
122 20
        return $this->properties[$propertyName];
123
    }
124
125 29
    final public function hasNotProperty($propertyName)
126
    {
127 29
        return !$this->hasProperty($propertyName);
128
    }
129
130 2
    final public function hasProperties(array $properties)
131
    {
132 2
        foreach ($properties as $property) {
133 2
            if ($this->hasNotProperty($property)) {
134 1
                return false;
135
            }
136
        }
137
138 1
        return true;
139
    }
140
141 38
    final public function properties()
142
    {
143 38
        $properties = $this->properties;
144
145 38
        foreach ($properties as $k => $v) {
146 30
            if ('object' === gettype($v)) {
147 5
                $this->ensurePropertyIsAnObjectDefined($k);
148
149 4
                if ($this->rules()[$k]['object'] === '\\Sensorario\\Resources\\Resource') {
150 1
                    $properties[$k] = $v->properties();
151
                }
152
            }
153
        }
154
155 37
        return $properties;
156
    }
157
158 38
    public function validate()
159
    {
160 38
        $this->validator->validate($this);
0 ignored issues
show
Compatibility introduced by
$this of type object<Sensorario\Resources\MagicResource> is not a sub-type of object<Sensorario\Resources\Resource>. It seems like you assume a child class of the class Sensorario\Resources\MagicResource to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
161 24
    }
162
163 39
    public function ensurePropertiesConsistency()
164
    {
165 39
        foreach ($this->properties as $k => $v) {
166 31
            if ('object' === gettype($v) && !isset($this->rules()[$k])) {
167 1
                throw new \Sensorario\Resources\Exceptions\PropertyWithoutRuleException(
168 1
                    'When property `' . $k . '` is an object class, must be defined in Resources::rules()' .
169 1
                    ' but rules here are equals to ' . var_export($this->rules(), true)
170 1
                    . ' And properties are ' . var_export($this->properties, true)
171
                );
172
            }
173
        }
174 38
    }
175
176 24
    public function ensurePropertyNameIsNotEmpty($propertyName)
177
    {
178 24
        if ('' == $propertyName) {
179 1
            throw new \Sensorario\Resources\Exceptions\PropertyNameEmptyException(
180 1
                'Oops! Property name requested is empty string!!'
181
            );
182
        }
183 23
    }
184
185 5
    public function ensurePropertyIsAnObjectDefined($k)
186
    {
187 5
        if (!isset($this->rules()[$k]['object'])) {
188 1
            throw new \Sensorario\Resources\Exceptions\PropertyWithoutRuleException(
189 1
                'Property ' . $k . ' is an object but is not defined in rules'
190
            );
191
        }
192 4
    }
193
}
194