PrivateStaticTransformer::isConfigValue()   A
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6.288

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 10
cp 0.8
rs 9.2222
c 0
b 0
f 0
cc 6
nc 4
nop 1
crap 6.288
1
<?php
2
3
namespace SilverStripe\Config\Transformer;
4
5
use SilverStripe\Config\Collections\MutableConfigCollectionInterface;
6
use ReflectionClass;
7
use ReflectionProperty;
8
use LogicException;
9
10
class PrivateStaticTransformer implements TransformerInterface
11
{
12
    /**
13
     * @var array|callable
14
     */
15
    protected $classes = null;
16
17
    /**
18
     * @var int
19
     */
20
    protected $sort = 0;
21
22
    /**
23
     * @param array|callable $classes List of classes, or callback to lazy-load
24
     */
25 4
    public function __construct($classes)
26
    {
27 4
        $this->classes = $classes;
28 4
    }
29
30
    /**
31
     * This loops through each class and fetches the private static config for each class.
32
     *
33
     * @param MutableConfigCollectionInterface $collection
34
     * @return MutableConfigCollectionInterface
35
     */
36 4
    public function transform(MutableConfigCollectionInterface $collection)
37
    {
38
        // Lazy-resolve class list
39 4
        $classes = $this->getClasses();
40
41 4
        foreach ($classes as $class) {
42
            // Skip if the class doesn't exist
43 4
            if (!class_exists($class)) {
44 1
                continue;
45
            }
46
47
            // returns an array of value and metadata
48 3
            $item = $this->getClassConfig($class);
49
50
            // Add the item to the collection
51 3
            $collection->set($class, null, $item['value'], $item['metadata']);
52 4
        }
53
54 3
        return $collection;
55
    }
56
57
    /**
58
     * This is responsible for introspecting a given class and returning an
59
     * array continaing all of its private statics
60
     *
61
     * @param  string $class
62
     * @return mixed
63
     */
64 3
    protected function getClassConfig($class)
65
    {
66 3
        $reflection = new ReflectionClass($class);
67
68
        /** @var ReflectionProperty[] $props **/
69 3
        $props = $reflection->getProperties(ReflectionProperty::IS_STATIC);
70
71 3
        $classConfig = [];
72 3
        foreach ($props as $prop) {
73
            // Check if this property is configurable
74 3
            if (!$this->isConfigProperty($prop)) {
75 2
                continue;
76
            }
77
78
            // Note that some non-config private statics may be assigned
79
            // un-serializable values. Detect these here
80 3
            $prop->setAccessible(true);
81 3
            $value = $prop->getValue();
82 3
            if ($this->isConfigValue($value)) {
83 3
                $classConfig[$prop->getName()] = $value;
84 3
            }
85 3
        }
86
87
        // Create the metadata for our new item
88
        $metadata = [
89 3
            'filename' => $reflection->getFileName(),
90 3
            'class' => $class,
91 3
            'transformer' => static::class
92 3
        ];
93
94 3
        return ['value' => $classConfig, 'metadata' => $metadata];
95
    }
96
97
    /**
98
     * Is a var config or not?
99
     *
100
     * @param ReflectionProperty $prop
101
     * @return bool
102
     */
103 3
    protected function isConfigProperty(ReflectionProperty $prop)
104
    {
105 3
        if (!$prop->isPrivate()) {
106
            // If this non-private static overrides any private configs, make this an error
107 3
            $class = $prop->getDeclaringClass();
108 3
            while ($class = $class->getParentClass()) {
109 1
                if (!$class->hasProperty($prop->getName())) {
110
                    continue;
111
                }
112 1
                $parentProp = $class->getProperty($prop->getName());
113 1
                if (!$parentProp->isPrivate()) {
114
                    continue;
115
                }
116 1
                throw new LogicException(
117 1
                    $prop->getDeclaringClass()->getName().'::'.$prop->getName()
0 ignored issues
show
introduced by
Consider using $prop->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
118 1
                    . ' is not private but overrides a private static config in parent class '
119 1
                    . $class->getName()
120 1
                );
121
            }
122 2
            return false;
123
        }
124 3
        $annotations = $prop->getDocComment();
125
        // Whitelist @config
126 3
        if (strstr($annotations, '@config')) {
127
            return true;
128
        }
129
        // Don't treat @internal as config
130 3
        if (strstr($annotations, '@internal')) {
131
            return false;
132
        }
133 3
        return true;
134
    }
135
136
    /**
137
     * Detect if a value is a valid config
138
     *
139
     * @param mixed $input
140
     * @return true
141
     */
142 3
    protected function isConfigValue($input)
143
    {
144 3
        if (is_object($input) || is_resource($input)) {
145
            return false;
146
        }
147 3
        if (is_array($input)) {
148 2
            foreach ($input as $next) {
149 2
                if (!$this->isConfigValue($next)) {
150
                    return false;
151
                }
152 2
            }
153 2
        }
154 3
        return true;
155
    }
156
157
    /**
158
     * @return array
159
     */
160 4
    public function getClasses()
161
    {
162 4
        if (!is_array($this->classes) && is_callable($this->classes)) {
163
            return call_user_func($this->classes);
164
        }
165
166 4
        return $this->classes;
167
    }
168
}
169