Completed
Push — master ( fafe1b...0eebaa )
by Philip
07:26 queued 01:18
created

ServiceProvider   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 439
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 99.42%

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 15
dl 0
loc 439
ccs 171
cts 172
cp 0.9942
rs 7.3961
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A boot() 0 6 1
A getData() 0 7 2
A getEntities() 0 4 1
A setTemplate() 0 4 1
A isManageI18n() 0 4 1
A setManageI18n() 0 4 1
A generateURL() 0 4 1
A initMissingServiceProviders() 0 19 4
A initLocales() 0 10 2
A initChildren() 0 11 4
A getLocaleLabels() 0 10 3
A configureDefinition() 0 21 3
A createDefinition() 0 21 2
A validateEntityDefinition() 0 10 4
A init() 0 21 3
A register() 0 13 3
A getEntitiesNavBar() 0 13 3
A getTemplate() 0 18 3
A setLocale() 0 6 2
A getLocales() 0 18 4

How to fix   Complexity   

Complex Class

Complex classes like ServiceProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ServiceProvider, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the CRUDlex package.
5
 *
6
 * (c) Philip Lehmann-Böhm <[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 CRUDlex\Silex;
13
14
use CRUDlex\EntityDefinition;
15
use CRUDlex\EntityDefinitionFactory;
16
use CRUDlex\EntityDefinitionFactoryInterface;
17
use CRUDlex\EntityDefinitionValidator;
18
use CRUDlex\YamlReader;
19
use League\Flysystem\Adapter\Local;
20
use League\Flysystem\Filesystem;
21
use Pimple\Container;
22
use Pimple\ServiceProviderInterface;
23
use Silex\Api\BootableProviderInterface;
24
use Silex\Application;
25
use Silex\Provider\LocaleServiceProvider;
26
use Silex\Provider\SessionServiceProvider;
27
use Silex\Provider\TranslationServiceProvider;
28
use Silex\Provider\TwigServiceProvider;
29
use Symfony\Component\Translation\Loader\YamlFileLoader;
30
use Symfony\Component\Translation\Translator;
31
32
/**
33
 * The ServiceProvider setups and initializes the whole CRUD system.
34
 * After adding it to your Silex-setup, it offers access to AbstractData
35
 * instances, one for each defined entity off the CRUD YAML file.
36
 */
37
class ServiceProvider implements ServiceProviderInterface, BootableProviderInterface
38
{
39
40
    /**
41
     * Holds the data instances.
42
     * @var array
43
     */
44
    protected $datas;
45
46
    /**
47
     * Holds the map for overriding templates.
48
     * @var array
49
     */
50
    protected $templates = [];
51
52
    /**
53
     * Holds whether CRUDlex manages i18n.
54
     * @var bool
55
     */
56
    protected $manageI18n = true;
57
58
    /**
59
     * Holds the URL generator.
60
     * @var \Symfony\Component\Routing\Generator\UrlGenerator
61
     */
62
    protected $urlGenerator;
63
64
    /**
65
     * Initializes needed but yet missing service providers.
66
     *
67
     * @param Container $app
68
     * the application container
69
     */
70 79
    protected function initMissingServiceProviders(Container $app)
71
    {
72
73 79
        if (!$app->offsetExists('translator')) {
74 79
            $app->register(new LocaleServiceProvider());
75 79
            $app->register(new TranslationServiceProvider(), [
76 79
                'locale_fallbacks' => ['en'],
77 79
            ]);
78 79
        }
79
80 79
        if (!$app->offsetExists('session')) {
81 69
            $app->register(new SessionServiceProvider());
82 69
        }
83
84 79
        if (!$app->offsetExists('twig')) {
85 69
            $app->register(new TwigServiceProvider());
86 69
        }
87 79
        $app['twig.loader.filesystem']->addPath(__DIR__.'/../../views/', 'crud');
88 79
    }
89
90
    /**
91
     * Initializes the available locales.
92
     *
93
     * @param Translator $translator
94
     * the translator
95
     *
96
     * @return array
97
     * the available locales
98
     */
99 79
    protected function initLocales(Translator $translator)
100
    {
101 78
        $locales   = $this->getLocales();
102 78
        $localeDir = __DIR__.'/../../locales';
103 78
        $translator->addLoader('yaml', new YamlFileLoader());
104 78
        foreach ($locales as $locale) {
105 78
            $translator->addResource('yaml', $localeDir.'/'.$locale.'.yml', $locale);
106 78
        }
107 79
        return $locales;
108
    }
109
110
    /**
111
     * Initializes the children of the data entries.
112
     */
113 78
    protected function initChildren()
114
    {
115 78
        foreach ($this->datas as $name => $data) {
116 78
            $fields = $data->getDefinition()->getFieldNames();
117 78
            foreach ($fields as $field) {
118 78
                if ($data->getDefinition()->getType($field) == 'reference') {
119 77
                    $this->datas[$data->getDefinition()->getSubTypeField($field, 'reference', 'entity')]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name);
120 77
                }
121 78
            }
122 78
        }
123 78
    }
124
125
    /**
126
     * Gets a map with localized entity labels from the CRUD YML.
127
     *
128
     * @param array $locales
129
     * the available locales
130
     * @param array $crud
131
     * the CRUD entity map
132
     *
133
     * @return array
134
     * the map with localized entity labels
135
     */
136 78
    protected function getLocaleLabels(array $locales, array $crud)
137
    {
138 78
        $localeLabels = [];
139 78
        foreach ($locales as $locale) {
140 78
            if (array_key_exists('label_'.$locale, $crud)) {
141 78
                $localeLabels[$locale] = $crud['label_'.$locale];
142 78
            }
143 78
        }
144 78
        return $localeLabels;
145
    }
146
147
    /**
148
     * Configures the EntityDefinition according to the given
149
     * CRUD entity map.
150
     *
151
     * @param EntityDefinition $definition
152
     * the definition to configure
153
     * @param array $crud
154
     * the CRUD entity map
155
     */
156 78
    protected function configureDefinition(EntityDefinition $definition, array $crud)
157
    {
158
        $toConfigure = [
159 78
            'deleteCascade',
160 78
            'listFields',
161 78
            'filter',
162 78
            'childrenLabelFields',
163 78
            'pageSize',
164 78
            'initialSortField',
165 78
            'initialSortAscending',
166 78
            'navBarGroup',
167 78
            'optimisticLocking',
168 78
            'hardDeletion',
169 78
        ];
170 78
        foreach ($toConfigure as $field) {
171 78
            if (array_key_exists($field, $crud)) {
172 78
                $function = 'set'.ucfirst($field);
173 78
                $definition->$function($crud[$field]);
174 78
            }
175 78
        }
176 78
    }
177
178
    /**
179
     * Creates and setups an EntityDefinition instance.
180
     *
181
     * @param Translator $translator
182
     * the Translator to use for some standard field labels
183
     * @param EntityDefinitionFactoryInterface $entityDefinitionFactory
184
     * the EntityDefinitionFactory to use
185
     * @param array $locales
186
     * the available locales
187
     * @param array $crud
188
     * the parsed YAML of a CRUD entity
189
     * @param string $name
190
     * the name of the entity
191
     *
192
     * @return EntityDefinition
193
     * the EntityDefinition good to go
194
     */
195 78
    protected function createDefinition(Translator $translator, EntityDefinitionFactoryInterface $entityDefinitionFactory, array $locales, array $crud, $name)
196
    {
197 78
        $label               = array_key_exists('label', $crud) ? $crud['label'] : $name;
198 78
        $localeLabels        = $this->getLocaleLabels($locales, $crud);
199
        $standardFieldLabels = [
200 78
            'id' => $translator->trans('crudlex.label.id'),
201 78
            'created_at' => $translator->trans('crudlex.label.created_at'),
202 78
            'updated_at' => $translator->trans('crudlex.label.updated_at')
203 78
        ];
204
205 78
        $definition = $entityDefinitionFactory->createEntityDefinition(
206 78
            $crud['table'],
207 78
            $crud['fields'],
208 78
            $label,
209 78
            $localeLabels,
210 78
            $standardFieldLabels,
211
            $this
212 78
        );
213 78
        $this->configureDefinition($definition, $crud);
214 78
        return $definition;
215
    }
216
217
    /**
218
     * Validates the parsed entity definition.
219
     *
220
     * @param Container $app
221
     * the application container
222
     * @param array $entityDefinition
223
     * the entity definition to validate
224
     */
225 78
    protected function validateEntityDefinition(Container $app, array $entityDefinition)
226
    {
227 78
        $doValidate = !$app->offsetExists('crud.validateentitydefinition') || $app['crud.validateentitydefinition'] === true;
228 78
        if ($doValidate) {
229 77
            $validator = $app->offsetExists('crud.entitydefinitionvalidator')
230 77
                ? $app['crud.entitydefinitionvalidator']
231 77
                : new EntityDefinitionValidator();
232 77
            $validator->validate($entityDefinition);
233 77
        }
234 78
    }
235
236
    /**
237
     * Initializes the instance.
238
     *
239
     * @param string|null $crudFileCachingDirectory
240
     * the writable directory to store the CRUD YAML file cache
241
     * @param Container $app
242
     * the application container
243
     */
244 79
    public function init($crudFileCachingDirectory, Container $app)
245
    {
246
247 79
        $this->urlGenerator = $app['url_generator'];
248
249 79
        $reader     = new YamlReader($crudFileCachingDirectory);
250 79
        $parsedYaml = $reader->read($app['crud.file']);
251
252 78
        $this->validateEntityDefinition($app, $parsedYaml);
253
254 78
        $locales                 = $this->initLocales($app['translator']);
255 78
        $this->datas             = [];
256 78
        $entityDefinitionFactory = $app->offsetExists('crud.entitydefinitionfactory') ? $app['crud.entitydefinitionfactory'] : new EntityDefinitionFactory();
257 78
        foreach ($parsedYaml as $name => $crud) {
258 78
            $definition         = $this->createDefinition($app['translator'], $entityDefinitionFactory, $locales, $crud, $name);
259 78
            $this->datas[$name] = $app['crud.datafactory']->createData($definition, $app['crud.filesystem']);
260 78
        }
261
262 78
        $this->initChildren();
263
264 78
    }
265
266
    /**
267
     * Implements ServiceProviderInterface::register() registering $app['crud'].
268
     * $app['crud'] contains an instance of the ServiceProvider afterwards.
269
     *
270
     * @param Container $app
271
     * the Container instance of the Silex application
272
     */
273 11
    public function register(Container $app)
274
    {
275 11
        if (!$app->offsetExists('crud.filesystem')) {
276 11
            $app['crud.filesystem'] = new Filesystem(new Local(getcwd()));
277 11
        }
278 11
        $app['crud'] = function() use ($app) {
279 11
            $result = new static();
280 11
            $result->setTemplate('layout', '@crud/layout.twig');
281 11
            $crudFileCachingDirectory = $app->offsetExists('crud.filecachingdirectory') ? $app['crud.filecachingdirectory'] : null;
282 11
            $result->init($crudFileCachingDirectory, $app);
283 11
            return $result;
284
        };
285 11
    }
286
287
    /**
288
     * Initializes the crud service right after boot.
289
     *
290
     * @param Application $app
291
     * the Container instance of the Silex application
292
     */
293 79
    public function boot(Application $app)
294
    {
295 79
        $this->initMissingServiceProviders($app);
296 79
        $twigSetup = new TwigSetup();
297 79
        $twigSetup->registerTwigExtensions($app);
298 79
    }
299
300
    /**
301
     * Getter for the AbstractData instances.
302
     *
303
     * @param string $name
304
     * the entity name of the desired Data instance
305
     *
306
     * @return AbstractData
307
     * the AbstractData instance or null on invalid name
308
     */
309 70
    public function getData($name)
310
    {
311 70
        if (!array_key_exists($name, $this->datas)) {
312 8
            return null;
313
        }
314 70
        return $this->datas[$name];
315
    }
316
317
    /**
318
     * Getter for all available entity names.
319
     *
320
     * @return string[]
321
     * a list of all available entity names
322
     */
323 7
    public function getEntities()
324
    {
325 7
        return array_keys($this->datas);
326
    }
327
328
    /**
329
     * Getter for the entities for the navigation bar.
330
     *
331
     * @return string[]
332
     * a list of all available entity names with their group
333
     */
334 10
    public function getEntitiesNavBar()
335
    {
336 10
        $result = [];
337 10
        foreach ($this->datas as $entity => $data) {
338 10
            $navBarGroup = $data->getDefinition()->getNavBarGroup();
339 10
            if ($navBarGroup !== 'main') {
340 10
                $result[$navBarGroup][] = $entity;
341 10
            } else {
342
                $result[$entity] = 'main';
343
            }
344 10
        }
345 10
        return $result;
346
    }
347
348
    /**
349
     * Sets a template to use instead of the build in ones.
350
     *
351
     * @param string $key
352
     * the template key to use in this format:
353
     * $section.$action.$entity
354
     * $section.$action
355
     * $section
356
     * @param string $template
357
     * the template to use for this key
358
     */
359 12
    public function setTemplate($key, $template)
360
    {
361 12
        $this->templates[$key] = $template;
362 12
    }
363
364
    /**
365
     * Determines the Twig template to use for the given parameters depending on
366
     * the existance of certain template keys set in this order:
367
     *
368
     * $section.$action.$entity
369
     * $section.$action
370
     * $section
371
     *
372
     * If nothing exists, this string is returned: "@crud/<action>.twig"
373
     *
374
     * @param string $section
375
     * the section of the template, either "layout" or "template"
376
     * @param string $action
377
     * the current calling action like "create" or "show"
378
     * @param string $entity
379
     * the current calling entity
380
     *
381
     * @return string
382
     * the best fitting template
383
     */
384 11
    public function getTemplate($section, $action, $entity)
385
    {
386 11
        $sectionAction = $section.'.'.$action;
387
388
        $offsets = [
389 11
            $sectionAction.'.'.$entity,
390 11
            $section.'.'.$entity,
391 11
            $sectionAction,
392
            $section
393 11
        ];
394 11
        foreach ($offsets as $offset) {
395 11
            if (array_key_exists($offset, $this->templates)) {
396 11
                return $this->templates[$offset];
397
            }
398 11
        }
399
400 11
        return '@crud/'.$action.'.twig';
401
    }
402
403
    /**
404
     * Sets the locale to be used.
405
     *
406
     * @param string $locale
407
     * the locale to be used.
408
     */
409 10
    public function setLocale($locale)
410
    {
411 10
        foreach ($this->datas as $data) {
412 10
            $data->getDefinition()->setLocale($locale);
413 10
        }
414 10
    }
415
416
    /**
417
     * Gets the available locales.
418
     *
419
     * @return array
420
     * the available locales
421
     */
422 79
    public function getLocales()
423
    {
424 79
        $localeDir     = __DIR__.'/../../locales';
425 79
        $languageFiles = scandir($localeDir);
426 79
        $locales       = [];
427 79
        foreach ($languageFiles as $languageFile) {
428 79
            if (in_array($languageFile, ['.', '..'])) {
429 79
                continue;
430
            }
431 79
            $extensionPos = strpos($languageFile, '.yml');
432 79
            if ($extensionPos !== false) {
433 79
                $locale    = substr($languageFile, 0, $extensionPos);
434 79
                $locales[] = $locale;
435 79
            }
436 79
        }
437 79
        sort($locales);
438 79
        return $locales;
439
    }
440
441
    /**
442
     * Gets whether CRUDlex manages the i18n.
443
     * @return bool
444
     * true if so
445
     */
446 11
    public function isManageI18n()
447
    {
448 11
        return $this->manageI18n;
449
    }
450
451
    /**
452
     * Sets whether CRUDlex manages the i18n.
453
     * @param bool $manageI18n
454
     * true if so
455
     */
456 1
    public function setManageI18n($manageI18n)
457
    {
458 1
        $this->manageI18n = $manageI18n;
459 1
    }
460
461
    /**
462
     * Generates an URL.
463
     * @param string $name
464
     * the name of the route
465
     * @param mixed $parameters
466
     * an array of parameters
467
     * @return null|string
468
     * the generated URL
469
     */
470 11
    public function generateURL($name, $parameters)
471
    {
472 11
        return $this->urlGenerator->generate($name, $parameters);
473
    }
474
475
}
476