TypedObject   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 273
Duplicated Lines 6.59 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 57
c 5
b 1
f 0
lcom 1
cbo 0
dl 18
loc 273
ccs 135
cts 135
cp 1
rs 6.433

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __isset() 0 4 1
A keys() 0 4 1
A className() 0 4 1
A getIterator() 0 4 1
A count() 0 4 1
A toArray() 0 4 1
C __construct() 0 52 17
C extend() 18 42 7
A __get() 0 12 3
A __unset() 0 6 2
C __set() 0 49 17
A checkErrors() 0 11 3
A fromArray() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like TypedObject often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TypedObject, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * spindle/types
4
 *
5
 * @license CC0-1.0 (Public Domain) https://creativecommons.org/publicdomain/zero/1.0/
6
 */
7
namespace Spindle\Types;
8
9
/**
10
 * プロパティの型を保証するオブジェクト
11
 */
12
abstract class TypedObject implements
13
    Internal\TypedObjectInterface,
14
    \IteratorAggregate,
15
    \Countable
16
{
17
    private $_storage = array();
18
19
    private static $_schemaCache = array();
20
    private static $_defaultCache = array();
21
22
    public static $preventExtensions = true;
23
    public static $casting = false;
24
25 15
    final function __construct()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
26
    {
27 15
        $class = get_class($this);
28
29 15
        if (isset(self::$_schemaCache[$class])) {
30 13
            $this->_storage = self::$_defaultCache[$class];
31 13
            goto initialize;
32
        }
33
34 4
        $schema = $this->schema();
35 4
        if (! is_array($schema))
36 4
            throw new \DomainException("$class::schema() must return an array.");
37
38 4
        foreach ($schema as $key => $type) {
39 4
            if (is_int($key)) {
40 3
                if (empty($lastKey))
41 3
                    throw new \DomainException("$class::schema() is invalid.");
42
43 3
                $this->_storage[$lastKey] = $type;
44 3
                continue;
45
46
            } else {
47
                switch ($type) {
48 4
                case self::STR: case self::BOOL: case self::INT: case self::OBJ:
49 4
                case self::ARR: case self::RES: case self::DBL: case self::CALL:
50 4
                case self::MIX:
51 4
                    break;
52
53 3
                default:
54 3
                    if (!class_exists($type))
55 3
                        throw new \DomainException("$class::schema()[$key] class not found.");
56 3
                }
57 4
                $this->_storage[$key] = null;
58
            }
59
60 4
            $lastKey = $key;
61 4
        }
62
63
        //スキーマをキャッシュする
64 4
        self::$_schemaCache[$class] = $schema;
65
66
        //デフォルト値をキャッシュする
67 4
        self::$_defaultCache[$class] = $this->_storage;
68
69
        initialize:
70 15
        $initializer = array($this, 'initialize');
71 15
        if (is_callable($initializer)) {
72 11
            $args = func_get_args();
73
            //func_get_args()の結果を直接渡すとエラーになる
74 11
            call_user_func_array($initializer, $args);
75 11
        }
76 15
    }
77
78
    /**
79
     * template method
80
     * function initialize()
81
     * {
82
     * }
83
     */
84
85
    /**
86
     * schema()の定義を継承拡張する
87
     *
88
     * @param array $parent parent::schema()の結果
89
     * @param array $child  拡張するschema定義
90
     * @return array 継承拡張されたschema定義
91
     */
92 1
    final static function extend(array $parent, array $child)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
93
    {
94
        //parent側を整理
95 1
        $parentSchema = array();
96 1
        $lastKey = null;
97 1 View Code Duplication
        foreach ($parent as $key => $val) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
98 1
            if (is_int($key)) {
99 1
                $parentSchema[$lastKey][] = $val;
100 1
            } else {
101 1
                $parentSchema[$key][] = $val;
102
            }
103
104 1
            $lastKey = $key;
105 1
        }
106
107
        //child側を整理
108 1
        $childSchema = array();
109 1
        $lastKey = null;
110 1 View Code Duplication
        foreach ($child as $key => $val) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111 1
            if (is_int($key)) {
112 1
                $parentSchema[$lastKey][] = $val;
113 1
            } else {
114 1
                $parentSchema[$key][] = $val;
115
            }
116
117 1
            $lastKey = $key;
118 1
        }
119
120
        //マージ
121 1
        $mergedSchema = $childSchema + $parentSchema;
122
123
        //schemaの形式に復元
124 1
        $merged = array();
125 1
        foreach ($mergedSchema as $key => $val) {
126 1
            $merged[$key] = $val[0];
127 1
            if (isset($val[1])) {
128 1
                $merged[] = $val[1];
129 1
            }
130 1
        }
131
132 1
        return $merged;
133
    }
134
135 10
    final function __get($name)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
136
    {
137 10
        if (array_key_exists($name, $this->_storage)) {
138 8
            return $this->_storage[$name];
139
        }
140
141 2
        if (static::$preventExtensions) {
142 1
            throw new \OutOfRangeException(get_class($this) . "->$name is not defined.");
143
        }
144
145 1
        return null;
146
    }
147
148 1
    final function __isset($name)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
149
    {
150 1
        return isset($this->_storage[$name]);
151
    }
152
153 1
    final function __unset($name)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
154
    {
155 1
        if (isset($this->_storage[$name])) {
156 1
            $this->_storage[$name] = null;
157 1
        }
158 1
    }
159
160 13
    final function __set($name, $value)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
161
    {
162 13
        $class = get_class($this);
163 13
        $schema = self::$_schemaCache[$class];
164
165 13
        if (! array_key_exists($name, $schema)) {
166 2
            if (static::$preventExtensions) {
167 1
                throw new \OutOfRangeException("$class->$name is not defined.");
168
            } else {
169 1
                $this->_storage[$name] = $value;
170 1
                return;
171
            }
172
        }
173
174 13
        $type = $schema[$name];
175
        switch ($type) {
176 13
            case self::CALL:
177 2
                if (is_callable($value)) {
178 1
                    $this->_storage[$name] = $value;
179 1
                    return;
180
                }
181 1
                break;
182 13
            case self::BOOL: case self::INT: case self::DBL: case self::STR:
183 13
            case self::ARR: case self::OBJ: case self::RES:
184 6
                if (gettype($value) === $type) {
185 4
                    $this->_storage[$name] = $value;
186 4
                    return;
187 3
                } elseif (static::$casting) {
188 2
                    settype($value, $type);
189 2
                    $this->_storage[$name] = $value;
190 2
                    return;
191
                }
192 1
                break;
193 12
            case self::MIX:
194 11
                $this->_storage[$name] = $value;
195 11
                return;
196 3
            default:
197 3
                if ($value instanceof $type) {
198 2
                    $this->_storage[$name] = $value;
199 2
                    return;
200 1
                } elseif (static::$casting) {
201 1
                    $value = new $type($value);
202 1
                    $this->_storage[$name] = $value;
203 1
                    return;
204
                }
205 3
        }
206
207 2
        throw new \InvalidArgumentException("$class->$name must be $type.");
208
    }
209
210
    /**
211
     * エラーチェックのためのメソッド。
212
     * 要素間の関連であったり、null許可のチェックなどに使ってください。
213
     *
214
     * ここではデフォルトとしてnullチェックを行います。
215
     */
216 1
    function checkErrors()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
217
    {
218 1
        $errors = array();
219 1
        foreach ($this as $name => $val) {
220 1
            if ($val === null) {
221 1
                $errors[$name][] = 'value is null';
222 1
            }
223 1
        }
224
225 1
        return $errors;
226
    }
227
228
    /**
229
     * クラスが持っている属性の一覧を取得します。
230
     *
231
     * @return array
232
     */
233 1
    function keys()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
234
    {
235 1
        return array_keys($this->_storage);
236
    }
237
238
    /**
239
     * クラス名を返します。
240
     *
241
     * @return string
242
     */
243 1
    static function className()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
244
    {
245 1
        return get_called_class();
246
    }
247
248
    /**
249
     * override \IteratorAggregate::getIterator
250
     */
251 3
    function getIterator()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
252
    {
253 3
        return new \ArrayIterator($this->_storage);
254
    }
255
256
    /**
257
     * override \Countable::count
258
     */
259 2
    function count()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
260
    {
261 2
        return count($this->_storage);
262
    }
263
264
    /**
265
     * @param array $arr
266
     * @return TypedObject
267
     */
268 1
    static function fromArray(array $arr)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
269
    {
270 1
        $self = new static;
271 1
        foreach ($arr as $key => $val) {
272 1
            $self->__set($key, $val);
273 1
        }
274 1
        return $self;
275
    }
276
277
    /**
278
     * @return array;
0 ignored issues
show
Documentation introduced by
The doc-type array; could not be parsed: Expected "|" or "end of type", but got ";" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
279
     */
280 1
    function toArray()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
281
    {
282 1
        return $this->_storage;
283
    }
284
}
285