Completed
Push — master ( 14f560...15f305 )
by
unknown
08:01
created

ContainerManager::replaceInterfaceDependencies()   F

Complexity

Conditions 24
Paths 1530

Size

Total Lines 85
Code Lines 44

Duplication

Lines 25
Ratio 29.41 %

Importance

Changes 0
Metric Value
cc 24
eloc 44
c 0
b 0
f 0
nc 1530
nop 1
dl 25
loc 85
rs 2.1072

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace samsonphp\core\loader;
4
5
use Doctrine\Common\Annotations\AnnotationReader;
6
use samson\core\Core;
7
use samson\core\ExternalModule;
8
use samsonframework\container\Builder;
9
use samsonframework\container\ContainerBuilderInterface;
10
use samsonframework\container\ContainerInterface;
11
use samsonframework\container\metadata\ClassMetadata;
12
use samsonframework\container\metadata\MethodMetadata;
13
use samsonframework\containerannotation\AnnotationClassResolver;
14
use samsonframework\containerannotation\AnnotationMetadataCollector;
15
use samsonframework\containerannotation\AnnotationMethodResolver;
16
use samsonframework\containerannotation\AnnotationPropertyResolver;
17
use samsonframework\containerannotation\AnnotationResolver;
18
use samsonframework\containerannotation\Injectable;
19
use samsonframework\containerannotation\InjectArgument;
20
use samsonframework\containerannotation\Service;
21
use samsonframework\containercollection\attribute\ArrayValue;
22
use samsonframework\containercollection\attribute\ClassName;
23
use samsonframework\containercollection\attribute\Name;
24
use samsonframework\containercollection\attribute\Scope;
25
use samsonframework\containercollection\attribute\Value;
26
use samsonframework\containercollection\CollectionClassResolver;
27
use samsonframework\containercollection\CollectionMethodResolver;
28
use samsonframework\containercollection\CollectionParameterResolver;
29
use samsonframework\containercollection\CollectionPropertyResolver;
30
use samsonframework\containerxml\XmlMetadataCollector;
31
use samsonframework\containerxml\XmlResolver;
32
use samsonframework\core\SystemInterface;
33
use samsonframework\resource\ResourceMap;
34
35
/**
36
 * Class ContainerLoader
37
 *
38
 * @package samsonphp\container\loader
39
 * TODO Change class name
40
 */
41
class ContainerManager
42
{
43
    /** @var ContainerBuilderInterface Container builder */
44
    protected $containerBuilder;
45
    /** @var string Application path */
46
    protected $applicationPath;
47
    /** @var string Config path */
48
    protected $configPath;
49
    /** @var string Cache path */
50
    protected $cachePath;
51
52
    /**
53
     * ContainerLoader constructor.
54
     *
55
     * @param ContainerBuilderInterface $containerBuilder
56
     * @param string $applicationPath
57
     * @param string $configPath
58
     * @param string $cachePath
59
     */
60
    public function __construct(
61
        ContainerBuilderInterface $containerBuilder,
62
        string $applicationPath,
63
        string $configPath,
64
        string $cachePath
65
    ) {
66
        $this->containerBuilder = $containerBuilder;
67
        $this->applicationPath = $applicationPath;
68
        $this->configPath = $applicationPath . $configPath;
69
        $this->cachePath = $applicationPath . $cachePath;
70
    }
71
72
    /**
73
     * Get xml metadata collection
74
     *
75
     * @param array $metadataCollection
76
     * @return array
77
     * @throws \Exception
78
     */
79
    public function collectXmlMetadata(array $metadataCollection) : array
80
    {
81
        // If config path is exists
82
        if (file_exists($this->configPath)) {
83
            // Init resolver
84
            $xmlConfigurator = new XmlResolver(new CollectionClassResolver([
85
                Scope::class,
86
                Name::class,
87
                ClassName::class,
88
                \samsonframework\containercollection\attribute\Service::class
89
            ]), new CollectionPropertyResolver([
90
                ClassName::class,
91
                Value::class
92
            ]), new CollectionMethodResolver([], new CollectionParameterResolver([
93
                ClassName::class,
94
                Value::class,
95
                ArrayValue::class,
96
                \samsonframework\containercollection\attribute\Service::class
97
            ])));
98
            // Collect new metadata
99
            $xmlCollector = new XmlMetadataCollector($xmlConfigurator);
100
            return $xmlCollector->collect(file_get_contents($this->configPath), $metadataCollection);
101
        }
102
        return $metadataCollection;
103
    }
104
105
    /**
106
     * Get default property value by property name
107
     *
108
     * @param $className
109
     * @param $propertyName
110
     * @return null
111
     */
112
    public function getDefaultPropertyValue($className, $propertyName)
113
    {
114
        $reflection = new \ReflectionClass($className);
115
        $values = $reflection->getDefaultProperties();
116
        return $values[$propertyName] ?? null;
117
    }
118
119
    /**
120
     * Create metadata for module
121
     *
122
     * @param $class
123
     * @param $name
124
     * @param $path
125
     * @param string $scope
126
     * @return ClassMetadata
127
     */
128
    public function createMetadata($class, $name, $path, $scope = 'module') : ClassMetadata
129
    {
130
        $metadata = new ClassMetadata();
131
        $class = ltrim($class, '\\');
132
        $name = strtolower(ltrim($name, '\\'));
133
        $metadata->className = $class;
134
        $metadata->name = str_replace(['\\', '/'], '_', $name ?? $class);
135
        $metadata->scopes[] = Builder::SCOPE_SERVICES;
136
        $metadata->scopes[] = $scope;
137
138
        // TODO: Now we need to remove and change constructors
139
        $metadata->methodsMetadata['__construct'] = new MethodMetadata($metadata);
140
141
        // Iterate constructor arguments to preserve arguments order and inject dependencies
142
        foreach ((new \ReflectionMethod($class, '__construct'))->getParameters() as $parameter) {
143
            if ($parameter->getName() === 'path') {
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
144
                $metadata->methodsMetadata['__construct']->dependencies['path'] = $path;
145
            } elseif ($parameter->getName() === 'resources') {
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
146
                $metadata->methodsMetadata['__construct']->dependencies['resources'] = ResourceMap::class;
147
            } elseif ($parameter->getName() === 'system') {
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
148
                $metadata->methodsMetadata['__construct']->dependencies['system'] = Core::class;
149
            } elseif (!$parameter->isOptional()) {
150
                $metadata->methodsMetadata['__construct']->dependencies[$parameter->getName()] = '';
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
151
            }
152
        }
153
        return $metadata;
154
    }
155
156
    /**
157
     * Replace interface dependency on their implementation
158
     *
159
     * @param array $metadataCollection
160
     */
161
    public function replaceInterfaceDependencies(array $metadataCollection) {
162
163
        $list = [];
164
        $listPath = [];
165
        /** @var ClassMetadata $classMetadata */
166
        foreach ($metadataCollection as $classPath => $classMetadata) {
167
            $list[$classMetadata->className] = $classMetadata;
168
            $listPath[$classMetadata->name ?? $classMetadata->className] = $classPath;
169
        }
170
        $metadataCollection = $list;
171
172
        // Gather all interface implementations
173
        $implementsByAlias = $implementsByAlias ?? [];
174
        foreach (get_declared_classes() as $class) {
175
            $classImplements = class_implements($class);
176
            foreach (get_declared_interfaces() as $interface) {
177
                if (in_array($interface, $classImplements, true)) {
178
                    if (array_key_exists($class, $metadataCollection)) {
179
                        $implementsByAlias[$interface][] = $metadataCollection[$class]->name;
180
                    }
181
                }
182
            }
183
        }
184
185
        // Gather all class implementations
186
        $serviceAliasesByClass = $serviceAliasesByClass ?? [];
187
        foreach (get_declared_classes() as $class) {
188
            if (array_key_exists($class, $metadataCollection)) {
189
                $serviceAliasesByClass[$class][] = $metadataCollection[$class]->name;
190
            }
191
        }
192
193
        /**
194
         * TODO: now we need to implement not forcing to load fixed dependencies into modules
195
         * to give ability to change constructors and inject old variable into properties
196
         * and them after refactoring remove them. With this we can only specify needed dependencies
197
         * in new modules, and still have old ones working.
198
         */
199
200
        foreach ($metadataCollection as $alias => $metadata) {
201 View Code Duplication
            foreach ($metadata->propertiesMetadata as $property => $propertyMetadata) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
                if (is_string($propertyMetadata->dependency)) {
203
                    $dependency = $propertyMetadata->dependency;
204
                    if (array_key_exists($dependency, $implementsByAlias)) {
205
                        $propertyMetadata->dependency = $implementsByAlias[$dependency][0];
206
                    } elseif (array_key_exists($dependency, $serviceAliasesByClass)) {
207
                        $propertyMetadata->dependency = $serviceAliasesByClass[$dependency][0];
208
                    } else {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
209
210
                    }
211
                }
212
            }
213
214
            // Iterate constructor arguments to preserve arguments order and inject dependencies
215
            $reflectionClass = new \ReflectionClass($metadata->className);
216
            // Check if instance has a constructor or it instance of external module
217
            if ($reflectionClass->hasMethod('__construct') && is_subclass_of($metadata->className, ExternalModule::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \samson\core\ExternalModule::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
218
                foreach ((new \ReflectionMethod($metadata->className, '__construct'))->getParameters() as $parameter) {
219
                    if ($parameter->getName() === 'path') {
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
220
                        $metadata->methodsMetadata['__construct']->dependencies['path'] = dirname($listPath[$metadata->name ?? $metadata->className]);
221
                    } elseif ($parameter->getName() === 'resources') {
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
222
                        $metadata->methodsMetadata['__construct']->dependencies['resources'] = ResourceMap::class;
223
                    } elseif ($parameter->getName() === 'system') {
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
224
                        $metadata->methodsMetadata['__construct']->dependencies['system'] = 'core';
225
                    }
226
                }
227
            }
228
229
            foreach ($metadata->methodsMetadata as $method => $methodMetadata) {
230 View Code Duplication
                foreach ($methodMetadata->dependencies as $argument => $dependency) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
231
                    if (is_string($dependency)) {
232
                        if (array_key_exists($dependency, $implementsByAlias)) {
233
                            $methodMetadata->dependencies[$argument] = $implementsByAlias[$dependency][0];
234
                            //$methodMetadata->parametersMetadata[$argument]->dependency = $implementsByAlias[$dependency][0];
235
                        } elseif (array_key_exists($dependency, $serviceAliasesByClass)) {
236
                            $methodMetadata->dependencies[$argument] = $serviceAliasesByClass[$dependency][0];
237
                            //$methodMetadata->parametersMetadata[$argument]->dependency = $serviceAliasesByClass[$dependency][0];
238
                        } else {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
239
240
                        }
241
                    }
242
                }
243
            }
244
        }
245
    }
246
247
    /**
248
     * @param ClassMetadata[] $metadataCollection
249
     * @param string $containerName
250
     * @param ContainerInterface $parentContainer
251
     * @return ContainerInterface
252
     */
253
    public function createContainer(array $metadataCollection, $containerName = 'Container', ContainerInterface $parentContainer = null) : ContainerInterface
254
    {
255
        $containerPath = $this->cachePath . $containerName . '.php';
256
        file_put_contents($containerPath, $this->containerBuilder->build($metadataCollection, $containerName, '', $parentContainer));
0 ignored issues
show
Unused Code introduced by
The call to ContainerBuilderInterface::build() has too many arguments starting with $parentContainer.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
257
        require_once($containerPath);
258
259
        return new $containerName();
260
    }
261
262
    /**
263
     * Get annotation metadata
264
     *
265
     * @param $classes
266
     * @param $metadataCollection
267
     * @return array|\samsonframework\container\metadata\ClassMetadata[]
268
     */
269
    public function collectAnnotationMetadata($classes, $metadataCollection)
270
    {
271
        // Load annotation and parse classes
272
        new Injectable();
273
        new InjectArgument(['var' => 'type']);
274
        new Service(['value' => '']);
275
276
        $reader = new AnnotationReader();
277
        $resolver = new AnnotationResolver(
278
            new AnnotationClassResolver($reader),
279
            new AnnotationPropertyResolver($reader),
280
            new AnnotationMethodResolver($reader)
281
        );
282
283
        $annotationCollector = new AnnotationMetadataCollector($resolver);
284
285
        // Rewrite collection by entity name
286
        return $annotationCollector->collect($classes, $metadataCollection);
287
    }
288
289
    /**
290
     * Convert class name to alias
291
     *
292
     * @param $className
293
     * @return string
294
     */
295
    public function classNameToAlias(string $className) : string
296
    {
297
        return str_replace(['\\', '/'], '_', $className);
298
    }
299
}
300