Completed
Push — master ( 00ced4...d3edd8 )
by
unknown
07:54
created

DefinitionCompiler::getClassDependencies()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 10
cts 10
cp 1
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 12
nc 7
nop 1
crap 5
1
<?php declare(strict_types = 1);
2
/**
3
 * Created by PhpStorm.
4
 * User: root
5
 * Date: 02.08.16
6
 * Time: 0:46.
7
 */
8
namespace samsonframework\container\definition\builder;
9
10
use samsonframework\container\ContainerInterface;
11
use samsonframework\container\definition\AbstractPropertyDefinition;
12
use samsonframework\container\definition\analyzer\DefinitionAnalyzer;
13
use samsonframework\container\definition\analyzer\exception\ParameterNotFoundException;
14
use samsonframework\container\definition\builder\exception\ImplementerForTypeNotFoundException;
15
use samsonframework\container\definition\exception\ClassDefinitionAlreadyExistsException;
16
use samsonframework\container\definition\builder\exception\ReferenceNotImplementsException;
17
use samsonframework\container\definition\reference\ClassReference;
18
use samsonframework\container\definition\reference\ReferenceInterface;
19
use samsonframework\di\Container;
20
21
/**
22
 * Class DefinitionCompiler
23
 *
24
 * @author Ruslan Molodyko <[email protected]>
25
 */
26
class DefinitionCompiler
27
{
28
    /** @var DefinitionGenerator */
29
    protected $generator;
30
31
    /** @var DefinitionAnalyzer */
32
    protected $analyzer;
33
34
    /** @var array All registered dependencies*/
35
    protected $dependencies = [];
36
37
    /**
38
     * DefinitionCompiler constructor.
39
     *
40
     * @param DefinitionGenerator $generator
41
     * @param DefinitionAnalyzer $analyzer
42
     */
43 4
    public function __construct(DefinitionGenerator $generator, DefinitionAnalyzer $analyzer)
44
    {
45 4
        $this->generator = $generator;
46 4
        $this->analyzer = $analyzer;
47 4
    }
48
49
    /**
50
     * Compile and get container
51
     *
52
     * @param DefinitionBuilder $definitionBuilder
53
     * @param $containerName
54
     * @param $namespace
55
     * @param $containerDir
56
     * @return ContainerInterface
57
     * @throws ImplementerForTypeNotFoundException
58
     * @throws ParameterNotFoundException
59
     * @throws ClassDefinitionAlreadyExistsException
60
     * @throws ReferenceNotImplementsException
61
     * @throws \InvalidArgumentException
62
     */
63 2
    public function compile(DefinitionBuilder $definitionBuilder, $containerName, $namespace, $containerDir)
64
    {
65 2
        $this->generator->getClassGenerator()
66 2
            ->defName($containerName)
67 2
            ->defExtends('BaseContainer')
68 2
            ->defNamespace($namespace)
69 2
            ->defUse(Container::class, 'BaseContainer');
70
71
        // Max count analyzer iterations
72 2
        $count = 10;
73
74
        /**
75
         * Analyze builder metadata
76
         *
77
         * 1. Analyze definitions
78
         * 2. Add dependencies by analyzers
79
         * 3. Append missing definitions for dependencies
80
         * ... Analyze again
81
         */
82 2
        while ($this->analyzer->analyze($definitionBuilder)) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
83
84
            // Get dependencies
85 2
            $dependencies = $this->getClassDependencies($definitionBuilder);
86
            // Generate metadata
87 2
            $this->generateDefinitions($definitionBuilder, $dependencies);
88
89
            // Wrong behavior
90 2
            if ($count === 0) {
91
                throw new \InvalidArgumentException('Wrong analyze');
92
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
93
            }
94 2
            $count--;
95
        }
96
97
        // Get container code
98 2
        $code = $this->generator->generateClass($definitionBuilder);
99
        // Get file path
100 2
        $containerFilePath = rtrim($containerDir, '/') . '/' . $containerName . '.php';
101
        // Save file
102 2
        file_put_contents($containerFilePath, $code);
103
        // Get container class name
104
        $className = $namespace . '\\' . $containerName;
105
        // Require container
106
        require_once($containerFilePath);
107
        // Instantiate container
108
        return new $className();
109
    }
110
111
    /**
112
     * Get class dependencies form definition builder
113
     *
114
     * @param DefinitionBuilder $definitionBuilder
115
     * @return array
116
     * @throws ImplementerForTypeNotFoundException
117
     */
118 4
    protected function getClassDependencies(DefinitionBuilder $definitionBuilder): array
119
    {
120 4
        $dependencyList = [];
121
        // Get dependencies which will be used for generation definitions
122 4
        foreach ($definitionBuilder->getDefinitionCollection() as $classDefinition) {
123
            // Iterate properties and get their dependencies
124 4
            foreach ($classDefinition->getPropertiesCollection() as $propertyDefinition) {
125
                // Add dependency to list if valid
126 2
                $this->addDependency($dependencyList, $propertyDefinition->getDependency(), $propertyDefinition);
127
            }
128 4
            foreach ($classDefinition->getMethodsCollection() as $methodDefinition) {
129 4
                foreach ($methodDefinition->getParametersCollection() as $parameterDefinition) {
130 4
                    $this->addDependency(
131
                        $dependencyList,
132 4
                        $parameterDefinition->getDependency(),
133
                        $parameterDefinition
134
                    );
135
                }
136
            }
137
        }
138 4
        return $dependencyList;
139
    }
140
141
    /**
142
     * Generate definitions from dependencies for builder
143
     *
144
     * @param DefinitionBuilder $definitionBuilder
145
     * @param array $dependencyList
146
     * @throws ClassDefinitionAlreadyExistsException
147
     * @throws ImplementerForTypeNotFoundException
148
     * @throws \InvalidArgumentException
149
     */
150 3
    protected function generateDefinitions(
151
        DefinitionBuilder $definitionBuilder,
152
        array $dependencyList
153
    ) {
154
        // Iterate all classes and auto generate definition for missing
155 3
        foreach ($dependencyList as $className => $classReference) {
156 3
            if (!$definitionBuilder->hasDefinition($className)) {
157 3
                $definitionBuilder->addDefinition($className);
158
            }
159
        }
160 3
    }
161
162
    /**
163
     * Check if class name is interface or abstract class
164
     *
165
     * @param string $className
166
     * @return bool
167
     */
168 4
    protected function checkIfType(string $className): bool
169
    {
170 4
        $reflectionClass = new \ReflectionClass($className);
171 4
        return $reflectionClass->isInterface() || $reflectionClass->isAbstract();
172
    }
173
174
    /**
175
     * Get class which implements the interface
176
     *
177
     * @param string $interfaceName
178
     * @return string
179
     * @throws ImplementerForTypeNotFoundException
180
     * TODO Add interface resolvers functionality
181
     */
182
    protected function resolveTypeImplementer(string $interfaceName): string
183
    {
184
        // Gather all interface implementations
185
        foreach (get_declared_classes() as $class) {
186
            $classImplements = class_implements($class);
187
            // Remove slash for start of interface
188
            if (in_array(ltrim($interfaceName, '\\'), $classImplements, true)) {
189
                return $class;
190
            }
191
        }
192
        throw new ImplementerForTypeNotFoundException(
193
            sprintf('Type "%s" does not have some implementers', $interfaceName)
194
        );
195
    }
196
197
    /**
198
     * Add dependencies which then will be use for automatic creation the definitions
199
     *
200
     * @param array $dependencyList
201
     * @param ReferenceInterface $reference
202
     * @param AbstractPropertyDefinition $definition
203
     * @return array
204
     * @throws ImplementerForTypeNotFoundException
205
     */
206 4
    protected function addDependency(
207
        array &$dependencyList,
208
        ReferenceInterface $reference,
209
        AbstractPropertyDefinition $definition
210
    ) {
211
        // Add class dependency to list
212 4
        if ($reference instanceof ClassReference) {
213 4
            $className = $reference->getClassName();
214
            // When there is not simple class then resolve it by type
215 4
            if ($this->checkIfType($className)) {
216
                $className = $this->resolveTypeImplementer($className);
217
                $reference = new ClassReference($className);
218
                // Set new implementer dependency instead of type one
219
                $definition->setDependency($reference);
220
            }
221 4
            $dependencyList[$className] = $reference;
222
        }
223 4
    }
224
}
225