Completed
Pull Request — master (#33)
by Bernhard
22:58
created

FactoryManagerImpl::setServers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of the puli/manager package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Manager\Factory;
13
14
use Puli\Manager\Api\Config\Config;
15
use Puli\Manager\Api\Context\ProjectContext;
16
use Puli\Manager\Api\Event\GenerateFactoryEvent;
17
use Puli\Manager\Api\Event\PuliEvents;
18
use Puli\Manager\Api\Factory\FactoryManager;
19
use Puli\Manager\Api\Factory\Generator\GeneratorRegistry;
20
use Puli\Manager\Api\Php\Argument;
21
use Puli\Manager\Api\Php\Clazz;
22
use Puli\Manager\Api\Php\Import;
23
use Puli\Manager\Api\Php\Method;
24
use Puli\Manager\Api\Php\ReturnValue;
25
use Puli\Manager\Api\Server\ServerCollection;
26
use Puli\Manager\Assert\Assert;
27
use Puli\Manager\Php\ClassWriter;
28
use Webmozart\PathUtil\Path;
29
30
/**
31
 * The default {@link FactoryManager} implementation.
32
 *
33
 * @since  1.0
34
 *
35
 * @author Bernhard Schussek <[email protected]>
36
 */
37
class FactoryManagerImpl implements FactoryManager
38
{
39
    /**
40
     * The name of the resource repository variable.
41
     */
42
    const REPO_VAR_NAME = 'repo';
43
44
    /**
45
     * The name of the discovery variable.
46
     */
47
    const DISCOVERY_VAR_NAME = 'discovery';
48
49
    /**
50
     * @var ProjectContext
51
     */
52
    private $context;
53
54
    /**
55
     * @var Config
56
     */
57
    private $config;
58
59
    /**
60
     * @var string
61
     */
62
    private $rootDir;
63
64
    /**
65
     * @var GeneratorRegistry
66
     */
67
    private $generatorRegistry;
68
69
    /**
70
     * @var ClassWriter
71
     */
72
    private $classWriter;
73
74
    /**
75
     * @var ServerCollection
76
     */
77
    private $servers;
78
79
    /**
80
     * Creates a new factory generator.
81
     *
82
     * @param ProjectContext        $context           The project context.
83
     * @param GeneratorRegistry     $generatorRegistry The registry providing
84
     *                                                 the generators for the
85
     *                                                 services returned by the
86
     *                                                 factory.
87
     * @param ClassWriter           $classWriter       The writer that writes
88
     *                                                 the class to a file.
89
     * @param ServerCollection|null $servers           The configured servers.
90
     */
91 29
    public function __construct(ProjectContext $context, GeneratorRegistry $generatorRegistry, ClassWriter $classWriter, ServerCollection $servers = null)
92
    {
93 29
        $this->context = $context;
94 29
        $this->config = $context->getConfig();
95 29
        $this->rootDir = $context->getRootDirectory();
96 29
        $this->generatorRegistry = $generatorRegistry;
97 29
        $this->classWriter = $classWriter;
98 29
        $this->servers = $servers;
99 29
    }
100
101
    /**
102
     * Sets the servers included in the createUrlGenerator() method.
103
     *
104
     * @param ServerCollection $servers The configured servers.
105
     */
106
    public function setServers(ServerCollection $servers)
107
    {
108
        $this->servers = $servers;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 5
    public function createFactory($path = null, $className = null)
115
    {
116 5
        Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s');
117 5
        Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s');
118
119 5
        $this->refreshFactoryClass($path, $className);
120
121 5
        $className = $className ?: $this->config->get(Config::FACTORY_IN_CLASS);
122 5
        $path = $path ?: $this->config->get(Config::FACTORY_IN_FILE);
123
124 5
        if (null !== $path && !class_exists($className, false)) {
125 4
            require_once Path::makeAbsolute($path, $this->rootDir);
126 4
        }
127
128 5
        return new $className();
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134 1
    public function isFactoryClassAutoGenerated()
135
    {
136 1
        return $this->config->get(Config::FACTORY_AUTO_GENERATE);
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 23
    public function generateFactoryClass($path = null, $className = null)
143
    {
144 23
        Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s');
145 21
        Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s');
146
147 19
        $path = Path::makeAbsolute($path ?: $this->config->get(Config::FACTORY_OUT_FILE), $this->rootDir);
148 19
        $className = $className ?: $this->config->get(Config::FACTORY_OUT_CLASS);
149 19
        $dispatcher = $this->context->getEventDispatcher();
150
151 19
        $class = new Clazz($className);
152 19
        $class->setFilePath($path);
153 19
        $class->setDescription(
154
<<<EOF
155
Creates Puli's core services.
156
157
This class was auto-generated by Puli.
158
159
IMPORTANT: Before modifying the code below, set the "factory.auto-generate"
160
configuration key to false:
161
162
    $ puli config factory.auto-generate false
163
164
Otherwise any modifications will be overwritten!
165
EOF
166 19
        );
167
168 19
        $this->addCreateRepositoryMethod($class);
169 19
        $this->addCreateDiscoveryMethod($class);
170 19
        $this->addCreateUrlGeneratorMethod($class);
171
172 19
        if ($dispatcher->hasListeners(PuliEvents::GENERATE_FACTORY)) {
173 1
            $dispatcher->dispatch(PuliEvents::GENERATE_FACTORY, new GenerateFactoryEvent($class));
174 1
        }
175
176 19
        $this->classWriter->writeClass($class);
177 19
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182 3
    public function autoGenerateFactoryClass($path = null, $className = null)
183
    {
184 3
        if (!$this->config->get(Config::FACTORY_AUTO_GENERATE)) {
185 1
            return;
186
        }
187
188 2
        $this->generateFactoryClass($path, $className);
189 2
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194 16
    public function refreshFactoryClass($path = null, $className = null)
195
    {
196 16
        Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s');
197 16
        Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s');
198
199 16
        $path = Path::makeAbsolute($path ?: $this->config->get(Config::FACTORY_OUT_FILE), $this->rootDir);
200 16
        $className = $className ?: $this->config->get(Config::FACTORY_OUT_CLASS);
201
202 16
        if (!$this->config->get(Config::FACTORY_AUTO_GENERATE)) {
203 1
            return;
204
        }
205
206 15
        if (!file_exists($path)) {
207 9
            $this->generateFactoryClass($path, $className);
208
209 9
            return;
210
        }
211
212 6
        $rootPackageFile = $this->context->getRootPackageFile()->getPath();
213
214 6
        if (!file_exists($rootPackageFile)) {
215 1
            return;
216
        }
217
218
        // Regenerate file if the configuration has changed and
219
        // auto-generation is enabled
220 5
        clearstatcache(true, $rootPackageFile);
221 5
        $lastConfigChange = filemtime($rootPackageFile);
222
223 5
        $configFile = $this->context->getConfigFile()
224 5
            ? $this->context->getConfigFile()->getPath()
225 5
            : '';
226
227 5
        if (file_exists($configFile)) {
228 2
            clearstatcache(true, $configFile);
229 2
            $lastConfigChange = max(filemtime($configFile), $lastConfigChange);
230 2
        }
231
232 5
        clearstatcache(true, $path);
233 5
        $lastFactoryUpdate = filemtime($path);
234
235 5
        if ($lastConfigChange > $lastFactoryUpdate) {
236 3
            $this->generateFactoryClass($path, $className);
237 3
        }
238 5
    }
239
240
    /**
241
     * Adds the createRepository() method.
242
     *
243
     * @param Clazz $class The factory class model.
244
     */
245 19
    private function addCreateRepositoryMethod(Clazz $class)
246
    {
247 19
        $method = new Method('createRepository');
248 19
        $method->setDescription('Creates the resource repository.');
249 19
        $method->setReturnValue(new ReturnValue(
250 19
            '$'.self::REPO_VAR_NAME,
251 19
            'ResourceRepository',
252
            'The created resource repository.'
253 19
        ));
254 19
        $method->addBody(
255
<<<EOF
256
if (!interface_exists('Puli\Repository\Api\ResourceRepository')) {
257
    throw new RuntimeException('Please install puli/repository to create ResourceRepository instances.');
258
}
259
260
EOF
261 19
        );
262
263 19
        $class->addImport(new Import('Puli\Repository\Api\ResourceRepository'));
264 19
        $class->addImport(new Import('RuntimeException'));
265 19
        $class->addMethod($method);
266
267
        // Add method body
268 19
        $config = $this->config;
269 19
        $type = $config->get(Config::REPOSITORY_TYPE);
270 19
        $options = $this->camelizeKeys($config->get(Config::REPOSITORY));
271 19
        $options['rootDir'] = $this->rootDir;
272
273 19
        $generator = $this->generatorRegistry->getServiceGenerator(GeneratorRegistry::REPOSITORY, $type);
274 19
        $generator->generateNewInstance(self::REPO_VAR_NAME, $method, $this->generatorRegistry, $options);
275 19
    }
276
277
    /**
278
     * Adds the createDiscovery() method.
279
     *
280
     * @param Clazz $class The factory class model.
281
     */
282 19
    private function addCreateDiscoveryMethod(Clazz $class)
283
    {
284 19
        $method = new Method('createDiscovery');
285 19
        $method->setDescription('Creates the resource discovery.');
286
287 19
        $arg = new Argument(self::REPO_VAR_NAME);
288 19
        $arg->setTypeHint('ResourceRepository');
289 19
        $arg->setType('ResourceRepository');
290 19
        $arg->setDescription('The resource repository to read from.');
291
292 19
        $method->addArgument($arg);
293
294 19
        $method->setReturnValue(new ReturnValue(
295 19
            '$'.self::DISCOVERY_VAR_NAME,
296 19
            'Discovery',
297
            'The created discovery.'
298 19
        ));
299
300 19
        $method->addBody(
301
<<<EOF
302
if (!interface_exists('Puli\Discovery\Api\Discovery')) {
303
    throw new RuntimeException('Please install puli/discovery to create Discovery instances.');
304
}
305
306
EOF
307 19
        );
308
309 19
        $class->addImport(new Import('Puli\Repository\Api\ResourceRepository'));
310 19
        $class->addImport(new Import('Puli\Discovery\Api\Discovery'));
311 19
        $class->addImport(new Import('RuntimeException'));
312 19
        $class->addMethod($method);
313
314
        // Add method body
315 19
        $config = $this->config;
316 19
        $type = $config->get(Config::DISCOVERY_TYPE);
317 19
        $options = $this->camelizeKeys($config->get(Config::DISCOVERY));
318 19
        $options['rootDir'] = $this->rootDir;
319
320 19
        $generator = $this->generatorRegistry->getServiceGenerator(GeneratorRegistry::DISCOVERY, $type);
321 19
        $generator->generateNewInstance(self::DISCOVERY_VAR_NAME, $method, $this->generatorRegistry, $options);
322 19
    }
323
324
    /**
325
     * Adds the createUrlGenerator() method.
326
     *
327
     * @param Clazz $class The factory class model.
328
     */
329 19
    public function addCreateUrlGeneratorMethod(Clazz $class)
330
    {
331 19
        $class->addImport(new Import('Puli\Discovery\Api\Discovery'));
332 19
        $class->addImport(new Import('Puli\Manager\Api\Server\ServerCollection'));
333 19
        $class->addImport(new Import('Puli\UrlGenerator\Api\UrlGenerator'));
334 19
        $class->addImport(new Import('Puli\UrlGenerator\DiscoveryUrlGenerator'));
335 19
        $class->addImport(new Import('RuntimeException'));
336
337 19
        $method = new Method('createUrlGenerator');
338 19
        $method->setDescription('Creates the URL generator.');
339
340 19
        $arg = new Argument('discovery');
341 19
        $arg->setTypeHint('Discovery');
342 19
        $arg->setType('Discovery');
343 19
        $arg->setDescription('The discovery to read from.');
344 19
        $method->addArgument($arg);
345
346 19
        $method->setReturnValue(new ReturnValue('$generator', 'UrlGenerator', 'The created URL generator.'));
347
348 19
        $method->addBody(
349
<<<EOF
350
if (!interface_exists('Puli\UrlGenerator\Api\UrlGenerator')) {
351
    throw new RuntimeException('Please install puli/url-generator to create UrlGenerator instances.');
352
}
353
354
EOF
355 19
        );
356
357 19
        $urlFormatsString = '';
358
359 19
        foreach ($this->servers as $server) {
360 19
            $urlFormatsString .= sprintf(
361 19
                "\n    %s => %s,",
362 19
                var_export($server->getName(), true),
363 19
                var_export($server->getUrlFormat(), true)
364 19
            );
365 19
        }
366
367 19
        if ($urlFormatsString) {
368 19
            $urlFormatsString .= "\n";
369 19
        }
370
371 19
        $method->addBody("\$generator = new DiscoveryUrlGenerator(\$discovery, array($urlFormatsString));");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $urlFormatsString instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
372
373 19
        $class->addMethod($method);
374 19
    }
375
376
    /**
377
     * Recursively camelizes the keys of an array.
378
     *
379
     * @param array $array The array to process.
380
     *
381
     * @return array The input array with camelized keys.
382
     */
383 19
    private function camelizeKeys(array $array)
384
    {
385 19
        $camelized = array();
386
387 19
        foreach ($array as $key => $value) {
388 19
            $camelized[$this->camelize($key)] = is_array($value)
389 19
                ? $this->camelizeKeys($value)
390 19
                : $value;
391 19
        }
392
393 19
        return $camelized;
394
    }
395
396
    /**
397
     * Camelizes a string.
398
     *
399
     * @param string $string A string.
400
     *
401
     * @return string The camelized string.
402
     */
403
    private function camelize($string)
404
    {
405 19
        return preg_replace_callback('/\W+([a-z])/', function ($matches) {
406
            return strtoupper($matches[1]);
407 19
        }, $string);
408
    }
409
}
410