How to use the translator service inside an Entity?

前端 未结 4 947
渐次进展
渐次进展 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:14

    I ran into the similar problem and finally found this solution. This is not a direct answer to your problem because I'm also aware that an entity should have nothing to do with a service, like translator. So you should leave the getDesignation function untouched. Instead, in the presentation layer, twig for example, you translate that French designation.

    <div>{% trans %}{{ entity.designation }}{% endtrans %} {{ entity.name }}</div>
    

    And in your messages.en.yml

    Monsieur: Mr.
    Madame: Mrs.
    
    0 讨论(0)
  • 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).

    0 讨论(0)
  • 2021-01-12 22:21

    You shouldn't and in general it is not possible. According to the Single Responsibility Principle the entity have already their purpose, which is representing data on a database. Moreover the translation is a matter of representation, so it is unlikely that you want to address such a problem in the entity layer (unless you want to provide entities translated in different languages, which totally a different problem and shouldn't even be solved using the translator).

    Rethink to your logic and try something different for this. Are you sure that you don't want to do this translation on the view layer? That would be the best thing probably. Otherwise (if your logic really need to have translation at a model level) you could create a wrapper class for entities and a factory to generate this "wrapped entities"; in that factory you could inject the translator service.

    0 讨论(0)
  • 2021-01-12 22:27

    The translator service is, like you say, a "service" you can use a service inside any class (i.e. defining it as a service too and using the dependency injector container). So, you can use the translator almost wherever you want.

    But the entities like aldo said shouldn't have that responsability. In the worst scenario if you really want to translate things inside the entity, you could pass the translator to the entity with a set method, i.e.

    $entity->setTranslator($translator);
    

    but I recommend you too to create a class that handles the problem outside the entity, i.e. using the twig template

    {{ entity.property|trans }}).
    
    0 讨论(0)
提交回复
热议问题