Completed
Push — master ( 9f26e2...6b5f50 )
by Philip
06:14 queued 03:38
created

ServiceProvider   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 390
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 45
c 0
b 0
f 0
lcom 1
cbo 14
dl 0
loc 390
ccs 132
cts 132
cp 1
rs 8.3673

17 Methods

Rating   Name   Duplication   Size   Complexity  
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 19 3
A register() 0 13 3
A boot() 0 6 1
A getData() 0 7 2
A getEntities() 0 4 1
A getEntitiesNavBar() 0 13 3
A setTemplate() 0 4 1
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;
13
14
use CRUDlex\Silex\TwigSetup;
15
use League\Flysystem\Adapter\Local;
16
use League\Flysystem\Filesystem;
17
use Pimple\Container;
18
use Pimple\ServiceProviderInterface;
19
use Silex\Api\BootableProviderInterface;
20
use Silex\Application;
21
use Silex\Provider\LocaleServiceProvider;
22
use Silex\Provider\SessionServiceProvider;
23
use Silex\Provider\TranslationServiceProvider;
24
use Silex\Provider\TwigServiceProvider;
25
use Symfony\Component\Translation\Loader\YamlFileLoader;
26
use Symfony\Component\Translation\Translator;
27
28
/**
29
 * The ServiceProvider setups and initializes the whole CRUD system.
30
 * After adding it to your Silex-setup, it offers access to AbstractData
31
 * instances, one for each defined entity off the CRUD YAML file.
32
 */
33
class ServiceProvider implements ServiceProviderInterface, BootableProviderInterface
34
{
35
36
    /**
37
     * Holds the data instances.
38
     * @var array
39
     */
40
    protected $datas;
41
42
    /**
43
     * Holds the map for overriding templates.
44
     * @var array
45
     */
46
    protected $templates = [];
47
48
    /**
49
     * Initializes needed but yet missing service providers.
50
     *
51
     * @param Container $app
52
     * the application container
53
     */
54 77
    protected function initMissingServiceProviders(Container $app)
55
    {
56
57 77
        if (!$app->offsetExists('translator')) {
58 77
            $app->register(new LocaleServiceProvider());
59 77
            $app->register(new TranslationServiceProvider(), [
60 77
                'locale_fallbacks' => ['en'],
61
            ]);
62
        }
63
64 77
        if (!$app->offsetExists('session')) {
65 67
            $app->register(new SessionServiceProvider());
66
        }
67
68 77
        if (!$app->offsetExists('twig')) {
69 67
            $app->register(new TwigServiceProvider());
70
        }
71 77
        $app['twig.loader.filesystem']->addPath(__DIR__.'/../views/', 'crud');
72 77
    }
73
74
    /**
75
     * Initializes the available locales.
76
     *
77
     * @param Translator $translator
78
     * the translator
79
     *
80
     * @return array
81
     * the available locales
82
     */
83 76
    protected function initLocales(Translator $translator)
84
    {
85 76
        $locales   = $this->getLocales();
86 76
        $localeDir = __DIR__.'/../locales';
87 76
        $translator->addLoader('yaml', new YamlFileLoader());
88 76
        foreach ($locales as $locale) {
89 76
            $translator->addResource('yaml', $localeDir.'/'.$locale.'.yml', $locale);
90
        }
91 76
        return $locales;
92
    }
93
94
    /**
95
     * Initializes the children of the data entries.
96
     */
97 76
    protected function initChildren()
98
    {
99 76
        foreach ($this->datas as $name => $data) {
100 76
            $fields = $data->getDefinition()->getFieldNames();
101 76
            foreach ($fields as $field) {
102 76
                if ($data->getDefinition()->getType($field) == 'reference') {
103 76
                    $this->datas[$data->getDefinition()->getSubTypeField($field, 'reference', 'entity')]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name);
104
                }
105
            }
106
        }
107 76
    }
108
109
    /**
110
     * Gets a map with localized entity labels from the CRUD YML.
111
     *
112
     * @param array $locales
113
     * the available locales
114
     * @param array $crud
115
     * the CRUD entity map
116
     *
117
     * @return array
118
     * the map with localized entity labels
119
     */
120 76
    protected function getLocaleLabels(array $locales, array $crud)
121
    {
122 76
        $localeLabels = [];
123 76
        foreach ($locales as $locale) {
124 76
            if (array_key_exists('label_'.$locale, $crud)) {
125 76
                $localeLabels[$locale] = $crud['label_'.$locale];
126
            }
127
        }
128 76
        return $localeLabels;
129
    }
130
131
    /**
132
     * Configures the EntityDefinition according to the given
133
     * CRUD entity map.
134
     *
135
     * @param EntityDefinition $definition
136
     * the definition to configure
137
     * @param array $crud
138
     * the CRUD entity map
139
     */
140 76
    protected function configureDefinition(EntityDefinition $definition, array $crud)
141
    {
142
        $toConfigure = [
143 76
            'deleteCascade',
144
            'listFields',
145
            'filter',
146
            'childrenLabelFields',
147
            'pageSize',
148
            'initialSortField',
149
            'initialSortAscending',
150
            'navBarGroup',
151
            'optimisticLocking',
152
            'hardDeletion',
153
        ];
154 76
        foreach ($toConfigure as $field) {
155 76
            if (array_key_exists($field, $crud)) {
156 76
                $function = 'set'.ucfirst($field);
157 76
                $definition->$function($crud[$field]);
158
            }
159
        }
160 76
    }
161
162
    /**
163
     * Creates and setups an EntityDefinition instance.
164
     *
165
     * @param Translator $translator
166
     * the Translator to use for some standard field labels
167
     * @param EntityDefinitionFactoryInterface $entityDefinitionFactory
168
     * the EntityDefinitionFactory to use
169
     * @param array $locales
170
     * the available locales
171
     * @param array $crud
172
     * the parsed YAML of a CRUD entity
173
     * @param string $name
174
     * the name of the entity
175
     *
176
     * @return EntityDefinition
177
     * the EntityDefinition good to go
178
     */
179 76
    protected function createDefinition(Translator $translator, EntityDefinitionFactoryInterface $entityDefinitionFactory, array $locales, array $crud, $name)
180
    {
181 76
        $label               = array_key_exists('label', $crud) ? $crud['label'] : $name;
182 76
        $localeLabels        = $this->getLocaleLabels($locales, $crud);
183
        $standardFieldLabels = [
184 76
            'id' => $translator->trans('crudlex.label.id'),
185 76
            'created_at' => $translator->trans('crudlex.label.created_at'),
186 76
            'updated_at' => $translator->trans('crudlex.label.updated_at')
187
        ];
188
189 76
        $definition = $entityDefinitionFactory->createEntityDefinition(
190 76
            $crud['table'],
191 76
            $crud['fields'],
192 76
            $label,
193 76
            $localeLabels,
194 76
            $standardFieldLabels,
195 76
            $this
196
        );
197 76
        $this->configureDefinition($definition, $crud);
198 76
        return $definition;
199
    }
200
201
    /**
202
     * Validates the parsed entity definition.
203
     *
204
     * @param Container $app
205
     * the application container
206
     * @param array $entityDefinition
207
     * the entity definition to validate
208
     */
209 76
    protected function validateEntityDefinition(Container $app, array $entityDefinition)
210
    {
211 76
        $doValidate = !$app->offsetExists('crud.validateentitydefinition') || $app['crud.validateentitydefinition'] === true;
212 76
        if ($doValidate) {
213 75
            $validator = $app->offsetExists('crud.entitydefinitionvalidator')
214 1
                ? $app['crud.entitydefinitionvalidator']
215 75
                : new EntityDefinitionValidator();
216 75
            $validator->validate($entityDefinition);
217
        }
218 76
    }
219
220
    /**
221
     * Initializes the instance.
222
     *
223
     * @param string|null $crudFileCachingDirectory
224
     * the writable directory to store the CRUD YAML file cache
225
     * @param Container $app
226
     * the application container
227
     */
228 77
    public function init($crudFileCachingDirectory, Container $app)
229
    {
230
231 77
        $reader     = new YamlReader($crudFileCachingDirectory);
232 77
        $parsedYaml = $reader->read($app['crud.file']);
233
234 76
        $this->validateEntityDefinition($app, $parsedYaml);
235
236 76
        $locales                 = $this->initLocales($app['translator']);
237 76
        $this->datas             = [];
238 76
        $entityDefinitionFactory = $app->offsetExists('crud.entitydefinitionfactory') ? $app['crud.entitydefinitionfactory'] : new EntityDefinitionFactory();
239 76
        foreach ($parsedYaml as $name => $crud) {
240 76
            $definition         = $this->createDefinition($app['translator'], $entityDefinitionFactory, $locales, $crud, $name);
241 76
            $this->datas[$name] = $app['crud.datafactory']->createData($definition, $app['crud.filesystem']);
242
        }
243
244 76
        $this->initChildren();
245
246 76
    }
247
248
    /**
249
     * Implements ServiceProviderInterface::register() registering $app['crud'].
250
     * $app['crud'] contains an instance of the ServiceProvider afterwards.
251
     *
252
     * @param Container $app
253
     * the Container instance of the Silex application
254
     */
255 11
    public function register(Container $app)
256
    {
257 11
        if (!$app->offsetExists('crud.filesystem')) {
258 11
            $app['crud.filesystem'] = new Filesystem(new Local(getcwd()));
259
        }
260 11
        $app['crud'] = function() use ($app) {
261 11
            $result                   = new static();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 19 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
262 11
            $result->setTemplate('layout', '@crud/layout.twig');
263 11
            $crudFileCachingDirectory = $app->offsetExists('crud.filecachingdirectory') ? $app['crud.filecachingdirectory'] : null;
264 11
            $result->init($crudFileCachingDirectory, $app);
265 11
            return $result;
266
        };
267 11
    }
268
269
    /**
270
     * Initializes the crud service right after boot.
271
     *
272
     * @param Application $app
273
     * the Container instance of the Silex application
274
     */
275 77
    public function boot(Application $app)
276
    {
277 77
        $this->initMissingServiceProviders($app);
278 77
        $twigSetup = new TwigSetup();
279 77
        $twigSetup->registerTwigExtensions($app);
280 77
    }
281
282
    /**
283
     * Getter for the AbstractData instances.
284
     *
285
     * @param string $name
286
     * the entity name of the desired Data instance
287
     *
288
     * @return AbstractData
289
     * the AbstractData instance or null on invalid name
290
     */
291 70
    public function getData($name)
292
    {
293 70
        if (!array_key_exists($name, $this->datas)) {
294 8
            return null;
295
        }
296 70
        return $this->datas[$name];
297
    }
298
299
    /**
300
     * Getter for all available entity names.
301
     *
302
     * @return string[]
303
     * a list of all available entity names
304
     */
305 7
    public function getEntities()
306
    {
307 7
        return array_keys($this->datas);
308
    }
309
310
    /**
311
     * Getter for the entities for the navigation bar.
312
     *
313
     * @return string[]
314
     * a list of all available entity names with their group
315
     */
316 10
    public function getEntitiesNavBar()
317
    {
318 10
        $result = [];
319 10
        foreach ($this->datas as $entity => $data) {
320 10
            $navBarGroup = $data->getDefinition()->getNavBarGroup();
321 10
            if ($navBarGroup !== 'main') {
322 10
                $result[$navBarGroup][] = $entity;
323
            } else {
324 10
                $result[$entity] = 'main';
325
            }
326
        }
327 10
        return $result;
328
    }
329
330
    /**
331
     * Sets a template to use instead of the build in ones.
332
     *
333
     * @param $key
334
     * the template key to use in this format:
335
     * $section.$action.$entity
336
     * $section.$action
337
     * $section
338
     * @param $template
339
     */
340 12
    public function setTemplate($key, $template)
341
    {
342 12
        $this->templates[$key] = $template;
343 12
    }
344
345
    /**
346
     * Determines the Twig template to use for the given parameters depending on
347
     * the existance of certain template keys set in this order:
348
     *
349
     * $section.$action.$entity
350
     * $section.$action
351
     * $section
352
     *
353
     * If nothing exists, this string is returned: "@crud/<action>.twig"
354
     *
355
     * @param string $section
356
     * the section of the template, either "layout" or "template"
357
     * @param string $action
358
     * the current calling action like "create" or "show"
359
     * @param string $entity
360
     * the current calling entity
361
     *
362
     * @return string
363
     * the best fitting template
364
     */
365 11
    public function getTemplate($section, $action, $entity)
366
    {
367 11
        $sectionAction = $section.'.'.$action;
368
369
        $offsets = [
370 11
            $sectionAction.'.'.$entity,
371 11
            $section.'.'.$entity,
372 11
            $sectionAction,
373 11
            $section
374
        ];
375 11
        foreach ($offsets as $offset) {
376 11
            if (array_key_exists($offset, $this->templates)) {
377 11
                return $this->templates[$offset];
378
            }
379
        }
380
381 11
        return '@crud/'.$action.'.twig';
382
    }
383
384
    /**
385
     * Sets the locale to be used.
386
     *
387
     * @param string $locale
388
     * the locale to be used.
389
     */
390 10
    public function setLocale($locale)
391
    {
392 10
        foreach ($this->datas as $data) {
393 10
            $data->getDefinition()->setLocale($locale);
394
        }
395 10
    }
396
397
    /**
398
     * Gets the available locales.
399
     *
400
     * @return array
401
     * the available locales
402
     */
403 77
    public function getLocales()
404
    {
405 77
        $localeDir     = __DIR__.'/../locales';
406 77
        $languageFiles = scandir($localeDir);
407 77
        $locales       = [];
408 77
        foreach ($languageFiles as $languageFile) {
409 77
            if (in_array($languageFile, ['.', '..'])) {
410 77
                continue;
411
            }
412 77
            $extensionPos = strpos($languageFile, '.yml');
413 77
            if ($extensionPos !== false) {
414 77
                $locale    = substr($languageFile, 0, $extensionPos);
415 77
                $locales[] = $locale;
416
            }
417
        }
418 77
        sort($locales);
419 77
        return $locales;
420
    }
421
422
}
423