Completed
Push — master ( 0eebaa...11762d )
by Philip
07:59 queued 03:04
created

ServiceProvider::setTemplate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
crap 1
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
            ]);
78
        }
79
80 79
        if (!$app->offsetExists('session')) {
81 69
            $app->register(new SessionServiceProvider());
82
        }
83
84 79
        if (!$app->offsetExists('twig')) {
85 69
            $app->register(new TwigServiceProvider());
86
        }
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 78
    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
        }
107 78
        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 78
                    $this->datas[$data->getDefinition()->getSubTypeField($field, 'reference', 'entity')]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name);
120
                }
121
            }
122
        }
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
            }
143
        }
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
            'listFields',
161
            'filter',
162
            'childrenLabelFields',
163
            'pageSize',
164
            'initialSortField',
165
            'initialSortAscending',
166
            'navBarGroup',
167
            'optimisticLocking',
168
            'hardDeletion',
169
        ];
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
            }
175
        }
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
        ];
204
205 78
        $definition = $entityDefinitionFactory->createEntityDefinition(
206 78
            $crud['table'],
207 78
            $crud['fields'],
208 78
            $label,
209 78
            $localeLabels,
210 78
            $standardFieldLabels,
211 78
            $this
212
        );
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 1
                ? $app['crud.entitydefinitionvalidator']
231 77
                : new EntityDefinitionValidator();
232 77
            $validator->validate($entityDefinition);
233
        }
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
        }
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
        }
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
            } else {
342 10
                $result[$entity] = 'main';
343
            }
344
        }
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 11
            $section
393
        ];
394 11
        foreach ($offsets as $offset) {
395 11
            if (array_key_exists($offset, $this->templates)) {
396 11
                return $this->templates[$offset];
397
            }
398
        }
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
        }
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
            }
436
        }
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