Failed Conditions
Pull Request — master (#41)
by Bernhard
31:03 queued 15:13
created

FactoryManagerImpl::setServers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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\Package\PackageCollection;
21
use Puli\Manager\Api\Php\Argument;
22
use Puli\Manager\Api\Php\Clazz;
23
use Puli\Manager\Api\Php\Import;
24
use Puli\Manager\Api\Php\Method;
25
use Puli\Manager\Api\Php\ReturnValue;
26
use Puli\Manager\Api\Server\ServerCollection;
27
use Puli\Manager\Assert\Assert;
28
use Puli\Manager\Conflict\OverrideGraph;
29
use Puli\Manager\Php\ClassWriter;
30
use Webmozart\PathUtil\Path;
31
32
/**
33
 * The default {@link FactoryManager} implementation.
34
 *
35
 * @since  1.0
36
 *
37
 * @author Bernhard Schussek <[email protected]>
38
 */
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 PackageCollection
78
     */
79
    private $packages;
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 43
     *                                                 the generators for the
92
     *                                                 services returned by the
93 43
     *                                                 factory.
94 43
     * @param ClassWriter           $classWriter       The writer that writes
95 43
     *                                                 the class to a file.
96 43
     * @param PackageCollection     $packages          The loaded packages.
97 43
     * @param ServerCollection|null $servers           The configured servers.
98 43
     */
99 43
    public function __construct(ProjectContext $context, GeneratorRegistry $generatorRegistry, ClassWriter $classWriter, PackageCollection $packages, ServerCollection $servers = null)
100
    {
101
        $this->context = $context;
102
        $this->config = $context->getConfig();
103
        $this->rootDir = $context->getRootDirectory();
104
        $this->generatorRegistry = $generatorRegistry;
105
        $this->classWriter = $classWriter;
106 14
        $this->packages = $packages;
107
        $this->servers = $servers;
108 14
    }
109 14
110
    /**
111
     * Sets the servers included in the createUrlGenerator() method.
112
     *
113
     * @param ServerCollection $servers The configured servers.
114 13
     */
115
    public function setServers(ServerCollection $servers)
116 13
    {
117 13
        $this->servers = $servers;
118
    }
119 13
120
    /**
121 13
     * {@inheritdoc}
122 13
     */
123
    public function createFactory($path = null, $className = null)
124 13
    {
125 12
        Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s');
126 12
        Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s');
127
128 13
        $this->refreshFactoryClass($path, $className);
129
130
        $className = $className ?: $this->config->get(Config::FACTORY_IN_CLASS);
131
        $path = $path ?: $this->config->get(Config::FACTORY_IN_FILE);
132
133
        if (null !== $path && !class_exists($className, false)) {
134 1
            require_once Path::makeAbsolute($path, $this->rootDir);
135
        }
136 1
137
        return new $className();
138
    }
139
140
    /**
141
     * {@inheritdoc}
142 31
     */
143
    public function isFactoryClassAutoGenerated()
144 31
    {
145 29
        return $this->config->get(Config::FACTORY_AUTO_GENERATE);
146
    }
147 27
148 27
    /**
149 27
     * {@inheritdoc}
150
     */
151 27
    public function generateFactoryClass($path = null, $className = null)
152 27
    {
153 27
        Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s');
154
        Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s');
155
156
        $path = Path::makeAbsolute($path ?: $this->config->get(Config::FACTORY_OUT_FILE), $this->rootDir);
157
        $className = $className ?: $this->config->get(Config::FACTORY_OUT_CLASS);
158
        $dispatcher = $this->context->getEventDispatcher();
159
160
        $class = new Clazz($className);
161
        $class->setFilePath($path);
162
        $class->setDescription(
163
<<<EOF
164
Creates Puli's core services.
165
166 27
This class was auto-generated by Puli.
167
168 27
IMPORTANT: Before modifying the code below, set the "factory.auto-generate"
169 27
configuration key to false:
170 27
171
    $ puli config factory.auto-generate false
172 27
173 1
Otherwise any modifications will be overwritten!
174 1
EOF
175
        );
176 27
177 27
        $this->addCreateRepositoryMethod($class);
178
        $this->addCreateDiscoveryMethod($class);
179
        $this->addCreateUrlGeneratorMethod($class);
180
        $this->addGetPackageOrderMethod($class);
181
182 3
        if ($dispatcher->hasListeners(PuliEvents::GENERATE_FACTORY)) {
183
            $dispatcher->dispatch(PuliEvents::GENERATE_FACTORY, new GenerateFactoryEvent($class));
184 3
        }
185 1
186
        $this->classWriter->writeClass($class);
187
    }
188 2
189 2
    /**
190
     * {@inheritdoc}
191
     */
192
    public function autoGenerateFactoryClass($path = null, $className = null)
193
    {
194 24
        if (!$this->config->get(Config::FACTORY_AUTO_GENERATE)) {
195
            return;
196 24
        }
197 24
198
        $this->generateFactoryClass($path, $className);
199 24
    }
200 24
201
    /**
202 24
     * {@inheritdoc}
203 1
     */
204
    public function refreshFactoryClass($path = null, $className = null)
205
    {
206 23
        Assert::nullOrStringNotEmpty($path, 'The path to the generated factory file must be a non-empty string or null. Got: %s');
207 17
        Assert::nullOrStringNotEmpty($className, 'The class name of the generated factory must be a non-empty string or null. Got: %s');
208
209 17
        $path = Path::makeAbsolute($path ?: $this->config->get(Config::FACTORY_OUT_FILE), $this->rootDir);
210
        $className = $className ?: $this->config->get(Config::FACTORY_OUT_CLASS);
211
212 6
        if (!$this->config->get(Config::FACTORY_AUTO_GENERATE)) {
213
            return;
214 6
        }
215 1
216
        if (!file_exists($path)) {
217
            $this->generateFactoryClass($path, $className);
218
219
            return;
220 5
        }
221 5
222
        $rootPackageFile = $this->context->getRootPackageFile()->getPath();
223 5
224 5
        if (!file_exists($rootPackageFile)) {
225 5
            return;
226
        }
227 5
228 2
        // Regenerate file if the configuration has changed and
229 2
        // auto-generation is enabled
230 2
        clearstatcache(true, $rootPackageFile);
231
        $lastConfigChange = filemtime($rootPackageFile);
232 5
233 5
        $configFile = $this->context->getConfigFile()
234
            ? $this->context->getConfigFile()->getPath()
235 5
            : '';
236 3
237 3
        if (file_exists($configFile)) {
238 5
            clearstatcache(true, $configFile);
239
            $lastConfigChange = max(filemtime($configFile), $lastConfigChange);
240
        }
241
242
        clearstatcache(true, $path);
243
        $lastFactoryUpdate = filemtime($path);
244
245 27
        if ($lastConfigChange > $lastFactoryUpdate) {
246
            $this->generateFactoryClass($path, $className);
247 27
        }
248 27
    }
249 27
250 27
    /**
251 27
     * Adds the createRepository() method.
252
     *
253 27
     * @param Clazz $class The factory class model.
254 27
     */
255
    private function addCreateRepositoryMethod(Clazz $class)
256
    {
257
        $method = new Method('createRepository');
258
        $method->setDescription('Creates the resource repository.');
259
        $method->setReturnValue(new ReturnValue(
260
            '$'.self::REPO_VAR_NAME,
261 27
            'ResourceRepository',
262
            'The created resource repository.'
263 27
        ));
264 27
        $method->addBody(
265 27
<<<EOF
266
if (!interface_exists('Puli\Repository\Api\ResourceRepository')) {
267
    throw new RuntimeException('Please install puli/repository to create ResourceRepository instances.');
268 27
}
269 27
270 27
EOF
271 27
        );
272
273 27
        $class->addImport(new Import('Puli\Repository\Api\ResourceRepository'));
274 27
        $class->addImport(new Import('RuntimeException'));
275 27
        $class->addMethod($method);
276
277
        // Add method body
278
        $config = $this->config;
279
        $type = $config->get(Config::REPOSITORY_TYPE);
280
        $options = $this->camelizeKeys($config->get(Config::REPOSITORY));
281
        $options['rootDir'] = $this->rootDir;
282 27
283
        $generator = $this->generatorRegistry->getServiceGenerator(GeneratorRegistry::REPOSITORY, $type);
284 27
        $generator->generateNewInstance(self::REPO_VAR_NAME, $method, $this->generatorRegistry, $options);
285 27
    }
286
287 27
    /**
288 27
     * Adds the createDiscovery() method.
289 27
     *
290 27
     * @param Clazz $class The factory class model.
291
     */
292 27
    private function addCreateDiscoveryMethod(Clazz $class)
293
    {
294 27
        $method = new Method('createDiscovery');
295 27
        $method->setDescription('Creates the resource discovery.');
296 27
297
        $arg = new Argument(self::REPO_VAR_NAME);
298 27
        $arg->setTypeHint('ResourceRepository');
299
        $arg->setType('ResourceRepository');
300 27
        $arg->setDescription('The resource repository to read from.');
301
302
        $method->addArgument($arg);
303
304
        $method->setReturnValue(new ReturnValue(
305
            '$'.self::DISCOVERY_VAR_NAME,
306
            'Discovery',
307 27
            'The created discovery.'
308
        ));
309 27
310 27
        $method->addBody(
311 27
<<<EOF
312 27
if (!interface_exists('Puli\Discovery\Api\Discovery')) {
313
    throw new RuntimeException('Please install puli/discovery to create Discovery instances.');
314
}
315 27
316 27
EOF
317 27
        );
318 27
319
        $class->addImport(new Import('Puli\Repository\Api\ResourceRepository'));
320 27
        $class->addImport(new Import('Puli\Discovery\Api\Discovery'));
321 27
        $class->addImport(new Import('RuntimeException'));
322 27
        $class->addMethod($method);
323
324
        // Add method body
325
        $config = $this->config;
326
        $type = $config->get(Config::DISCOVERY_TYPE);
327
        $options = $this->camelizeKeys($config->get(Config::DISCOVERY));
328
        $options['rootDir'] = $this->rootDir;
329 27
330
        $generator = $this->generatorRegistry->getServiceGenerator(GeneratorRegistry::DISCOVERY, $type);
331 27
        $generator->generateNewInstance(self::DISCOVERY_VAR_NAME, $method, $this->generatorRegistry, $options);
332 27
    }
333 27
334 27
    /**
335 27
     * Adds the createUrlGenerator() method.
336
     *
337 27
     * @param Clazz $class The factory class model.
338 27
     */
339
    public function addCreateUrlGeneratorMethod(Clazz $class)
340 27
    {
341 27
        $class->addImport(new Import('Puli\Discovery\Api\Discovery'));
342 27
        $class->addImport(new Import('Puli\Manager\Api\Server\ServerCollection'));
343 27
        $class->addImport(new Import('Puli\UrlGenerator\Api\UrlGenerator'));
344 27
        $class->addImport(new Import('Puli\UrlGenerator\DiscoveryUrlGenerator'));
345
        $class->addImport(new Import('RuntimeException'));
346 27
347
        $method = new Method('createUrlGenerator');
348 27
        $method->setDescription('Creates the URL generator.');
349
350
        $arg = new Argument('discovery');
351
        $arg->setTypeHint('Discovery');
352
        $arg->setType('Discovery');
353
        $arg->setDescription('The discovery to read from.');
354
        $method->addArgument($arg);
355 27
356
        $method->setReturnValue(new ReturnValue('$generator', 'UrlGenerator', 'The created URL generator.'));
357 27
358
        $method->addBody(
359 27
<<<EOF
360 19
if (!interface_exists('Puli\UrlGenerator\Api\UrlGenerator')) {
361 19
    throw new RuntimeException('Please install puli/url-generator to create UrlGenerator instances.');
362 19
}
363 19
364 19
EOF
365 27
        );
366
367 27
        $urlFormatsString = '';
368 19
369 19
        foreach ($this->servers as $server) {
370
            $urlFormatsString .= sprintf(
371 27
                "\n    %s => %s,",
372
                var_export($server->getName(), true),
373 27
                var_export($server->getUrlFormat(), true)
374 27
            );
375
        }
376
377
        if ($urlFormatsString) {
378
            $urlFormatsString .= "\n";
379
        }
380
381
        $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...
382
383 27
        $class->addMethod($method);
384
    }
385 27
386
    /**
387 27
     * Adds the getPackageOrder() method.
388 27
     *
389 27
     * @param Clazz $class The factory class model.
390 27
     */
391 27
    public function addGetPackageOrderMethod(Clazz $class)
392
    {
393 27
        $class->addImport(new Import('Puli\Discovery\Api\Discovery'));
394
        $class->addImport(new Import('Puli\Manager\Api\Server\ServerCollection'));
395
        $class->addImport(new Import('Puli\UrlGenerator\Api\UrlGenerator'));
396
        $class->addImport(new Import('Puli\UrlGenerator\DiscoveryUrlGenerator'));
397
        $class->addImport(new Import('RuntimeException'));
398
399
        $method = new Method('getPackageOrder');
400
        $method->setDescription("Returns the order in which the installed packages should be loaded\naccording to the override statements.");
401
402
        $method->setReturnValue(new ReturnValue('$order', 'string[]', 'The sorted package names.'));
403
404
        $packageOrderString = '';
405 27
406
        if (count($this->packages) > 0) {
407 27
            $overrideGraph = OverrideGraph::forPackages($this->packages);
408
409
            foreach ($overrideGraph->getSortedPackageNames() as $packageName) {
410
                $packageOrderString .= sprintf(
411
                    "\n    %s,",
412
                    var_export($packageName, true)
413
                );
414
            }
415
416
            $packageOrderString .= "\n";
417
        }
418
419
        $method->addBody("\$order = array($packageOrderString);");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $packageOrderString 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...
420
421
        $class->addMethod($method);
422
    }
423
424
    /**
425
     * Recursively camelizes the keys of an array.
426
     *
427
     * @param array $array The array to process.
428
     *
429
     * @return array The input array with camelized keys.
430
     */
431
    private function camelizeKeys(array $array)
432
    {
433
        $camelized = array();
434
435
        foreach ($array as $key => $value) {
436
            $camelized[$this->camelize($key)] = is_array($value)
437
                ? $this->camelizeKeys($value)
438
                : $value;
439
        }
440
441
        return $camelized;
442
    }
443
444
    /**
445
     * Camelizes a string.
446
     *
447
     * @param string $string A string.
448
     *
449
     * @return string The camelized string.
450
     */
451
    private function camelize($string)
452
    {
453
        return preg_replace_callback('/\W+([a-z])/', function ($matches) {
454
            return strtoupper($matches[1]);
455
        }, $string);
456
    }
457
}
458