I have a language selector in my project to redirect to index page with the locale parameter {_locale}, but when the link is to default locale seted in routing.yml, the link looks different. This is my code:
I generate the link in code:
<a href="{{ path('ProjectBaseBundle_index', {'_locale': country.idlang}) }}">
...
</a>
And here is the routing.yml:
ProjectBaseBundle_index:
pattern: /{_locale}
defaults: { _controller: ProjectBaseBundle:Default:index, _locale: es }
requirements:
_locale: en|fr|de|es
This generate propertly the routes for all languages, buy for the default one looks different:
http://project.dev/app_dev.php/en
http://project.dev/app_dev.php/fr
http://project.dev/app_dev.php/de
http://project.dev/app_dev.php/?_locale=es
es is our default language code. I'm using Symfony 2.1
Anyone know How to generate identical route for the default lang?
It happens because of conditions for requirements and provided parameters in UrlGenerator - unknown parameters passed through the arguments array are automatically appended at the end of the URL as GET parameters.
Did you try to put default locale in config.yml instead of having it in defaults section of route?
Symfony-2.0:
framework:
session: { default_locale: es }
Symfony-2.1:
framework:
default_locale: es
I just discovered that it makes sense, because the parameter {_locale} does not exists for the default language, so the system appended the parameter as a GET parameter.
I found a workaround:
ProjectBaseBundle_default:
pattern: /{_locale}
defaults: { _controller: ProjectBaseBundle:Default:index , _locale: %locale% }
ProjectBaseBundle_index:
pattern: /{_locale}
defaults: { _controller: ProjectBaseBundle:Default:index }
requirements:
_locale: en|fr|de|es
and I have opened a issue in Symfony board: https://github.com/symfony/symfony/issues/5135
Related
I'm updating old jHipster gateway to 7.5.0 version. New version uses Spring Cloud Gateway (with Eureka), while the old one used Zuul. In previous version working with Service Discovery having service named 'foo' and path 'bar' would register it without any prefix on the gateway so it could be accessed as:
GATEWAY_URL/foo/bar
right now all services register with 'services/' prefix which results requires to call following url:
GATEWAY_URL/services/foo/bar
I can't find configuration responsible for that. I found a property spring.webservices.path, but changing this to other value does not make any change and in Spring Boot 2.6.3 its value cannot be empty or '/' (but Im not sure if this is a property I should be checking). I also experimented with spring.cloud.gateway.routes in form:
spring:
webservices:
path: /test
main:
allow-bean-definition-overriding: true
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: user-service-route
uri: lb://user
predicates:
- Path=/user/**
but without any luck. Also Im not sure if this is jHipster issue or SCG
I need to change that so that other systems using my API won't need to update their paths, I know I can always add nginx before so that it will rewrite te path but that feels not correct.
This behavior is done by SCG autoconfiguration - GatewayDiscoveryClientAutoConfiguration, it registers DiscoveryLocatorProperties bean with predicate:
PredicateDefinition{name='Path', args={pattern='/'+serviceId+'/**'}}
I didnt want to change autoconfigration, so I did WebFilter that is executed as first one and mutates request path
public class ServicesFilter implements WebFilter {
private final ServicesMappingConfigration mapping;
public ServicesFilter(ServicesMappingConfigration mapping) {
this.mapping = mapping;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
RequestPath path = exchange.getRequest().getPath();
if (path.elements().size() > 1) {
PathContainer pathContainer = path.subPath(1, 2);
if (mapping.getServices().contains(pathContainer.value())) {
ServerHttpRequest mutatedRequest = exchange
.getRequest()
.mutate()
.path("/services" + exchange.getRequest().getPath())
.build();
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
return chain.filter(mutatedExchange);
}
}
return chain.filter(exchange);
}}
I'm working on application written in symfony2 and I want to send email after some action/event... the problem is, that the users can define something like "email templates" which are stores in db like simple string, for example: "This is some email from {{ user }}" and I need to render body for that email which should use that template...
In symfony documentation from this link: https://symfony.com/doc/2.0/cookbook/email/email.html#sending-emails the method for render view is $this->renderView and it expects the path to file such as "bundle:controller:file.html.twig", but my template is simple string from database...
How can I render it?
Twig_Loader_String is deprecated and was always designed for internal use anyway. The usage of this loader is strongly discouraged.
From the API doc:
This loader should NEVER be used. It only exists for Twig internal
purposes. When using this loader with a cache mechanism, you should
know that a new cache key is generated each time a template content
"changes" (the cache key being the source code of the template). If
you don't want to see your cache grows out of control, you need to
take care of clearing the old cache file by yourself.
Also check out this issue: https://github.com/symfony/symfony/issues/10865
The best way I know to load a template from a String source are:
From a controller:
$template = $this->get('twig')->createTemplate('Hello {{ name }}');
$template->render(array('name'=>'World'));
as described here: http://twig.sensiolabs.org/doc/recipes.html#loading-a-template-from-a-string
From a twig template:
{{ include(template_from_string("Hello {{ name }}", {'name' : 'Peter'})) }}
as described here: http://twig.sensiolabs.org/doc/functions/template_from_string.html
Note, that the 'template_from_string' - function is not available by default and needs to be loaded. In symfony you would do this by adding a new service:
# services.yml
services:
appbundle.twig.extension.string:
class: Twig_Extension_StringLoader
tags:
- { name: 'twig.extension' }
This should work. Replace "Hello {{ name }}" with your template text, and fill the array that is passed into the render function with any variables that you need.
$env = new \Twig_Environment(new \Twig_Loader_String());
echo $env->render(
"Hello {{ name }}",
array("name" => "World")
);
Here's a solution that works with Symfony 4 (and possibly older versions as well, although I haven't tested it) and allows you to work with templates stored in the database the same way you would work with templates in the filesystem.
This answer assumes you're using Doctrine, but is relatively easy to adapt if you're using another database library.
Create the Template entity
This is an example class that uses annotations, but you can use whatever configuration method you're already using.
src/Entity/Template.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="templates")
* #ORM\Entity
*/
class Template
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", nullable=false)
*/
private $filename;
/**
* #var string
*
* #ORM\Column(type="text", nullable=false)
*/
private $source;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", nullable=false)
*/
private $last_updated;
}
The bare minimum fields are filename and source, but it's a very good idea to include last_updated or you'll lose the benefits of caching.
Create a DatabaseLoader class
src/Twig/Loader/DatabaseLoader.php
<?php
namespace App\Twig\Loader;
use App\Entity\Template;
use Doctrine\ORM\EntityManagerInterface;
use Twig_Error_Loader;
use Twig_LoaderInterface;
use Twig_Source;
class DatabaseLoader implements Twig_LoaderInterface
{
protected $repo;
public function __construct(EntityManagerInterface $em)
{
$this->repo = $em->getRepository(Template::class);
}
public function getSourceContext($name)
{
if (false === $template = $this->getTemplate($name)) {
throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
}
return new Twig_Source($template->getSource(), $name);
}
public function exists($name)
{
return (bool)$this->getTemplate($name);
}
public function getCacheKey($name)
{
return $name;
}
public function isFresh($name, $time)
{
if (false === $template = $this->getTemplate($name)) {
return false;
}
return $template->getLastUpdated()->getTimestamp() <= $time;
}
/**
* #param $name
* #return Template|null
*/
protected function getTemplate($name)
{
return $this->repo->findOneBy(['filename' => $name]);
}
}
The class is relatively simple. getTemplate looks up the template filename from the database, and the rest of the methods use getTemplate to implement the interface that Twig needs.
Add the DatabaseLoader to your service config
config/services.yaml
services:
App\Twig\Loader\DatabaseLoader:
tags:
- { name: twig.loader }
Now you can use your database templates in the same way as filesystem templates.
Rendering from a controller:
return $this->render('home.html.twig');
Including from another Twig template (which can be in the database or filesystem):
{{ include('welcome.html.twig') }}
Rendering to a string (where $twig is an instance of Twig\Environment)
$html = $twig->render('email.html.twig')
In each of these cases, Twig will check the database first. If getTemplate in your DatabaseLoader returns null, Twig will then check the filesystem. If the template isn't available in the database or the filesystem, Twig will throw a Twig_Error_Loader.
Clone the native twig service and replace the filesystem loader with the native twig string loader:
<service id="my.twigstring" class="%twig.class%">
<argument type="service" id="my.twigstring.loader" />
<argument>%twig.options%</argument>
</service>
<service id="my.twigstring.loader" class="Twig_Loader_String"></service>
Usage example from within a controller:
$this->get('my.twigstring')->render('Hello {{ name }}', array('name' => 'Fabien'));
The best way to do it is to use template_from_string twig function.
{{ include(template_from_string("Hello {{ name }}")) }}
{{ include(template_from_string(page.template)) }}
See documentation of template_from_string
See why it is not a good idea to use Twig_Loader_Chain or Twig_Loader_String for that purpose on this github issue by stof.
As of Twig 1.10, the Twig Engine doesn't support rendering strings. But there is a bundle available which adds this behavior called TwigstringBundle.
It adds the $this->get('twigstring') service wich you can use to render your strings.
(As September '19, the current version of Twig is 2.X, and version 3 is around the corner; so this is only applies to very old versions of Twig).
This work for me:
$loader = new \Twig\Loader\ArrayLoader([
'Temp_File.html' => 'Hello {{ name }}!',
]);
$twig = new \Twig\Environment($loader);
echo $twig->render('Temp_File.html', ['name' => 'Fabien']);
https://twig.symfony.com/doc/2.x/api.html
FYI, This feature was suggested to be added in the core of Twig as of 1.11.0, but will be needed to be activated by the developper.
With Symfony 2.2 you can use the Twig_Chain_Loader
How to register another (custom) Twig loader in Symfony2 environment?
I recently had to implement a CMS used by multiple parties where each party could completely customize their templates. To achieve this I implemented a custom Twig Loader.
The most difficult part was coming up with a naming convention for the templates guaranteed not to overlap with any existing templates, for example <organisation_slug>!AppBundle:template.html.twig.
In case the template was not customised, the template AppBundle:template.html.twig would have to be loaded as fallback template.
However, this is not possible with the Chain Loader (AFAIK) because there the template name cannot be modified. Therefore I had to inject the default loader (i.e. the loader chain) into my loader and use it to load the fallback template.
Another solution would be to pass the request stack or the session to the template loader, making it possible to automatically detect the organisation, but this is difficult because the security component depends on the templating subsystem, causing circular dependency issues.
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
->setFrom('send#example.com')
->setTo('recipient#example.com')
->setBody('hai its a sample mail')
;
$this->get('mailer')->send($message);
FYI: Products is my cms element name.
As shown in shopware 6 guides, I have created a file
DataResolver/ProductsCmsElementResolver.php
which has an enrich method which helps to extend data. In there I try to access the configs of my custom cms element with:
$config = $slot->getFieldConfig();
$productListType = $config->get('products')->getValue();
That, however always returns the default value that was set during the registration of the element:
Shopware.Service('cmsService').registerCmsElement({
name: 'products',
label: 'das.elements.customProductsElement.label',
component: 'sw-cms-el-products',
configComponent: 'sw-cms-el-config-products',
previewComponent: 'sw-cms-el-preview-products',
defaultConfig: {
products: {
source: 'static',
value: ''
}
}
});
I did it exactly as it is shown in the following guides:
https://developer.shopware.com/docs/guides/plugins/plugins/content/cms/add-cms-element
https://developer.shopware.com/docs/guides/plugins/plugins/content/cms/add-data-to-cms-elements#create-a-data-resolver
Could anyone share a sample of code where you get the value of config as a variable and not static value?
What I was doing wrong was that I forgot to write the .value in computed methods:
computed: {
products() {
return this.element.config.products.value;
}
},
However I also found such function call in shopware source codes which was not mentioned in the docs:
methods: {
createdComponent() {
this.initElementConfig('youtube-video');
this.initElementData('youtube-video'); // this line was not present in docs
}
}
I assume you haven't done any further handling of the product data in your config component, as you do not mention it.
I suggest having a look at the default cms components like for example shopware/administration/Resources/app/administration/src/module/sw-cms/elements/product-slider/config/index.js where you can see how product data is handled :)
I want to use trancate filter in twig but i have the error:
The file "D:\projets\dzairdeals\config/services.yaml" does not contain valid YAML: Indentation problem in "D:\\projets\\dzairdeals\\config/services.yaml" at line 30 (near " twig.extension.text:") in D:\projets\dzairdeals\config/services.yaml (which is loaded in resource "D:\projets\dzairdeals\config/services.yaml").
When I try to add this lines to my services.yaml
twig.extension.text:
class: Twig_Extensions_Extension_Text
tags: - { name: twig.extension }
For anyone coming to this for Symfony 5:
composer require twig/string-extra
composer require twig/extra-bundle
Then you can use the truncate filter like so:
{{ project.title|u.truncate(30, '...') }}
Truncate filter is passed a length and an optional string to append to the end if truncated.
u. meaning the string on the left is encapsulated in a Unicode object, see https://twig.symfony.com/doc/2.x/filters/u.html
Make sure twig/extensions is installed:composer require twig/extensions, if so, you should see a config file auto-generated containing:
#config/packages/twig_extensions.yaml
services:
_defaults:
public: false
autowire: true
autoconfigure: true
# Uncomment any lines below to activate that Twig extension
#Twig\Extensions\ArrayExtension: null
#Twig\Extensions\DateExtension: null
#Twig\Extensions\IntlExtension: null
#Twig\Extensions\TextExtension: null
A correct indentation for your yaml file could be :
twig.extension.text:
class: Twig_Extensions_Extension_Text
tags:
- { name: twig.extension }
or
twig.extension.text:
class: Twig_Extensions_Extension_Text
tags: [twig.extension]
I am using shoulda matchers to test critical routes on Rails 4.2.1 with Ruby 2.2.0 on a new application. I just moved my API namespace to a subdomain, and I can't figure out how to get the shoulda routes matcher (or any other concise routes test) to work.
Here is some example code:
config/routes.rb (using versionist for versioning, but that shouldn't be relevant)
namespace :api, path: '', constraints: { subdomain: 'api'} do
api_version(module: 'V1',
path: {value: 'v1'},
defaults: {format: 'json'}, default: true) do
resources :bills, only: :index
end
end
app/controllers/api/v1/bills_controller.rb
module API
module V1
class Bill < APIVersionsController
# GET api.example.com/v1/bills.json
def index
#bills = Bill.all.limit(10)
render json: #bills
end
end
end
end
test/controllers/api/v1/routing_test.rb
module API
module V1
class RoutingTest < ActionController::TestCase
setup { #request.host = 'http://api.example.com' }
should route('/v1/bills')
.to(controller: :bill, action: :index, format: :json)
end
end
end
Before I was using a subdomain, should route('/api/v1/bills').to(action: :index, format: :json) in my BillsControllerTest worked just fine.
Now, when I run rake test, I get Minitest::Assertion: No route matches "/v1/bills".
I've tried putting this in the BillControllerTest (no change);
I've tried an integration test (route matcher doesn't work);
I've tried setting the host with setup { host! 'api.example.com' } and setup { #request.host = 'api.example.com' }
I've tried putting the full URL in the get request ( { get 'http://api.example.com/v1/bills' } );
and I've tried putting subdomain: 'api' and constraints: subdomain: 'api' anywhere that might make sense.
What is a concise way to do route testing with subdomains/what is the current best practice? Is there a way to get the shoulda route matcher to work with them?
This ended up being a simple fix. I just needed to add a subdomain constraint in the #to method and ensure the #route method had the full url:
module API
module V1
class RoutingTest < ActionController::TestCase
should route(:get, 'http://api.example.com/v1')
.to('api/v1/data#index',
subdomain: 'api',
format: :json)
...
Or, if you are in data_controller_test.rb,
module API
module V1
class DataControllerTest < ActionController::TestCase
should route(:get, 'http://api.example.com/v1')
.to(action: :index,
subdomain: 'api',
format: :json)
...