How to use the translator service inside an Entity?

前端 未结 4 975
渐次进展
渐次进展 2021-01-12 22:07

Let\'s say I have a User Entity :

$user = new User(007);
echo $user->getName(); // display Bond
echo $user->getGender(); // display \"Male         


        
4条回答
  •  感动是毒
    2021-01-12 22:17

    I ran into this problem several times over the last years and always found a good enough workaround. This time my getIdentifyingName() methods that are heavily used across the whole project (like an explicit __toString()) had to translate some keywords used in the data layer, so there was no elegant workaround.

    My solution this time is a TranslateObject and a corresponding helper service. The TranslateObject is a plain object holding a translation key and an array of placeholders which also can be TranslateObjects to allow multi level translation (like a getIdentifyingNameTranslateObject() calling another related object's getIdentifyingNameTranslateObject() within one of the placeholders):

    namespace App\Utils;
    
    class TranslateObject
    {
        /** @var string */
        protected $transKey;
        /** @var array */
        protected $placeholders;
    
        public function __construct(string $transKey, array $placeholders = [])
        {
            $this->transKey = $transKey;
            $this->placeholders = self::normalizePlaceholders($placeholders);
        }
    
        public static function normalizePlaceholders(array $placeholders): array
        {
            foreach ($placeholders as $key => &$placeholder) {
                if (substr($key, 0, 1) !== '%' || substr($key, -1, 1) !== '%') {
                    throw new \InvalidArgumentException('The $placeholder attribute must only contain keys in format "%placeholder%".');
                }
                if ($placeholder instanceof TranslateObject) {
                    continue;
                }
                if (is_scalar($placeholder)) {
                    $placeholder = ['value' => $placeholder];
                }
                if (!isset($placeholder['value']) || !is_scalar($placeholder['value'])) {
                    throw new \InvalidArgumentException('$placeholders[\'%placeholder%\'][\'value\'] must be present and a scalar value.');
                }
                if (!isset($placeholder['translate'])) {
                    $placeholder['translate'] = false;
                }
                if (!is_bool($placeholder['translate'])) {
                    throw new \InvalidArgumentException('$placeholders[\'%placeholder%\'][\'translate\'] must be a boolean.');
                }
            }
            return $placeholders;
        }
    
        public function getTransKey(): string
        {
            return $this->transKey;
        }
    
        public function getPlaceholders(): array
        {
            return $this->placeholders;
        }
    }
    

    The helper looks like this and does the work:

    namespace App\Services;
    
    use App\Utils\TranslateObject;
    use Symfony\Contracts\Translation\TranslatorInterface;
    
    class TranslateObjectHelper
    {
        /** @var TranslatorInterface */
        protected $translator;
    
        public function __construct(TranslatorInterface $translator)
        {
            $this->translator = $translator;
        }
    
        public function trans(TranslateObject $translateObject): string
        {
            $placeholders = $translateObject->getPlaceholders();
            foreach ($placeholders as $key => &$placeholder) {
                if ($placeholder instanceof TranslateObject) {
                    $placeholder = $this->trans($placeholder);
                }
                elseif (true === $placeholder['translate']) {
                    $placeholder = $this->translator->trans($placeholder['value']);
                }
                else {
                    $placeholder = $placeholder['value'];
                }
            }
    
            return $this->translator->trans($translateObject->getTransKey(), $placeholders);
        }
    }
    

    And then within the Entity there is a getIdentifyingNameTranslateObject() method returning a TranslateObject.

    /**
     * Get an identifying name as a TranslateObject (for use with TranslateObjectHelper)
     */
    public function getIdentifyingNameTranslateObject(): TranslateObject
    {
        return new TranslateObject('my.whatever.translation.key', [
            '%placeholder1%' => $this->myEntityProperty1,
            '%placeholderWithANeedOfTranslation%' => [
                'value' => 'my.whatever.translation.values.' . $this->myPropertyWithANeedOfTranslation,
                'translate' => true,
            ],
            '%placeholderWithCascadingTranslationNeeds%' => $this->getRelatedEntity()->getIdentifyingNameTranslateObject(),
        ]);
    }
    

    When I need to return such a translated property, I need access to my injected TranslateObjectHelper service and use its trans() method like in a controller or any other service:

    $this->translateObjectHelper->trans($myObject->getIdentifyingNameTranslateObject());
    

    Then I created a twig filter as a simple helper like this:

    namespace App\Twig;
    
    use App\Services\TranslateObjectHelper;
    use App\Utils\TranslateObject;
    
    class TranslateObjectExtension extends \Twig_Extension
    {
        /** @var TranslateObjectHelper */
        protected $translateObjectHelper;
    
        public function __construct(TranslateObjectHelper $translateObjectHelper)
        {
            $this->translateObjectHelper = $translateObjectHelper;
        }
    
        public function getFilters()
        {
            return array(
                new \Twig_SimpleFilter('translateObject', [$this, 'translateObject']),
            );
        }
    
        /**
        * sends a TranslateObject through a the translateObjectHelper->trans() method
        */
        public function translateObject(TranslateObject $translateObject): string
        {
            return $this->translateObjectHelper->trans($translateObject);
        }
    
        public function getName(): string
        {
            return 'translate_object_twig_extension';
        }
    }
    

    So in Twig I can translate like this:

    {{ myObject.getIdentifyingNameTranslateObject()|translateObject }}
    

    In the end, I "just" needed to find all getIdentifyingName() calls (or .identifyingName in Twig) on that entities and replace them with getIdentifyingNameTranslateObject() with a call to the trans() method of the TranslateObjectHelper (or the translateObject filter in Twig).

提交回复
热议问题