Completed
Push — master ( bbfaf5...9f26e2 )
by Philip
23:49 queued 21:17
created

ServiceProvider::validateEntityDefinition()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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