In my Symfony 2.8 project I have an extension that adds some extra logic to the trans method:
parameters:
translator.class: MyBundle\Twig\TranslationExtension
The class looks like this:
namespace MyBundle\Twig\TranslationExtension;
use Symfony\Bundle\FrameworkBundle\Translation\Translator as BaseTranslator;
class TranslationExtension extends BaseTranslator
{
private $currentLocale;
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
$translation = parent::trans($id, $parameters, $domain, $locale);
// Some extra logic here
return $translation;
}
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{
return parent::transChoice($id, $number, $parameters, $domain, $locale);
}
}
Now, I'm migrating to Symfony 3, where those class parameters are deprecated, but how can I implement this by overwriting the translator service?
Instead of extending, it would be better to decorate the translator service. Right now you overriding the class name, which will also override other bundles that want to decorate the service. And I see you made it an extension because of Twig, the original Twig {{ trans() }} filter will use the decorated service too.
services:
app.decorating_translator:
class: AppBundle\DecoratingTranslator
decorates: translator
arguments: ['#app.decorating_translator.inner'] # original translator
public: false
See documentation about decorating here: http://symfony.com/doc/current/service_container/service_decoration.html
Here is full working example how to decorate translator service in symfony 3 and replace parameter in all translated strings.
Decorate service in config:
# app/config/services.yml
app.decorating_translator:
class: AppBundle\Translation\Translator
decorates: translator
arguments:
- '#app.decorating_translator.inner'
# passing custom parameters
- {'%%app_name%%': '%app_name%', '%%PRETTY_ERROR%%': 'This is not nice:'}
public: false
Create new translator that reuses original translator and adds parameters defined in service config. The only new code is updateParameters() method and call to it:
# AppBundle/Translation/Translator.php
namespace AppBundle\Translation;
use Symfony\Component\Translation\TranslatorBagInterface;
use Symfony\Component\Translation\TranslatorInterface;
class Translator implements TranslatorInterface, TranslatorBagInterface
{
/** #var TranslatorBagInterface|TranslatorInterface */
protected $translator;
/** #var array */
private $parameters;
/**
* #param TranslatorInterface|TranslatorBagInterface $translator
* #param array $parameters
*/
public function __construct($translator, $parameters)
{
$this->translator = $translator;
$this->parameters = $parameters;
}
/**
* #param string $id
* #param array $parameters
* #param null $domain
* #param null $locale
*
* #return string
*/
public function trans($id, array $parameters = [], $domain = null, $locale = null)
{
$parameters = $this->updateParameters($parameters);
return $this->translator->trans($id, $parameters, $domain, $locale);
}
/**
* #param string $id
* #param int $number
* #param array $parameters
* #param null $domain
* #param null $locale
*
* #return string
*/
public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null)
{
$parameters = $this->updateParameters($parameters);
return $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
}
/**
* #param string $locale
*/
public function setLocale($locale)
{
$this->translator->setLocale($locale);
}
/**
* #return string
*/
public function getLocale()
{
return $this->translator->getLocale();
}
/**
* #param string|null $locale
*
* #return \Symfony\Component\Translation\MessageCatalogueInterface
*/
public function getCatalogue($locale = null)
{
return $this->translator->getCatalogue($locale);
}
/**
* #param array $parameters
*
* #return array
*/
protected function updateParameters($parameters)
{
return array_merge($this->parameters, $parameters);
}
}
Now every time you translate message %app_config% will replaced with parameter from config (e.g. parameters.yml) and %PRETTY_ERROR% will be replace with static string.
If needed it is possible to override same parameters when calling trans:
{{ 'layout.title.home'|trans({'%app_name%': 'Real App No. 1'}) }}
Read about service decoration here
#Aurelijus Rozenas:
add this in your Translator:
public function __call($method, $args)
{
return \call_user_func_array(array($this->translator, $method), $args);
}
Related
I am looking how to add a dynamic variable into an API Platform #ApiProperty annotation.
I found that Symfony allows that but it does not seem to work in API Platform annotations.
For example :
/**
* Redirection URL.
*
* #Groups({"authorization_code_login_write", "authorization_code_logout_write"})
* #ApiProperty(
* attributes={
* "openapi_context"={
* "type"="string",
* "example"="%app.auth.default.redirect%"
* }
* }
* )
*/
protected ?string $redirectionUrl = null;
%app.auth.default.redirect% is not replaced by the container parameter with the same name.
How should I do ?
At first sight, I see here only the one way - to create your own attribute in openapi_context, let's say my_redirect_example.
Smth like this, in example:
"openapi_context"={
"type"="string",
"my_redirect_example"=true
}
Then you need to decorate like in documentation
Smth, like that:
public function normalize($object, $format = null, array $context = [])
{
$docs = $this->decorated->normalize($object, $format, $context);
$redirectUrl = .... # your own logic to get this dynamical value
foreach ($docs['paths'] as $pathName => $path) {
foreach ($path as $operationName => $operation) {
if ($operation['my_redirect_example'] ?? false) {
$docs['paths'][$pathName][$operationName]['example'] = $redirectUrl;
}
}
}
return $docs;
}
It should work. Anyway - it is just an example (I didn't test it), just to understanding how you can handle it.
Sure, you can replace true value with your own and use it inside the if statement to get it depending on some yours own logic.
The way to go is to follow the documentation to decorate the Swagger Open API generator service (https://api-platform.com/docs/core/swagger/#overriding-the-openapi-specification).
Add your own service :
# api/config/services.yaml
services:
'App\Swagger\SwaggerDecorator':
decorates: 'api_platform.swagger.normalizer.api_gateway'
arguments: [ '#App\Swagger\SwaggerDecorator.inner' ]
autoconfigure: false
Then create you service class :
<?php
namespace App\Swagger;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Custom Swagger decorator to remove/edit some API documentation information.
*/
final class SwaggerDecorator implements NormalizerInterface
{
/**
* Decorated base Swagger normalizer.
*
* #var NormalizerInterface
*/
protected NormalizerInterface $decorated;
/**
* SwaggerDecorator constructor.
*
* #param NormalizerInterface $decorated
*/
public function __construct(NormalizerInterface $decorated)
{
$this->decorated = $decorated;
}
/**
* {#inheritDoc}
*/
public function normalize($object, string $format = null, array $context = [])
{
$docs = $this->decorated->normalize($object, $format, $context);
$docs['components']['schemas']['authorization-authorization_code_login_write']['properties']['redirectionUrl']['example'] = 'https://example.com/my-dynamic-redirection';
$docs['components']['schemas']['authorization:jsonld-authorization_code_login_write']['properties']['redirectionUrl']['example'] = 'https://example.com/my-dynamic-redirection';
return $docs;
}
/**
* {#inheritDoc}
*/
public function supportsNormalization($data, string $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
}
You'll just have to find which keys to use, browsing the schemas can help on your Swagger UI. In my example, authorization is the short name of my API resource entity and authorization_code_login_write is the denormalization context value of the operation.
And here you go :
Of course, the ideal solution will iterate over all schemas and replace found configuration parameters with their real values. Maybe this feature could be done in API Platform itself (Follow issue : https://github.com/api-platform/api-platform/issues/1711)
I try to do a demo app with Symfony 4.3 & Api Platform
I created an entity called Event:
/**
* #ApiResource(
* itemOperations={
* "put"={"denormalization_context"={"groups"={"event:update"}}},
* "get"={
* "normalization_context"={"groups"={"event:read", "event:item:get"}}
* },
* "delete"
* },
* collectionOperations={"get", "post"},
* normalizationContext={"groups"={"event:read"}, "swagger_definition_name"="Read"},
* denormalizationContext={"groups"={"event:write"}, "swagger_definition_name"="Write"},
* shortName="Event",
* attributes={
* "pagination_items_per_page"=10,
* "formats"={"jsonld", "json", "csv", "jsonhal"}
* }
* )
* #ORM\Entity(repositoryClass="App\Repository\EventRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Event
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"event:read", "event:item:get"})
*/
private $id;
...
public function getId(): ?int
{
return $this->id;
}
...
And an EventItemDataProvider class, my goal is to do something else before sending the entity to the response.
<?php
namespace App\DataProvider;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
use App\Entity\Event;
final class EventItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
{
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Event::class === $resourceClass;
}
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Event
{
// Retrieve the blog post item from somewhere then return it or null if not found
return new Event($id);
// return null;
}
}
When it comes to Event($id) I have this error:
Unable to generate an IRI for the item of type \"App\Entity\Event\"
What do you thing is wrong with my code?
I think is about the logic, api platform uses a restfull component. When you intercept the getItem basically you are using this route:
http://example/api/event/id
in this part is how we need to try to figure what is happening
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Event
{
return new Event($id);
}
in the previous code of the question, you didnt mention about the Event class has a contructor, so basically when the ApiPlatform try to extract the index or id attribute the response is null, and then the restfull structure is broken. They cannot generate the restfull url like this:
http://example/api/event/null ????
Try to set in a constructor the $id parameter for example like this:
class
{
private $id;
public function __constructor($id)
{
$this->id = $id;
}
}
also as a comment is not mandatory return the exact Class in getItem in ApiPlatform, you can try with this:
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])
{
return ['id'=> $id]
}
UPDATE:
<?php
namespace App\DataProvider;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Event;
final class EventItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
{
private $repository;
/**
* UserDataProvider constructor.
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Event::class);
}
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Event::class === $resourceClass;
}
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Event
{
// Retrieve the blog post item from somewhere then return it or null if not found
return $this->repository->find($id);
}
}
add identifier
#ApiProperty(identifier=true)
I recently found this laravel package on github and after installing it, I noticed that the setLocale() function in Translation.php class didn't work as expected. Many issues have been opened about this bug and I started trying to fix it as the maintainer's time was limited due to other open source projects : (Adldap2/Adldap2 and Adldap2/Adldap2-Laravel as stated here.
In Translation.php class, I first changed getLocale() function from
public function getLocale()
{
if ($this->request->hasCookie('locale')) {
return $this->request->cookie('locale');
} else {
return $this->getConfigDefaultLocale();
}
}
to
public function getLocale()
{
if ($this->request->session()->has('locale')) {
return $this->request->session()->get('locale');
} else {
return $this->getConfigDefaultLocale();
}
}
because the comments in the Translation contract specified that sessions were to be used for the getLocale() function.
Then, after inserting some text to translate with the provided helper function {{_t('text')}} in my blade templates, I dumped the result in my localization middleware to see if everything was fine with dd(Translate::getLocale).
I got a 'Session store not set on request.' error. After some googling, I found this thread on Stack Overflow and decided to use the helper function session() and later the Session facade as I found it more convenient.
After these operations, dd(Translate::getLocale) worked fine and I proceeded to modify setLocale() function from
public function setLocale($code = '')
{
$this->locale = $code;
}
to
public function setLocale($code = '')
{
$this->locale = $code;
$this->request->session()->put('locale', $locale);
}
and lastly
public function setLocale($code = '')
{
$this->locale = $code;
session(['locale' => $this->locale]);
}
in order to avoid the 'Session store not set on request' error.
Everything worked correctly.
Despite these actions, the translation still didn't work as expected.
Then, I dumped Translation::getLocale() first in my localization middleware and then in my Translation.php class and I got two different results : 'en' for the Translation.php class and 'fr'(Automatically detected by the middleware).
I also added automatic locale detection to the Translation following this code snippet and tried again with no success.
Finally, I dumped the session variable with dd(Session::all) in the two files and got this from Translation.php class :
array:4 [▼
"locale" => "en"
"_token" => "3FYWDEaPk47ZcqIS2u5KzGmwz7vI0x3G8h9GkNZP"
"_previous" => array:1 [▶]
"_flash" => array:2 [▶]
]
and that from localization middleware :
array:4 [▼
"locale" => "fr"
]
After checking my database where my sessions are stored, I found only one session, so I don't understand why this is happening.
I also checked some blog posts about laravel service container as $app->make() have been used in Translation class constructor, but I'm stuck.
Here are my current Translation.php and localization middleware :
<?php
namespace App\Http\Middleware;
use Closure;
use Carbon\Carbon;
use Session;
use App;
use Config;
use Translation;
use Illuminate\Http\Request;
class Localization
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ( Session::has('locale')) {
$locale = Session::get('locale', Config::get('app.locale'));
} else {
$locale = substr($request->server('HTTP_ACCEPT_LANGUAGE'), 0, 2);
/*$this->config->get('translation.locales')*/
if ($locale != 'fr' && $locale != 'en') {
$locale = 'en';
}
Session::put('locale', $locale);
}
App::setLocale($locale);
//dd(Session::get('locale'));
Translation::setLocale($locale);
return $next($request);
}
}
<?php
namespace Stevebauman\Translation;
use ErrorException;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\App;
use InvalidArgumentException;
use Stevebauman\Translation\Contracts\Client as ClientInterface;
use Stevebauman\Translation\Contracts\Translation as TranslationInterface;
use UnexpectedValueException;
//use Illuminate\Http\Request;
class Translation implements TranslationInterface
{
/**
* Holds the application .
*
* #var object
*/
protected $app ;
/**
* Holds the application locale.
*
* #var string
*/
protected $locale = '';
/**
* Holds the locale model.
*
* #var Model
*/
protected $localeModel;
/**
* Holds the translation model.
*
* #var Model
*/
protected $translationModel;
/**
* Holds the translation client.
*
* #var ClientInterface
*/
protected $client;
/**
* Holds the current cache instance.
*
* #var \Illuminate\Cache\CacheManager
*/
protected $cache;
/**
* Holds the current config instance.
*
* #var \Illuminate\Config\Repository
*/
protected $config;
/**
* Holds the current request instance.
*
* #var \Illuminate\Http\Request
*/
protected $request;
/**
* The default amount of time (minutes) to store the cached translations.
*
* #var int
*/
private $cacheTime = 30;
/**
* {#inheritdoc}
*/
public function __construct(Application $app)
{
$this->app = $app;
$this->config = $this->app->make('config');
$this->cache = $this->app->make('cache');
$this->request = $this->app->make('request');
$this->localeModel = $this->app->make($this->getConfigLocaleModel());
$this->translationModel = $this->app->make($this->getConfigTranslationModel());
$this->client = $this->app->make($this->getConfigClient());
// Set the default locale to the current application locale
$this->setLocale($this->getConfigDefaultLocale());
// Set the cache time from the configuration
$this->setCacheTime($this->getConfigCacheTime());
}
/**
* {#inheritdoc}
*/
public function translate($text = '', $replacements = [], $toLocale = '', $runOnce = false)
{
try {
// Make sure $text is actually a string and not and object / int
$this->validateText($text);
// Get the default translation text. This will insert the translation
// and the default application locale if they don't
// exist using firstOrCreate
$defaultTranslation = $this->getDefaultTranslation($text);
// If there are replacements inside the array we need to convert them
// into google translate safe placeholders. ex :name to __name__
if (count($replacements) > 0) {
$defaultTranslation->translation = $this->makeTranslationSafePlaceholders($text, $replacements);
}
if ( Session::has('locale')) {
$locale = Session::get('locale');
} else {
$locale = substr($request->server('HTTP_ACCEPT_LANGUAGE'), 0, 2);
/*$this->config->get('translation.locales')*/
if ($locale != 'fr' && $locale != 'en') {
$locale = 'en';
}
Session::put('locale', $locale);
}
App::setLocale($locale);
//dd($locale);
Translation::setLocale($locale);
// If a toLocale has been provided, we're only translating a single string, so
// we won't call the getLocale method as it retrieves and sets the default
// session locale. If it has not been provided, we'll get the
// default locale, and set it on the current session.
if ($toLocale) {
$toLocale = $this->firstOrCreateLocale($toLocale);
} else {
//dd(Session::all());
$toLocale = $this->firstOrCreateLocale($this->getLocale());
}
// Check if translation is requested for default locale.
// If it is default locale we can just return default translation.
if ($defaultTranslation->getAttribute($this->localeModel->getForeignKey()) == $toLocale->getKey()) {
return $this->makeReplacements($defaultTranslation->translation, $replacements);
}
// Since we are not on default translation locale, we will have to
// create (or get first) translation for provided locale where
// parent translation is our default translation.
$translation = $this->firstOrCreateTranslation(
$toLocale,
$defaultTranslation->translation,
$defaultTranslation
);
//dd($this->makeReplacements($translation->translation, $replacements));
return $this->makeReplacements($translation->translation, $replacements);
} catch (\Illuminate\Database\QueryException $e) {
// If foreign key integrity constrains fail, we have a caching issue
if (!$runOnce) {
// If this has not been run before, proceed
// Burst locale cache
$this->removeCacheLocale($toLocale->code);
// Burst translation cache
$this->removeCacheTranslation($this->translationModel->firstOrNew([
$toLocale->getForeignKey() => $toLocale->getKey(),
'translation' => $text,
])
);
// Attempt translation 1 more time
return $this->translate($text, $replacements, $toLocale->code, $runOnce = true);
} else {
// If it has already tried translating once and failed again,
// prevent infinite loops and just return the text
return $text;
}
}
}
/**
* {#inheritdoc}
*/
public function getAppLocale()
{
return $this->config->get('app.locale');
}
/**
* {#inheritdoc}
*/
public function getRoutePrefix()
{
$locale = $this->request->segment($this->getConfigRequestSegment());
$locales = $this->getConfigLocales();
if (is_array($locales) && in_array($locale, array_keys($locales))) {
return $locale;
}
}
/**
* {#inheritdoc}
*/
public function getLocale()
{
//dd($this->request->session()->has('locale'));
/*if ($this->request->session()->has('locale')) {
return $this->request->session()->get('locale');
} else {
return $this->getConfigDefaultLocale();
}*/
/*if (session('locale')) {
return session('locale');
} else {
return $this->getConfigDefaultLocale();
}*/
return Session::get('locale'); //$this->locale;
}
/**
* {#inheritdoc}
*/
public function setLocale($code = '')
{
$this->locale = $code;
/*if(in_array($code, $this->config->get('translation.locales'))){*/
//$this->request->session()->put('locale', $locale);
session(['locale' => $this->locale]);
/* } else {} */
}
/**
* {#inheritdoc}
*/
public function getDefaultTranslation($text)
{
$locale = $this->firstOrCreateLocale($this->getConfigDefaultLocale());
return $this->firstOrCreateTranslation($locale, $text);
}
/**
* Replaces laravel translation placeholders with google
* translate safe placeholders. Ex:.
*
* Converts:
* :name
*
* Into:
* __name__
*
* #param string $text
* #param array $replace
*
* #return mixed
*/
protected function makeTranslationSafePlaceholders($text, array $replace = [])
{
if (count($replace) > 0) {
foreach ($replace as $key => $value) {
// Search for :key
$search = ':'.$key;
// Replace it with __key__
$replace = $this->makeTranslationSafePlaceholder($key);
// Perform the replacements
$text = str_replace($search, $replace, $text);
}
}
return $text;
}
/**
* Makes a placeholder by the specified key.
*
* #param string $key
*
* #return string
*/
protected function makeTranslationSafePlaceholder($key = '')
{
return '___'.strtolower($key).'___';
}
/**
* Make the place-holder replacements on the specified text.
*
* #param string $text
* #param array $replacements
*
* #return string
*/
protected function makeReplacements($text, array $replacements)
{
if (count($replacements) > 0) {
foreach ($replacements as $key => $value) {
$replace = $this->makeTranslationSafePlaceholder($key);
$text = str_ireplace($replace, $value, $text);
}
}
return $text;
}
/**
* Retrieves or creates a locale from the specified code.
*
* #param string $code
*
* #return Model
*/
protected function firstOrCreateLocale($code)
{
$cachedLocale = $this->getCacheLocale($code);
if ($cachedLocale) {
return $cachedLocale;
}
$name = $this->getConfigLocaleByCode($code);
$locale = $this->localeModel->firstOrCreate([
'code' => $code,
'name' => $name,
]);
$this->setCacheLocale($locale);
return $locale;
}
/**
* Creates a translation.
*
* #param Model $locale
* #param string $text
* #param Model $parentTranslation
*
* #return Model
*/
protected function firstOrCreateTranslation(Model $locale, $text, $parentTranslation = null)
{
// We'll check to see if there's a cached translation
// first before we try and hit the database.
$cachedTranslation = $this->getCacheTranslation($locale, $text);
if ($cachedTranslation instanceof Model) {
return $cachedTranslation;
}
// Check if auto translation is enabled. If so we'll run
// the text through google translate and
// save it, then cache it.
if ($parentTranslation && $this->autoTranslateEnabled()) {
$this->client->setSource($parentTranslation->locale->code);
$this->client->setTarget($locale->code);
try {
$text = $this->client->translate($text);
} catch (ErrorException $e) {
// Request to translate failed, set the text
// to the parent translation.
$text = $parentTranslation->translation;
} catch (UnexpectedValueException $e) {
// Looks like something other than text was passed in,
// we'll set the text to the parent translation
// for this exception as well.
$text = $parentTranslation->translation;
}
}
if ($parentTranslation) {
// If a parent translation is given we're looking for it's child translation.
$translation = $this->translationModel->firstOrNew([
$locale->getForeignKey() => $locale->getKey(),
$this->translationModel->getForeignKey() => $parentTranslation->getKey(),
]);
} else {
// Otherwise we're creating the parent translation.
$translation = $this->translationModel->firstOrNew([
$locale->getForeignKey() => $locale->getKey(),
'translation' => $text,
]);
}
if (empty($translation->getAttribute('translation'))) {
// We need to make sure we don't overwrite the translation
// if it exists already in case it was modified.
$translation->setAttribute('translation', $text);
}
if ($translation->isDirty()) {
$translation->save();
}
// Cache the translation so it's retrieved faster next time
$this->setCacheTranslation($translation);
return $translation;
}
/**
* Sets a cache key to the specified locale and text.
*
* #param Model $translation
*/
protected function setCacheTranslation(Model $translation)
{
if ($translation->parent instanceof Model) {
$id = $this->getTranslationCacheId($translation->locale, $translation->parent->translation);
} else {
$id = $this->getTranslationCacheId($translation->locale, $translation->translation);
}
if (!$this->cache->has($id)) {
$this->cache->put($id, $translation, $this->cacheTime);
}
}
/**
* Remove the translation from the cache manually.
*
* #param Model $translation
*/
protected function removeCacheTranslation(Model $translation)
{
$id = $this->getTranslationCacheId($translation->locale, $translation->translation);
if ($this->cache->has($id)) {
$this->cache->forget($id);
}
}
/**
* Retrieves the cached translation from the specified locale
* and text.
*
* #param Model $locale
* #param string $text
*
* #return bool|Model
*/
protected function getCacheTranslation(Model $locale, $text)
{
$id = $this->getTranslationCacheId($locale, $text);
$cachedTranslation = $this->cache->get($id);
if ($cachedTranslation instanceof Model) {
return $cachedTranslation;
}
// Cached translation wasn't found, let's return
// false so we know to generate one.
return false;
}
/**
* Sets a cache key to the specified locale.
*
* #param Model $locale
*/
protected function setCacheLocale(Model $locale)
{
if (!$this->cache->has($locale->code)) {
$id = sprintf('translation.%s', $locale->code);
$this->cache->put($id, $locale, $this->cacheTime);
}
}
/**
* Retrieves a cached locale from the specified locale code.
*
* #param string $code
*
* #return bool
*/
protected function getCacheLocale($code)
{
$id = sprintf('translation.%s', $code);
if ($this->cache->has($id)) {
return $this->cache->get($id);
}
return false;
}
/**
* Remove a locale from the cache.
*
* #param string $code
*/
protected function removeCacheLocale($code)
{
$id = sprintf('translation.%s', $code);
if ($this->cache->has($id)) {
$this->cache->forget($id);
}
}
/**
* Returns a unique translation code by compressing the text
* using a PHP compression function.
*
* #param Model $locale
* #param string $text
*
* #return string
*/
protected function getTranslationCacheId(Model $locale, $text)
{
$compressed = $this->compressString($text);
return sprintf('translation.%s.%s', $locale->code, $compressed);
}
/**
* Returns a the english name of the locale code entered from the config file.
*
* #param string $code
*
* #return string
*/
protected function getConfigLocaleByCode($code)
{
$locales = $this->getConfigLocales();
if (is_array($locales) && array_key_exists($code, $locales)) {
return $locales[$code];
}
return $code;
}
/**
* Sets the time to store the translations and locales in cache.
*
* #param int $time
*/
protected function setCacheTime($time)
{
if (is_numeric($time)) {
$this->cacheTime = $time;
}
}
/**
* Returns the default locale from the configuration.
*
* #return string
*/
protected function getConfigDefaultLocale()
{
return $this->config->get('translation.default_locale', 'en');
}
/**
* Returns the locale model from the configuration.
*
* #return string
*/
protected function getConfigLocaleModel()
{
return $this->config->get('translation.models.locale', Models\Locale::class);
}
/**
* Returns the translation model from the configuration.
*
* #return string
*/
protected function getConfigTranslationModel()
{
return $this->config->get('translation.models.translation', Models\Translation::class);
}
/**
* Returns the translation client from the configuration.
*
* #return string
*/
protected function getConfigClient()
{
return $this->config->get('translation.clients.client', Clients\GoogleTranslate::class);
}
/**
* Returns the request segment to retrieve the locale from.
*
* #return int
*/
protected function getConfigRequestSegment()
{
return $this->config->get('translation.request_segment', 1);
}
/**
* Returns the array of configuration locales.
*
* #return array
*/
protected function getConfigLocales()
{
return $this->config->get('translation.locales');
}
/**
* Returns the cache time set from the configuration file.
*
* #return string|int
*/
protected function getConfigCacheTime()
{
return $this->config->get('translation.cache_time', $this->cacheTime);
}
/**
* Returns the auto translate configuration option.
*
* #return bool
*/
protected function autoTranslateEnabled()
{
return $this->config->get('translation.auto_translate', true);
}
/**
* Calculates the md5 hash of a string.
*
* Used for storing cache keys for translations.
*
* #param $string
*
* #return string
*/
protected function compressString($string)
{
return md5($string);
}
/**
* Validates the inserted text to make sure it's a string.
*
* #param $text
*
* #throws InvalidArgumentException
*
* #return bool
*/
protected function validateText($text)
{
if (!is_string($text)) {
$message = 'Invalid Argument. You must supply a string to be translated.';
throw new InvalidArgumentException($message);
}
return true;
}
}
Thank you!!
I have a strange error with Twig and the WebProfiler when I enable a Doctrine filter.
request.CRITICAL: Uncaught PHP Exception Twig_Error_Runtime: "An exception has been thrown
during the rendering of a template ("Error when rendering "http://community.localhost:8000/
_profiler/e94abf?community_subdomain=community&panel=request" (Status code is 404).")." at
/../vendor/symfony/symfony/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/
layout.html.twig line 103
This {{ render(path('_profiler_search_bar', request.query.all)) }} causes the error.
My doctrine filter allows to add filter constraint on some classes (multi tenant app with dynamic subdomains)
<?php
namespace AppBundle\Group\Community;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
/**
* Class CommunityAwareFilter
*/
class CommunityAwareFilter extends SQLFilter
{
/**
* Gets the SQL query part to add to a query.
*
* #param ClassMetadata $targetEntity
* #param string $targetTableAlias
*
* #return string The constraint SQL if there is available, empty string otherwise.
*/
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if (!$targetEntity->reflClass->implementsInterface(CommunityAwareInterface::class)) {
return '';
}
return sprintf('%s.community_id = %s', $targetTableAlias, $this->getParameter('communityId')); // <-- error
// return ''; <-- no error
}
}
I have also extended Symfony Router to add subdomain placeholder automatically in routing.
Do you have any idea what can cause this ?
UPDATE
<?php
namespace AppBundle\Routing;
use AppBundle\Group\Community\CommunityResolver;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Bundle\FrameworkBundle\Routing\Router as BaseRouter;
class Router implements RouterInterface
{
/**
* #var BaseRouter
*/
private $router;
/**
* #var RequestStack
*/
private $request;
/**
* #var CommunityResolver
*/
private $communityResolver;
/**
* Router constructor.
*
* #param BaseRouter $router
* #param RequestStack $request
* #param CommunityResolver $communityResolver
*/
public function __construct(BaseRouter $router, RequestStack $request, CommunityResolver $communityResolver)
{
$this->router = $router;
$this->request = $request;
$this->communityResolver = $communityResolver;
}
/**
* Sets the request context.
*
* #param RequestContext $context The context
*/
public function setContext(RequestContext $context)
{
$this->router->setContext($context);
}
/**
* Gets the request context.
*
* #return RequestContext The context
*/
public function getContext()
{
return $this->router->getContext();
}
/**
* Gets the RouteCollection instance associated with this Router.
*
* #return RouteCollection A RouteCollection instance
*/
public function getRouteCollection()
{
return $this->router->getRouteCollection();
}
/**
* Tries to match a URL path with a set of routes.
*
* If the matcher can not find information, it must throw one of the exceptions documented
* below.
*
* #param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded)
*
* #return array An array of parameters
*
* #throws ResourceNotFoundException If the resource could not be found
* #throws MethodNotAllowedException If the resource was found but the request method is not allowed
*/
public function match($pathinfo)
{
return $this->router->match($pathinfo);
}
public function generate($name, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
if (null !== ($community = $this->communityResolver->getCommunity())) {
$parameters['community_subdomain'] = $community->getSubDomain();
}
return $this->router->generate($name, $parameters, $referenceType);
}
}
I found the solution, in fact I passed my "tenant" (here my "community") object in the Session like this (in a subscriber onKernelRequest)
if (null === ($session = $request->getSession())) {
$session = new Session();
$session->start();
$request->setSession($session);
}
$session->set('community', $community);
I changed to store this object in a service and it works. Maybe using the Session to store data is a bad practice.
I think your Symmfony Router override may cause the problem. Can you paste us the code ?
I like clean docs and phpdoc will automagically look up the type. When documenting a controller function that returns View::make, I have no idea what type to use for the #return in my documentation.
<?php
class FooController extends BaseController {
/**
* Show a view.
*
* #return ??? description of the view
*/
public function show(){
return View::make('bar');
}
}
What is the type here or is there a better way to document the function for this purpose?
The return value is
Illuminate\View\View
I traced through the ServiceProvider which lead me to
Illuminate\View\Environment::make
Which is line 113 of vendor/laravel/framework/src/Illuminate/View/Environment.php (in 4.1 at least)
/**
* Get a evaluated view contents for the given view.
*
* #param string $view
* #param array $data
* #param array $mergeData
* #return \Illuminate\View\View
*/
public function make($view, $data = array(), $mergeData = array())
{
$path = $this->finder->find($view);
$data = array_merge($mergeData, $this->parseData($data));
$this->callCreator($view = new View($this, $this->getEngineFromPath($path), $view, $path, $data));
return $view;
}