Completed
Push — master ( 5325ff...c31ace )
by Philip
05:38
created

ServiceProvider::init()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 11
cts 11
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 2
crap 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 League\Flysystem\Adapter\Local;
15
use League\Flysystem\Filesystem;
16
use Pimple\Container;
17
use Pimple\ServiceProviderInterface;
18
use Silex\Api\BootableProviderInterface;
19
use Silex\Application;
20
use Silex\Provider\LocaleServiceProvider;
21
use Silex\Provider\SessionServiceProvider;
22
use Silex\Provider\TranslationServiceProvider;
23
use Silex\Provider\TwigServiceProvider;
24
use Symfony\Component\Translation\Loader\YamlFileLoader;
25
26
/**
27
 * The ServiceProvider setups and initializes the whole CRUD system.
28
 * After adding it to your Silex-setup, it offers access to 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 data instances.
35
     * @var AbstractData
36
     */
37
    protected $datas;
38
39
    /**
40
     * Initializes needed but yet missing service providers.
41
     *
42
     * @param Container $app
43
     * the application container
44
     */
45 75
    protected function initMissingServiceProviders(Container $app) {
46
47 75
        if (!$app->offsetExists('translator')) {
48 75
            $app->register(new LocaleServiceProvider());
49 75
            $app->register(new TranslationServiceProvider(), [
50 75
                'locale_fallbacks' => ['en'],
51
            ]);
52
        }
53
54 75
        if (!$app->offsetExists('session')) {
55 65
            $app->register(new SessionServiceProvider());
56
        }
57
58 75
        if (!$app->offsetExists('twig')) {
59 65
            $app->register(new TwigServiceProvider());
60
        }
61 75
        $app['twig.loader.filesystem']->addPath(__DIR__.'/../views/', 'crud');
62 75
    }
63
64
    /**
65
     * Initializes the available locales.
66
     *
67
     * @param Container $app
68
     * the application container
69
     *
70
     * @return array
71
     * the available locales
72
     */
73 74
    protected function initLocales(Container $app) {
74 74
        $locales   = $this->getLocales();
75 74
        $localeDir = __DIR__.'/../locales';
76 74
        $app['translator']->addLoader('yaml', new YamlFileLoader());
77 74
        foreach ($locales as $locale) {
78 74
            $app['translator']->addResource('yaml', $localeDir.'/'.$locale.'.yml', $locale);
79
        }
80 74
        return $locales;
81
    }
82
83
    /**
84
     * Initializes the children of the data entries.
85
     */
86 74
    protected function initChildren() {
87 74
        foreach ($this->datas as $name => $data) {
0 ignored issues
show
Bug introduced by
The expression $this->datas of type object<CRUDlex\AbstractData> is not traversable.
Loading history...
88 74
            $fields = $data->getDefinition()->getFieldNames();
89 74
            foreach ($fields as $field) {
90 74
                if ($data->getDefinition()->getType($field) == 'reference') {
91 74
                    $this->datas[$data->getDefinition()->getSubTypeField($field, 'reference', 'entity')]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name);
92
                }
93
            }
94
        }
95 74
    }
96
97
    /**
98
     * Gets a map with localized entity labels from the CRUD YML.
99
     *
100
     * @param array $locales
101
     * the available locales
102
     * @param array $crud
103
     * the CRUD entity map
104
     *
105
     * @return array
106
     * the map with localized entity labels
107
     */
108 74
    protected function getLocaleLabels($locales, $crud) {
109 74
        $localeLabels = [];
110 74
        foreach ($locales as $locale) {
111 74
            if (array_key_exists('label_'.$locale, $crud)) {
112 74
                $localeLabels[$locale] = $crud['label_'.$locale];
113
            }
114
        }
115 74
        return $localeLabels;
116
    }
117
118
    /**
119
     * Configures the EntityDefinition according to the given
120
     * CRUD entity map.
121
     *
122
     * @param EntityDefinition $definition
123
     * the definition to configure
124
     * @param array $crud
125
     * the CRUD entity map
126
     */
127 74
    protected function configureDefinition(EntityDefinition $definition, array $crud) {
128
        $toConfigure = [
129 74
            'deleteCascade',
130
            'listFields',
131
            'filter',
132
            'childrenLabelFields',
133
            'pageSize',
134
            'initialSortField',
135
            'initialSortAscending',
136
            'navBarGroup',
137
            'optimisticLocking'
138
        ];
139 74
        foreach ($toConfigure as $field) {
140 74
            if (array_key_exists($field, $crud)) {
141 74
                $function = 'set'.ucfirst($field);
142 74
                $definition->$function($crud[$field]);
143
            }
144
        }
145 74
    }
146
147
    /**
148
     * Creates and setups an EntityDefinition instance.
149
     *
150
     * @param Container $app
151
     * the application container
152
     * @param array $locales
153
     * the available locales
154
     * @param array $crud
155
     * the parsed YAML of a CRUD entity
156
     * @param string $name
157
     * the name of the entity
158
     *
159
     * @return EntityDefinition
160
     * the EntityDefinition good to go
161
     */
162 74
    protected function createDefinition(Container $app, array $locales, array $crud, $name) {
163 74
        $label               = array_key_exists('label', $crud) ? $crud['label'] : $name;
164 74
        $localeLabels        = $this->getLocaleLabels($locales, $crud);
165
        $standardFieldLabels = [
166 74
            'id' => $app['translator']->trans('crudlex.label.id'),
167 74
            'created_at' => $app['translator']->trans('crudlex.label.created_at'),
168 74
            'updated_at' => $app['translator']->trans('crudlex.label.updated_at')
169
        ];
170
171 74
        $factory = $app->offsetExists('crud.entitydefinitionfactory') ? $app['crud.entitydefinitionfactory'] : new EntityDefinitionFactory();
172
173 74
        $definition = $factory->createEntityDefinition(
174 74
            $crud['table'],
175 74
            $crud['fields'],
176 74
            $label,
177 74
            $localeLabels,
178 74
            $standardFieldLabels,
179 74
            $this
180
        );
181 74
        $this->configureDefinition($definition, $crud);
182 74
        return $definition;
183
    }
184
185
    /**
186
     * Validates the parsed entity definition.
187
     *
188
     * @param Container $app
189
     * the application container
190
     * @param array $entityDefinition
191
     * the entity definition to validate
192
     */
193 74
    protected function validateEntityDefinition(Container $app, array $entityDefinition) {
194 74
        $doValidate = !$app->offsetExists('crud.validateentitydefinition') || $app['crud.validateentitydefinition'] === true;
195 74
        if ($doValidate) {
196 73
            $validator = $app->offsetExists('crud.entitydefinitionvalidator')
197 1
                ? $app['crud.entitydefinitionvalidator']
198 73
                : new EntityDefinitionValidator();
199 73
            $validator->validate($entityDefinition);
200
        }
201 74
    }
202
203
    /**
204
     * Initializes the instance.
205
     *
206
     * @param string|null $crudFileCachingDirectory
207
     * the writable directory to store the CRUD YAML file cache
208
     * @param Container $app
209
     * the application container
210
     */
211 75
    public function init($crudFileCachingDirectory, Container $app) {
212
213 75
        $reader     = new YamlReader($crudFileCachingDirectory);
214 75
        $parsedYaml = $reader->read($app['crud.file']);
215
216 74
        $this->validateEntityDefinition($app, $parsedYaml);
217
218 74
        $locales     = $this->initLocales($app);
219 74
        $this->datas = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type object<CRUDlex\AbstractData> of property $datas.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
220 74
        foreach ($parsedYaml as $name => $crud) {
221 74
            $definition         = $this->createDefinition($app, $locales, $crud, $name);
222 74
            $this->datas[$name] = $app['crud.datafactory']->createData($definition, $app['crud.filesystem']);
223
        }
224
225 74
        $this->initChildren();
226
227 74
    }
228
229
    /**
230
     * Implements ServiceProviderInterface::register() registering $app['crud'].
231
     * $app['crud'] contains an instance of the ServiceProvider afterwards.
232
     *
233
     * @param Container $app
234
     * the Container instance of the Silex application
235
     */
236 11
    public function register(Container $app) {
237 11
        if (!$app->offsetExists('crud.filesystem')) {
238 11
            $app['crud.filesystem'] = new Filesystem(new Local(getcwd()));
239
        }
240 11
        $app['crud'] = function() use ($app) {
241 11
            $result                   = new static();
242 11
            $crudFileCachingDirectory = $app->offsetExists('crud.filecachingdirectory') ? $app['crud.filecachingdirectory'] : null;
243 11
            $result->init($crudFileCachingDirectory, $app);
244 11
            return $result;
245
        };
246 11
    }
247
248
    /**
249
     * Initializes the crud service right after boot.
250
     *
251
     * @param Application $app
252
     * the Container instance of the Silex application
253
     */
254 75
    public function boot(Application $app) {
255 75
        $this->initMissingServiceProviders($app);
256 75
        $twigExtensions = new TwigExtensions();
257 75
        $twigExtensions->registerTwigExtensions($app);
258 75
    }
259
260
    /**
261
     * Getter for the AbstractData instances.
262
     *
263
     * @param string $name
264
     * the entity name of the desired Data instance
265
     *
266
     * @return AbstractData
267
     * the AbstractData instance or null on invalid name
268
     */
269 68
    public function getData($name) {
270 68
        if (!array_key_exists($name, $this->datas)) {
271 8
            return null;
272
        }
273 68
        return $this->datas[$name];
274
    }
275
276
    /**
277
     * Getter for all available entity names.
278
     *
279
     * @return string[]
280
     * a list of all available entity names
281
     */
282 3
    public function getEntities() {
283 3
        return array_keys($this->datas);
284
    }
285
286
    /**
287
     * Getter for the entities for the navigation bar.
288
     *
289
     * @return string[]
290
     * a list of all available entity names with their group
291
     */
292 10
    public function getEntitiesNavBar() {
293 10
        $result = [];
294 10
        foreach ($this->datas as $entity => $data) {
0 ignored issues
show
Bug introduced by
The expression $this->datas of type object<CRUDlex\AbstractData> is not traversable.
Loading history...
295 10
            $navBarGroup = $data->getDefinition()->getNavBarGroup();
296 10
            if ($navBarGroup !== 'main') {
297 10
                $result[$navBarGroup][] = $entity;
298
            } else {
299
                $result[$entity] = 'main';
300
            }
301
        }
302 10
        return $result;
303
    }
304
305
    /**
306
     * Determines the Twig template to use for the given parameters depending on
307
     * the existance of certain keys in the Container $app in this order:
308
     *
309
     * crud.$section.$action.$entity
310
     * crud.$section.$action
311
     * crud.$section
312
     *
313
     * If nothing exists, this string is returned: "@crud/<action>.twig"
314
     *
315
     * @param Container $app
316
     * the Silex application
317
     * @param string $section
318
     * the section of the template, either "layout" or "template"
319
     * @param string $action
320
     * the current calling action like "create" or "show"
321
     * @param string $entity
322
     * the current calling entity
323
     *
324
     * @return string
325
     * the best fitting template
326
     */
327 11
    public function getTemplate(Container $app, $section, $action, $entity) {
328 11
        $crudSection       = 'crud.'.$section;
329 11
        $crudSectionAction = $crudSection.'.'.$action;
330
331
        $offsets = [
332 11
            $crudSectionAction.'.'.$entity,
333 11
            $crudSection.'.'.$entity,
334 11
            $crudSectionAction,
335 11
            $crudSection
336
        ];
337 11
        foreach ($offsets as $offset) {
338 11
            if ($app->offsetExists($offset)) {
339 9
                return $app[$offset];
340
            }
341
        }
342
343 11
        return '@crud/'.$action.'.twig';
344
    }
345
346
    /**
347
     * Sets the locale to be used.
348
     *
349
     * @param string $locale
350
     * the locale to be used.
351
     */
352 10
    public function setLocale($locale) {
353 10
        foreach ($this->datas as $data) {
0 ignored issues
show
Bug introduced by
The expression $this->datas of type object<CRUDlex\AbstractData> is not traversable.
Loading history...
354 10
            $data->getDefinition()->setLocale($locale);
355
        }
356 10
    }
357
358
    /**
359
     * Gets the available locales.
360
     *
361
     * @return array
362
     * the available locales
363
     */
364 75
    public function getLocales() {
365 75
        $localeDir     = __DIR__.'/../locales';
366 75
        $languageFiles = scandir($localeDir);
367 75
        $locales       = [];
368 75
        foreach ($languageFiles as $languageFile) {
369 75
            if (in_array($languageFile, ['.', '..'])) {
370 75
                continue;
371
            }
372 75
            $extensionPos = strpos($languageFile, '.yml');
373 75
            if ($extensionPos !== false) {
374 75
                $locale    = substr($languageFile, 0, $extensionPos);
375 75
                $locales[] = $locale;
376
            }
377
        }
378 75
        sort($locales);
379 75
        return $locales;
380
    }
381
382
}
383