Completed
Push — master ( 7cea37...63e42d )
by Philip
07:22
created

ServiceProvider::setLocale()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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