Completed
Push — master ( 052af3...483127 )
by Philip
02:47
created

ServiceProvider::validateEntityDefinition()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.2
cc 4
eloc 7
nc 6
nop 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;
13
14
use Pimple\Container;
15
use Pimple\ServiceProviderInterface;
16
use Symfony\Component\Translation\Loader\YamlFileLoader;
17
use Symfony\Component\Yaml\Yaml;
18
19
/**
20
 * The ServiceProvider setups and initializes the whole CRUD system.
21
 * After adding it to your Silex-setup, it offers access to {@see AbstractData}
22
 * instances, one for each defined entity off the CRUD YAML file.
23
 */
24
class ServiceProvider implements ServiceProviderInterface {
25
26
    /**
27
     * Holds the {@see AbstractData} instances.
28
     */
29
    protected $datas;
30
31
    /**
32
     * Holds whether we manage the i18n.
33
     */
34
    protected $manageI18n;
35
36
    /**
37
     * Formats the given time value to a timestring defined by the $pattern
38
     * parameter.
39
     *
40
     * If the value is false (like null), an empty string is
41
     * returned. Else, the value is tried to be parsed as datetime via the
42
     * given pattern. If that fails, it is tried to be parsed with the pattern
43
     * 'Y-m-d H:i:s'. If that fails, the value is returned unchanged. Else, it
44
     * is returned formatted with the given pattern. The effect is to shorten
45
     * 'Y-m-d H:i:s' to 'Y-m-d' for example.
46
     *
47
     * @param string $value
48
     * the value to be formatted
49
     * @param string $timezone
50
     * the timezone of the value
51
     * @param string $pattern
52
     * the pattern with which the value is parsed and formatted
53
     *
54
     * @return string
55
     * the formatted value
56
     */
57
    protected function formatTime($value, $timezone, $pattern) {
58
        if (!$value) {
59
            return '';
60
        }
61
        $result = \DateTime::createFromFormat($pattern, $value, new \DateTimeZone($timezone));
62
        if ($result === false) {
63
            $result = \DateTime::createFromFormat('Y-m-d H:i:s', $value, new \DateTimeZone($timezone));
64
        }
65
        if ($result === false) {
66
            return $value;
67
        }
68
        $result->setTimezone(new \DateTimeZone(date_default_timezone_get()));
69
        return $result->format($pattern);
70
    }
71
72
    /**
73
     * Reads and returns the contents of the given Yaml file. If
74
     * it goes wrong, it throws an exception.
75
     *
76
     * @param string $fileName
77
     * the file to read
78
     *
79
     * @return array
80
     * the file contents
81
     */
82
    protected function readYaml($fileName) {
83
        try {
84
            $fileContent = file_get_contents($fileName);
85
            $parsedYaml  = Yaml::parse($fileContent);
86
            if (!is_array($parsedYaml)) {
87
                $parsedYaml = [];
88
            }
89
            return $parsedYaml;
90
        } catch (\Exception $e) {
91
            throw new \Exception('Could not open CRUD file '.$fileName);
92
        }
93
    }
94
95
    /**
96
     * Initializes needed but yet missing service providers.
97
     *
98
     * @param Container $app
99
     * the application container
100
     */
101
    protected function initMissingServiceProviders(Container $app) {
102
103
        if (!$app->offsetExists('translator')) {
104
            $app->register(new \Silex\Provider\LocaleServiceProvider());
105
            $app->register(new \Silex\Provider\TranslationServiceProvider(), [
106
                'locale_fallbacks' => ['en'],
107
            ]);
108
        }
109
110
        if (!$app->offsetExists('session')) {
111
            $app->register(new \Silex\Provider\SessionServiceProvider());
112
        }
113
114
        if (!$app->offsetExists('twig')) {
115
            $app->register(new \Silex\Provider\TwigServiceProvider());
116
            $app['twig.loader.filesystem']->addPath(__DIR__.'/../views/', 'crud');
117
        }
118
    }
119
120
    /**
121
     * Initializes the available locales.
122
     *
123
     * @param Container $app
124
     * the application container
125
     *
126
     * @return array
127
     * the available locales
128
     */
129
    protected function initLocales(Container $app) {
130
        $app['translator']->addLoader('yaml', new YamlFileLoader());
131
        $localeDir = __DIR__.'/../locales';
132
        $locales   = $this->getLocales();
133
        foreach ($locales as $locale) {
134
            $app['translator']->addResource('yaml', $localeDir.'/'.$locale.'.yml', $locale);
135
        }
136
        return $locales;
137
    }
138
139
    /**
140
     * Initializes the children of the data entries.
141
     */
142
    protected function initChildren() {
143
        foreach ($this->datas as $name => $data) {
144
            $fields = $data->getDefinition()->getFieldNames();
145
            foreach ($fields as $field) {
146
                if ($data->getDefinition()->getType($field) == 'reference') {
147
                    $this->datas[$data->getDefinition()->getReferenceEntity($field)]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name);
148
                }
149
            }
150
        }
151
    }
152
153
    /**
154
     * Gets a map with localized entity labels from the CRUD YML.
155
     *
156
     * @param array $locales
157
     * the available locales
158
     * @param array $crud
159
     * the CRUD entity map
160
     *
161
     * @return array
162
     * the map with localized entity labels
163
     */
164
    protected function getLocaleLabels($locales, $crud) {
165
        $localeLabels = [];
166
        foreach ($locales as $locale) {
167
            if (array_key_exists('label_'.$locale, $crud)) {
168
                $localeLabels[$locale] = $crud['label_'.$locale];
169
            }
170
        }
171
        return $localeLabels;
172
    }
173
174
    /**
175
     * Configures the EntityDefinition according to the given
176
     * CRUD entity map.
177
     *
178
     * @param EntityDefinition $definition
179
     * the definition to configure
180
     * @param array $crud
181
     * the CRUD entity map
182
     */
183
    protected function configureDefinition(EntityDefinition $definition, array $crud) {
184
        $toConfigure = [
185
            'deleteCascade',
186
            'listFields',
187
            'filter',
188
            'childrenLabelFields',
189
            'pageSize',
190
            'initialSortField',
191
            'initialSortAscending'
192
        ];
193
        foreach ($toConfigure as $field) {
194
            if (array_key_exists($field, $crud)) {
195
                $function = 'set'.ucfirst($field);
196
                $definition->$function($crud[$field]);
197
            }
198
        }
199
    }
200
201
    /**
202
     * Creates and setups an EntityDefinition instance.
203
     *
204
     * @param Container $app
205
     * the application container
206
     * @param array $locales
207
     * the available locales
208
     * @param array $crud
209
     * the parsed YAML of a CRUD entity
210
     * @param string $name
211
     * the name of the entity
212
     *
213
     * @return EntityDefinition
214
     * the EntityDefinition good to go
215
     */
216
    protected function createDefinition(Container $app, array $locales, array $crud, $name) {
217
        $label               = array_key_exists('label', $crud) ? $crud['label'] : $name;
218
        $localeLabels        = $this->getLocaleLabels($locales, $crud);
219
        $standardFieldLabels = [
220
            'id' => $app['translator']->trans('crudlex.label.id'),
221
            'created_at' => $app['translator']->trans('crudlex.label.created_at'),
222
            'updated_at' => $app['translator']->trans('crudlex.label.updated_at')
223
        ];
224
225
        $entityDefinitionFactory = $app->offsetExists('crud.entitydefinitionfactory') ? $app['crud.entitydefinitionfactory'] : new EntityDefinitionFactory();
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $entityDefinitionFactory exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
226
227
        $definition = $entityDefinitionFactory->createEntityDefinition(
228
            $crud['table'],
229
            $crud['fields'],
230
            $label,
231
            $localeLabels,
232
            $standardFieldLabels,
233
            $this
234
        );
235
        $this->configureDefinition($definition, $crud);
236
        return $definition;
237
    }
238
239
    /**
240
     * Validates the parsed entity definition.
241
     *
242
     * @param Container $app
243
     * the application container
244
     * @param array $entityDefinition
245
     * the entity definition to validate
246
     */
247
    protected function validateEntityDefinition(Container $app, array $entityDefinition) {
248
        $doValidate = !$app->offsetExists('crud.validateentitydefinition') || $app['crud.validateentitydefinition'] === true;
249
        if ($doValidate) {
250
            $validator = $app->offsetExists('crud.entitydefinitionvalidator')
251
                ? $app['crud.entitydefinitionvalidator']
252
                : new EntityDefinitionValidator();
253
            $validator->validate($entityDefinition);
254
        }
255
    }
256
257
    /**
258
     * Initializes the instance.
259
     *
260
     * @param DataFactoryInterface $dataFactory
261
     * the factory to create the concrete AbstractData instances
262
     * @param string $crudFile
263
     * the CRUD YAML file to parse
264
     * @param FileProcessorInterface $fileProcessor
265
     * the file processor used for file fields
266
     * @param boolean $manageI18n
267
     * holds whether we manage the i18n
268
     * @param Container $app
269
     * the application container
270
     */
271
    public function init(DataFactoryInterface $dataFactory, $crudFile, FileProcessorInterface $fileProcessor, $manageI18n, Container $app) {
272
273
        $parsedYaml = $this->readYaml($crudFile);
274
275
        $this->validateEntityDefinition($app, $parsedYaml);
276
277
        $this->initMissingServiceProviders($app);
278
279
        $this->manageI18n = $manageI18n;
280
        $locales          = $this->initLocales($app);
281
        $this->datas      = [];
282
        foreach ($parsedYaml as $name => $crud) {
283
            $definition         = $this->createDefinition($app, $locales, $crud, $name);
284
            $this->datas[$name] = $dataFactory->createData($definition, $fileProcessor);
285
        }
286
287
        $this->initChildren();
288
289
    }
290
291
    /**
292
     * Implements ServiceProviderInterface::register() registering $app['crud'].
293
     * $app['crud'] contains an instance of the ServiceProvider afterwards.
294
     *
295
     * @param Container $app
296
     * the Container instance of the Silex application
297
     */
298
    public function register(Container $app) {
299
        $app['crud'] = function() use ($app) {
300
            $result        = new static();
301
            $fileProcessor = $app->offsetExists('crud.fileprocessor') ? $app['crud.fileprocessor'] : new SimpleFilesystemFileProcessor();
302
            $manageI18n    = $app->offsetExists('crud.manageI18n') ? $app['crud.manageI18n'] : true;
303
            $result->init($app['crud.datafactory'], $app['crud.file'], $fileProcessor, $manageI18n, $app);
304
            return $result;
305
        };
306
    }
307
308
    /**
309
     * Getter for the {@see AbstractData} instances.
310
     *
311
     * @param string $name
312
     * the entity name of the desired Data instance
313
     *
314
     * @return AbstractData
315
     * the AbstractData instance or null on invalid name
316
     */
317
    public function getData($name) {
318
        if (!array_key_exists($name, $this->datas)) {
319
            return null;
320
        }
321
        return $this->datas[$name];
322
    }
323
324
    /**
325
     * Getter for all available entity names.
326
     *
327
     * @return string[]
328
     * a list of all available entity names
329
     */
330
    public function getEntities() {
331
        return array_keys($this->datas);
332
    }
333
334
    /**
335
     * Formats the given value to a date of the format 'Y-m-d'.
336
     *
337
     * @param string $value
338
     * the value, might be of the format 'Y-m-d H:i' or 'Y-m-d'
339
     * @param boolean $isUTC
340
     * whether the given value is in UTC
341
     *
342
     * @return string
343
     * the formatted result or an empty string on null value
344
     */
345
    public function formatDate($value, $isUTC) {
346
        $timezone = $isUTC ? 'UTC' : date_default_timezone_get();
347
        return $this->formatTime($value, $timezone, 'Y-m-d');
348
    }
349
350
    /**
351
     * Formats the given value to a date of the format 'Y-m-d H:i'.
352
     *
353
     * @param string $value
354
     * the value, might be of the format 'Y-m-d H:i'
355
     * @param boolean $isUTC
356
     * whether the given value is in UTC
357
     *
358
     * @return string
359
     * the formatted result or an empty string on null value
360
     */
361
    public function formatDateTime($value, $isUTC) {
362
        $timezone = $isUTC ? 'UTC' : date_default_timezone_get();
363
        return $this->formatTime($value, $timezone, 'Y-m-d H:i');
364
    }
365
366
    /**
367
     * Calls PHPs
368
     * {@link http://php.net/manual/en/function.basename.php basename} and
369
     * returns it's result.
370
     *
371
     * @param string $value
372
     * the value to be handed to basename
373
     *
374
     * @return string
375
     * the result of basename
376
     */
377
    public function basename($value) {
378
        return basename($value);
379
    }
380
381
    /**
382
     * Determines the Twig template to use for the given parameters depending on
383
     * the existance of certain keys in the Container $app in this order:
384
     *
385
     * crud.$section.$action.$entity
386
     * crud.$section.$action
387
     * crud.$section
388
     *
389
     * If nothing exists, this string is returned: "@crud/<action>.twig"
390
     *
391
     * @param Container $app
392
     * the Silex application
393
     * @param string $section
394
     * the section of the template, either "layout" or "template"
395
     * @param string $action
396
     * the current calling action like "create" or "show"
397
     * @param string $entity
398
     * the current calling entity
399
     *
400
     * @return string
401
     * the best fitting template
402
     */
403
    public function getTemplate(Container $app, $section, $action, $entity) {
404
        $crudSection       = 'crud.'.$section;
405
        $crudSectionAction = $crudSection.'.'.$action;
406
407
        $offsets = [
408
            $crudSectionAction.'.'.$entity,
409
            $crudSection.'.'.$entity,
410
            $crudSectionAction,
411
            $crudSection
412
        ];
413
        foreach ($offsets as $offset) {
414
            if ($app->offsetExists($offset)) {
415
                return $app[$offset];
416
            }
417
        }
418
419
        return '@crud/'.$action.'.twig';
420
    }
421
422
    /**
423
     * Gets whether CRUDlex manages the i18n system.
424
     *
425
     * @return boolean
426
     * true if CRUDlex manages the i18n system
427
     */
428
    public function isManagingI18n() {
429
        return $this->manageI18n;
430
    }
431
432
    /**
433
     * Sets the locale to be used.
434
     *
435
     * @param string $locale
436
     * the locale to be used.
437
     */
438
    public function setLocale($locale) {
439
        foreach ($this->datas as $data) {
440
            $data->getDefinition()->setLocale($locale);
441
        }
442
    }
443
444
    /**
445
     * Gets the available locales.
446
     *
447
     * @return array
448
     * the available locales
449
     */
450
    public function getLocales() {
451
        $localeDir     = __DIR__.'/../locales';
452
        $languageFiles = scandir($localeDir);
453
        $locales       = [];
454
        foreach ($languageFiles as $languageFile) {
455
            if (in_array($languageFile, ['.', '..'])) {
456
                continue;
457
            }
458
            $extensionPos = strpos($languageFile, '.yml');
459
            if ($extensionPos !== false) {
460
                $locale    = substr($languageFile, 0, $extensionPos);
461
                $locales[] = $locale;
462
            }
463
        }
464
        sort($locales);
465
        return $locales;
466
    }
467
468
    /**
469
     * Gets a language name in the given language.
470
     *
471
     * @param string $language
472
     * the language code of the desired language name
473
     *
474
     * @return string
475
     * the language name in the given language or null if not available
476
     */
477
    public function getLanguageName($language) {
478
        return \Symfony\Component\Intl\Intl::getLanguageBundle()->getLanguageName($language, $language, $language);
479
    }
480
481
    /**
482
     * Formats a float to not display in scientific notation.
483
     *
484
     * @param float $float
485
     * the float to format
486
     *
487
     * @return double|string
488
     * the formated float
489
     */
490
    public function formatFloat($float) {
491
492
        if (!$float) {
493
            return $float;
494
        }
495
496
        $zeroFraction = $float - floor($float) == 0 ? '0' : '';
497
498
        // We don't want values like 0.004 converted to  0.00400000000000000008
499
        if ($float > 0.0001) {
500
            return $float.($zeroFraction === '0' ? '.'.$zeroFraction : '');
501
        }
502
503
        // We don't want values like 0.00004 converted to its scientific notation 4.0E-5
504
        return rtrim(sprintf('%.20F', $float), '0').$zeroFraction;
505
    }
506
507
}
508