Completed
Push — master ( a9994d...fef649 )
by Philip
07:17
created

ServiceProvider::arrayColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
        $factory = $app->offsetExists('crud.entitydefinitionfactory') ? $app['crud.entitydefinitionfactory'] : new EntityDefinitionFactory();
234
235
        $definition = $factory->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
        $twigExtensions = new TwigExtensions();
296
        $twigExtensions->registerTwigExtensions($app);
297
298
        $this->initChildren();
299
300
    }
301
302
    /**
303
     * Implements ServiceProviderInterface::register() registering $app['crud'].
304
     * $app['crud'] contains an instance of the ServiceProvider afterwards.
305
     *
306
     * @param Container $app
307
     * the Container instance of the Silex application
308
     */
309
    public function register(Container $app) {
310
        $app['crud'] = function() use ($app) {
311
            $result        = new static();
312
            $fileProcessor = $app->offsetExists('crud.fileprocessor') ? $app['crud.fileprocessor'] : new SimpleFilesystemFileProcessor();
313
            $manageI18n    = $app->offsetExists('crud.manageI18n') ? $app['crud.manageI18n'] : true;
314
            $result->init($app['crud.datafactory'], $app['crud.file'], $fileProcessor, $manageI18n, $app);
315
            return $result;
316
        };
317
    }
318
319
    /**
320
     * Getter for the {@see AbstractData} instances.
321
     *
322
     * @param string $name
323
     * the entity name of the desired Data instance
324
     *
325
     * @return AbstractData
326
     * the AbstractData instance or null on invalid name
327
     */
328
    public function getData($name) {
329
        if (!array_key_exists($name, $this->datas)) {
330
            return null;
331
        }
332
        return $this->datas[$name];
333
    }
334
335
    /**
336
     * Getter for all available entity names.
337
     *
338
     * @return string[]
339
     * a list of all available entity names
340
     */
341
    public function getEntities() {
342
        return array_keys($this->datas);
343
    }
344
345
    /**
346
     * Formats the given value to a date of the format 'Y-m-d'.
347
     *
348
     * @param string $value
349
     * the value, might be of the format 'Y-m-d H:i' or 'Y-m-d'
350
     * @param boolean $isUTC
351
     * whether the given value is in UTC
352
     *
353
     * @return string
354
     * the formatted result or an empty string on null value
355
     */
356
    public function formatDate($value, $isUTC) {
357
        $timezone = $isUTC ? 'UTC' : date_default_timezone_get();
358
        return $this->formatTime($value, $timezone, 'Y-m-d');
359
    }
360
361
    /**
362
     * Formats the given value to a date of the format 'Y-m-d H:i'.
363
     *
364
     * @param string $value
365
     * the value, might be of the format 'Y-m-d H:i'
366
     * @param boolean $isUTC
367
     * whether the given value is in UTC
368
     *
369
     * @return string
370
     * the formatted result or an empty string on null value
371
     */
372
    public function formatDateTime($value, $isUTC) {
373
        $timezone = $isUTC ? 'UTC' : date_default_timezone_get();
374
        return $this->formatTime($value, $timezone, 'Y-m-d H:i');
375
    }
376
377
    /**
378
     * Calls PHPs
379
     * {@link http://php.net/manual/en/function.basename.php basename} and
380
     * returns it's result.
381
     *
382
     * @param string $value
383
     * the value to be handed to basename
384
     *
385
     * @return string
386
     * the result of basename
387
     */
388
    public function basename($value) {
389
        return basename($value);
390
    }
391
392
    /**
393
     * Determines the Twig template to use for the given parameters depending on
394
     * the existance of certain keys in the Container $app in this order:
395
     *
396
     * crud.$section.$action.$entity
397
     * crud.$section.$action
398
     * crud.$section
399
     *
400
     * If nothing exists, this string is returned: "@crud/<action>.twig"
401
     *
402
     * @param Container $app
403
     * the Silex application
404
     * @param string $section
405
     * the section of the template, either "layout" or "template"
406
     * @param string $action
407
     * the current calling action like "create" or "show"
408
     * @param string $entity
409
     * the current calling entity
410
     *
411
     * @return string
412
     * the best fitting template
413
     */
414
    public function getTemplate(Container $app, $section, $action, $entity) {
415
        $crudSection       = 'crud.'.$section;
416
        $crudSectionAction = $crudSection.'.'.$action;
417
418
        $offsets = [
419
            $crudSectionAction.'.'.$entity,
420
            $crudSection.'.'.$entity,
421
            $crudSectionAction,
422
            $crudSection
423
        ];
424
        foreach ($offsets as $offset) {
425
            if ($app->offsetExists($offset)) {
426
                return $app[$offset];
427
            }
428
        }
429
430
        return '@crud/'.$action.'.twig';
431
    }
432
433
    /**
434
     * Gets whether CRUDlex manages the i18n system.
435
     *
436
     * @return boolean
437
     * true if CRUDlex manages the i18n system
438
     */
439
    public function isManagingI18n() {
440
        return $this->manageI18n;
441
    }
442
443
    /**
444
     * Sets the locale to be used.
445
     *
446
     * @param string $locale
447
     * the locale to be used.
448
     */
449
    public function setLocale($locale) {
450
        foreach ($this->datas as $data) {
451
            $data->getDefinition()->setLocale($locale);
452
        }
453
    }
454
455
    /**
456
     * Gets the available locales.
457
     *
458
     * @return array
459
     * the available locales
460
     */
461
    public function getLocales() {
462
        $localeDir     = __DIR__.'/../locales';
463
        $languageFiles = scandir($localeDir);
464
        $locales       = [];
465
        foreach ($languageFiles as $languageFile) {
466
            if (in_array($languageFile, ['.', '..'])) {
467
                continue;
468
            }
469
            $extensionPos = strpos($languageFile, '.yml');
470
            if ($extensionPos !== false) {
471
                $locale    = substr($languageFile, 0, $extensionPos);
472
                $locales[] = $locale;
473
            }
474
        }
475
        sort($locales);
476
        return $locales;
477
    }
478
479
    /**
480
     * Gets a language name in the given language.
481
     *
482
     * @param string $language
483
     * the language code of the desired language name
484
     *
485
     * @return string
486
     * the language name in the given language or null if not available
487
     */
488
    public function getLanguageName($language) {
489
        return Intl::getLanguageBundle()->getLanguageName($language, $language, $language);
490
    }
491
492
    /**
493
     * Formats a float to not display in scientific notation.
494
     *
495
     * @param float $float
496
     * the float to format
497
     *
498
     * @return double|string
499
     * the formated float
500
     */
501
    public function formatFloat($float) {
502
503
        if (!$float) {
504
            return $float;
505
        }
506
507
        $zeroFraction = $float - floor($float) == 0 ? '0' : '';
508
509
        // We don't want values like 0.004 converted to  0.00400000000000000008
510
        if ($float > 0.0001) {
511
            return $float.($zeroFraction === '0' ? '.'.$zeroFraction : '');
512
        }
513
514
        // We don't want values like 0.00004 converted to its scientific notation 4.0E-5
515
        return rtrim(sprintf('%.20F', $float), '0').$zeroFraction;
516
    }
517
518
}
519