Completed
Push — master ( eaeb56...0fd03b )
by Philip
02:37
created

ServiceProvider::init()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 2
eloc 13
nc 2
nop 5
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\Translation\Loader\YamlFileLoader;
21
use Symfony\Component\Yaml\Yaml;
22
23
/**
24
 * The ServiceProvider setups and initializes the whole CRUD system.
25
 * After adding it to your Silex-setup, it offers access to {@see AbstractData}
26
 * instances, one for each defined entity off the CRUD YAML file.
27
 */
28
class ServiceProvider implements ServiceProviderInterface {
29
30
    /**
31
     * Holds the {@see AbstractData} instances.
32
     */
33
    protected $datas;
34
35
    /**
36
     * Holds whether we manage the i18n.
37
     */
38
    protected $manageI18n;
39
40
    /**
41
     * Reads and returns the contents of the given Yaml file. If
42
     * it goes wrong, it throws an exception.
43
     *
44
     * @param string $fileName
45
     * the file to read
46
     *
47
     * @return array
48
     * the file contents
49
     *
50
     * @throws \RuntimeException
51
     * thrown if the file could not be read or parsed
52
     */
53
    protected function readYaml($fileName) {
54
        try {
55
            $fileContent = file_get_contents($fileName);
56
            $parsedYaml  = Yaml::parse($fileContent);
57
            if (!is_array($parsedYaml)) {
58
                $parsedYaml = [];
59
            }
60
            return $parsedYaml;
61
        } catch (\Exception $e) {
62
            throw new \RuntimeException('Could not open CRUD file '.$fileName, $e->getCode(), $e);
63
        }
64
    }
65
66
    /**
67
     * Initializes needed but yet missing service providers.
68
     *
69
     * @param Container $app
70
     * the application container
71
     */
72
    protected function initMissingServiceProviders(Container $app) {
73
74
        if (!$app->offsetExists('translator')) {
75
            $app->register(new LocaleServiceProvider());
76
            $app->register(new TranslationServiceProvider(), [
77
                'locale_fallbacks' => ['en'],
78
            ]);
79
        }
80
81
        if (!$app->offsetExists('session')) {
82
            $app->register(new SessionServiceProvider());
83
        }
84
85
        if (!$app->offsetExists('twig')) {
86
            $app->register(new TwigServiceProvider());
87
            $app['twig.loader.filesystem']->addPath(__DIR__.'/../views/', 'crud');
88
        }
89
    }
90
91
    /**
92
     * Initializes the available locales.
93
     *
94
     * @param Container $app
95
     * the application container
96
     *
97
     * @return array
98
     * the available locales
99
     */
100
    protected function initLocales(Container $app) {
101
        $app['translator']->addLoader('yaml', new YamlFileLoader());
102
        $localeDir = __DIR__.'/../locales';
103
        $locales   = $this->getLocales();
104
        foreach ($locales as $locale) {
105
            $app['translator']->addResource('yaml', $localeDir.'/'.$locale.'.yml', $locale);
106
        }
107
        return $locales;
108
    }
109
110
    /**
111
     * Initializes the children of the data entries.
112
     */
113
    protected function initChildren() {
114
        foreach ($this->datas as $name => $data) {
115
            $fields = $data->getDefinition()->getFieldNames();
116
            foreach ($fields as $field) {
117
                if ($data->getDefinition()->getType($field) == 'reference') {
118
                    $this->datas[$data->getDefinition()->getReferenceEntity($field)]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name);
119
                }
120
            }
121
        }
122
    }
123
124
    /**
125
     * Gets a map with localized entity labels from the CRUD YML.
126
     *
127
     * @param array $locales
128
     * the available locales
129
     * @param array $crud
130
     * the CRUD entity map
131
     *
132
     * @return array
133
     * the map with localized entity labels
134
     */
135
    protected function getLocaleLabels($locales, $crud) {
136
        $localeLabels = [];
137
        foreach ($locales as $locale) {
138
            if (array_key_exists('label_'.$locale, $crud)) {
139
                $localeLabels[$locale] = $crud['label_'.$locale];
140
            }
141
        }
142
        return $localeLabels;
143
    }
144
145
    /**
146
     * Configures the EntityDefinition according to the given
147
     * CRUD entity map.
148
     *
149
     * @param EntityDefinition $definition
150
     * the definition to configure
151
     * @param array $crud
152
     * the CRUD entity map
153
     */
154
    protected function configureDefinition(EntityDefinition $definition, array $crud) {
155
        $toConfigure = [
156
            'deleteCascade',
157
            'listFields',
158
            'filter',
159
            'childrenLabelFields',
160
            'pageSize',
161
            'initialSortField',
162
            'initialSortAscending'
163
        ];
164
        foreach ($toConfigure as $field) {
165
            if (array_key_exists($field, $crud)) {
166
                $function = 'set'.ucfirst($field);
167
                $definition->$function($crud[$field]);
168
            }
169
        }
170
    }
171
172
    /**
173
     * Creates and setups an EntityDefinition instance.
174
     *
175
     * @param Container $app
176
     * the application container
177
     * @param array $locales
178
     * the available locales
179
     * @param array $crud
180
     * the parsed YAML of a CRUD entity
181
     * @param string $name
182
     * the name of the entity
183
     *
184
     * @return EntityDefinition
185
     * the EntityDefinition good to go
186
     */
187
    protected function createDefinition(Container $app, array $locales, array $crud, $name) {
188
        $label               = array_key_exists('label', $crud) ? $crud['label'] : $name;
189
        $localeLabels        = $this->getLocaleLabels($locales, $crud);
190
        $standardFieldLabels = [
191
            'id' => $app['translator']->trans('crudlex.label.id'),
192
            'created_at' => $app['translator']->trans('crudlex.label.created_at'),
193
            'updated_at' => $app['translator']->trans('crudlex.label.updated_at')
194
        ];
195
196
        $factory = $app->offsetExists('crud.entitydefinitionfactory') ? $app['crud.entitydefinitionfactory'] : new EntityDefinitionFactory();
197
198
        $definition = $factory->createEntityDefinition(
199
            $crud['table'],
200
            $crud['fields'],
201
            $label,
202
            $localeLabels,
203
            $standardFieldLabels,
204
            $this
205
        );
206
        $this->configureDefinition($definition, $crud);
207
        return $definition;
208
    }
209
210
    /**
211
     * Validates the parsed entity definition.
212
     *
213
     * @param Container $app
214
     * the application container
215
     * @param array $entityDefinition
216
     * the entity definition to validate
217
     */
218
    protected function validateEntityDefinition(Container $app, array $entityDefinition) {
219
        $doValidate = !$app->offsetExists('crud.validateentitydefinition') || $app['crud.validateentitydefinition'] === true;
220
        if ($doValidate) {
221
            $validator = $app->offsetExists('crud.entitydefinitionvalidator')
222
                ? $app['crud.entitydefinitionvalidator']
223
                : new EntityDefinitionValidator();
224
            $validator->validate($entityDefinition);
225
        }
226
    }
227
228
    /**
229
     * Initializes the instance.
230
     *
231
     * @param DataFactoryInterface $dataFactory
232
     * the factory to create the concrete AbstractData instances
233
     * @param string $crudFile
234
     * the CRUD YAML file to parse
235
     * @param FileProcessorInterface $fileProcessor
236
     * the file processor used for file fields
237
     * @param boolean $manageI18n
238
     * holds whether we manage the i18n
239
     * @param Container $app
240
     * the application container
241
     */
242
    public function init(DataFactoryInterface $dataFactory, $crudFile, FileProcessorInterface $fileProcessor, $manageI18n, Container $app) {
243
244
        $parsedYaml = $this->readYaml($crudFile);
245
246
        $this->validateEntityDefinition($app, $parsedYaml);
247
248
        $this->initMissingServiceProviders($app);
249
250
        $this->manageI18n = $manageI18n;
251
        $locales          = $this->initLocales($app);
252
        $this->datas      = [];
253
        foreach ($parsedYaml as $name => $crud) {
254
            $definition         = $this->createDefinition($app, $locales, $crud, $name);
255
            $this->datas[$name] = $dataFactory->createData($definition, $fileProcessor);
256
        }
257
258
        $twigExtensions = new TwigExtensions();
259
        $twigExtensions->registerTwigExtensions($app);
260
261
        $this->initChildren();
262
263
    }
264
265
    /**
266
     * Implements ServiceProviderInterface::register() registering $app['crud'].
267
     * $app['crud'] contains an instance of the ServiceProvider afterwards.
268
     *
269
     * @param Container $app
270
     * the Container instance of the Silex application
271
     */
272
    public function register(Container $app) {
273
        $app['crud'] = function() use ($app) {
274
            $result        = new static();
275
            $fileProcessor = $app->offsetExists('crud.fileprocessor') ? $app['crud.fileprocessor'] : new SimpleFilesystemFileProcessor();
276
            $manageI18n    = $app->offsetExists('crud.manageI18n') ? $app['crud.manageI18n'] : true;
277
            $result->init($app['crud.datafactory'], $app['crud.file'], $fileProcessor, $manageI18n, $app);
278
            return $result;
279
        };
280
    }
281
282
    /**
283
     * Getter for the {@see AbstractData} instances.
284
     *
285
     * @param string $name
286
     * the entity name of the desired Data instance
287
     *
288
     * @return AbstractData
289
     * the AbstractData instance or null on invalid name
290
     */
291
    public function getData($name) {
292
        if (!array_key_exists($name, $this->datas)) {
293
            return null;
294
        }
295
        return $this->datas[$name];
296
    }
297
298
    /**
299
     * Getter for all available entity names.
300
     *
301
     * @return string[]
302
     * a list of all available entity names
303
     */
304
    public function getEntities() {
305
        return array_keys($this->datas);
306
    }
307
308
    /**
309
     * Determines the Twig template to use for the given parameters depending on
310
     * the existance of certain keys in the Container $app in this order:
311
     *
312
     * crud.$section.$action.$entity
313
     * crud.$section.$action
314
     * crud.$section
315
     *
316
     * If nothing exists, this string is returned: "@crud/<action>.twig"
317
     *
318
     * @param Container $app
319
     * the Silex application
320
     * @param string $section
321
     * the section of the template, either "layout" or "template"
322
     * @param string $action
323
     * the current calling action like "create" or "show"
324
     * @param string $entity
325
     * the current calling entity
326
     *
327
     * @return string
328
     * the best fitting template
329
     */
330
    public function getTemplate(Container $app, $section, $action, $entity) {
331
        $crudSection       = 'crud.'.$section;
332
        $crudSectionAction = $crudSection.'.'.$action;
333
334
        $offsets = [
335
            $crudSectionAction.'.'.$entity,
336
            $crudSection.'.'.$entity,
337
            $crudSectionAction,
338
            $crudSection
339
        ];
340
        foreach ($offsets as $offset) {
341
            if ($app->offsetExists($offset)) {
342
                return $app[$offset];
343
            }
344
        }
345
346
        return '@crud/'.$action.'.twig';
347
    }
348
349
    /**
350
     * Gets whether CRUDlex manages the i18n system.
351
     *
352
     * @return boolean
353
     * true if CRUDlex manages the i18n system
354
     */
355
    public function isManagingI18n() {
356
        return $this->manageI18n;
357
    }
358
359
    /**
360
     * Sets the locale to be used.
361
     *
362
     * @param string $locale
363
     * the locale to be used.
364
     */
365
    public function setLocale($locale) {
366
        foreach ($this->datas as $data) {
367
            $data->getDefinition()->setLocale($locale);
368
        }
369
    }
370
371
    /**
372
     * Gets the available locales.
373
     *
374
     * @return array
375
     * the available locales
376
     */
377
    public function getLocales() {
378
        $localeDir     = __DIR__.'/../locales';
379
        $languageFiles = scandir($localeDir);
380
        $locales       = [];
381
        foreach ($languageFiles as $languageFile) {
382
            if (in_array($languageFile, ['.', '..'])) {
383
                continue;
384
            }
385
            $extensionPos = strpos($languageFile, '.yml');
386
            if ($extensionPos !== false) {
387
                $locale    = substr($languageFile, 0, $extensionPos);
388
                $locales[] = $locale;
389
            }
390
        }
391
        sort($locales);
392
        return $locales;
393
    }
394
395
}
396