Let's say I have a User Entity :
$user = new User(007);
echo $user->getName(); // display Bond
echo $user->getGender(); // display "Male";
echo $user->getDesignation() // display "Monsieur Bond" or "Mister Bond"
With this function :
public function getDesignation() {
if ($this->getGender() == 'Male') return "Monsieur ".$this->getName();
else return "Madame ".$this->getName();
}
How can I use the translator service inside this Entity to translate "Monsieur" and "Madame" ?
It seems the translator service should be used only inside a Controller, but I think it's appropriate in that case to use it inside this Entity.
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 }}).
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.
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.
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).
Related
actually im working on a project, where i want to have all DB-tables as Models. But now im stucking at one Point.
Lets say i have a "Master"-Table where many different relations are defined like the following (easy example):
Human has one heart; Human has one brain... and so on...
Is it possible, to fill up the Master-Model with other Models?
In PHP it would looks like that:
$human = new Human();
$human->heart = new Heart();
$human->brain = new Brain();
Finally i want to say:
$human-save(TRUE);
to VALIDATE all relational models AND save all relational data and the human object in DB.
Is that possible? I cant find something like that on the whole internet O_o.
Thank you very much!
You can override ActiveModel Save method, according to docs:
public function save($runValidation = true, $attributeNames = null)
{
if ($this->getIsNewRecord()) {
$save = $this->insert($runValidation, $attributeNames);
} else {
$save = $this->update($runValidation, $attributeNames) !== false;
}
/* Condition Work if heart and brain is also ActiveModel then
you can trigger save method on these models as well
or you can add your custom logic as well.
*/
if($this->heart && $this->brain) {
return $this->heart->save() && $this->brain->save();
}
return $save;
}
I suggest you following approach:
Let's say you have same relation names as property names for nested objects (some rule needed to call $model->link() method)
Declare common class for Models with nested Models (for example ActiveRecordWithNestedModels)
Override in common class methods save and validate to perform cascade for these operations (using reflection)
Let your models will inherit this common class
Or, as an alternative for overriding validate method, you can build some suitable implementation for rules method in common class.
This common class can looks as follows (this is a simple draft, not tested, just to show the conception):
<?php
namespace app\models;
use yii\db\ActiveRecord;
class ActiveRecordWithNestedModels extends ActiveRecord
{
public function save($runValidation = true, $attributeNames = null)
{
$saveResult = parent::save($runValidation, $attributeNames);
$class = new \ReflectionClass($this);
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$propertyValue = $property->getValue($this);
if (!empty($propertyValue) && is_subclass_of($propertyValue, ActiveRecord::className())) {
/* #var ActiveRecord $nestedModel */
$nestedModel = $propertyValue;
$nestedModel->save($runValidation);
$relation = $property->name;
$this->link($relation, $nestedModel);
}
}
return $saveResult;
}
public function validate($attributeNames = null, $clearErrors = true)
{
$class = new \ReflectionClass($this);
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$propertyValue = $property->getValue($this);
if (!empty($propertyValue) && is_subclass_of($propertyValue, ActiveRecord::className())) {
/* #var ActiveRecord $nestedModel */
$nestedModel = $propertyValue;
if (!$nestedModel->validate(null, $clearErrors)) {
array_push($this->errors, [
$property->name => $nestedModel->errors
]);
}
}
}
parent::validate($attributeNames, $clearErrors);
if ($this->hasErrors()) return false;
return true;
}
}
Then your models can looks like this:
class Heart extends ActiveRecordWithNestedModels
{
}
class Human extends ActiveRecordWithNestedModels
{
/* #var Heart $heart */
public $heart = null;
/**
* The relation name will be 'heart', same as property `heart'
*
* #return \yii\db\ActiveQuery
*/
public function getHeart()
{
return $this->hasOne(Heart::className(), ['id', 'heart_id']);
}
}
And (in theory) you can do:
$human = new Human();
$human->heart = new Heart();
$human->save();
P.S. here can be many complex details in further implementation, as for example
using transactions to rollback save if some child object fails save
overriding delete
serving one-to-many and many-to-many relations
skip cascade if property has no corresponding relation
serving $attributeNames in cascade operations
etc
Usually to eager load a relationship I would do something like this:
Model::with('foo', 'bar', 'baz')...
A solution might be to set $with = ['foo','bar','baz'] however that will always load these three relations whenever I call Model
Is it possible to do something like this: Model::with('*')?
No it's not, at least not without some additional work, because your model doesn't know which relations it supports until they are actually loaded.
I had this problem in one of my own Laravel packages. There is no way to get a list of the relations of a model with Laravel. It's pretty obvious though if you look at how they are defined. Simple functions which return a Relation object. You can't even get the return type of a function with php's reflection classes, so there is no way to distinguish between a relation function and any other function.
What you can do to make it easier is defining a function that adds all the relationships.
To do this you can use eloquents query scopes (Thanks to Jarek Tkaczyk for mentioning it in the comments).
public function scopeWithAll($query)
{
$query->with('foo', 'bar', 'baz');
}
Using scopes instead of static functions allows you to not only use your function directly on the model but for example also when chaining query builder methods like where in any order:
Model::where('something', 'Lorem ipsum dolor')->withAll()->where('somethingelse', '>', 10)->get();
Alternatives to get supported relations
Although Laravel does not support something like that out of the box you can allways add it yourself.
Annotations
I used annotations to determine if a function is a relation or not in my package mentioned above. Annotations are not officially part of php but a lot of people use doc blocks to simulate them.
Laravel 5 is going to use annotations in its route definitions too so I figuered it not to be bad practice in this case. The advantage is, that you don't need to maintain a seperate list of supported relations.
Add an annotation to each of your relations:
/**
* #Relation
*/
public function foo()
{
return $this->belongsTo('Foo');
}
And write a function that parses the doc blocks of all methods in the model and returns the name. You can do this in a model or in a parent class:
public static function getSupportedRelations()
{
$relations = [];
$reflextionClass = new ReflectionClass(get_called_class());
foreach($reflextionClass->getMethods() as $method)
{
$doc = $method->getDocComment();
if($doc && strpos($doc, '#Relation') !== false)
{
$relations[] = $method->getName();
}
}
return $relations;
}
And then just use them in your withAll function:
public function scopeWithAll($query)
{
$query->with($this->getSupportedRelations());
}
Some like annotations in php and some don't. I like it for this simple use case.
Array of supported relations
You can also maintain an array of all the supported relations. This however needs you to always sync it with the available relations which, especially if there are multiple developers involved, is not allways that easy.
protected $supportedRelations = ['foo','bar', 'baz'];
And then just use them in your withAll function:
public function scopeWithAll($query)
{
return $query->with($this->supportedRelations);
}
You can of course also override with like lukasgeiter mentioned in his answer. This seems cleaner than using withAll. If you use annotations or a config array however is a matter of opinion.
There's no way to know what all the relations are without specifying them yourself. How the other answers posted are good, but I wanted to add a few things.
Base Model
I kind of have the feeling that you want to do this in multiple models, so at first I'd create a BaseModel if you haven't already.
class BaseModel extends Eloquent {
public $allRelations = array();
}
"Config" array
Instead of hard coding the relationships into a method I suggest you use a member variable. As you can see above I already added $allRelations. Be aware that you can't name it $relations since Laravel already uses that internally.
Override with()
Since you wanted with(*) you can do that too. Add this to the BaseModel
public static function with($relations){
$instance = new static;
if($relations == '*'){
$relations = $instance->allRelations;
}
else if(is_string($relations)){
$relations = func_get_args();
}
return $instance->newQuery()->with($relations);
}
(By the way, some parts of this function come from the original Model class)
Usage
class MyModel extends BaseModel {
public $allRelations = array('foo', 'bar');
}
MyModel::with('*')->get();
I wouldn't use static methods like suggested since... it's Eloquent ;)
Just leverage what it already offers - a scope.
Of course it won't do it for you (the main question), however this is definitely the way to go:
// SomeModel
public function scopeWithAll($query)
{
$query->with([ ... all relations here ... ]);
// or store them in protected variable - whatever you prefer
// the latter would be the way if you want to have the method
// in your BaseModel. Then simply define it as [] there and use:
// $query->with($this->allRelations);
}
This way you're free to use this as you like:
// static-like
SomeModel::withAll()->get();
// dynamically on the eloquent Builder
SomeModel::query()->withAll()->get();
SomeModel::where('something', 'some value')->withAll()->get();
Also, in fact you can let Eloquent do it for you, just like Doctrine does - using doctrine/annotations and DocBlocks. You could do something like this:
// SomeModel
/**
* #Eloquent\Relation
*/
public function someRelation()
{
return $this->hasMany(..);
}
It's a bit too long story to include it here, so learn how it works: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html
Since i've met with a similar problem, and found a good solution that isn't described here and doesn't require filling some custom arrays or whatever, i'll post it for the future.
What i do, is first create a trait, called RelationsManager:
trait RelationsManager
{
protected static $relationsList = [];
protected static $relationsInitialized = false;
protected static $relationClasses = [
HasOne::class,
HasMany::class,
BelongsTo::class,
BelongsToMany::class
];
public static function getAllRelations($type = null) : array
{
if (!self::$relationsInitialized) {
self::initAllRelations();
}
return $type ? (self::$relationsList[$type] ?? []) : self::$relationsList;
}
protected static function initAllRelations()
{
self::$relationsInitialized = true;
$reflect = new ReflectionClass(static::class);
foreach($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
/** #var ReflectionMethod $method */
if ($method->hasReturnType() && in_array((string)$method->getReturnType(), self::$relationClasses)) {
self::$relationsList[(string)$method->getReturnType()][] = $method->getName();
}
}
}
public static function withAll() : Builder
{
$relations = array_flatten(static::getAllRelations());
return $relations ? self::with($relations) : self::query();
}
}
Now you can use it with any class, like -
class Project extends Model
{
use RelationsManager;
//... some relations
}
and then when you need to fetch them from the database:
$projects = Project::withAll()->get();
Some notes - my example relation classes list doesn't include morph relations, so if you want to get them as well - you need to add them to $relationClasses variable. Also, this solution only works with PHP 7.
You could attempt to detect the methods specific to your model using reflection, such as:
$base_methods = get_class_methods('Illuminate\Database\Eloquent\Model');
$model_methods = get_class_methods(get_class($entry));
$maybe_relations = array_diff($model_methods, $base_methods);
dd($maybe_relations);
Then attempt to load each in a well-controlled try/catch. The Model class of Laravel has a load and a loadMissing methods for eager loading.
See the api reference.
You can create method in your Model
public static function withAllRelations() {
return static::with('foo', 'bar', 'baz');
}
And call Model::withAllRelations()
Or
$instance->withAllRelations()->first(); // or ->get()
You can't have a dynamic loading of relationships for a certain model. you need to tell the model which relations to support.
composer require adideas/laravel-get-relationship-eloquent-model
https://packagist.org/packages/adideas/laravel-get-relationship-eloquent-model
Laravel get relationship all eloquent models!
You don't need to know the names of the methods in the model to do this. Having one or many Eloquent models, thanks to this package, you can get all of its relationships and their type at runtime
The Best Solution
first create a trait, called RelationsManager:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use ReflectionClass;
use ReflectionMethod;
trait RelationsManager
{
protected static $relationsList = [];
protected static $relationsInitialized = false;
protected static $relationClasses = [
HasOne::class,
HasMany::class,
BelongsTo::class,
BelongsToMany::class,
HasOneThrough::class,
HasManyThrough::class,
MorphTo::class,
MorphOne::class,
MorphMany::class,
MorphToMany::class,
];
public static function getAllRelations($type = null): array
{
if (!self::$relationsInitialized) {
self::initAllRelations();
}
return $type ? (self::$relationsList[$type] ?? []) : self::$relationsList;
}
protected static function initAllRelations()
{
self::$relationsInitialized = true;
$reflect = new ReflectionClass(static::class);
foreach ($reflect->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
/** #var ReflectionMethod $method */
if ($method->hasReturnType() && in_array((string) $method->getReturnType(), self::$relationClasses)) {
self::$relationsList[(string) $method->getReturnType()][] = $method->getName();
}
}
}
public static function withAll(): Builder
{
$relations = array_flatten(static::getAllRelations());
return $relations ? self::with($relations) : self::query();
}
}
Now you can use it with any class, like -
class Company extends Model
{
use RelationsManager;
//... some relations
}
and then when you need to fetch them from the database:
$companies = Company::withAll()->get();
this solution only works with PHP 7 Or Higher.
Done
I would like to create my own filter for Latte templating engine. There is an example in their documentation but it doesn't describe how to register it inside presenter.
$latte = new Latte\Engine;
$latte->addFilter('myFilter', function ($s) {
return someMagic($s)
});
I bet there will be simple way to get instance of Latte\Engine inside presenter but I'm not sure how.
Filters can be registered through config.neon too.
services:
nette.latteFactory:
setup:
- addFilter(abs, #App\Latte\AbsFilter)
- App\Latte\AbsFilter
Filter class can look like this:
namespace App\Latte;
class AbsFilter extends \Nette\Object
{
/**
* #param int $number
* #return int
*/
public function __invoke($number)
{
return abs($number);
}
}
In presenter, there is instance of Latte\Engine available in $this->template so everything you have to do is register filter like this:
<?php
abstract class BasePresenter extends Nette\Application\UI\Presenter
{
public function beforeRender()
{
// register filters
$this->template->addFilter('myFilter', function ($s) {
// don't forget to set your own magic
return someMagic($s);
});
}
}
?>
I postend an example using BasePresenter which is parent of all others presenters but you can register it only in presenter you want to and speed up your application.
In addition to #Nortys answer.
Sometimes it's usefull to inject some data from Presenter into anonymous function:
<?php
abstract class BasePresenter extends Nette\Application\UI\Presenter
{
public function beforeRender()
{
$locale = 'en';
// register filters
$this->template->addFilter('myFilter', function ($s) use ($locale) {
// don't forget to set your own magic
return someMagic($s, $locale);
});
}
}
?>
Nette 3.0 / 2020 Approach with Dependency Injection
Registering filters in control or presenter can lead to forgotten registration of filter. They should be registered once, at one place for whole application.
This approach is easy for extension, 1 new filter = 1 new class, no configuration, no touching other code. Apart other answers here, this one respects open-closed solid principle.
LatteFactory service with FilterProvider
First, we make a filter provide service:
interface FilterProviderInterface
{
public function getName(): string;
}
final class PlusFilterProvider implements FilterProviderInterface
{
public function __invoke(int $number, int $anotherNumber): int
{
return SomeStaticClass::plus($number, $anotherNumber);
}
public function getName(): string
{
return 'plus';
}
}
<?php
declare(strict_types=1);
namespace App\Latte;
use Latte\Engine;
use Latte\Runtime\FilterInfo;
final class LatteFactory
{
/**
* #var FilterProviderInterface[]
*/
private array $filterProviders;
/**
* #param FilterProviderInterface[] $filterProviders
*/
public function __construct(array $filterProviders)
{
$this->filterProviders = $filterProviders;
}
public function create(): Engine
{
$engine = new Engine();
// register your filters here
foreach ($this->filterProviders as $filterProvider) {
$engine->addFilter($filterProvider->getName(), $filterProvider);
}
return $engine;
}
}
How to add a new filter?
create new class that implements your interface FilterProviderInterface
register is as a service in your config, or better autodiscover it with search extension
that's it :)
Do you want to learn more?
I wrote more detail post How to Get Rid of Magic, Static and Chaos from Latte Filters where I explain the pros and cons of alternatives and why this one wins.
I'm starting to get into Zend Framework 2, and one of the things that I'd like to do is create an intercept that strips all the tabs out of template files before the view vars are inserted into them.
I gather that I'd have to implement my own render strategy, but I can't quite figure out how to replace the default one (phprenderer). Is it just a matter of setting a strategy of the same name with a higher value then the default one?
For reference, I've solved the issue by adding a Filter
namespace Application\Filter;
use Zend\Filter\FilterInterface;
class FilterMinifyHTML implements FilterInterface {
public function filter($value) {
return \Minify_HTML::minify($value, array(
'cssMinifier' => array('Minify_CSS', 'minify'),
'jsMinifier' => array('JSMin', 'minify'),
));
}
}
And then applied the filter as such (within PhpRenderStrategy.php)
/**
* Constructor
*
* #param PhpRenderer $renderer
*/
public function __construct(PhpRenderer $renderer) {
$this->renderer = $renderer;
$filterChain = new FilterChain();
$filterChain->attach(new FilterMinifyHTML());
$this->renderer->setFilterChain($filterChain);
}
I have a Symfony2 project and I am using Translation component for translating text. I have all translations in yml file like so
translation-identifier: Translated text here
Translating text looks like this from Twig
'translation-identifier'|trans({}, 'domain')
The thing is, in some cases I would like to have two different texts for same translation (not for pluralization). Here's how I would like it to work:
Define two texts in yml file for translations that need to have different texts. Each would have it's own unique suffix
translation-identifier-suffix1
translation-identifier-suffix2
Define a global rule that would define which suffix should be choosen. Psuedocode below:
public function getSuffix() {
return rand(0, 10) < 5 ? '-suffix1' : '-suffix2';
}
Twig (and PHP) would look the same - I would still specify just the identifier without suffix. Translator would then append suffix to the identifier and try to find a match. If there would be no match it would try to find a match again without suffix.
AFAIK, Translator component doesn't support it.
But if you want same kind of behavior, you could do by overriding the translator service.
1) Override the service
# app/config/config.yml
parameters:
translator.class: Acme\HelloBundle\Translation\Translator
First, you can set the parameter holding the service's class name to your own class by setting it in app/config/config.yml.
FYI: https://github.com/symfony/FrameworkBundle/blob/master/Resources/config/translation.xml
2) Extend the translator class provided symfony framework bundle.
FYI: https://github.com/symfony/FrameworkBundle/blob/master/Translation/Translator.php
3) Overwrite the trans function which is provider by translator component.
https://github.com/symfony/Translation/blob/master/Translator.php
Hope this helps!
Here is the extended translator class in case anyone ever needs it
<?php
namespace Acme\HelloBundle\Translation;
use Symfony\Bundle\FrameworkBundle\Translation\Translator as BaseTranslator;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Translator extends BaseTranslator {
const SUFFIX_1 = '_suffix1';
const SUFFIX_2 = '_suffix2';
private $suffix;
public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array()) {
parent::__construct($container, $selector, $loaderIds, $options);
$this->suffix = $this->getSuffix($container);
}
public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null) {
if ($locale === null)
$locale = $this->getLocale();
if (!isset($this->catalogues[$locale]))
$this->loadCatalogue($locale);
if($this->suffix !== null && $this->catalogues[$locale]->has((string) ($id . $this->suffix), $domain))
$id .= $this->suffix;
return strtr($this->catalogues[$locale]->get((string) $id, $domain), $parameters);
}
private function getSuffix($container) {
return rand(0, 10) < 5 ? self::SUFFIX_1 : self::SUFFIX_2;
}
}
?>
As of Symfony 3, Venu's answer no longer works completely, as the translator.class parameter is no longer used.
To load your custom translator class, you now need to create a compiler pass.
<?php
namespace Acme\HelloBundle\DependencyInjection\Compiler;
use Acme\HelloBundle\Translation\Translator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class TranslatorOverridePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$container->getDefinition('translator.default')->setClass(Translator::class);
}
}
And this compiler pass needs to be added to the container.
<?php
namespace Acme\HelloBundle;
use Acme\HelloBundle\DependencyInjection\Compiler\TranslatorOverridePass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeHelloBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new TranslatorOverridePass());
}
}