Completed
Push — master ( de5258...7edf3b )
by Philip
05:48
created

ServiceProvider::createDefinition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 2

Importance

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