These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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\FilesystemInterface; |
||
15 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; |
||
16 | use Symfony\Component\Translation\Loader\YamlFileLoader; |
||
17 | use Symfony\Component\Translation\TranslatorInterface; |
||
18 | |||
19 | /** |
||
20 | * The Service setups and initializes the whole CRUD system and is initialized via the framework |
||
21 | * specific implementation, the Silex one for example. |
||
22 | * It offers access to AbstractData instances, one for each defined entity off the CRUD YAML file |
||
23 | * and various other helper functions. |
||
24 | */ |
||
25 | class Service |
||
26 | { |
||
27 | |||
28 | /** |
||
29 | * Holds the data instances. |
||
30 | * @var array |
||
31 | */ |
||
32 | protected $datas; |
||
33 | |||
34 | /** |
||
35 | * Holds the map for overriding templates. |
||
36 | * @var array |
||
37 | */ |
||
38 | protected $templates = []; |
||
39 | |||
40 | /** |
||
41 | * Holds whether CRUDlex manages i18n. |
||
42 | * @var bool |
||
43 | */ |
||
44 | protected $manageI18n = true; |
||
45 | |||
46 | /** |
||
47 | * Holds the URL generator. |
||
48 | * @var \Symfony\Component\Routing\Generator\UrlGenerator |
||
49 | */ |
||
50 | protected $urlGenerator; |
||
51 | |||
52 | /** |
||
53 | * Initializes the available locales. |
||
54 | * |
||
55 | * @param TranslatorInterface $translator |
||
56 | * the translator |
||
57 | * |
||
58 | * @return array |
||
59 | * the available locales |
||
60 | */ |
||
61 | 80 | protected function initLocales(TranslatorInterface $translator) |
|
62 | { |
||
63 | 80 | $locales = $this->getLocales(); |
|
64 | 80 | $localeDir = __DIR__.'/../locales'; |
|
65 | 80 | $translator->addLoader('yaml', new YamlFileLoader()); |
|
0 ignored issues
–
show
|
|||
66 | 80 | foreach ($locales as $locale) { |
|
67 | 80 | $translator->addResource('yaml', $localeDir.'/'.$locale.'.yml', $locale); |
|
0 ignored issues
–
show
It seems like you code against a concrete implementation and not the interface
Symfony\Component\Translation\TranslatorInterface as the method addResource() does only exist in the following implementations of said interface: Symfony\Component\Translation\Translator .
Let’s take a look at an example: interface User
{
/** @return string */
public function getPassword();
}
class MyUser implements User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
68 | } |
||
69 | 80 | return $locales; |
|
70 | } |
||
71 | |||
72 | /** |
||
73 | * Initializes the children of the data entries. |
||
74 | */ |
||
75 | 80 | protected function initChildren() |
|
76 | { |
||
77 | 80 | foreach ($this->datas as $name => $data) { |
|
78 | 80 | $fields = $data->getDefinition()->getFieldNames(); |
|
79 | 80 | foreach ($fields as $field) { |
|
80 | 80 | if ($data->getDefinition()->getType($field) == 'reference') { |
|
81 | 80 | $this->datas[$data->getDefinition()->getSubTypeField($field, 'reference', 'entity')]->getDefinition()->addChild($data->getDefinition()->getTable(), $field, $name); |
|
82 | } |
||
83 | } |
||
84 | } |
||
85 | 80 | } |
|
86 | |||
87 | /** |
||
88 | * Gets a map with localized entity labels from the CRUD YML. |
||
89 | * |
||
90 | * @param array $locales |
||
91 | * the available locales |
||
92 | * @param array $crud |
||
93 | * the CRUD entity map |
||
94 | * |
||
95 | * @return array |
||
96 | * the map with localized entity labels |
||
97 | */ |
||
98 | 80 | protected function getLocaleLabels(array $locales, array $crud) |
|
99 | { |
||
100 | 80 | $localeLabels = []; |
|
101 | 80 | foreach ($locales as $locale) { |
|
102 | 80 | if (array_key_exists('label_'.$locale, $crud)) { |
|
103 | 80 | $localeLabels[$locale] = $crud['label_'.$locale]; |
|
104 | } |
||
105 | } |
||
106 | 80 | return $localeLabels; |
|
107 | } |
||
108 | |||
109 | /** |
||
110 | * Configures the EntityDefinition according to the given |
||
111 | * CRUD entity map. |
||
112 | * |
||
113 | * @param EntityDefinition $definition |
||
114 | * the definition to configure |
||
115 | * @param array $crud |
||
116 | * the CRUD entity map |
||
117 | */ |
||
118 | 80 | protected function configureDefinition(EntityDefinition $definition, array $crud) |
|
119 | { |
||
120 | $toConfigure = [ |
||
121 | 80 | 'deleteCascade', |
|
122 | 'listFields', |
||
123 | 'filter', |
||
124 | 'childrenLabelFields', |
||
125 | 'pageSize', |
||
126 | 'initialSortField', |
||
127 | 'initialSortAscending', |
||
128 | 'navBarGroup', |
||
129 | 'optimisticLocking', |
||
130 | 'hardDeletion', |
||
131 | ]; |
||
132 | 80 | foreach ($toConfigure as $field) { |
|
133 | 80 | if (array_key_exists($field, $crud)) { |
|
134 | 80 | $function = 'set'.ucfirst($field); |
|
135 | 80 | $definition->$function($crud[$field]); |
|
136 | } |
||
137 | } |
||
138 | 80 | } |
|
139 | |||
140 | /** |
||
141 | * Creates and setups an EntityDefinition instance. |
||
142 | * |
||
143 | * @param TranslatorInterface $translator |
||
144 | * the Translator to use for some standard field labels |
||
145 | * @param EntityDefinitionFactoryInterface $entityDefinitionFactory |
||
146 | * the EntityDefinitionFactory to use |
||
147 | * @param array $locales |
||
148 | * the available locales |
||
149 | * @param array $crud |
||
150 | * the parsed YAML of a CRUD entity |
||
151 | * @param string $name |
||
152 | * the name of the entity |
||
153 | * |
||
154 | * @return EntityDefinition |
||
155 | * the EntityDefinition good to go |
||
156 | */ |
||
157 | 80 | protected function createDefinition(TranslatorInterface $translator, EntityDefinitionFactoryInterface $entityDefinitionFactory, array $locales, array $crud, $name) |
|
158 | { |
||
159 | 80 | $label = array_key_exists('label', $crud) ? $crud['label'] : $name; |
|
160 | 80 | $localeLabels = $this->getLocaleLabels($locales, $crud); |
|
161 | $standardFieldLabels = [ |
||
162 | 80 | 'id' => $translator->trans('crudlex.label.id'), |
|
163 | 80 | 'created_at' => $translator->trans('crudlex.label.created_at'), |
|
164 | 80 | 'updated_at' => $translator->trans('crudlex.label.updated_at') |
|
165 | ]; |
||
166 | |||
167 | 80 | $definition = $entityDefinitionFactory->createEntityDefinition( |
|
168 | 80 | $crud['table'], |
|
169 | 80 | $crud['fields'], |
|
170 | 80 | $label, |
|
171 | 80 | $localeLabels, |
|
172 | 80 | $standardFieldLabels, |
|
173 | 80 | $this |
|
174 | ); |
||
175 | 80 | $this->configureDefinition($definition, $crud); |
|
176 | 80 | return $definition; |
|
177 | } |
||
178 | |||
179 | /** |
||
180 | * Initializes the instance. |
||
181 | * |
||
182 | * @param string $crudFile |
||
183 | * the CRUD YAML file |
||
184 | * @param string|null $crudFileCachingDirectory |
||
185 | * the writable directory to store the CRUD YAML file cache |
||
186 | * @param UrlGeneratorInterface $urlGenerator |
||
187 | * the URL generator to use |
||
188 | * @param TranslatorInterface $translator |
||
189 | * the translator to use |
||
190 | * @param DataFactoryInterface $dataFactory |
||
191 | * the data factory to use |
||
192 | * @param EntityDefinitionFactoryInterface $entityDefinitionFactory |
||
193 | * the EntityDefinitionFactory to use |
||
194 | * @param FilesystemInterface $filesystem |
||
195 | * the filesystem to use |
||
196 | * @param EntityDefinitionValidatorInterface|null $validator |
||
197 | * the validator to use, null if no validation required |
||
198 | */ |
||
199 | 82 | public function __construct($crudFile, $crudFileCachingDirectory, UrlGeneratorInterface $urlGenerator, TranslatorInterface $translator, DataFactoryInterface $dataFactory, EntityDefinitionFactoryInterface $entityDefinitionFactory, FilesystemInterface $filesystem, ?EntityDefinitionValidatorInterface $validator) |
|
200 | { |
||
201 | |||
202 | 82 | $this->urlGenerator = $urlGenerator; |
|
0 ignored issues
–
show
$urlGenerator is of type object<Symfony\Component...\UrlGeneratorInterface> , but the property $urlGenerator was declared to be of type object<Symfony\Component...Generator\UrlGenerator> . Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly. Either this assignment is in error or an instanceof check should be added for that assignment. class Alien {}
class Dalek extends Alien {}
class Plot
{
/** @var Dalek */
public $villain;
}
$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
$plot->villain = $alien;
}
Loading history...
|
|||
203 | |||
204 | 82 | $reader = new YamlReader($crudFileCachingDirectory); |
|
205 | 82 | $parsedYaml = $reader->read($crudFile); |
|
206 | |||
207 | 80 | if ($validator !== null) { |
|
208 | 80 | $validator->validate($parsedYaml); |
|
209 | } |
||
210 | |||
211 | 80 | $locales = $this->initLocales($translator); |
|
212 | 80 | $this->datas = []; |
|
213 | 80 | foreach ($parsedYaml as $name => $crud) { |
|
214 | 80 | $definition = $this->createDefinition($translator, $entityDefinitionFactory, $locales, $crud, $name); |
|
215 | 80 | $this->datas[$name] = $dataFactory->createData($definition, $filesystem); |
|
216 | } |
||
217 | |||
218 | 80 | $this->initChildren(); |
|
219 | |||
220 | 80 | } |
|
221 | |||
222 | /** |
||
223 | * Getter for the AbstractData instances. |
||
224 | * |
||
225 | * @param string $name |
||
226 | * the entity name of the desired Data instance |
||
227 | * |
||
228 | * @return AbstractData |
||
229 | * the AbstractData instance or null on invalid name |
||
230 | */ |
||
231 | 71 | public function getData($name) |
|
232 | { |
||
233 | 71 | if (!array_key_exists($name, $this->datas)) { |
|
234 | 7 | return null; |
|
235 | } |
||
236 | 71 | return $this->datas[$name]; |
|
237 | } |
||
238 | |||
239 | /** |
||
240 | * Getter for all available entity names. |
||
241 | * |
||
242 | * @return string[] |
||
243 | * a list of all available entity names |
||
244 | */ |
||
245 | 7 | public function getEntities() |
|
246 | { |
||
247 | 7 | return array_keys($this->datas); |
|
248 | } |
||
249 | |||
250 | /** |
||
251 | * Getter for the entities for the navigation bar. |
||
252 | * |
||
253 | * @return string[] |
||
254 | * a list of all available entity names with their group |
||
255 | */ |
||
256 | 11 | public function getEntitiesNavBar() |
|
257 | { |
||
258 | 11 | $result = []; |
|
259 | 11 | foreach ($this->datas as $entity => $data) { |
|
260 | 11 | $navBarGroup = $data->getDefinition()->getNavBarGroup(); |
|
261 | 11 | if ($navBarGroup !== 'main') { |
|
262 | 11 | $result[$navBarGroup][] = $entity; |
|
263 | } else { |
||
264 | 11 | $result[$entity] = 'main'; |
|
265 | } |
||
266 | } |
||
267 | 11 | return $result; |
|
268 | } |
||
269 | |||
270 | /** |
||
271 | * Sets a template to use instead of the build in ones. |
||
272 | * |
||
273 | * @param string $key |
||
274 | * the template key to use in this format: |
||
275 | * $section.$action.$entity |
||
276 | * $section.$action |
||
277 | * $section |
||
278 | * @param string $template |
||
279 | * the template to use for this key |
||
280 | */ |
||
281 | 13 | public function setTemplate($key, $template) |
|
282 | { |
||
283 | 13 | $this->templates[$key] = $template; |
|
284 | 13 | } |
|
285 | |||
286 | /** |
||
287 | * Determines the Twig template to use for the given parameters depending on |
||
288 | * the existance of certain template keys set in this order: |
||
289 | * |
||
290 | * $section.$action.$entity |
||
291 | * $section.$action |
||
292 | * $section |
||
293 | * |
||
294 | * If nothing exists, this string is returned: "@crud/<action>.twig" |
||
295 | * |
||
296 | * @param string $section |
||
297 | * the section of the template, either "layout" or "template" |
||
298 | * @param string $action |
||
299 | * the current calling action like "create" or "show" |
||
300 | * @param string $entity |
||
301 | * the current calling entity |
||
302 | * |
||
303 | * @return string |
||
304 | * the best fitting template |
||
305 | */ |
||
306 | 12 | public function getTemplate($section, $action, $entity) |
|
307 | { |
||
308 | 12 | $sectionAction = $section.'.'.$action; |
|
309 | |||
310 | $offsets = [ |
||
311 | 12 | $sectionAction.'.'.$entity, |
|
312 | 12 | $section.'.'.$entity, |
|
313 | 12 | $sectionAction, |
|
314 | 12 | $section |
|
315 | ]; |
||
316 | 12 | foreach ($offsets as $offset) { |
|
317 | 12 | if (array_key_exists($offset, $this->templates)) { |
|
318 | 12 | return $this->templates[$offset]; |
|
319 | } |
||
320 | } |
||
321 | |||
322 | 12 | return '@crud/'.$action.'.twig'; |
|
323 | } |
||
324 | |||
325 | /** |
||
326 | * Sets the locale to be used. |
||
327 | * |
||
328 | * @param string $locale |
||
329 | * the locale to be used. |
||
330 | */ |
||
331 | 9 | public function setLocale($locale) |
|
332 | { |
||
333 | 9 | foreach ($this->datas as $data) { |
|
334 | 9 | $data->getDefinition()->setLocale($locale); |
|
335 | } |
||
336 | 9 | } |
|
337 | |||
338 | /** |
||
339 | * Gets the available locales. |
||
340 | * |
||
341 | * @return array |
||
342 | * the available locales |
||
343 | */ |
||
344 | 80 | public function getLocales() |
|
345 | { |
||
346 | 80 | $localeDir = __DIR__.'/../locales'; |
|
347 | 80 | $languageFiles = scandir($localeDir); |
|
348 | 80 | $locales = []; |
|
349 | 80 | foreach ($languageFiles as $languageFile) { |
|
350 | 80 | if (in_array($languageFile, ['.', '..'])) { |
|
351 | 80 | continue; |
|
352 | } |
||
353 | 80 | $extensionPos = strpos($languageFile, '.yml'); |
|
354 | 80 | if ($extensionPos !== false) { |
|
355 | 80 | $locale = substr($languageFile, 0, $extensionPos); |
|
356 | 80 | $locales[] = $locale; |
|
357 | } |
||
358 | } |
||
359 | 80 | sort($locales); |
|
360 | 80 | return $locales; |
|
361 | } |
||
362 | |||
363 | /** |
||
364 | * Gets whether CRUDlex manages the i18n. |
||
365 | * @return bool |
||
366 | * true if so |
||
367 | */ |
||
368 | 12 | public function isManageI18n() |
|
369 | { |
||
370 | 12 | return $this->manageI18n; |
|
371 | } |
||
372 | |||
373 | /** |
||
374 | * Sets whether CRUDlex manages the i18n. |
||
375 | * @param bool $manageI18n |
||
376 | * true if so |
||
377 | */ |
||
378 | 1 | public function setManageI18n($manageI18n) |
|
379 | { |
||
380 | 1 | $this->manageI18n = $manageI18n; |
|
381 | 1 | } |
|
382 | |||
383 | /** |
||
384 | * Generates an URL. |
||
385 | * @param string $name |
||
386 | * the name of the route |
||
387 | * @param mixed $parameters |
||
388 | * an array of parameters |
||
389 | * @return null|string |
||
390 | * the generated URL |
||
391 | */ |
||
392 | 12 | public function generateURL($name, $parameters) |
|
393 | { |
||
394 | 12 | return $this->urlGenerator->generate($name, $parameters); |
|
395 | } |
||
396 | |||
397 | } |
||
398 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: