ExtensionCompilerPass::getManagedClass()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\DependencyInjection\Compiler;
15
16
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
18
use Symfony\Component\DependencyInjection\Definition;
19
use Symfony\Component\DependencyInjection\Reference;
20
21
/**
22
 * @author Thomas Rabaix <[email protected]>
23
 */
24
final class ExtensionCompilerPass implements CompilerPassInterface
25
{
26
    public function process(ContainerBuilder $container): void
27
    {
28
        $universalExtensions = [];
29
        $targets = [];
30
31
        foreach ($container->findTaggedServiceIds('sonata.admin.extension') as $id => $tags) {
32
            foreach ($tags as $attributes) {
33
                $target = false;
34
35
                if (isset($attributes['target'])) {
36
                    $target = $attributes['target'];
37
                }
38
39
                if (isset($attributes['global']) && $attributes['global']) {
40
                    $universalExtensions[$id] = $attributes;
41
                }
42
43
                if (!$target || !$container->hasDefinition($target)) {
44
                    continue;
45
                }
46
47
                $this->addExtension($targets, $target, $id, $attributes);
48
            }
49
        }
50
51
        $extensionConfig = $container->getParameter('sonata.admin.extension.map');
52
        $extensionMap = $this->flattenExtensionConfiguration($extensionConfig);
53
54
        foreach ($container->findTaggedServiceIds('sonata.admin') as $id => $attributes) {
55
            $admin = $container->getDefinition($id);
56
57
            if (!isset($targets[$id])) {
58
                $targets[$id] = new \SplPriorityQueue();
59
            }
60
61
            foreach ($universalExtensions as $extension => $extensionAttributes) {
62
                $this->addExtension($targets, $id, $extension, $extensionAttributes);
63
            }
64
65
            $extensions = $this->getExtensionsForAdmin($id, $admin, $container, $extensionMap);
66
67
            foreach ($extensions as $extension => $attributes) {
68
                if (!$container->has($extension)) {
69
                    throw new \InvalidArgumentException(sprintf(
70
                        'Unable to find extension service for id %s',
71
                        $extension
72
                    ));
73
                }
74
75
                $this->addExtension($targets, $id, $extension, $attributes);
76
            }
77
        }
78
79
        foreach ($targets as $target => $extensions) {
80
            $extensions = iterator_to_array($extensions);
81
            krsort($extensions);
82
            $admin = $container->getDefinition($target);
83
84
            foreach (array_values($extensions) as $extension) {
85
                $admin->addMethodCall('addExtension', [$extension]);
86
            }
87
        }
88
    }
89
90
    /**
91
     * @param string $id
92
     *
93
     * @return array
94
     */
95
    private function getExtensionsForAdmin($id, Definition $admin, ContainerBuilder $container, array $extensionMap)
96
    {
97
        $extensions = [];
98
        $classReflection = $subjectReflection = null;
99
100
        $excludes = $extensionMap['excludes'];
101
        unset($extensionMap['excludes']);
102
103
        foreach ($extensionMap as $type => $subjects) {
104
            foreach ($subjects as $subject => $extensionList) {
105
                if ('admins' === $type) {
106
                    if ($id === $subject) {
107
                        $extensions = array_merge($extensions, $extensionList);
108
                    }
109
                } else {
110
                    $class = $this->getManagedClass($admin, $container);
111
                    if (!class_exists($class)) {
112
                        continue;
113
                    }
114
                    $classReflection = new \ReflectionClass($class);
115
                    $subjectReflection = new \ReflectionClass($subject);
116
                }
117
118
                if ('instanceof' === $type) {
119
                    if ($subjectReflection->getName() === $classReflection->getName() || $classReflection->isSubclassOf($subject)) {
120
                        $extensions = array_merge($extensions, $extensionList);
121
                    }
122
                }
123
124
                if ('implements' === $type) {
125
                    if ($classReflection->implementsInterface($subject)) {
126
                        $extensions = array_merge($extensions, $extensionList);
127
                    }
128
                }
129
130
                if ('extends' === $type) {
131
                    if ($classReflection->isSubclassOf($subject)) {
132
                        $extensions = array_merge($extensions, $extensionList);
133
                    }
134
                }
135
136
                if ('uses' === $type) {
137
                    if ($this->hasTrait($classReflection, $subject)) {
0 ignored issues
show
Bug introduced by
It seems like $classReflection defined by $subjectReflection = null on line 98 can be null; however, Sonata\AdminBundle\Depen...ompilerPass::hasTrait() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
138
                        $extensions = array_merge($extensions, $extensionList);
139
                    }
140
                }
141
            }
142
        }
143
144
        if (isset($excludes[$id])) {
145
            $extensions = array_diff_key($extensions, $excludes[$id]);
146
        }
147
148
        return $extensions;
149
    }
150
151
    /**
152
     * Resolves the class argument of the admin to an actual class (in case of %parameter%).
153
     *
154
     * @return string
155
     */
156
    private function getManagedClass(Definition $admin, ContainerBuilder $container)
157
    {
158
        return $container->getParameterBag()->resolveValue($admin->getArgument(1));
159
    }
160
161
    /**
162
     * @return array an array with the following structure.
163
     *
164
     * [
165
     *     'excludes'   => ['<admin_id>'  => ['<extension_id>' => ['priority' => <int>]]],
166
     *     'admins'     => ['<admin_id>'  => ['<extension_id>' => ['priority' => <int>]]],
167
     *     'implements' => ['<interface>' => ['<extension_id>' => ['priority' => <int>]]],
168
     *     'extends'    => ['<class>'     => ['<extension_id>' => ['priority' => <int>]]],
169
     *     'instanceof' => ['<class>'     => ['<extension_id>' => ['priority' => <int>]]],
170
     *     'uses'       => ['<trait>'     => ['<extension_id>' => ['priority' => <int>]]],
171
     * ]
172
     */
173
    private function flattenExtensionConfiguration(array $config)
174
    {
175
        $extensionMap = [
176
            'excludes' => [],
177
            'admins' => [],
178
            'implements' => [],
179
            'extends' => [],
180
            'instanceof' => [],
181
            'uses' => [],
182
        ];
183
184
        foreach ($config as $extension => $options) {
185
            $optionsMap = array_intersect_key($options, $extensionMap);
186
187
            foreach ($optionsMap as $key => $value) {
188
                foreach ($value as $source) {
189
                    if (!isset($extensionMap[$key][$source])) {
190
                        $extensionMap[$key][$source] = [];
191
                    }
192
                    $extensionMap[$key][$source][$extension]['priority'] = $options['priority'];
193
                }
194
            }
195
        }
196
197
        return $extensionMap;
198
    }
199
200
    /**
201
     * @return bool
202
     */
203
    private function hasTrait(\ReflectionClass $class, $traitName)
204
    {
205
        if (\in_array($traitName, $class->getTraitNames(), true)) {
206
            return true;
207
        }
208
209
        if (!$parentClass = $class->getParentClass()) {
210
            return false;
211
        }
212
213
        return $this->hasTrait($parentClass, $traitName);
214
    }
215
216
    /**
217
     * Add extension configuration to the targets array.
218
     */
219
    private function addExtension(
220
        array &$targets,
221
        string $target,
222
        string $extension,
223
        array $attributes
224
    ): void {
225
        if (!isset($targets[$target])) {
226
            $targets[$target] = new \SplPriorityQueue();
227
        }
228
229
        $priority = $attributes['priority'] ?? 0;
230
        $targets[$target]->insert(new Reference($extension), $priority);
231
    }
232
}
233