Completed
Push — 3.x ( 3da661...ec6eec )
by Grégoire
03:21
created

SonataAdminExtension   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 624
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 17

Importance

Changes 0
Metric Value
wmc 69
lcom 5
cbo 17
dl 0
loc 624
rs 2.856
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 39 5
A getFilters() 0 45 1
A getFunctions() 0 8 1
A getName() 0 4 1
A renderListElement() 0 21 1
A output() 0 13 1
A getValueFromFieldDescription() 0 21 5
A renderViewElement() 0 24 2
A renderViewElementCompare() 0 47 3
B renderRelationElement() 0 41 6
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 getTemplate() 0 34 4
A render() 0 32 2
A getTemplateRegistry() 0 11 2
A getUrlSafeIdentifier() 0 8 2

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\Component\Translation\TranslatorInterface as LegacyTranslationInterface;
30
use Symfony\Contracts\Translation\TranslatorInterface;
31
use Twig\Environment;
32
use Twig\Error\LoaderError;
33
use Twig\Extension\AbstractExtension;
34
use Twig\Template;
35
use Twig\TemplateWrapper;
36
use Twig\TwigFilter;
37
use Twig\TwigFunction;
38
39
/**
40
 * @final since sonata-project/admin-bundle 3.52
41
 *
42
 * @author Thomas Rabaix <[email protected]>
43
 */
44
class SonataAdminExtension extends AbstractExtension
45
{
46
    // @todo: there are more locales which are not supported by moment and they need to be translated/normalized/canonicalized here
47
    public const MOMENT_UNSUPPORTED_LOCALES = [
48
        'de' => ['de', 'de-at'],
49
        'es' => ['es', 'es-do'],
50
        'nl' => ['nl', 'nl-be'],
51
        'fr' => ['fr', 'fr-ca', 'fr-ch'],
52
    ];
53
54
    /**
55
     * @var Pool
56
     */
57
    protected $pool;
58
59
    /**
60
     * @var LoggerInterface
61
     */
62
    protected $logger;
63
64
    /**
65
     * @var TranslatorInterface|null
66
     */
67
    protected $translator;
68
69
    /**
70
     * @var string[]
71
     */
72
    private $xEditableTypeMapping = [];
73
74
    /**
75
     * @var ContainerInterface
76
     */
77
    private $templateRegistries;
78
79
    /**
80
     * @var AuthorizationCheckerInterface
81
     */
82
    private $securityChecker;
83
84
    public function __construct(
85
        Pool $pool,
86
        ?LoggerInterface $logger = null,
87
        $translator = null,
88
        ?ContainerInterface $templateRegistries = null,
89
        ?AuthorizationCheckerInterface $securityChecker = null
90
    ) {
91
        // NEXT_MAJOR: make the translator parameter required, move TranslatorInterface check to method signature
92
        // and remove this block
93
94
        if (null === $translator) {
95
            @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...
96
                'The $translator parameter will be required fields with the 4.0 release.',
97
                E_USER_DEPRECATED
98
            );
99
        } else {
100
            if (!$translator instanceof TranslatorInterface) {
101
                @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...
102
                    'The $translator parameter should be an instance of "%s" and will be mandatory in 4.0.',
103
                    TranslatorInterface::class
104
                ), E_USER_DEPRECATED);
105
            }
106
107
            if (!$translator instanceof TranslatorInterface && !$translator instanceof LegacyTranslationInterface) {
108
                throw new \TypeError(sprintf(
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('Argument 2 must...get_class($translator)).

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...
109
                    'Argument 2 must be an instance of "%s" or preferably "%s", "%s given"',
110
                    TranslatorInterface::class,
111
                    LegacyTranslationInterface::class,
112
                    \get_class($translator)
113
                ));
114
            }
115
        }
116
117
        $this->pool = $pool;
118
        $this->logger = $logger;
119
        $this->translator = $translator;
120
        $this->templateRegistries = $templateRegistries;
121
        $this->securityChecker = $securityChecker;
122
    }
123
124
    public function getFilters()
125
    {
126
        return [
127
            new TwigFilter(
128
                'render_list_element',
129
                [$this, 'renderListElement'],
130
                [
131
                    'is_safe' => ['html'],
132
                    'needs_environment' => true,
133
                ]
134
            ),
135
            new TwigFilter(
136
                'render_view_element',
137
                [$this, 'renderViewElement'],
138
                [
139
                    'is_safe' => ['html'],
140
                    'needs_environment' => true,
141
                ]
142
            ),
143
            new TwigFilter(
144
                'render_view_element_compare',
145
                [$this, 'renderViewElementCompare'],
146
                [
147
                    'is_safe' => ['html'],
148
                    'needs_environment' => true,
149
                ]
150
            ),
151
            new TwigFilter(
152
                'render_relation_element',
153
                [$this, 'renderRelationElement']
154
            ),
155
            new TwigFilter(
156
                'sonata_urlsafeid',
157
                [$this, 'getUrlSafeIdentifier']
158
            ),
159
            new TwigFilter(
160
                'sonata_xeditable_type',
161
                [$this, 'getXEditableType']
162
            ),
163
            new TwigFilter(
164
                'sonata_xeditable_choices',
165
                [$this, 'getXEditableChoices']
166
            ),
167
        ];
168
    }
169
170
    public function getFunctions()
171
    {
172
        return [
173
            new TwigFunction('canonicalize_locale_for_moment', [$this, 'getCanonicalizedLocaleForMoment'], ['needs_context' => true]),
174
            new TwigFunction('canonicalize_locale_for_select2', [$this, 'getCanonicalizedLocaleForSelect2'], ['needs_context' => true]),
175
            new TwigFunction('is_granted_affirmative', [$this, 'isGrantedAffirmative']),
176
        ];
177
    }
178
179
    public function getName()
180
    {
181
        return 'sonata_admin';
182
    }
183
184
    /**
185
     * render a list element from the FieldDescription.
186
     *
187
     * @param object $object
188
     * @param array  $params
189
     *
190
     * @return string
191
     */
192
    public function renderListElement(
193
        Environment $environment,
194
        $object,
195
        FieldDescriptionInterface $fieldDescription,
196
        $params = []
197
    ) {
198
        $template = $this->getTemplate(
199
            $fieldDescription,
200
            // NEXT_MAJOR: Remove this line and use commented line below instead
201
            $fieldDescription->getAdmin()->getTemplate('base_list_field'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

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...
202
            //$this->getTemplateRegistry($fieldDescription->getAdmin()->getCode())->getTemplate('base_list_field'),
203
            $environment
204
        );
205
206
        return $this->render($fieldDescription, $template, array_merge($params, [
207
            'admin' => $fieldDescription->getAdmin(),
208
            'object' => $object,
209
            'value' => $this->getValueFromFieldDescription($object, $fieldDescription),
210
            'field_description' => $fieldDescription,
211
        ]), $environment);
212
    }
213
214
    /**
215
     * @deprecated since sonata-project/admin-bundle 3.33, to be removed in 4.0. Use render instead
216
     *
217
     * @return string
218
     */
219
    public function output(
220
        FieldDescriptionInterface $fieldDescription,
221
        Template $template,
222
        array $parameters,
223
        Environment $environment
224
    ) {
225
        return $this->render(
226
            $fieldDescription,
227
            new TemplateWrapper($environment, $template),
228
            $parameters,
229
            $environment
230
        );
231
    }
232
233
    /**
234
     * return the value related to FieldDescription, if the associated object does no
235
     * exists => a temporary one is created.
236
     *
237
     * @param object $object
238
     *
239
     * @throws \RuntimeException
240
     *
241
     * @return mixed
242
     */
243
    public function getValueFromFieldDescription(
244
        $object,
245
        FieldDescriptionInterface $fieldDescription,
246
        array $params = []
247
    ) {
248
        if (isset($params['loop']) && $object instanceof \ArrayAccess) {
249
            throw new \RuntimeException('remove the loop requirement');
250
        }
251
252
        $value = null;
253
254
        try {
255
            $value = $fieldDescription->getValue($object);
256
        } catch (NoValueException $e) {
257
            if ($fieldDescription->getAssociationAdmin()) {
258
                $value = $fieldDescription->getAssociationAdmin()->getNewInstance();
259
            }
260
        }
261
262
        return $value;
263
    }
264
265
    /**
266
     * render a view element.
267
     *
268
     * @param object $object
269
     *
270
     * @return string
271
     */
272
    public function renderViewElement(
273
        Environment $environment,
274
        FieldDescriptionInterface $fieldDescription,
275
        $object
276
    ) {
277
        $template = $this->getTemplate(
278
            $fieldDescription,
279
            '@SonataAdmin/CRUD/base_show_field.html.twig',
280
            $environment
281
        );
282
283
        try {
284
            $value = $fieldDescription->getValue($object);
285
        } catch (NoValueException $e) {
286
            $value = null;
287
        }
288
289
        return $this->render($fieldDescription, $template, [
290
            'field_description' => $fieldDescription,
291
            'object' => $object,
292
            'value' => $value,
293
            'admin' => $fieldDescription->getAdmin(),
294
        ], $environment);
295
    }
296
297
    /**
298
     * render a compared view element.
299
     *
300
     * @param mixed $baseObject
301
     * @param mixed $compareObject
302
     *
303
     * @return string
304
     */
305
    public function renderViewElementCompare(
306
        Environment $environment,
307
        FieldDescriptionInterface $fieldDescription,
308
        $baseObject,
309
        $compareObject
310
    ) {
311
        $template = $this->getTemplate(
312
            $fieldDescription,
313
            '@SonataAdmin/CRUD/base_show_field.html.twig',
314
            $environment
315
        );
316
317
        try {
318
            $baseValue = $fieldDescription->getValue($baseObject);
319
        } catch (NoValueException $e) {
320
            $baseValue = null;
321
        }
322
323
        try {
324
            $compareValue = $fieldDescription->getValue($compareObject);
325
        } catch (NoValueException $e) {
326
            $compareValue = null;
327
        }
328
329
        $baseValueOutput = $template->render([
330
            'admin' => $fieldDescription->getAdmin(),
331
            'field_description' => $fieldDescription,
332
            'value' => $baseValue,
333
        ]);
334
335
        $compareValueOutput = $template->render([
336
            'field_description' => $fieldDescription,
337
            'admin' => $fieldDescription->getAdmin(),
338
            'value' => $compareValue,
339
        ]);
340
341
        // Compare the rendered output of both objects by using the (possibly) overridden field block
342
        $isDiff = $baseValueOutput !== $compareValueOutput;
343
344
        return $this->render($fieldDescription, $template, [
345
            'field_description' => $fieldDescription,
346
            'value' => $baseValue,
347
            'value_compare' => $compareValue,
348
            'is_diff' => $isDiff,
349
            'admin' => $fieldDescription->getAdmin(),
350
        ], $environment);
351
    }
352
353
    /**
354
     * @param mixed $element
355
     *
356
     * @throws \RuntimeException
357
     *
358
     * @return mixed
359
     */
360
    public function renderRelationElement($element, FieldDescriptionInterface $fieldDescription)
361
    {
362
        if (!\is_object($element)) {
363
            return $element;
364
        }
365
366
        $propertyPath = $fieldDescription->getOption('associated_property');
367
368
        if (null === $propertyPath) {
369
            // For BC kept associated_tostring option behavior
370
            $method = $fieldDescription->getOption('associated_tostring');
371
372
            if ($method) {
373
                @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...
374
                    'Option "associated_tostring" is deprecated since version 2.3 and will be removed in 4.0. '
375
                    .'Use "associated_property" instead.',
376
                    E_USER_DEPRECATED
377
                );
378
            } else {
379
                $method = '__toString';
380
            }
381
382
            if (!method_exists($element, $method)) {
383
                throw new \RuntimeException(sprintf(
384
                    'You must define an `associated_property` option or '.
385
                    'create a `%s::__toString` method to the field option %s from service %s is ',
386
                    \get_class($element),
387
                    $fieldDescription->getName(),
388
                    $fieldDescription->getAdmin()->getCode()
389
                ));
390
            }
391
392
            return $element->{$method}();
393
        }
394
395
        if (\is_callable($propertyPath)) {
396
            return $propertyPath($element);
397
        }
398
399
        return $this->pool->getPropertyAccessor()->getValue($element, $propertyPath);
400
    }
401
402
    /**
403
     * Get the identifiers as a string that is safe to use in a url.
404
     *
405
     * @param object $model
406
     *
407
     * @return string string representation of the id that is safe to use in a url
408
     */
409
    public function getUrlSafeIdentifier($model, ?AdminInterface $admin = null)
410
    {
411
        if (null === $admin) {
412
            $admin = $this->pool->getAdminByClass(ClassUtils::getClass($model));
413
        }
414
415
        return $admin->getUrlSafeIdentifier($model);
416
    }
417
418
    /**
419
     * @param string[] $xEditableTypeMapping
420
     */
421
    public function setXEditableTypeMapping($xEditableTypeMapping)
422
    {
423
        $this->xEditableTypeMapping = $xEditableTypeMapping;
424
    }
425
426
    /**
427
     * @return string|bool
428
     */
429
    public function getXEditableType($type)
430
    {
431
        return isset($this->xEditableTypeMapping[$type]) ? $this->xEditableTypeMapping[$type] : false;
432
    }
433
434
    /**
435
     * Return xEditable choices based on the field description choices options & catalogue options.
436
     * With the following choice options:
437
     *     ['Status1' => 'Alias1', 'Status2' => 'Alias2']
438
     * The method will return:
439
     *     [['value' => 'Status1', 'text' => 'Alias1'], ['value' => 'Status2', 'text' => 'Alias2']].
440
     *
441
     * @return array
442
     */
443
    public function getXEditableChoices(FieldDescriptionInterface $fieldDescription)
444
    {
445
        $choices = $fieldDescription->getOption('choices', []);
446
        $catalogue = $fieldDescription->getOption('catalogue');
447
        $xEditableChoices = [];
448
        if (!empty($choices)) {
449
            reset($choices);
450
            $first = current($choices);
451
            // the choices are already in the right format
452
            if (\is_array($first) && \array_key_exists('value', $first) && \array_key_exists('text', $first)) {
453
                $xEditableChoices = $choices;
454
            } else {
455
                foreach ($choices as $value => $text) {
456
                    if ($catalogue) {
457
                        if (null !== $this->translator) {
458
                            $text = $this->translator->trans($text, [], $catalogue);
459
                        // NEXT_MAJOR: Remove this check
460
                        } elseif (method_exists($fieldDescription->getAdmin(), 'trans')) {
461
                            $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...
462
                        }
463
                    }
464
465
                    $xEditableChoices[] = [
466
                        'value' => $value,
467
                        'text' => $text,
468
                    ];
469
                }
470
            }
471
        }
472
473
        if (false === $fieldDescription->getOption('required', true)
474
            && false === $fieldDescription->getOption('multiple', false)
475
        ) {
476
            $xEditableChoices = array_merge([[
477
                'value' => '',
478
                'text' => '',
479
            ]], $xEditableChoices);
480
        }
481
482
        return $xEditableChoices;
483
    }
484
485
    /**
486
     * Returns a canonicalized locale for "moment" NPM library,
487
     * or `null` if the locale's language is "en", which doesn't require localization.
488
     *
489
     * @return string|null
490
     */
491
    final public function getCanonicalizedLocaleForMoment(array $context)
492
    {
493
        $locale = strtolower(str_replace('_', '-', $context['app']->getRequest()->getLocale()));
494
495
        // "en" language doesn't require localization.
496
        if (('en' === $lang = substr($locale, 0, 2)) && !\in_array($locale, ['en-au', 'en-ca', 'en-gb', 'en-ie', 'en-nz'], true)) {
497
            return null;
498
        }
499
500
        foreach (self::MOMENT_UNSUPPORTED_LOCALES as $language => $locales) {
501
            if ($language === $lang && !\in_array($locale, $locales, true)) {
502
                $locale = $language;
503
            }
504
        }
505
506
        return $locale;
507
    }
508
509
    /**
510
     * Returns a canonicalized locale for "select2" NPM library,
511
     * or `null` if the locale's language is "en", which doesn't require localization.
512
     *
513
     * @return string|null
514
     */
515
    final public function getCanonicalizedLocaleForSelect2(array $context)
516
    {
517
        $locale = str_replace('_', '-', $context['app']->getRequest()->getLocale());
518
519
        // "en" language doesn't require localization.
520
        if ('en' === $lang = substr($locale, 0, 2)) {
521
            return null;
522
        }
523
524
        switch ($locale) {
525
            case 'pt':
526
                $locale = 'pt-PT';
527
                break;
528
            case 'ug':
529
                $locale = 'ug-CN';
530
                break;
531
            case 'zh':
532
                $locale = 'zh-CN';
533
                break;
534
            default:
535
                if (!\in_array($locale, ['pt-BR', 'pt-PT', 'ug-CN', 'zh-CN', 'zh-TW'], true)) {
536
                    $locale = $lang;
537
                }
538
        }
539
540
        return $locale;
541
    }
542
543
    /**
544
     * @param string|array $role
545
     * @param object|null  $object
546
     * @param string|null  $field
547
     *
548
     * @return bool
549
     */
550
    public function isGrantedAffirmative($role, $object = null, $field = null)
551
    {
552
        if (null === $this->securityChecker) {
553
            return false;
554
        }
555
556
        if (null !== $field) {
557
            $object = new FieldVote($object, $field);
558
        }
559
560
        if (!\is_array($role)) {
561
            $role = [$role];
562
        }
563
564
        foreach ($role as $oneRole) {
565
            try {
566
                if ($this->securityChecker->isGranted($oneRole, $object)) {
567
                    return true;
568
                }
569
            } catch (AuthenticationCredentialsNotFoundException $e) {
570
                // empty on purpose
571
            }
572
        }
573
574
        return false;
575
    }
576
577
    /**
578
     * Get template.
579
     *
580
     * @param string $defaultTemplate
581
     *
582
     * @return TemplateWrapper
583
     */
584
    protected function getTemplate(
585
        FieldDescriptionInterface $fieldDescription,
586
        $defaultTemplate,
587
        Environment $environment
588
    ) {
589
        $templateName = $fieldDescription->getTemplate() ?: $defaultTemplate;
590
591
        try {
592
            $template = $environment->load($templateName);
593
        } catch (LoaderError $e) {
594
            @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...
595
                sprintf(
596
                    'Relying on default template loading on field template loading exception '.
597
                    'is deprecated since 3.1 and will be removed in 4.0. '.
598
                    'A %s exception will be thrown instead',
599
                    LoaderError::class
600
                ),
601
                E_USER_DEPRECATED
602
            );
603
            $template = $environment->load($defaultTemplate);
604
605
            if (null !== $this->logger) {
606
                $this->logger->warning(sprintf(
607
                    'An error occured trying to load the template "%s" for the field "%s", '.
608
                    'the default template "%s" was used instead.',
609
                    $templateName,
610
                    $fieldDescription->getFieldName(),
611
                    $defaultTemplate
612
                ), ['exception' => $e]);
613
            }
614
        }
615
616
        return $template;
617
    }
618
619
    private function render(
620
        FieldDescriptionInterface $fieldDescription,
621
        TemplateWrapper $template,
622
        array $parameters,
623
        Environment $environment
624
    ): ?string {
625
        $content = $template->render($parameters);
626
627
        if ($environment->isDebug()) {
628
            $commentTemplate = <<<'EOT'
629
630
<!-- START
631
    fieldName: %s
632
    template: %s
633
    compiled template: %s
634
    -->
635
    %s
636
<!-- END - fieldName: %s -->
637
EOT;
638
639
            return sprintf(
640
                $commentTemplate,
641
                $fieldDescription->getFieldName(),
642
                $fieldDescription->getTemplate(),
643
                $template->getSourceContext()->getName(),
644
                $content,
645
                $fieldDescription->getFieldName()
646
            );
647
        }
648
649
        return $content;
650
    }
651
652
    /**
653
     * @throws ServiceCircularReferenceException
654
     * @throws ServiceNotFoundException
655
     */
656
    private function getTemplateRegistry(string $adminCode): TemplateRegistryInterface
657
    {
658
        $serviceId = $adminCode.'.template_registry';
659
        $templateRegistry = $this->templateRegistries->get($serviceId);
660
661
        if ($templateRegistry instanceof TemplateRegistryInterface) {
662
            return $templateRegistry;
663
        }
664
665
        throw new ServiceNotFoundException($serviceId);
666
    }
667
}
668