1 | <?php |
||
39 | class FactoryManagerImpl implements FactoryManager |
||
40 | { |
||
41 | /** |
||
42 | * The name of the resource repository variable. |
||
43 | */ |
||
44 | const REPO_VAR_NAME = 'repo'; |
||
45 | |||
46 | /** |
||
47 | * The name of the discovery variable. |
||
48 | */ |
||
49 | const DISCOVERY_VAR_NAME = 'discovery'; |
||
50 | |||
51 | /** |
||
52 | * @var ProjectContext |
||
53 | */ |
||
54 | private $context; |
||
55 | |||
56 | /** |
||
57 | * @var Config |
||
58 | */ |
||
59 | private $config; |
||
60 | |||
61 | /** |
||
62 | * @var string |
||
63 | */ |
||
64 | private $rootDir; |
||
65 | |||
66 | /** |
||
67 | * @var GeneratorRegistry |
||
68 | */ |
||
69 | private $generatorRegistry; |
||
70 | |||
71 | /** |
||
72 | * @var ClassWriter |
||
73 | */ |
||
74 | private $classWriter; |
||
75 | |||
76 | /** |
||
77 | * @var ModuleList |
||
78 | */ |
||
79 | private $modules; |
||
80 | |||
81 | /** |
||
82 | * @var ServerCollection |
||
83 | */ |
||
84 | private $servers; |
||
85 | |||
86 | /** |
||
87 | * Creates a new factory generator. |
||
88 | * |
||
89 | * @param ProjectContext $context The project context. |
||
90 | * @param GeneratorRegistry $generatorRegistry The registry providing |
||
91 | * the generators for the |
||
92 | * services returned by the |
||
93 | * factory. |
||
94 | * @param ClassWriter $classWriter The writer that writes |
||
95 | * the class to a file. |
||
96 | * @param ModuleList|null $modules The loaded modules. |
||
97 | * @param ServerCollection|null $servers The configured servers. |
||
98 | */ |
||
99 | 44 | public function __construct(ProjectContext $context, GeneratorRegistry $generatorRegistry, ClassWriter $classWriter, ModuleList $modules = null, ServerCollection $servers = null) |
|
100 | { |
||
101 | 44 | $this->context = $context; |
|
102 | 44 | $this->config = $context->getConfig(); |
|
103 | 44 | $this->rootDir = $context->getRootDirectory(); |
|
104 | 44 | $this->generatorRegistry = $generatorRegistry; |
|
105 | 44 | $this->classWriter = $classWriter; |
|
106 | 44 | $this->modules = $modules; |
|
107 | 44 | $this->servers = $servers; |
|
108 | 44 | } |
|
109 | |||
110 | /** |
||
111 | * Sets the modules included in the getModuleOrder() method. |
||
112 | * |
||
113 | * @param ModuleList $modules The loaded modules. |
||
114 | */ |
||
115 | 14 | public function setModules(ModuleList $modules) |
|
116 | { |
||
117 | 14 | $this->modules = $modules; |
|
118 | 14 | } |
|
119 | |||
120 | /** |
||
121 | * Sets the servers included in the createUrlGenerator() method. |
||
122 | * |
||
123 | * @param ServerCollection $servers The configured servers. |
||
124 | */ |
||
125 | 14 | public function setServers(ServerCollection $servers) |
|
126 | { |
||
127 | 14 | $this->servers = $servers; |
|
128 | 14 | } |
|
129 | |||
130 | /** |
||
131 | * {@inheritdoc} |
||
132 | */ |
||
133 | 13 | public function createFactory($path = null, $className = null) |
|
134 | { |
||
135 | 13 | Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s'); |
|
136 | 13 | Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s'); |
|
137 | |||
138 | 13 | $this->refreshFactoryClass($path, $className); |
|
139 | |||
140 | 13 | $className = $className ?: $this->config->get(Config::FACTORY_IN_CLASS); |
|
141 | 13 | $path = $path ?: $this->config->get(Config::FACTORY_IN_FILE); |
|
142 | |||
143 | 13 | if (null !== $path && !class_exists($className, false)) { |
|
144 | 12 | require_once Path::makeAbsolute($path, $this->rootDir); |
|
145 | } |
||
146 | |||
147 | 13 | return new $className(); |
|
148 | } |
||
149 | |||
150 | /** |
||
151 | * {@inheritdoc} |
||
152 | */ |
||
153 | 1 | public function isFactoryClassAutoGenerated() |
|
157 | |||
158 | /** |
||
159 | * {@inheritdoc} |
||
160 | */ |
||
161 | 32 | public function generateFactoryClass($path = null, $className = null) |
|
162 | { |
||
163 | 32 | Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s'); |
|
164 | 30 | Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s'); |
|
165 | |||
166 | 28 | $path = Path::makeAbsolute($path ?: $this->config->get(Config::FACTORY_OUT_FILE), $this->rootDir); |
|
167 | 28 | $className = $className ?: $this->config->get(Config::FACTORY_OUT_CLASS); |
|
168 | 28 | $dispatcher = $this->context->getEventDispatcher(); |
|
169 | |||
170 | 28 | $class = new Clazz($className); |
|
171 | 28 | $class->setFilePath($path); |
|
172 | 28 | $class->setDescription( |
|
173 | <<<'EOF' |
||
174 | Creates Puli's core services. |
||
175 | |||
176 | This class was auto-generated by Puli. |
||
177 | |||
178 | IMPORTANT: Before modifying the code below, set the "factory.auto-generate" |
||
179 | configuration key to false: |
||
180 | |||
181 | $ puli config factory.auto-generate false |
||
182 | |||
183 | 28 | Otherwise any modifications will be overwritten! |
|
184 | EOF |
||
185 | ); |
||
186 | |||
187 | 28 | $this->addCreateRepositoryMethod($class); |
|
188 | 28 | $this->addCreateDiscoveryMethod($class); |
|
189 | 28 | $this->addCreateUrlGeneratorMethod($class); |
|
190 | 28 | $this->addGetModuleOrderMethod($class); |
|
191 | |||
192 | 28 | if ($dispatcher->hasListeners(PuliEvents::GENERATE_FACTORY)) { |
|
193 | 1 | $dispatcher->dispatch(PuliEvents::GENERATE_FACTORY, new GenerateFactoryEvent($class)); |
|
194 | } |
||
195 | |||
196 | 28 | $this->classWriter->writeClass($class); |
|
197 | 28 | } |
|
198 | |||
199 | /** |
||
200 | * {@inheritdoc} |
||
201 | */ |
||
202 | 3 | public function autoGenerateFactoryClass($path = null, $className = null) |
|
210 | |||
211 | /** |
||
212 | * {@inheritdoc} |
||
213 | */ |
||
214 | 24 | public function refreshFactoryClass($path = null, $className = null) |
|
215 | { |
||
216 | 24 | Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s'); |
|
217 | 24 | Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s'); |
|
218 | |||
219 | 24 | $path = Path::makeAbsolute($path ?: $this->config->get(Config::FACTORY_OUT_FILE), $this->rootDir); |
|
220 | 24 | $className = $className ?: $this->config->get(Config::FACTORY_OUT_CLASS); |
|
221 | |||
222 | 24 | if (!$this->config->get(Config::FACTORY_AUTO_GENERATE)) { |
|
223 | 1 | return; |
|
224 | } |
||
225 | |||
226 | 23 | if (!file_exists($path)) { |
|
227 | 17 | $this->generateFactoryClass($path, $className); |
|
228 | |||
229 | 17 | return; |
|
230 | } |
||
231 | |||
232 | 6 | $rootModuleFile = $this->context->getRootModuleFile()->getPath(); |
|
233 | |||
234 | 6 | if (!file_exists($rootModuleFile)) { |
|
235 | 1 | return; |
|
236 | } |
||
237 | |||
238 | // Regenerate file if the configuration has changed and |
||
239 | // auto-generation is enabled |
||
240 | 5 | clearstatcache(true, $rootModuleFile); |
|
241 | 5 | $lastConfigChange = filemtime($rootModuleFile); |
|
242 | |||
243 | 5 | $configFile = $this->context->getConfigFile() |
|
244 | 5 | ? $this->context->getConfigFile()->getPath() |
|
245 | 5 | : ''; |
|
246 | |||
247 | 5 | if (file_exists($configFile)) { |
|
248 | 2 | clearstatcache(true, $configFile); |
|
249 | 2 | $lastConfigChange = max(filemtime($configFile), $lastConfigChange); |
|
250 | } |
||
251 | |||
252 | 5 | clearstatcache(true, $path); |
|
253 | 5 | $lastFactoryUpdate = filemtime($path); |
|
254 | |||
255 | 5 | if ($lastConfigChange > $lastFactoryUpdate) { |
|
256 | 3 | $this->generateFactoryClass($path, $className); |
|
257 | } |
||
258 | 5 | } |
|
259 | |||
260 | /** |
||
261 | * Adds the createRepository() method. |
||
262 | * |
||
263 | * @param Clazz $class The factory class model. |
||
264 | */ |
||
265 | 28 | private function addCreateRepositoryMethod(Clazz $class) |
|
297 | |||
298 | /** |
||
299 | * Adds the createDiscovery() method. |
||
300 | * |
||
301 | * @param Clazz $class The factory class model. |
||
302 | */ |
||
303 | 28 | private function addCreateDiscoveryMethod(Clazz $class) |
|
344 | |||
345 | /** |
||
346 | * Adds the createUrlGenerator() method. |
||
347 | * |
||
348 | * @param Clazz $class The factory class model. |
||
349 | */ |
||
350 | 28 | public function addCreateUrlGeneratorMethod(Clazz $class) |
|
351 | { |
||
352 | 28 | $class->addImport(new Import('Puli\Discovery\Api\Discovery')); |
|
353 | 28 | $class->addImport(new Import('Puli\Manager\Api\Server\ServerCollection')); |
|
354 | 28 | $class->addImport(new Import('Puli\UrlGenerator\Api\UrlGenerator')); |
|
355 | 28 | $class->addImport(new Import('Puli\UrlGenerator\DiscoveryUrlGenerator')); |
|
356 | 28 | $class->addImport(new Import('RuntimeException')); |
|
357 | |||
358 | 28 | $method = new Method('createUrlGenerator'); |
|
359 | 28 | $method->setDescription('Creates the URL generator.'); |
|
360 | |||
361 | 28 | $arg = new Argument('discovery'); |
|
362 | 28 | $arg->setTypeHint('Discovery'); |
|
363 | 28 | $arg->setType('Discovery'); |
|
364 | 28 | $arg->setDescription('The discovery to read from.'); |
|
365 | 28 | $method->addArgument($arg); |
|
366 | |||
367 | 28 | $method->setReturnValue(new ReturnValue('$generator', 'UrlGenerator', 'The created URL generator.')); |
|
368 | |||
369 | 28 | $method->addBody( |
|
370 | <<<EOF |
||
371 | if (!interface_exists('Puli\UrlGenerator\Api\UrlGenerator')) { |
||
372 | throw new RuntimeException('Please install puli/url-generator to create UrlGenerator instances.'); |
||
373 | } |
||
374 | |||
375 | EOF |
||
376 | ); |
||
377 | |||
378 | 28 | $urlFormatsString = ''; |
|
379 | |||
380 | 28 | foreach ($this->servers as $server) { |
|
381 | 20 | $urlFormatsString .= sprintf( |
|
382 | 20 | "\n %s => %s,", |
|
383 | 20 | var_export($server->getName(), true), |
|
384 | 20 | var_export($server->getUrlFormat(), true) |
|
385 | ); |
||
386 | } |
||
387 | |||
388 | 28 | if ($urlFormatsString) { |
|
389 | 20 | $urlFormatsString .= "\n"; |
|
390 | } |
||
391 | |||
392 | 28 | $method->addBody("\$generator = new DiscoveryUrlGenerator(\$discovery, array($urlFormatsString));"); |
|
|
|||
393 | |||
394 | 28 | $class->addMethod($method); |
|
395 | 28 | } |
|
396 | |||
397 | /** |
||
398 | * Adds the getModuleOrder() method. |
||
399 | * |
||
400 | * @param Clazz $class The factory class model. |
||
401 | */ |
||
402 | 28 | public function addGetModuleOrderMethod(Clazz $class) |
|
434 | |||
435 | /** |
||
436 | * Recursively camelizes the keys of an array. |
||
437 | * |
||
438 | * @param array $array The array to process. |
||
439 | * |
||
440 | * @return array The input array with camelized keys. |
||
441 | */ |
||
442 | 28 | private function camelizeKeys(array $array) |
|
454 | |||
455 | /** |
||
456 | * Camelizes a string. |
||
457 | * |
||
458 | * @param string $string A string. |
||
459 | * |
||
460 | * @return string The camelized string. |
||
461 | */ |
||
462 | private function camelize($string) |
||
468 | } |
||
469 |
It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.