FlexibleEntity::extract()   B
last analyzed

Complexity

Conditions 8
Paths 1

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.4444
c 0
b 0
f 0
cc 8
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
        $this->addModifiedColumn($var);
103
104
        return $this;
105
    }
106
107
    /**
108
     * add
109
     *
110
     * When the corresponding attribute is an array, call this method
111
     * to set values.
112
     *
113
     * @access public
114
     * @param  string         $var
115
     * @param  mixed          $value
116
     * @return FlexibleEntity $this
117
     * @throws ModelException
118
     */
119
    public function add($var, $value)
120
    {
121
        if ($this->has($var)) {
122
            if (is_array($this->container[$var])) {
123
                $this->container[$var][] = $value;
124
            } else {
125
                throw new ModelException(sprintf("Field '%s' exists and is not an array.", $var));
126
            }
127
        } else {
128
            $this->container[$var] = [$value];
129
        }
130
        $this->touch();
131
        $this->addModifiedColumn($var);
132
133
        return $this;
134
    }
135
136
    /**
137
     * clear
138
     *
139
     * Drop an attribute from the var holder.
140
     *
141
     * @final
142
     * @access public
143
     * @param  String         $offset Attribute name.
144
     * @return FlexibleEntity $this
145
     */
146
    final public function clear($offset)
147
    {
148
        if ($this->has($offset)) {
149
            unset($this->container[$offset]);
150
            $this->touch();
151
            $this->removeModifiedColumn($offset);
152
        }
153
154
        return $this;
155
    }
156
157
    /**
158
     * __call
159
     *
160
     * Allows dynamic methods getXxx, setXxx, hasXxx, addXxx or clearXxx.
161
     *
162
     * @access  public
163
     * @throws  ModelException if method does not exist.
164
     * @param   mixed $method
165
     * @param   mixed $arguments
166
     * @return  mixed
167
     */
168
    public function __call($method, $arguments)
169
    {
170
        list($operation, $attribute) = $this->extractMethodName($method);
171
172
        switch ($operation) {
173
        case 'set':
174
            return $this->set($attribute, $arguments[0]);
175
        case 'get':
176
            return $this->get($attribute);
177
        case 'add':
178
            return $this->add($attribute, $arguments[0]);
179
        case 'has':
180
            return $this->has($attribute);
181
        case 'clear':
182
            return $this->clear($attribute);
183
        default:
184
            throw new ModelException(sprintf('No such method "%s:%s()"', get_class($this), $method));
185
        }
186
    }
187
188
    /**
189
     * convert
190
     *
191
     * Make all keys lowercase and hydrate the object.
192
     *
193
     * @access  public
194
     * @param   Array          $values
195
     * @return  FlexibleEntity
196
     */
197
    public function convert(array $values)
198
    {
199
        $tmp = [];
200
201
        foreach ($values as $key => $value) {
202
            $tmp[strtolower($key)] = $value;
203
        }
204
205
        return $this->hydrate($tmp);
206
    }
207
208
    /**
209
     * extract
210
     *
211
     * Returns the fields flatten as arrays.
212
     *
213
     * The complex stuff in here is when there is an array, since all elements
214
     * in arrays are the same type, we check only its first value to know if we need
215
     * to traverse it or not.
216
     *
217
     * @see FlexibleEntityInterface
218
     */
219
    public function extract()
220
    {
221
        $array_recurse = function ($val) use (&$array_recurse) {
222
            if (is_scalar($val)) {
223
                return $val;
224
            }
225
226
            if (is_array($val)) {
227
                if (is_array(current($val)) || (is_object(current($val)) && current($val) instanceof FlexibleEntityInterface)) {
228
                    return array_map($array_recurse, $val);
229
                } else {
230
                    return $val;
231
                }
232
            }
233
234
            if (is_object($val) && $val instanceof FlexibleEntityInterface) {
235
                return $val->extract();
236
            }
237
238
            return $val;
239
        };
240
241
242
        return array_map($array_recurse, array_merge($this->container, $this->getCustomFields()));
243
    }
244
245
    /**
246
     * getCustomFields
247
     *
248
     * Return a list of custom methods with has() accessor.
249
     *
250
     * @access  private
251
     * @return  array
252
     */
253
    private function getCustomFields()
254
    {
255
        if (static::$has_methods === null) {
256
            static::fillHasMethods($this);
257
        }
258
259
        $custom_fields = [];
260
261
        foreach (static::$has_methods as $method) {
262
            if (call_user_func([$this, sprintf("has%s", $method)]) === true) {
263
                $custom_fields[Inflector::underscore(lcfirst($method))] = call_user_func([$this, sprintf("get%s", $method)]);
264
            }
265
        }
266
267
        return $custom_fields;
268
    }
269
270
    /**
271
     * getIterator
272
     *
273
     * @see FlexibleEntityInterface
274
     */
275
    public function getIterator()
276
    {
277
        return new \ArrayIterator(array_merge($this->container, $this->getCustomFields()));
278
    }
279
280
    /**
281
     * __set
282
     *
283
     * PHP magic to set attributes.
284
     *
285
     * @access  public
286
     * @param   String         $var   Attribute name.
287
     * @param   Mixed          $value Attribute value.
288
     * @return  FlexibleEntity $this
289
     */
290
    public function __set($var, $value)
291
    {
292
        $method_name = "set".Inflector::studlyCaps($var);
293
        $this->$method_name($value);
294
295
        return $this;
296
    }
297
298
    /**
299
     * __get
300
     *
301
     * PHP magic to get attributes.
302
     *
303
     * @access  public
304
     * @param   String $var Attribute name.
305
     * @return  Mixed  Attribute value.
306
     */
307
    public function __get($var)
308
    {
309
        $method_name = "get".Inflector::studlyCaps($var);
310
311
        return $this->$method_name();
312
    }
313
314
    /**
315
     * __isset
316
     *
317
     * Easy value check.
318
     *
319
     * @access  public
320
     * @param   string $var
321
     * @return  bool
322
     */
323
    public function __isset($var)
324
    {
325
        $method_name = "has".Inflector::studlyCaps($var);
326
327
        return $this->$method_name();
328
    }
329
330
    /**
331
     * __unset
332
     *
333
     * Clear an attribute.
334
     *
335
     * @access  public
336
     * @param   string $var
337
     * @return  FlexibleEntity   $this
338
     */
339
    public function __unset($var)
340
    {
341
        $method_name = "clear".Inflector::studlyCaps($var);
342
343
        return $this->$method_name();
344
    }
345
346
    /**
347
     * {@inheritdoc}
348
     */
349
    public function offsetExists($offset)
350
    {
351
        $method_name = "has".Inflector::studlyCaps($offset);
352
353
        return $this->$method_name();
354
    }
355
356
    /**
357
     * {@inheritdoc}
358
     */
359
    public function offsetSet($offset, $value)
360
    {
361
        $this->__set($offset, $value);
362
    }
363
364
    /**
365
     * {@inheritdoc}
366
     */
367
    public function offsetGet($offset)
368
    {
369
        return $this->__get($offset);
370
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375
    public function offsetUnset($offset)
376
    {
377
        $this->clear($offset);
378
    }
379
380
    /**
381
     * fillHasMethods
382
     *
383
     * When getIterator is called the first time, the list of "has" methods is
384
     * set in a static attribute to boost performances.
385
     *
386
     * @access  protected
387
     * @param   FlexibleEntity   $entity
388
     * @return  null
389
     */
390
    protected static function fillHasMethods(FlexibleEntity $entity)
391
    {
392
        static::$has_methods = [];
393
394
        foreach (get_class_methods($entity) as $method) {
395
            if (preg_match('/^has([A-Z].*)$/', $method, $matches)) {
396
                static::$has_methods[] = $matches[1];
397
            }
398
        }
399
    }
400
}
401