Completed
Push — master ( 39b308...0f3d85 )
by Philip
03:06
created

ServiceProvider::arrayColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
nc 1
cc 1
eloc 2
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 Silex\Provider\LocaleServiceProvider;
17
use Silex\Provider\SessionServiceProvider;
18
use Silex\Provider\TranslationServiceProvider;
19
use Silex\Provider\TwigServiceProvider;
20
use Symfony\Component\Intl\Intl;
21
use Symfony\Component\Translation\Loader\YamlFileLoader;
22
use Symfony\Component\Yaml\Yaml;
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 {
30
31
    /**
32
     * Holds the {@see AbstractData} instances.
33
     */
34
    protected $datas;
35
36
    /**
37
     * Holds whether we manage the i18n.
38
     */
39
    protected $manageI18n;
40
41
    /**
42
     * Formats the given time value to a timestring defined by the $pattern
43
     * parameter.
44
     *
45
     * If the value is false (like null), an empty string is
46
     * returned. Else, the value is tried to be parsed as datetime via the
47
     * given pattern. If that fails, it is tried to be parsed with the pattern
48
     * 'Y-m-d H:i:s'. If that fails, the value is returned unchanged. Else, it
49
     * is returned formatted with the given pattern. The effect is to shorten
50
     * 'Y-m-d H:i:s' to 'Y-m-d' for example.
51
     *
52
     * @param string $value
53
     * the value to be formatted
54
     * @param string $timezone
55
     * the timezone of the value
56
     * @param string $pattern
57
     * the pattern with which the value is parsed and formatted
58
     *
59
     * @return string
60
     * the formatted value
61
     */
62
    protected function formatTime($value, $timezone, $pattern) {
63
        if (!$value) {
64
            return '';
65
        }
66
        $result = \DateTime::createFromFormat($pattern, $value, new \DateTimeZone($timezone));
67
        if ($result === false) {
68
            $result = \DateTime::createFromFormat('Y-m-d H:i:s', $value, new \DateTimeZone($timezone));
69
        }
70
        if ($result === false) {
71
            return $value;
72
        }
73
        $result->setTimezone(new \DateTimeZone(date_default_timezone_get()));
74
        return $result->format($pattern);
75
    }
76
77
    /**
78
     * Reads and returns the contents of the given Yaml file. If
79
     * it goes wrong, it throws an exception.
80
     *
81
     * @param string $fileName
82
     * the file to read
83
     *
84
     * @return array
85
     * the file contents
86
     *
87
     * @throws \RuntimeException
88
     * thrown if the file could not be read or parsed
89
     */
90
    protected function readYaml($fileName) {
91
        try {
92
            $fileContent = file_get_contents($fileName);
93
            $parsedYaml  = Yaml::parse($fileContent);
94
            if (!is_array($parsedYaml)) {
95
                $parsedYaml = [];
96
            }
97
            return $parsedYaml;
98
        } catch (\Exception $e) {
99
            throw new \RuntimeException('Could not open CRUD file '.$fileName, $e->getCode(), $e);
100
        }
101
    }
102
103
    /**
104
     * Initializes needed but yet missing service providers.
105
     *
106
     * @param Container $app
107
     * the application container
108
     */
109
    protected function initMissingServiceProviders(Container $app) {
110
111
        if (!$app->offsetExists('translator')) {
112
            $app->register(new LocaleServiceProvider());
113
            $app->register(new TranslationServiceProvider(), [
114
                'locale_fallbacks' => ['en'],
115
            ]);
116
        }
117
118
        if (!$app->offsetExists('session')) {
119
            $app->register(new SessionServiceProvider());
120
        }
121
122
        if (!$app->offsetExists('twig')) {
123
            $app->register(new TwigServiceProvider());
124
            $app['twig.loader.filesystem']->addPath(__DIR__.'/../views/', 'crud');
125
        }
126
    }
127
128
    /**
129
     * Initializes the available locales.
130
     *
131
     * @param Container $app
132
     * the application container
133
     *
134
     * @return array
135
     * the available locales
136
     */
137
    protected function initLocales(Container $app) {
138
        $app['translator']->addLoader('yaml', new YamlFileLoader());
139
        $localeDir = __DIR__.'/../locales';
140
        $locales   = $this->getLocales();
141
        foreach ($locales as $locale) {
142
            $app['translator']->addResource('yaml', $localeDir.'/'.$locale.'.yml', $locale);
143
        }
144
        return $locales;
145
    }
146
147
    /**
148
     * Initializes the children of the data entries.
149
     */
150
    protected function initChildren() {
151
        foreach ($this->datas as $name => $data) {
152
            $fields = $data->getDefinition()->getFieldNames();
153
            foreach ($fields as $field) {
154
                if ($data->getDefinition()->getType($field) == 'reference') {
155
                    $this->datas[$data->getDefinition()->getReferenceEntity($field)]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name);
156
                }
157
            }
158
        }
159
    }
160
161
    /**
162
     * Gets a map with localized entity labels from the CRUD YML.
163
     *
164
     * @param array $locales
165
     * the available locales
166
     * @param array $crud
167
     * the CRUD entity map
168
     *
169
     * @return array
170
     * the map with localized entity labels
171
     */
172
    protected function getLocaleLabels($locales, $crud) {
173
        $localeLabels = [];
174
        foreach ($locales as $locale) {
175
            if (array_key_exists('label_'.$locale, $crud)) {
176
                $localeLabels[$locale] = $crud['label_'.$locale];
177
            }
178
        }
179
        return $localeLabels;
180
    }
181
182
    /**
183
     * Configures the EntityDefinition according to the given
184
     * CRUD entity map.
185
     *
186
     * @param EntityDefinition $definition
187
     * the definition to configure
188
     * @param array $crud
189
     * the CRUD entity map
190
     */
191
    protected function configureDefinition(EntityDefinition $definition, array $crud) {
192
        $toConfigure = [
193
            'deleteCascade',
194
            'listFields',
195
            'filter',
196
            'childrenLabelFields',
197
            'pageSize',
198
            'initialSortField',
199
            'initialSortAscending'
200
        ];
201
        foreach ($toConfigure as $field) {
202
            if (array_key_exists($field, $crud)) {
203
                $function = 'set'.ucfirst($field);
204
                $definition->$function($crud[$field]);
205
            }
206
        }
207
    }
208
209
    /**
210
     * Creates and setups an EntityDefinition instance.
211
     *
212
     * @param Container $app
213
     * the application container
214
     * @param array $locales
215
     * the available locales
216
     * @param array $crud
217
     * the parsed YAML of a CRUD entity
218
     * @param string $name
219
     * the name of the entity
220
     *
221
     * @return EntityDefinition
222
     * the EntityDefinition good to go
223
     */
224
    protected function createDefinition(Container $app, array $locales, array $crud, $name) {
225
        $label               = array_key_exists('label', $crud) ? $crud['label'] : $name;
226
        $localeLabels        = $this->getLocaleLabels($locales, $crud);
227
        $standardFieldLabels = [
228
            'id' => $app['translator']->trans('crudlex.label.id'),
229
            'created_at' => $app['translator']->trans('crudlex.label.created_at'),
230
            'updated_at' => $app['translator']->trans('crudlex.label.updated_at')
231
        ];
232
233
        $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...
234
235
        $definition = $entityDefinitionFactory->createEntityDefinition(
236
            $crud['table'],
237
            $crud['fields'],
238
            $label,
239
            $localeLabels,
240
            $standardFieldLabels,
241
            $this
242
        );
243
        $this->configureDefinition($definition, $crud);
244
        return $definition;
245
    }
246
247
    /**
248
     * Validates the parsed entity definition.
249
     *
250
     * @param Container $app
251
     * the application container
252
     * @param array $entityDefinition
253
     * the entity definition to validate
254
     */
255
    protected function validateEntityDefinition(Container $app, array $entityDefinition) {
256
        $doValidate = !$app->offsetExists('crud.validateentitydefinition') || $app['crud.validateentitydefinition'] === true;
257
        if ($doValidate) {
258
            $validator = $app->offsetExists('crud.entitydefinitionvalidator')
259
                ? $app['crud.entitydefinitionvalidator']
260
                : new EntityDefinitionValidator();
261
            $validator->validate($entityDefinition);
262
        }
263
    }
264
265
    /**
266
     * Initializes the instance.
267
     *
268
     * @param DataFactoryInterface $dataFactory
269
     * the factory to create the concrete AbstractData instances
270
     * @param string $crudFile
271
     * the CRUD YAML file to parse
272
     * @param FileProcessorInterface $fileProcessor
273
     * the file processor used for file fields
274
     * @param boolean $manageI18n
275
     * holds whether we manage the i18n
276
     * @param Container $app
277
     * the application container
278
     */
279
    public function init(DataFactoryInterface $dataFactory, $crudFile, FileProcessorInterface $fileProcessor, $manageI18n, Container $app) {
280
281
        $parsedYaml = $this->readYaml($crudFile);
282
283
        $this->validateEntityDefinition($app, $parsedYaml);
284
285
        $this->initMissingServiceProviders($app);
286
287
        $this->manageI18n = $manageI18n;
288
        $locales          = $this->initLocales($app);
289
        $this->datas      = [];
290
        foreach ($parsedYaml as $name => $crud) {
291
            $definition         = $this->createDefinition($app, $locales, $crud, $name);
292
            $this->datas[$name] = $dataFactory->createData($definition, $fileProcessor);
293
        }
294
295
        $this->initChildren();
296
297
    }
298
299
    /**
300
     * Implements ServiceProviderInterface::register() registering $app['crud'].
301
     * $app['crud'] contains an instance of the ServiceProvider afterwards.
302
     *
303
     * @param Container $app
304
     * the Container instance of the Silex application
305
     */
306
    public function register(Container $app) {
307
        $app['crud'] = function() use ($app) {
308
            $result        = new static();
309
            $fileProcessor = $app->offsetExists('crud.fileprocessor') ? $app['crud.fileprocessor'] : new SimpleFilesystemFileProcessor();
310
            $manageI18n    = $app->offsetExists('crud.manageI18n') ? $app['crud.manageI18n'] : true;
311
            $result->init($app['crud.datafactory'], $app['crud.file'], $fileProcessor, $manageI18n, $app);
312
            return $result;
313
        };
314
    }
315
316
    /**
317
     * Getter for the {@see AbstractData} instances.
318
     *
319
     * @param string $name
320
     * the entity name of the desired Data instance
321
     *
322
     * @return AbstractData
323
     * the AbstractData instance or null on invalid name
324
     */
325
    public function getData($name) {
326
        if (!array_key_exists($name, $this->datas)) {
327
            return null;
328
        }
329
        return $this->datas[$name];
330
    }
331
332
    /**
333
     * Getter for all available entity names.
334
     *
335
     * @return string[]
336
     * a list of all available entity names
337
     */
338
    public function getEntities() {
339
        return array_keys($this->datas);
340
    }
341
342
    /**
343
     * Formats the given value to a date of the format 'Y-m-d'.
344
     *
345
     * @param string $value
346
     * the value, might be of the format 'Y-m-d H:i' or 'Y-m-d'
347
     * @param boolean $isUTC
348
     * whether the given value is in UTC
349
     *
350
     * @return string
351
     * the formatted result or an empty string on null value
352
     */
353
    public function formatDate($value, $isUTC) {
354
        $timezone = $isUTC ? 'UTC' : date_default_timezone_get();
355
        return $this->formatTime($value, $timezone, 'Y-m-d');
356
    }
357
358
    /**
359
     * Formats the given value to a date of the format 'Y-m-d H:i'.
360
     *
361
     * @param string $value
362
     * the value, might be of the format 'Y-m-d H:i'
363
     * @param boolean $isUTC
364
     * whether the given value is in UTC
365
     *
366
     * @return string
367
     * the formatted result or an empty string on null value
368
     */
369
    public function formatDateTime($value, $isUTC) {
370
        $timezone = $isUTC ? 'UTC' : date_default_timezone_get();
371
        return $this->formatTime($value, $timezone, 'Y-m-d H:i');
372
    }
373
374
    /**
375
     * Calls PHPs
376
     * {@link http://php.net/manual/en/function.basename.php basename} and
377
     * returns it's result.
378
     *
379
     * @param string $value
380
     * the value to be handed to basename
381
     *
382
     * @return string
383
     * the result of basename
384
     */
385
    public function basename($value) {
386
        return basename($value);
387
    }
388
389
    /**
390
     * Determines the Twig template to use for the given parameters depending on
391
     * the existance of certain keys in the Container $app in this order:
392
     *
393
     * crud.$section.$action.$entity
394
     * crud.$section.$action
395
     * crud.$section
396
     *
397
     * If nothing exists, this string is returned: "@crud/<action>.twig"
398
     *
399
     * @param Container $app
400
     * the Silex application
401
     * @param string $section
402
     * the section of the template, either "layout" or "template"
403
     * @param string $action
404
     * the current calling action like "create" or "show"
405
     * @param string $entity
406
     * the current calling entity
407
     *
408
     * @return string
409
     * the best fitting template
410
     */
411
    public function getTemplate(Container $app, $section, $action, $entity) {
412
        $crudSection       = 'crud.'.$section;
413
        $crudSectionAction = $crudSection.'.'.$action;
414
415
        $offsets = [
416
            $crudSectionAction.'.'.$entity,
417
            $crudSection.'.'.$entity,
418
            $crudSectionAction,
419
            $crudSection
420
        ];
421
        foreach ($offsets as $offset) {
422
            if ($app->offsetExists($offset)) {
423
                return $app[$offset];
424
            }
425
        }
426
427
        return '@crud/'.$action.'.twig';
428
    }
429
430
    /**
431
     * Gets whether CRUDlex manages the i18n system.
432
     *
433
     * @return boolean
434
     * true if CRUDlex manages the i18n system
435
     */
436
    public function isManagingI18n() {
437
        return $this->manageI18n;
438
    }
439
440
    /**
441
     * Sets the locale to be used.
442
     *
443
     * @param string $locale
444
     * the locale to be used.
445
     */
446
    public function setLocale($locale) {
447
        foreach ($this->datas as $data) {
448
            $data->getDefinition()->setLocale($locale);
449
        }
450
    }
451
452
    /**
453
     * Gets the available locales.
454
     *
455
     * @return array
456
     * the available locales
457
     */
458
    public function getLocales() {
459
        $localeDir     = __DIR__.'/../locales';
460
        $languageFiles = scandir($localeDir);
461
        $locales       = [];
462
        foreach ($languageFiles as $languageFile) {
463
            if (in_array($languageFile, ['.', '..'])) {
464
                continue;
465
            }
466
            $extensionPos = strpos($languageFile, '.yml');
467
            if ($extensionPos !== false) {
468
                $locale    = substr($languageFile, 0, $extensionPos);
469
                $locales[] = $locale;
470
            }
471
        }
472
        sort($locales);
473
        return $locales;
474
    }
475
476
    /**
477
     * Gets a language name in the given language.
478
     *
479
     * @param string $language
480
     * the language code of the desired language name
481
     *
482
     * @return string
483
     * the language name in the given language or null if not available
484
     */
485
    public function getLanguageName($language) {
486
        return Intl::getLanguageBundle()->getLanguageName($language, $language, $language);
487
    }
488
489
    /**
490
     * Formats a float to not display in scientific notation.
491
     *
492
     * @param float $float
493
     * the float to format
494
     *
495
     * @return double|string
496
     * the formated float
497
     */
498
    public function formatFloat($float) {
499
500
        if (!$float) {
501
            return $float;
502
        }
503
504
        $zeroFraction = $float - floor($float) == 0 ? '0' : '';
505
506
        // We don't want values like 0.004 converted to  0.00400000000000000008
507
        if ($float > 0.0001) {
508
            return $float.($zeroFraction === '0' ? '.'.$zeroFraction : '');
509
        }
510
511
        // We don't want values like 0.00004 converted to its scientific notation 4.0E-5
512
        return rtrim(sprintf('%.20F', $float), '0').$zeroFraction;
513
    }
514
515
    public function arrayColumn($array, $key) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
516
        return array_column($array, $key);
517
    }
518
519
}
520