Completed
Pull Request — 2.0 (#62)
by
unknown
05:20
created

FlexibleEntity::extract()   C

Complexity

Conditions 8
Paths 1

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 25
rs 5.3846
cc 8
eloc 13
nc 1
nop 0
1
<?php
2
/*
3
 * This file is part of the PommProject/ModelManager package.
4
 *
5
 * (c) 2014 - 2015 Grégoire HUBERT <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace PommProject\ModelManager\Model;
11
12
use PommProject\Foundation\Inflector;
13
use PommProject\ModelManager\Exception\ModelException;
14
use PommProject\ModelManager\Model\FlexibleEntity\FlexibleContainer;
15
use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface;
16
17
/**
18
 * FlexibleEntity
19
 *
20
 * Parent for entity classes.
21
 *
22
 * @abstract
23
 * @package   ModelManager
24
 * @copyright 2014 - 2015 Grégoire HUBERT
25
 * @author    Grégoire HUBERT <[email protected]>
26
 * @license   MIT/X11 {@link http://opensource.org/licenses/mit-license.php}
27
 */
28
abstract class FlexibleEntity extends FlexibleContainer implements \ArrayAccess
29
{
30
    public static $strict = true;
31
    protected static $has_methods;
32
33
    /**
34
     * __construct
35
     *
36
     * Instantiate the entity and hydrate it with the given values.
37
     *
38
     * @access public
39
     * @param  array $values Optional starting values.
40
     */
41
    public function __construct(array $values = null)
42
    {
43
        if ($values !== null) {
44
            $this->hydrate($values);
45
        }
46
    }
47
48
    /**
49
     * get
50
     *
51
     * Returns the $var value
52
     *
53
     * @final
54
     * @access public
55
     * @param  string|array $var Key(s) you want to retrieve value from.
56
     * @throws  ModelException if strict and the attribute does not exist.
57
     * @return mixed
58
     */
59
    final public function get($var)
60
    {
61
        if (is_scalar($var)) {
62
            if ($this->has($var)) {
63
                return $this->container[$var];
64
            } elseif (static::$strict === true) {
65
                throw new ModelException(sprintf("No such key '%s'.", $var));
66
            }
67
        } elseif (is_array($var)) {
68
            return array_intersect_key($this->container, array_flip($var));
69
        }
70
    }
71
72
    /**
73
     * has
74
     *
75
     * Returns true if the given key exists.
76
     *
77
     * @final
78
     * @access public
79
     * @param  string  $var
80
     * @return boolean
81
     */
82
    final public function has($var)
83
    {
84
        return isset($this->container[$var]) || array_key_exists($var, $this->container);
85
    }
86
87
    /**
88
     * set
89
     *
90
     * Set a value in the var holder.
91
     *
92
     * @final
93
     * @access public
94
     * @param  String         $var   Attribute name.
95
     * @param  Mixed          $value Attribute value.
96
     * @return FlexibleEntity $this
97
     */
98
    final public function set($var, $value)
99
    {
100
        $this->container[$var] = $value;
101
        $this->touch();
102
103
        return $this;
104
    }
105
106
    /**
107
     * add
108
     *
109
     * When the corresponding attribute is an array, call this method
110
     * to set values.
111
     *
112
     * @access public
113
     * @param  string         $var
114
     * @param  mixed          $value
115
     * @return FlexibleEntity $this
116
     * @throws ModelException
117
     */
118
    public function add($var, $value)
119
    {
120
        if ($this->has($var)) {
121
            if (is_array($this->container[$var])) {
122
                $this->container[$var][] = $value;
123
            } else {
124
                throw new ModelException(sprintf("Field '%s' exists and is not an array.", $var));
125
            }
126
        } else {
127
            $this->container[$var] = [$value];
128
        }
129
130
        return $this;
131
    }
132
133
    /**
134
     * clear
135
     *
136
     * Drop an attribute from the var holder.
137
     *
138
     * @final
139
     * @access public
140
     * @param  String         $offset Attribute name.
141
     * @return FlexibleEntity $this
142
     */
143
    final public function clear($offset)
144
    {
145
        if ($this->has($offset)) {
146
            unset($this->container[$offset]);
147
            $this->touch();
148
        }
149
150
        return $this;
151
    }
152
153
    /**
154
     * __call
155
     *
156
     * Allows dynamic methods getXxx, setXxx, hasXxx, addXxx or clearXxx.
157
     *
158
     * @access  public
159
     * @throws  ModelException if method does not exist.
160
     * @param   mixed $method
161
     * @param   mixed $arguments
162
     * @return  mixed
163
     */
164
    public function __call($method, $arguments)
165
    {
166
        list($operation, $attribute) = $this->extractMethodName($method);
167
168
        switch ($operation) {
169
        case 'set':
170
            return $this->set($attribute, $arguments[0]);
171
        case 'get':
172
            return $this->get($attribute);
173
        case 'add':
174
            return $this->add($attribute, $arguments[0]);
175
        case 'has':
176
            return $this->has($attribute);
177
        case 'clear':
178
            return $this->clear($attribute);
179
        default:
180
            throw new ModelException(sprintf('No such method "%s:%s()"', get_class($this), $method));
181
        }
182
    }
183
184
    /**
185
     * convert
186
     *
187
     * Make all keys lowercase and hydrate the object.
188
     *
189
     * @access  public
190
     * @param   Array          $values
191
     * @return  FlexibleEntity
192
     */
193
    public function convert(array $values)
194
    {
195
        $tmp = [];
196
197
        foreach ($values as $key => $value) {
198
            $tmp[strtolower($key)] = $value;
199
        }
200
201
        return $this->hydrate($tmp);
202
    }
203
204
    /**
205
     * extract
206
     *
207
     * Returns the fields flatten as arrays.
208
     *
209
     * The complex stuff in here is when there is an array, since all elements
210
     * in arrays are the same type, we check only its first value to know if we need
211
     * to traverse it or not.
212
     *
213
     * @see FlexibleEntityInterface
214
     */
215
    public function extract()
216
    {
217
        $array_recurse = function ($val) use (&$array_recurse) {
218
            if (is_scalar($val)) {
219
                return $val;
220
            }
221
222
            if (is_array($val)) {
223
                if (is_array(current($val)) || (is_object(current($val)) && current($val) instanceof FlexibleEntityInterface)) {
224
                    return array_map($array_recurse, $val);
225
                } else {
226
                    return $val;
227
                }
228
            }
229
230
            if (is_object($val) && $val instanceof FlexibleEntityInterface) {
231
                return $val->extract();
232
            }
233
234
            return $val;
235
        };
236
237
238
        return array_map($array_recurse, array_merge($this->container, $this->getCustomFields()));
239
    }
240
241
    /**
242
     * getCustomFields
243
     *
244
     * Return a list of custom methods with has() accessor.
245
     *
246
     * @access  private
247
     * @return  array
248
     */
249
    private function getCustomFields()
250
    {
251
        if (static::$has_methods === null) {
252
            static::fillHasMethods($this);
253
        }
254
255
        $custom_fields = [];
256
257
        foreach (static::$has_methods as $method) {
258
            if (call_user_func([$this, sprintf("has%s", $method)]) === true) {
259
                $custom_fields[Inflector::underscore(lcfirst($method))] = call_user_func([$this, sprintf("get%s", $method)]);
260
            }
261
        }
262
263
        return $custom_fields;
264
    }
265
266
    /**
267
     * getIterator
268
     *
269
     * @see FlexibleEntityInterface
270
     */
271
    public function getIterator()
272
    {
273
        return new \ArrayIterator(array_merge($this->container, $this->getCustomFields()));
274
    }
275
276
    /**
277
     * __set
278
     *
279
     * PHP magic to set attributes.
280
     *
281
     * @access  public
282
     * @param   String         $var   Attribute name.
283
     * @param   Mixed          $value Attribute value.
284
     * @return  FlexibleEntity $this
285
     */
286
    public function __set($var, $value)
287
    {
288
        $method_name = "set".Inflector::studlyCaps($var);
289
        $this->$method_name($value);
290
291
        return $this;
292
    }
293
294
    /**
295
     * __get
296
     *
297
     * PHP magic to get attributes.
298
     *
299
     * @access  public
300
     * @param   String $var Attribute name.
301
     * @return  Mixed  Attribute value.
302
     */
303
    public function __get($var)
304
    {
305
        $method_name = "get".Inflector::studlyCaps($var);
306
307
        return $this->$method_name();
308
    }
309
310
    /**
311
     * __isset
312
     *
313
     * Easy value check.
314
     *
315
     * @access  public
316
     * @param   string $var
317
     * @return  bool
318
     */
319
    public function __isset($var)
320
    {
321
        $method_name = "has".Inflector::studlyCaps($var);
322
323
        return $this->$method_name();
324
    }
325
326
    /**
327
     * __unset
328
     *
329
     * Clear an attribute.
330
     *
331
     * @access  public
332
     * @param   string $var
333
     * @return  FlexibleEntity   $this
334
     */
335
    public function __unset($var)
336
    {
337
        $method_name = "clear".Inflector::studlyCaps($var);
338
339
        return $this->$method_name();
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     */
345
    public function offsetExists($offset)
346
    {
347
        $method_name = "has".Inflector::studlyCaps($offset);
348
349
        return $this->$method_name();
350
    }
351
352
    /**
353
     * {@inheritdoc}
354
     */
355
    public function offsetSet($offset, $value)
356
    {
357
        $this->__set($offset, $value);
358
    }
359
360
    /**
361
     * {@inheritdoc}
362
     */
363
    public function offsetGet($offset)
364
    {
365
        return $this->__get($offset);
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     */
371
    public function offsetUnset($offset)
372
    {
373
        $this->clear($offset);
374
    }
375
376
    /**
377
     * fillHasMethods
378
     *
379
     * When getIterator is called the first time, the list of "has" methods is
380
     * set in a static attribute to boost performances.
381
     *
382
     * @access  protected
383
     * @param   FlexibleEntity   $entity
384
     * @return  null
385
     */
386
    protected static function fillHasMethods(FlexibleEntity $entity)
387
    {
388
        static::$has_methods = [];
389
390
        foreach (get_class_methods($entity) as $method) {
391
            if (preg_match('/^has([A-Z].*)$/', $method, $matches)) {
392
                static::$has_methods[] = $matches[1];
393
            }
394
        }
395
    }
396
}
397