DefinitionCompiler::generateDefinitions()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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