Completed
Push — master ( 6c20a7...4b2e50 )
by Ben
01:14
created

HasMagicAttributes   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 149
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 1
dl 0
loc 149
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A attr() 0 8 2
A magicAttribute() 0 19 5
A retrieveAttributeValue() 0 18 3
A retrieveValue() 0 17 3
A isMultiDimensional() 0 19 4
A pluck() 0 16 5
A retrieveProperty() 0 8 2
A camelCaseToDotSyntax() 0 4 1
A isAccessibleAsArray() 0 4 2
A isCollection() 0 4 1
1
<?php
2
3
namespace Thinktomorrow\MagicAttributes;
4
5
trait HasMagicAttributes
6
{
7
    /**
8
     * Flag that allows to allow or disallow public retrieval of the magic attributes
9
     * via the provided 'attr()' method. Set this to true in case you'd like to
10
     * control the attribute retrieval yourself. Any attempt to use
11
     * the 'attr' method will throw an exception.
12
     *
13
     * @var bool
14
     */
15
    public $disallow_magic_api = false;
16
17
    public function attr($key, $default = null, $closure = null)
18
    {
19
        if($this->disallow_magic_api){
20
            throw new DisallowedMagicAttributeUsage('Attempt to fetch magic value for ['.$key.'], but magic attribute retrieval is set to prohibited.');
21
        }
22
23
        return $this->magicAttribute($key, $default, $closure);
24
    }
25
26
    protected function magicAttribute($key, $default = null, $closure = null)
27
    {
28
        // First try to fetch the key as is.
29
        $value = $this->retrieveAttributeValue($key);
30
31
        // If this is not found, we try to fetch by converting camelcase to dot syntax as well.
32
        if(null === $value){
33
            $value = $this->retrieveAttributeValue( $this->camelCaseToDotSyntax($key) );
34
        }
35
36
        // If by now the value is still not found, we return our default
37
        if (null === $value) {
38
            return $default;
39
        }
40
41
        return (null != $closure && is_callable($closure))
42
            ? call_user_func_array($closure, [$value, $this])
43
            : $value;
44
    }
45
46
    private function retrieveAttributeValue($key)
47
    {
48
        $keys = explode('.', $key);
49
        $parent = $this;
50
        $value = null;
51
52
        foreach ($keys as $k) {
53
            $value = $this->retrieveValue($k, $parent);
54
55
            if (null === $value) {
56
                return null;
57
            }
58
59
            $parent = $value;
60
        }
61
62
        return $value;
63
    }
64
65
    private function retrieveValue($key, $data)
66
    {
67
        if (null !== ($value = $this->retrieveProperty($key, $data))) {
68
            return $value;
69
        }
70
71
        /**
72
         * At this point, we know that the key isn't present as property.
73
         * We now check if its an array consisting itself of nested items
74
         * so we can try to pluck the values by key from those arrays / objects.
75
         */
76
        if (! $this->isMultiDimensional($data)) {
77
            return null;
78
        }
79
80
        return $this->pluck($key, $data);
81
    }
82
83
    private function isMultiDimensional($data): bool
84
    {
85
        // A eloquent collection is always considered multidimensional
86
        if ($this->isCollection($data)) {
87
            return true;
88
        }
89
90
        if (!is_array($data)) {
91
            return false;
92
        }
93
94
        if (count($data) != count($data, COUNT_RECURSIVE)) {
95
            return true;
96
        }
97
98
        // If count is the same, it still could be a list of objects
99
        // which we will treat the same as a multidim. array
100
        return is_object(reset($data));
101
    }
102
103
    private function pluck($key, $list)
104
    {
105
        if (! $this->isAccessibleAsArray($list)) {
106
            return null;
107
        }
108
109
        $values = [];
110
111
        foreach ($list as $item) {
112
            if ($value = $this->retrieveProperty($key, $item)) {
113
                $values[] = $value;
114
            }
115
        }
116
117
        return count($values) > 0 ? $values : null;
118
    }
119
120
    private function retrieveProperty($key, $data)
121
    {
122
        if ($this->isAccessibleAsArray($data)) {
123
            return $data[$key] ?? null;
124
        }
125
126
        return $data->$key ?? null;
127
    }
128
129
    /**
130
     * @param $key
131
     * @return string
132
     */
133
    private function camelCaseToDotSyntax($key): string
134
    {
135
        return strtolower(preg_replace('/(?<!^)[A-Z]/', '.$0', $key));
136
    }
137
138
    private function isAccessibleAsArray($value)
139
    {
140
        return is_array($value) || $this->isCollection($value);
141
    }
142
143
    /**
144
     * Check if value is a Collection with ArrayAccess.
145
     *
146
     * @param $value
147
     * @return bool
148
     */
149
    private function isCollection($value)
150
    {
151
        return $value instanceof \ArrayAccess;
152
    }
153
}
154