Completed
Pull Request — master (#85)
by
unknown
02:34
created

ServiceProvider   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 353
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 44
c 0
b 0
f 0
lcom 1
cbo 12
dl 0
loc 353
rs 8.3396

16 Methods

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

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
290
     * a list of all available entity names with their group
291
     */
292
    public function getEntitiesNavBar() {
293
        foreach ($this->datas as $entity => $data) {
294
            $navBarGroup = $data->getDefinition()->getNavBarGroup();
295
            if ($navBarGroup !== 'main'){
296
                $result[$navBarGroup][] = $entity;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
297
            }else{
298
                $result[$entity] = 'main';
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
299
            }
300
        }
301
        return $result;
302
    }
303
304
    /**
305
     * Determines the Twig template to use for the given parameters depending on
306
     * the existance of certain keys in the Container $app in this order:
307
     *
308
     * crud.$section.$action.$entity
309
     * crud.$section.$action
310
     * crud.$section
311
     *
312
     * If nothing exists, this string is returned: "@crud/<action>.twig"
313
     *
314
     * @param Container $app
315
     * the Silex application
316
     * @param string $section
317
     * the section of the template, either "layout" or "template"
318
     * @param string $action
319
     * the current calling action like "create" or "show"
320
     * @param string $entity
321
     * the current calling entity
322
     *
323
     * @return string
324
     * the best fitting template
325
     */
326
    public function getTemplate(Container $app, $section, $action, $entity) {
327
        $crudSection       = 'crud.'.$section;
328
        $crudSectionAction = $crudSection.'.'.$action;
329
330
        $offsets = [
331
            $crudSectionAction.'.'.$entity,
332
            $crudSection.'.'.$entity,
333
            $crudSectionAction,
334
            $crudSection
335
        ];
336
        foreach ($offsets as $offset) {
337
            if ($app->offsetExists($offset)) {
338
                return $app[$offset];
339
            }
340
        }
341
342
        return '@crud/'.$action.'.twig';
343
    }
344
345
    /**
346
     * Sets the locale to be used.
347
     *
348
     * @param string $locale
349
     * the locale to be used.
350
     */
351
    public function setLocale($locale) {
352
        foreach ($this->datas as $data) {
353
            $data->getDefinition()->setLocale($locale);
354
        }
355
    }
356
357
    /**
358
     * Gets the available locales.
359
     *
360
     * @return array
361
     * the available locales
362
     */
363
    public function getLocales() {
364
        $localeDir     = __DIR__.'/../locales';
365
        $languageFiles = scandir($localeDir);
366
        $locales       = [];
367
        foreach ($languageFiles as $languageFile) {
368
            if (in_array($languageFile, ['.', '..'])) {
369
                continue;
370
            }
371
            $extensionPos = strpos($languageFile, '.yml');
372
            if ($extensionPos !== false) {
373
                $locale    = substr($languageFile, 0, $extensionPos);
374
                $locales[] = $locale;
375
            }
376
        }
377
        sort($locales);
378
        return $locales;
379
    }
380
381
}
382