Completed
Push — master ( 8788dd...b06ea8 )
by Philip
02:14
created

ServiceProvider::boot()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 4
nop 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;
13
14
use Pimple\Container;
15
use Pimple\ServiceProviderInterface;
16
use Silex\Api\BootableProviderInterface;
17
use Silex\Application;
18
use Silex\Provider\LocaleServiceProvider;
19
use Silex\Provider\SessionServiceProvider;
20
use Silex\Provider\TranslationServiceProvider;
21
use Silex\Provider\TwigServiceProvider;
22
use Symfony\Component\Translation\Loader\YamlFileLoader;
23
use Symfony\Component\Translation\Translator;
24
use Symfony\Component\Yaml\Yaml;
25
26
/**
27
 * The ServiceProvider setups and initializes the whole CRUD system.
28
 * After adding it to your Silex-setup, it offers access to {@see AbstractData}
29
 * instances, one for each defined entity off the CRUD YAML file.
30
 */
31
class ServiceProvider implements ServiceProviderInterface, BootableProviderInterface {
32
33
    /**
34
     * Holds the {@see AbstractData} instances.
35
     */
36
    protected $datas;
37
38
    /**
39
     * Holds whether we manage the i18n.
40
     */
41
    protected $manageI18n;
42
43
    /**
44
     * Reads and returns the contents of the given Yaml file. If
45
     * it goes wrong, it throws an exception.
46
     *
47
     * @param string $fileName
48
     * the file to read
49
     *
50
     * @return array
51
     * the file contents
52
     *
53
     * @throws \RuntimeException
54
     * thrown if the file could not be read or parsed
55
     */
56
    protected function readYaml($fileName) {
57
        try {
58
            $fileContent = file_get_contents($fileName);
59
            $parsedYaml  = Yaml::parse($fileContent);
60
            if (!is_array($parsedYaml)) {
61
                $parsedYaml = [];
62
            }
63
            return $parsedYaml;
64
        } catch (\Exception $e) {
65
            throw new \RuntimeException('Could not open CRUD file '.$fileName, $e->getCode(), $e);
66
        }
67
    }
68
69
    /**
70
     * Initializes needed but yet missing service providers.
71
     *
72
     * @param Container $app
73
     * the application container
74
     */
75
    protected function initMissingServiceProviders(Container $app) {
76
77
        if (!$app->offsetExists('translator')) {
78
            $app->register(new LocaleServiceProvider());
79
            $app->register(new TranslationServiceProvider(), [
80
                'locale_fallbacks' => ['en'],
81
            ]);
82
        }
83
84
        if (!$app->offsetExists('session')) {
85
            $app->register(new SessionServiceProvider());
86
        }
87
88
        if (!$app->offsetExists('twig')) {
89
            $app->register(new TwigServiceProvider());
90
        }
91
        $app['twig.loader.filesystem']->addPath(__DIR__.'/../views/', 'crud');
92
    }
93
94
    /**
95
     * Initializes the available locales.
96
     *
97
     * @param Container $app
98
     * the application container
99
     *
100
     * @return array
101
     * the available locales
102
     */
103
    protected function initLocales(Container $app) {
104
        $locales = $this->getLocales();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
105
        $localeDir = __DIR__.'/../locales';
106
        $app['translator']->addLoader('yaml', new YamlFileLoader());
107
        foreach ($locales as $locale) {
108
            $app['translator']->addResource('yaml', $localeDir.'/'.$locale.'.yml', $locale);
109
        }
110
        return $locales;
111
    }
112
113
    /**
114
     * Initializes the children of the data entries.
115
     */
116
    protected function initChildren() {
117
        foreach ($this->datas as $name => $data) {
118
            $fields = $data->getDefinition()->getFieldNames();
119
            foreach ($fields as $field) {
120
                if ($data->getDefinition()->getType($field) == 'reference') {
121
                    $this->datas[$data->getDefinition()->getSubTypeField($field, 'reference', 'entity')]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name);
122
                }
123
            }
124
        }
125
    }
126
127
    /**
128
     * Gets a map with localized entity labels from the CRUD YML.
129
     *
130
     * @param array $locales
131
     * the available locales
132
     * @param array $crud
133
     * the CRUD entity map
134
     *
135
     * @return array
136
     * the map with localized entity labels
137
     */
138
    protected function getLocaleLabels($locales, $crud) {
139
        $localeLabels = [];
140
        foreach ($locales as $locale) {
141
            if (array_key_exists('label_'.$locale, $crud)) {
142
                $localeLabels[$locale] = $crud['label_'.$locale];
143
            }
144
        }
145
        return $localeLabels;
146
    }
147
148
    /**
149
     * Configures the EntityDefinition according to the given
150
     * CRUD entity map.
151
     *
152
     * @param EntityDefinition $definition
153
     * the definition to configure
154
     * @param array $crud
155
     * the CRUD entity map
156
     */
157
    protected function configureDefinition(EntityDefinition $definition, array $crud) {
158
        $toConfigure = [
159
            'deleteCascade',
160
            'listFields',
161
            'filter',
162
            'childrenLabelFields',
163
            'pageSize',
164
            'initialSortField',
165
            'initialSortAscending'
166
        ];
167
        foreach ($toConfigure as $field) {
168
            if (array_key_exists($field, $crud)) {
169
                $function = 'set'.ucfirst($field);
170
                $definition->$function($crud[$field]);
171
            }
172
        }
173
    }
174
175
    /**
176
     * Creates and setups an EntityDefinition instance.
177
     *
178
     * @param Container $app
179
     * the application container
180
     * @param array $locales
181
     * the available locales
182
     * @param array $crud
183
     * the parsed YAML of a CRUD entity
184
     * @param string $name
185
     * the name of the entity
186
     *
187
     * @return EntityDefinition
188
     * the EntityDefinition good to go
189
     */
190
    protected function createDefinition(Container $app, array $locales, array $crud, $name) {
191
        $label               = array_key_exists('label', $crud) ? $crud['label'] : $name;
192
        $localeLabels        = $this->getLocaleLabels($locales, $crud);
193
        $standardFieldLabels = [
194
            'id' => $app['translator']->trans('crudlex.label.id'),
195
            'created_at' => $app['translator']->trans('crudlex.label.created_at'),
196
            'updated_at' => $app['translator']->trans('crudlex.label.updated_at')
197
        ];
198
199
        $factory = $app->offsetExists('crud.entitydefinitionfactory') ? $app['crud.entitydefinitionfactory'] : new EntityDefinitionFactory();
200
201
        $definition = $factory->createEntityDefinition(
202
            $crud['table'],
203
            $crud['fields'],
204
            $label,
205
            $localeLabels,
206
            $standardFieldLabels,
207
            $this
208
        );
209
        $this->configureDefinition($definition, $crud);
210
        return $definition;
211
    }
212
213
    /**
214
     * Validates the parsed entity definition.
215
     *
216
     * @param Container $app
217
     * the application container
218
     * @param array $entityDefinition
219
     * the entity definition to validate
220
     */
221
    protected function validateEntityDefinition(Container $app, array $entityDefinition) {
222
        $doValidate = !$app->offsetExists('crud.validateentitydefinition') || $app['crud.validateentitydefinition'] === true;
223
        if ($doValidate) {
224
            $validator = $app->offsetExists('crud.entitydefinitionvalidator')
225
                ? $app['crud.entitydefinitionvalidator']
226
                : new EntityDefinitionValidator();
227
            $validator->validate($entityDefinition);
228
        }
229
    }
230
231
    /**
232
     * Initializes the instance.
233
     *
234
     * @param DataFactoryInterface $dataFactory
235
     * the factory to create the concrete AbstractData instances
236
     * @param string $crudFile
237
     * the CRUD YAML file to parse
238
     * @param FileProcessorInterface $fileProcessor
239
     * the file processor used for file fields
240
     * @param boolean $manageI18n
241
     * holds whether we manage the i18n
242
     * @param Container $app
243
     * the application container
244
     */
245
    public function init(DataFactoryInterface $dataFactory, $crudFile, FileProcessorInterface $fileProcessor, $manageI18n, Container $app) {
246
247
        $parsedYaml = $this->readYaml($crudFile);
248
249
        $this->validateEntityDefinition($app, $parsedYaml);
250
251
        $this->initMissingServiceProviders($app);
252
253
        $this->manageI18n = $manageI18n;
254
        $locales          = $this->initLocales($app);
255
        $this->datas      = [];
256
        foreach ($parsedYaml as $name => $crud) {
257
            $definition         = $this->createDefinition($app, $locales, $crud, $name);
258
            $this->datas[$name] = $dataFactory->createData($definition, $fileProcessor);
259
        }
260
261
        $twigExtensions = new TwigExtensions();
262
        $twigExtensions->registerTwigExtensions($app);
263
264
        $this->initChildren();
265
266
    }
267
268
    /**
269
     * Implements ServiceProviderInterface::register() registering $app['crud'].
270
     * $app['crud'] contains an instance of the ServiceProvider afterwards.
271
     *
272
     * @param Container $app
273
     * the Container instance of the Silex application
274
     */
275
    public function register(Container $app) {
276
        $app['crud'] = function() use ($app) {
277
            $result        = new static();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 8 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
278
            return $result;
279
        };
280
    }
281
282
    /**
283
     * Initializes the crud service right after boot.
284
     *
285
     * @param Application $app
286
     * the Container instance of the Silex application
287
     */
288
    public function boot(Application $app) {
289
        $fileProcessor = $app->offsetExists('crud.fileprocessor') ? $app['crud.fileprocessor'] : new SimpleFilesystemFileProcessor();
290
        $manageI18n    = $app->offsetExists('crud.manageI18n') ? $app['crud.manageI18n'] : true;
291
        $app['crud']->init($app['crud.datafactory'], $app['crud.file'], $fileProcessor, $manageI18n, $app);
292
    }
293
294
    /**
295
     * Getter for the {@see AbstractData} instances.
296
     *
297
     * @param string $name
298
     * the entity name of the desired Data instance
299
     *
300
     * @return AbstractData
301
     * the AbstractData instance or null on invalid name
302
     */
303
    public function getData($name) {
304
        if (!array_key_exists($name, $this->datas)) {
305
            return null;
306
        }
307
        return $this->datas[$name];
308
    }
309
310
    /**
311
     * Getter for all available entity names.
312
     *
313
     * @return string[]
314
     * a list of all available entity names
315
     */
316
    public function getEntities() {
317
        return array_keys($this->datas);
318
    }
319
320
    /**
321
     * Determines the Twig template to use for the given parameters depending on
322
     * the existance of certain keys in the Container $app in this order:
323
     *
324
     * crud.$section.$action.$entity
325
     * crud.$section.$action
326
     * crud.$section
327
     *
328
     * If nothing exists, this string is returned: "@crud/<action>.twig"
329
     *
330
     * @param Container $app
331
     * the Silex application
332
     * @param string $section
333
     * the section of the template, either "layout" or "template"
334
     * @param string $action
335
     * the current calling action like "create" or "show"
336
     * @param string $entity
337
     * the current calling entity
338
     *
339
     * @return string
340
     * the best fitting template
341
     */
342
    public function getTemplate(Container $app, $section, $action, $entity) {
343
        $crudSection       = 'crud.'.$section;
344
        $crudSectionAction = $crudSection.'.'.$action;
345
346
        $offsets = [
347
            $crudSectionAction.'.'.$entity,
348
            $crudSection.'.'.$entity,
349
            $crudSectionAction,
350
            $crudSection
351
        ];
352
        foreach ($offsets as $offset) {
353
            if ($app->offsetExists($offset)) {
354
                return $app[$offset];
355
            }
356
        }
357
358
        return '@crud/'.$action.'.twig';
359
    }
360
361
    /**
362
     * Gets whether CRUDlex manages the i18n system.
363
     *
364
     * @return boolean
365
     * true if CRUDlex manages the i18n system
366
     */
367
    public function isManagingI18n() {
368
        return $this->manageI18n;
369
    }
370
371
    /**
372
     * Sets the locale to be used.
373
     *
374
     * @param string $locale
375
     * the locale to be used.
376
     */
377
    public function setLocale($locale) {
378
        foreach ($this->datas as $data) {
379
            $data->getDefinition()->setLocale($locale);
380
        }
381
    }
382
383
    /**
384
     * Gets the available locales.
385
     *
386
     * @return array
387
     * the available locales
388
     */
389
    public function getLocales() {
390
        $localeDir     = __DIR__.'/../locales';
391
        $languageFiles = scandir($localeDir);
392
        $locales       = [];
393
        foreach ($languageFiles as $languageFile) {
394
            if (in_array($languageFile, ['.', '..'])) {
395
                continue;
396
            }
397
            $extensionPos = strpos($languageFile, '.yml');
398
            if ($extensionPos !== false) {
399
                $locale    = substr($languageFile, 0, $extensionPos);
400
                $locales[] = $locale;
401
            }
402
        }
403
        sort($locales);
404
        return $locales;
405
    }
406
407
}
408