How to properly enable the twig's sandbox extension in Symfony2? - php

In Symfony2, there is some Twig module disabled by default. One of them is the debug extension, that adds {% debug %} tag (useful on a development environment).
To enable it, nothing really difficult, you add this service to your configuration :
debug.twig.extension:
class: Twig_Extensions_Extension_Debug
tags:
- { name: 'twig.extension' }
But how to enable the {% sandbox %} tag?
My issue is that the extension's constructor takes security policies :
public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false)
{
$this->policy = $policy;
$this->sandboxedGlobally = $sandboxed;
}
By reading the twig documentation, I seen the way to do it natively (without Symfony2) :
$tags = array('if');
$filters = array('upper');
$methods = array(
'Article' => array('getTitle', 'getBody'),
);
$properties = array(
'Article' => array('title', 'body'),
);
$functions = array('range');
$policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
$sandbox = new Twig_Extension_Sandbox($policy);
$twig->addExtension($sandbox);
I can do something like that inside a service just before using the sandbox, but that's not as clear as the dependancy injection we're used to.
Is there a better / proper way to enable the twig's sandbox extension in Symfony2?

Why not create a private service of the security policy:
parameters:
twig.sandbox.tags:
- if
twig.sandbox.filters:
- upper
twig.sandbox.methods:
Article: [getTitle, getBody]
twig.sandbox.properties:
Article: [title, body]
twig.sandbox.functions:
- range
twig.sandbox.policy:
class: Twig_Sandbox_SecurityPolicy
arguments:
- %twig.sandbox.tags%
- %twig.sandbox.filters%
- %twig.sandbox.methods%
- %twig.sandbox.properties%
- %twig.sandbox.functions%
public: false
You can then inject this service into the twig.sandbox.extension service:
twig.sandbox.extension:
class: Twig_Extension_Sandbox
arguments:
- #twig.sandbox.policy
tags:
- { name: twig.extension }
Done. Marking the twig.sandbox.policy private ensures it won't be accessible using the container (it can still be injected into other services, but I think that's not an issue).
Disclaimer: I haven't tested this and it probably needs some tweaking before it actually works so don't copy paste!

Related

Using a different naming strategy for each class with JMS and Symfony 3.4

Hello dear Stackoverflow community,
I have 2 PHP class (considered as DTO) that I'm using to set the body of my HTTP calls.
Each DTO is used for a different API with different naming strategies (one is using camelCase and the other one is using snake_case.
I can't find a solution to inject my JMS serlializer with a different naming strategy for each of these classes.
Here is my current configuration for JMS :
jms_serializer:
default_context:
serialization:
serialize_null: false
deserialization:
serialize_null: false
property_naming:
id: 'jms_serializer.identical_property_naming_strategy'
Here is my services definition :
ApiBundle\Services\ApiOneService:
arguments:
- '%external_components%'
- '#jms_serializer'
- '#monolog.logger'
ApiBundle\Service\ApiTwoService:
arguments:
- '%external_components%'
- '#jms_serializer'
Note : I can't use the #SerializedName annotation because it's not working if you specify a naming strategy as stated in the JMS documentation
https://jmsyst.com/libs/serializer/master/reference/annotations#serializedname
Is there a way to "copy" my JMS service in my services.yml and just change the naming strategy for my ApiTwoService ?
Or any other solution ?
Best regards.
you can set the NamingStrategy on creating the Serializer, but for the preview with Swagger its not working for me
public function getApiResponse($entity, $groups = [], $asResponse = true)
{
$serializer = SerializerBuilder::create();
$serializer->setPropertyNamingStrategy(new SerializedNameAnnotationStrategy(new IdenticalPropertyNamingStrategy()));
$serializer = $serializer->build();
$context = null;
if (sizeof($groups) > 0) {
$context = SerializationContext::create()->setGroups($groups);
}
$result = $serializer->serialize($entity, 'json', $context);
if ($asResponse == false) {
return $result;
}
return new JsonResponse($result, 200, [], true);
}

Symfony: Can I change container parameters on run time?

I am totally new to Symfony, and I try to run some Acceptance tests. All good until now, but when I run the test I get a response like the following:
When I run my API controllers using the PostMan, I don't get any related information. I have this output only while I run the tests in my command line.
Based on the output message:
The "UsersBundle\Controller\UsersController" service is private, getting it from the contains is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead.
Apart from that I don't use the UsersController directly in my test method:
public function test_cannot_send_multiple_possword_reset_requests() {
$client = static::createClient( $this->defaults );
$client->request(
'POST',
'/api/v1/user-account/request/reset',
[
'username' => 'merianos',
],
[],
[
'Content-Type' => 'application/json',
]
);
$this->assertEquals( 400, $client->getResponse()->getStatusCode() );
$this->assertContains(
'An email containing your password reset token has been send.',
$client->getResponse()->getContent()
);
}
I was wondering if it is possible to override the following setting at runtime only for my unit/acceptance tests:
# /src/UsersBundle/Resources/config/services.yml
services:
_defaults:
public: false
Or of course, if there's any other way to achieve that same result.
Note, that I am using Symfony 3.4.
You should be able to override it in config_test.yml. You can read more about environments here: https://symfony.com/doc/current/configuration/environments.html
Finally I hack it in a totally different way :)
Here is my solution:
<!-- ./phpunit.xml.dist -->
<phpunit other="settings-here">
<php>
<!-- some other settings here -->
<env name="environment" value="test" />
</php>
<!-- Rest of the settings -->
</phpunit>
Then I did this:
<?php
// ./src/UsersBundle/Resources/config/environment-setup.php
if (
isset( $_ENV['environment'] ) &&
in_array( $_ENV['environment'], [ 'test', 'acceptance' ] )
) {
$container->setParameter( 'public_services', true );
} else {
$container->setParameter( 'public_services', false );
}
And finally I did this:
# ./src/UsersBundle/Resources/config/serviecs.yml
imports:
- { resource: environment-setup.php }
services:
_defaults:
public: '%public_services%'
# Rest of the setup.

symfony 3.3 custom router

I have an application, which is dual accessible through login and an OAuth client secret with the same routes. For Oauth access I need to pass a url parameter: "access_token" around on all urls.
It seems best to achieve this with a custom router:
app/config/services.yml
# Learn more about services, parameters and containers at
# https://symfony.com/doc/current/service_container.html
parameters:
router.class: AppBundle\Routing\AccessTokenRouter
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
AppBundle\:
resource: '../../src/AppBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Tests}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
# add more services, or override services that need manual wiring
# AppBundle\Service\ExampleService:
# arguments:
# $someArgument: 'some_value'
app.access_token_user_provider:
class: AppBundle\Security\AccessTokenuserProvider
arguments: ["#doctrine.orm.entity_manager"]
AppBundle\Routing\AccessTokenRouter
use Symfony\Bundle\FrameworkBundle\Routing\Router as BaseRouter;
class AccessTokenRouter extends BaseRouter
{
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
{
// parent router generates url
$url = parent::generate($name, $parameters, $referenceType);
// check for existing preview query string
parse_str($this->getContext()->getQueryString(), $contextQueryParams);
if(isset($contextQueryParams['access_token']))
{
// put possible query string params into $queryParams array
$urlParts = parse_url($url);
parse_str(isset($urlParts['query']) ? $urlParts['query'] : '', $urlQueryParams);
// strip everything after '?' from generated url
$url = preg_replace('/\?.*$/', '', $url);
// append merged query string to generated url
$url .= '?'.http_build_query(array_merge(
array('access_token' => $contextQueryParams['access_token']),
$urlQueryParams
));
}
return $url;
}
}
I get no errors, but the custom router is never called.
Also, when I debug routing:
bin/console debug:container |grep rout
data_collector.router Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector
monolog.logger.router Symfony\Bridge\Monolog\Logger
router alias for "router.default"
router_listener Symfony\Component\HttpKernel\EventListener\RouterListener
routing.loader Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader
web_profiler.controller.router Symfony\Bundle\WebProfilerBundle\Controller\RouterController
I'm confused about the line
alias for "router.default"
I can't find any documentation on this.
It seems something has changed in Symfony, but I can't find what
Are you sure about parameter router.class? I didn't find so parameter...
Try to make custom url generator
config
parameters:
router.options.generator_class: AppBundle\Routing\AccessTokenUrlGenerator
router.options.generator_base_class: AppBundle\Routing\AccessTokenUrlGenerator
and class
use Symfony\Component\Routing\Generator\UrlGenerator as BaseUrlGenerator ;
public function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes)
{
// parent router generates url
$url = parent::doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
// check for existing preview query string
parse_str($this->getContext()->getQueryString(), $contextQueryParams);
if(isset($contextQueryParams['access_token']))
{
// put possible query string params into $queryParams array
$urlParts = parse_url($url);
parse_str(isset($urlParts['query']) ? $urlParts['query'] : '', $urlQueryParams);
// strip everything after '?' from generated url
$url = preg_replace('/\?.*$/', '', $url);
// append merged query string to generated url
$url .= '?'.http_build_query(array_merge(
array('access_token' => $contextQueryParams['access_token']),
$urlQueryParams
));
}
return $url;
}
}
router.class i think in older symfoy version use router.options.generator_class instead

How to store and read values in cache using Symfony and RedisAdapter?

I am trying to store some values on cache the first time I load a page. This is the code I am using:
$cached_items = [
'main_nav' => $main_nav,
'sub_nav' => $sub_nav,
'footer_nav' => $footer_nav,
'view_as' => $view_as,
];
$redisConnection = new Client('tcp://redis:6379');
$cache = new RedisAdapter($redisConnection);
$menu = $cache->getItem('mmi_menus');
if ($menu->isHit()) {
return $menu->get();
} else {
$menu->set($cached_items);
$cache->save($menu);
}
This caching is being done from a non Symfony controller - let's say it's a standalone file.
First problem with the code above,
the else condition is reach out all the time and I think it should not be since values are stored. (check here)
Second problem, having this function in a Symfony controller:
public function GenerateMenuItemsAction()
{
$redisConnection = new Client('tcp://redis:6379');
$cache = new RedisAdapter($redisConnection);
$menu = $cache->getItem('mmi_menus');
if ($menu->isHit()) {
return $this->render(
'CommonBundle:Layout:menu.html.twig',
['menu' => $menu->get()]
);
}
}
$menu->isHit() is null so all the time I am getting this exception from Symfony:
An exception has been thrown during the rendering of a template ("The
controller must return a response (null given). Did you forget to add
a return statement somewhere in your controller?").
Update
I am not using any TTL afaik maybe somehow a default one is setup but this is how the section looks like on the config.yml:
framework:
cache:
app: cache.adapter.redis
default_redis_provider: "redis://%redis_host%"
pools:
cache.pool1:
public: true
What I am missing here? Any ideas?
my config.yml looks like that:
framework:
cache:
system: cache.adapter.apcu
default_redis_provider: redis://%redis_password%#%redis_host%:%redis_port%
pools:
redis_pool:
adapter: cache.adapter.redis
public: true
default_lifetime: 0
provider: cache.default_redis_provider
So I can easily (in my Controller) do something like:
$this->get('redis_pool')->getItem('myitem');
Or you can inject 'redis_pool' as an argument to a Service.
I don't need any 'new' or extra Connection information/configuration - anything is done in config.yml and available as a Service across the application.

Twig do not set the locale in Symfony 3

I created a EventListener to set the locale based on the user preferences, i set the langage like this in my listener:
$request->setLocale($user->getLanguage());
$request->getSession()->set('_locale',$user->getLanguage());
I tried both..
I register the Listener in the service.yml:
app.event_listener.locale:
class: 'AppBundle\EventListener\LocaleListener'
arguments:
- '#security.token_storage'
tags:
- {name: 'kernel.event_listener', event: 'kernel.request', method: 'onKernelRequest'}
I also tried to add a priority: 17 to the service but it does not change anything...
The listener seems to works, i can get the Locale in my controller with a $request->getLocale()(or session).
But Twig is still in the default language I defined in the config.yml:
parameters:
locale: fr
I'm pretty lost now, any tips ?
I tried a lot of stuff (change the priority, check if the locale is passed to the front etc...)
Finally i forced the translator in my EventListener:
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($this->tokenStorage->getToken()) {
$user = $this->tokenStorage->getToken()->getUser();
if ($user && $user instanceof User) {
$request->setLocale($user->getLanguage());
} elseif ($request->query->has('locale')) {
$request->setLocale($request->query->get('locale'));
} else {
$request->setLocale($request->getPreferredLanguage());
}
}
$this->translator->setLocale($request->getLocale());
}
I don't understand why, this should be done in the Symfony translator, but it works...
You have to set the locale for the translator to get the right translation in templates.
E.g in controller:
$this->get('translator')->setLocale($user->getLanguage());

Categories