Completed
Pull Request — master (#6093)
by Mathieu
29:00
created

SonataAdminExtension   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 615
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 19

Importance

Changes 0
Metric Value
wmc 70
lcom 5
cbo 19
dl 0
loc 615
rs 2.785
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A getFilters() 0 45 1
A getFunctions() 0 8 1
A getName() 0 4 1
A renderListElement() 0 21 1
A renderViewElement() 0 20 1
A renderViewElementCompare() 0 41 1
B renderRelationElement() 0 40 6
A getUrlSafeIdentifier() 0 13 3
A setXEditableTypeMapping() 0 4 1
A getXEditableType() 0 4 2
B getXEditableChoices() 0 41 11
A getCanonicalizedLocaleForMoment() 0 17 6
B getCanonicalizedLocaleForSelect2() 0 27 6
B isGrantedAffirmative() 0 26 7
A getValueFromFieldDescription() 0 23 5
A getTemplate() 0 30 4
A render() 0 32 2
A getTemplateRegistry() 0 11 2
B getObjectAndValueFromListElement() 0 35 8

How to fix   Complexity   

Complex Class

Complex classes like SonataAdminExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SonataAdminExtension, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Twig\Extension;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Psr\Log\LoggerInterface;
18
use Sonata\AdminBundle\Admin\AdminInterface;
19
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
20
use Sonata\AdminBundle\Admin\Pool;
21
use Sonata\AdminBundle\Exception\NoValueException;
22
use Sonata\AdminBundle\Templating\TemplateRegistryInterface;
23
use Symfony\Component\DependencyInjection\ContainerInterface;
24
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
25
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
26
use Symfony\Component\Security\Acl\Voter\FieldVote;
27
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
28
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
29
use Symfony\Contracts\Translation\TranslatorInterface;
30
use Twig\Environment;
31
use Twig\Error\LoaderError;
32
use Twig\Extension\AbstractExtension;
33
use Twig\TemplateWrapper;
34
use Twig\TwigFilter;
35
use Twig\TwigFunction;
36
37
/**
38
 * @final since sonata-project/admin-bundle 3.52
39
 *
40
 * @author Thomas Rabaix <[email protected]>
41
 */
42
final class SonataAdminExtension extends AbstractExtension
43
{
44
    // @todo: there are more locales which are not supported by moment and they need to be translated/normalized/canonicalized here
45
    public const MOMENT_UNSUPPORTED_LOCALES = [
46
        'de' => ['de', 'de-at'],
47
        'es' => ['es', 'es-do'],
48
        'nl' => ['nl', 'nl-be'],
49
        'fr' => ['fr', 'fr-ca', 'fr-ch'],
50
    ];
51
52
    /**
53
     * @var TranslatorInterface|null
54
     */
55
    protected $translator;
56
    /**
57
     * @var Pool
58
     */
59
    private $pool;
60
61
    /**
62
     * @var LoggerInterface
63
     */
64
    private $logger;
65
66
    /**
67
     * @var string[]
68
     */
69
    private $xEditableTypeMapping = [];
70
71
    /**
72
     * @var ContainerInterface
73
     */
74
    private $templateRegistries;
75
76
    /**
77
     * @var AuthorizationCheckerInterface
78
     */
79
    private $securityChecker;
80
81
    public function __construct(
82
        Pool $pool,
83
        ?LoggerInterface $logger = null,
84
        TranslatorInterface $translator,
85
        ?ContainerInterface $templateRegistries = null,
86
        ?AuthorizationCheckerInterface $securityChecker = null
87
    ) {
88
        $this->pool = $pool;
89
        $this->logger = $logger;
90
        $this->translator = $translator;
91
        $this->templateRegistries = $templateRegistries;
92
        $this->securityChecker = $securityChecker;
93
    }
94
95
    public function getFilters()
96
    {
97
        return [
98
            new TwigFilter(
99
                'render_list_element',
100
                [$this, 'renderListElement'],
101
                [
102
                    'is_safe' => ['html'],
103
                    'needs_environment' => true,
104
                ]
105
            ),
106
            new TwigFilter(
107
                'render_view_element',
108
                [$this, 'renderViewElement'],
109
                [
110
                    'is_safe' => ['html'],
111
                    'needs_environment' => true,
112
                ]
113
            ),
114
            new TwigFilter(
115
                'render_view_element_compare',
116
                [$this, 'renderViewElementCompare'],
117
                [
118
                    'is_safe' => ['html'],
119
                    'needs_environment' => true,
120
                ]
121
            ),
122
            new TwigFilter(
123
                'render_relation_element',
124
                [$this, 'renderRelationElement']
125
            ),
126
            new TwigFilter(
127
                'sonata_urlsafeid',
128
                [$this, 'getUrlSafeIdentifier']
129
            ),
130
            new TwigFilter(
131
                'sonata_xeditable_type',
132
                [$this, 'getXEditableType']
133
            ),
134
            new TwigFilter(
135
                'sonata_xeditable_choices',
136
                [$this, 'getXEditableChoices']
137
            ),
138
        ];
139
    }
140
141
    public function getFunctions()
142
    {
143
        return [
144
            new TwigFunction('canonicalize_locale_for_moment', [$this, 'getCanonicalizedLocaleForMoment'], ['needs_context' => true]),
145
            new TwigFunction('canonicalize_locale_for_select2', [$this, 'getCanonicalizedLocaleForSelect2'], ['needs_context' => true]),
146
            new TwigFunction('is_granted_affirmative', [$this, 'isGrantedAffirmative']),
147
        ];
148
    }
149
150
    public function getName()
151
    {
152
        return 'sonata_admin';
153
    }
154
155
    /**
156
     * render a list element from the FieldDescription.
157
     *
158
     * @param object|array $listElement
159
     * @param array        $params
160
     *
161
     * @return string
162
     */
163
    public function renderListElement(
164
        Environment $environment,
165
        $listElement,
166
        FieldDescriptionInterface $fieldDescription,
167
        $params = []
168
    ) {
169
        $template = $this->getTemplate(
170
            $fieldDescription,
171
            $this->getTemplateRegistry($fieldDescription->getAdmin()->getCode())->getTemplate('base_list_field'),
172
            $environment
173
        );
174
175
        [$object, $value] = $this->getObjectAndValueFromListElement($listElement, $fieldDescription);
0 ignored issues
show
Bug introduced by
The variable $object does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
176
177
        return $this->render($fieldDescription, $template, array_merge($params, [
178
            'admin' => $fieldDescription->getAdmin(),
179
            'object' => $object,
180
            'value' => $value,
181
            'field_description' => $fieldDescription,
182
        ]), $environment);
183
    }
184
185
    /**
186
     * render a view element.
187
     *
188
     * @param object $object
189
     *
190
     * @return string
191
     */
192
    public function renderViewElement(
193
        Environment $environment,
194
        FieldDescriptionInterface $fieldDescription,
195
        $object
196
    ) {
197
        $template = $this->getTemplate(
198
            $fieldDescription,
199
            '@SonataAdmin/CRUD/base_show_field.html.twig',
200
            $environment
201
        );
202
203
        $value = $fieldDescription->getValue($object);
204
205
        return $this->render($fieldDescription, $template, [
206
            'field_description' => $fieldDescription,
207
            'object' => $object,
208
            'value' => $value,
209
            'admin' => $fieldDescription->getAdmin(),
210
        ], $environment);
211
    }
212
213
    /**
214
     * render a compared view element.
215
     *
216
     * @param mixed $baseObject
217
     * @param mixed $compareObject
218
     *
219
     * @return string
220
     */
221
    public function renderViewElementCompare(
222
        Environment $environment,
223
        FieldDescriptionInterface $fieldDescription,
224
        $baseObject,
225
        $compareObject
226
    ) {
227
        $template = $this->getTemplate(
228
            $fieldDescription,
229
            '@SonataAdmin/CRUD/base_show_field.html.twig',
230
            $environment
231
        );
232
233
        $baseValue = $fieldDescription->getValue($baseObject);
234
        $compareValue = $fieldDescription->getValue($compareObject);
235
236
        $baseValueOutput = $template->render([
237
            'admin' => $fieldDescription->getAdmin(),
238
            'field_description' => $fieldDescription,
239
            'value' => $baseValue,
240
            'object' => $baseObject,
241
        ]);
242
243
        $compareValueOutput = $template->render([
244
            'field_description' => $fieldDescription,
245
            'admin' => $fieldDescription->getAdmin(),
246
            'value' => $compareValue,
247
            'object' => $compareObject,
248
        ]);
249
250
        // Compare the rendered output of both objects by using the (possibly) overridden field block
251
        $isDiff = $baseValueOutput !== $compareValueOutput;
252
253
        return $this->render($fieldDescription, $template, [
254
            'field_description' => $fieldDescription,
255
            'value' => $baseValue,
256
            'value_compare' => $compareValue,
257
            'is_diff' => $isDiff,
258
            'admin' => $fieldDescription->getAdmin(),
259
            'object' => $baseObject,
260
        ], $environment);
261
    }
262
263
    /**
264
     * @param mixed $element
265
     *
266
     * @throws \RuntimeException
267
     *
268
     * @return mixed
269
     */
270
    public function renderRelationElement($element, FieldDescriptionInterface $fieldDescription)
271
    {
272
        if (!\is_object($element)) {
273
            return $element;
274
        }
275
276
        $propertyPath = $fieldDescription->getOption('associated_property');
277
278
        if (null === $propertyPath) {
279
            // For BC kept associated_tostring option behavior
280
            $method = $fieldDescription->getOption('associated_tostring');
281
282
            if ($method) {
283
                @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
284
                    'Option "associated_tostring" is deprecated since version 2.3 and will be removed in 4.0. Use "associated_property" instead.',
285
                    E_USER_DEPRECATED
286
                );
287
            } else {
288
                $method = '__toString';
289
            }
290
291
            if (!method_exists($element, $method)) {
292
                throw new \RuntimeException(sprintf(
293
                    'You must define an `associated_property` option or create a `%s::__toString` method'
294
                    .' to the field option %s from service %s is ',
295
                    \get_class($element),
296
                    $fieldDescription->getName(),
297
                    $fieldDescription->getAdmin()->getCode()
298
                ));
299
            }
300
301
            return $element->$method();
302
        }
303
304
        if (\is_callable($propertyPath)) {
305
            return $propertyPath($element);
306
        }
307
308
        return $this->pool->getPropertyAccessor()->getValue($element, $propertyPath);
309
    }
310
311
    /**
312
     * Get the identifiers as a string that is safe to use in a url.
313
     *
314
     * @param object $model
315
     *
316
     * @return string string representation of the id that is safe to use in a url
317
     */
318
    public function getUrlSafeIdentifier($model, ?AdminInterface $admin = null)
319
    {
320
        if (null === $admin) {
321
            $class = ClassUtils::getClass($model);
322
            if (!$this->pool->hasAdminByClass($class)) {
323
                throw new \InvalidArgumentException('You must pass an admin.');
324
            }
325
326
            $admin = $this->pool->getAdminByClass($class);
327
        }
328
329
        return $admin->getUrlSafeIdentifier($model);
330
    }
331
332
    /**
333
     * @param string[] $xEditableTypeMapping
334
     */
335
    public function setXEditableTypeMapping($xEditableTypeMapping): void
336
    {
337
        $this->xEditableTypeMapping = $xEditableTypeMapping;
338
    }
339
340
    /**
341
     * @return string|bool
342
     */
343
    public function getXEditableType($type)
344
    {
345
        return isset($this->xEditableTypeMapping[$type]) ? $this->xEditableTypeMapping[$type] : false;
346
    }
347
348
    /**
349
     * Return xEditable choices based on the field description choices options & catalogue options.
350
     * With the following choice options:
351
     *     ['Status1' => 'Alias1', 'Status2' => 'Alias2']
352
     * The method will return:
353
     *     [['value' => 'Status1', 'text' => 'Alias1'], ['value' => 'Status2', 'text' => 'Alias2']].
354
     *
355
     * @return array
356
     */
357
    public function getXEditableChoices(FieldDescriptionInterface $fieldDescription)
358
    {
359
        $choices = $fieldDescription->getOption('choices', []);
360
        $catalogue = $fieldDescription->getOption('catalogue');
361
        $xEditableChoices = [];
362
        if (!empty($choices)) {
363
            reset($choices);
364
            $first = current($choices);
365
            // the choices are already in the right format
366
            if (\is_array($first) && \array_key_exists('value', $first) && \array_key_exists('text', $first)) {
367
                $xEditableChoices = $choices;
368
            } else {
369
                foreach ($choices as $value => $text) {
370
                    if ($catalogue) {
371
                        if (null !== $this->translator) {
372
                            $text = $this->translator->trans($text, [], $catalogue);
373
                        // NEXT_MAJOR: Remove this check
374
                        } elseif (method_exists($fieldDescription->getAdmin(), 'trans')) {
375
                            $text = $fieldDescription->getAdmin()->trans($text, [], $catalogue);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin\AdminInterface::trans() has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed in 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
376
                        }
377
                    }
378
379
                    $xEditableChoices[] = [
380
                        'value' => $value,
381
                        'text' => $text,
382
                    ];
383
                }
384
            }
385
        }
386
387
        if (false === $fieldDescription->getOption('required', true)
388
            && false === $fieldDescription->getOption('multiple', false)
389
        ) {
390
            $xEditableChoices = array_merge([[
391
                'value' => '',
392
                'text' => '',
393
            ]], $xEditableChoices);
394
        }
395
396
        return $xEditableChoices;
397
    }
398
399
    /*
400
     * Returns a canonicalized locale for "moment" NPM library,
401
     * or `null` if the locale's language is "en", which doesn't require localization.
402
     *
403
     * @return string|null
404
     */
405
    public function getCanonicalizedLocaleForMoment(array $context)
406
    {
407
        $locale = strtolower(str_replace('_', '-', $context['app']->getRequest()->getLocale()));
408
409
        // "en" language doesn't require localization.
410
        if (('en' === $lang = substr($locale, 0, 2)) && !\in_array($locale, ['en-au', 'en-ca', 'en-gb', 'en-ie', 'en-nz'], true)) {
411
            return null;
412
        }
413
414
        foreach (self::MOMENT_UNSUPPORTED_LOCALES as $language => $locales) {
415
            if ($language === $lang && !\in_array($locale, $locales, true)) {
416
                $locale = $language;
417
            }
418
        }
419
420
        return $locale;
421
    }
422
423
    /**
424
     * Returns a canonicalized locale for "select2" NPM library,
425
     * or `null` if the locale's language is "en", which doesn't require localization.
426
     *
427
     * @return string|null
428
     */
429
    public function getCanonicalizedLocaleForSelect2(array $context)
430
    {
431
        $locale = str_replace('_', '-', $context['app']->getRequest()->getLocale());
432
433
        // "en" language doesn't require localization.
434
        if ('en' === $lang = substr($locale, 0, 2)) {
435
            return null;
436
        }
437
438
        switch ($locale) {
439
            case 'pt':
440
                $locale = 'pt-PT';
441
                break;
442
            case 'ug':
443
                $locale = 'ug-CN';
444
                break;
445
            case 'zh':
446
                $locale = 'zh-CN';
447
                break;
448
            default:
449
                if (!\in_array($locale, ['pt-BR', 'pt-PT', 'ug-CN', 'zh-CN', 'zh-TW'], true)) {
450
                    $locale = $lang;
451
                }
452
        }
453
454
        return $locale;
455
    }
456
457
    /**
458
     * @param string|array $role
459
     * @param object|null  $object
460
     * @param string|null  $field
461
     *
462
     * @return bool
463
     */
464
    public function isGrantedAffirmative($role, $object = null, $field = null)
465
    {
466
        if (null === $this->securityChecker) {
467
            return false;
468
        }
469
470
        if (null !== $field) {
471
            $object = new FieldVote($object, $field);
472
        }
473
474
        if (!\is_array($role)) {
475
            $role = [$role];
476
        }
477
478
        foreach ($role as $oneRole) {
479
            try {
480
                if ($this->securityChecker->isGranted($oneRole, $object)) {
481
                    return true;
482
                }
483
            } catch (AuthenticationCredentialsNotFoundException $e) {
484
                // empty on purpose
485
            }
486
        }
487
488
        return false;
489
    }
490
491
    /**
492
     * return the value related to FieldDescription, if the associated object does no
493
     * exists => a temporary one is created.
494
     *
495
     * @param object $object
496
     *
497
     * @throws \RuntimeException
498
     *
499
     * @return mixed
500
     */
501
    private function getValueFromFieldDescription(
502
        $object,
503
        FieldDescriptionInterface $fieldDescription,
504
        array $params = []
505
    ) {
506
        if (isset($params['loop']) && $object instanceof \ArrayAccess) {
507
            throw new \RuntimeException('remove the loop requirement');
508
        }
509
510
        $value = null;
511
512
        try {
513
            $value = $fieldDescription->getValue($object);
514
        } catch (NoValueException $e) {
515
            if ($fieldDescription->getAssociationAdmin()) {
516
                $value = $fieldDescription->getAssociationAdmin()->getNewInstance();
517
            } else {
518
                throw $e;
519
            }
520
        }
521
522
        return $value;
523
    }
524
525
    /**
526
     * Get template.
527
     *
528
     * @param string $defaultTemplate
529
     *
530
     * @return TemplateWrapper
531
     */
532
    private function getTemplate(
533
        FieldDescriptionInterface $fieldDescription,
534
        $defaultTemplate,
535
        Environment $environment
536
    ) {
537
        $templateName = $fieldDescription->getTemplate() ?: $defaultTemplate;
538
539
        try {
540
            $template = $environment->load($templateName);
541
        } catch (LoaderError $e) {
542
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
543
                'Relying on default template loading on field template loading exception is deprecated since 3.1'
544
                .' and will be removed in 4.0. A %s exception will be thrown instead',
545
                LoaderError::class
546
            ), E_USER_DEPRECATED);
547
            $template = $environment->load($defaultTemplate);
548
549
            if (null !== $this->logger) {
550
                $this->logger->warning(sprintf(
551
                    'An error occured trying to load the template "%s" for the field "%s",'
552
                    .' the default template "%s" was used instead.',
553
                    $templateName,
554
                    $fieldDescription->getFieldName(),
555
                    $defaultTemplate
556
                ), ['exception' => $e]);
557
            }
558
        }
559
560
        return $template;
561
    }
562
563
    private function render(
564
        FieldDescriptionInterface $fieldDescription,
565
        TemplateWrapper $template,
566
        array $parameters,
567
        Environment $environment
568
    ): ?string {
569
        $content = $template->render($parameters);
570
571
        if ($environment->isDebug()) {
572
            $commentTemplate = <<<'EOT'
573
574
<!-- START
575
    fieldName: %s
576
    template: %s
577
    compiled template: %s
578
    -->
579
    %s
580
<!-- END - fieldName: %s -->
581
EOT;
582
583
            return sprintf(
584
                $commentTemplate,
585
                $fieldDescription->getFieldName(),
586
                $fieldDescription->getTemplate(),
587
                $template->getSourceContext()->getName(),
588
                $content,
589
                $fieldDescription->getFieldName()
590
            );
591
        }
592
593
        return $content;
594
    }
595
596
    /**
597
     * @throws ServiceCircularReferenceException
598
     * @throws ServiceNotFoundException
599
     */
600
    private function getTemplateRegistry(string $adminCode): TemplateRegistryInterface
601
    {
602
        $serviceId = $adminCode.'.template_registry';
603
        $templateRegistry = $this->templateRegistries->get($serviceId);
604
605
        if ($templateRegistry instanceof TemplateRegistryInterface) {
606
            return $templateRegistry;
607
        }
608
609
        throw new ServiceNotFoundException($serviceId);
610
    }
611
612
    /**
613
     * Extracts the object and requested value from the $listElement.
614
     *
615
     * @param object|array $listElement
616
     *
617
     * @throws \TypeError when $listElement is not an object or an array with an object on offset 0
618
     *
619
     * @return array An array containing object and value
620
     */
621
    private function getObjectAndValueFromListElement(
622
        $listElement,
623
        FieldDescriptionInterface $fieldDescription
624
    ): array {
625
        if (\is_object($listElement)) {
626
            $object = $listElement;
627
        } elseif (\is_array($listElement)) {
628
            if (!isset($listElement[0]) || !\is_object($listElement[0])) {
629
                throw new \TypeError(sprintf('If argument 1 passed to %s() is an array it must contain an object at offset 0.', __METHOD__));
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('If argument 1 p...offset 0.', __METHOD__).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
630
            }
631
632
            $object = $listElement[0];
633
        } else {
634
            throw new \TypeError(sprintf('Argument 1 passed to %s() must be an object or an array, %s given.', __METHOD__, \gettype($listElement)));
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('Argument 1 pass...\gettype($listElement)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
635
        }
636
637
        if (\is_array($listElement) && \array_key_exists($fieldDescription->getName(), $listElement)) {
638
            $value = $listElement[$fieldDescription->getName()];
639
        } else {
640
            try {
641
                $value = $fieldDescription->getValue($object);
642
            } catch (NoValueException $e) {
643
                // NEXT_MAJOR: throw the NoValueException.
644
                @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
645
                    'Accessing a non existing value is deprecated'
646
                    .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
647
                    E_USER_DEPRECATED
648
                );
649
650
                $value = null;
651
            }
652
        }
653
654
        return [$object, $value];
655
    }
656
}
657