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
Related
I'm trying to implement the LegacyController that is listed on the Symfony documentation https://symfony.com/doc/current/migration.html#legacy-route-loader
Using the custom loader configs for services and routes from:
https://symfony.com/doc/current/routing/custom_route_loader.html#creating-a-custom-loader
I wish there was a complete working example listed somewhere.
When I try to run the debug:router or composer:install I just get an error. This after trying various slight variations on this initial config.
$ console debug:router
In FileLoader.php line 166:
Invalid service id: "App\Legacy\LegacyRouteLoader\" in /var/www/site/config/services.yaml (which is loaded in resource "/var/www/site/config/services.yaml").
In ContainerBuilder.php line 991:
Invalid service id: "App\Legacy\LegacyRouteLoader\"
--
<?php
// src/Legacy/LegacyRouteLoader.php
namespace App\Legacy;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
class LegacyRouteLoader extends Loader
{
public function supports($resource, $type = null)
{
return 'extra' === $type;
}
public function load($resource, $type = null)
{
$collection = new RouteCollection();
$finder = new Finder();
$finder->files()->name('*.php');
/** #var SplFileInfo $legacyScriptFile */
foreach ($finder->in($this->webDir) as $legacyScriptFile) {
// This assumes all legacy files use ".php" as extension
$filename = basename($legacyScriptFile->getRelativePathname(), '.php');
$routeName = sprintf('app.legacy.%s', str_replace('/', '__', $filename));
$collection->add($routeName, new Route($legacyScriptFile->getRelativePathname(), [
'_controller' => 'App\Controller\LegacyController::loadLegacyScript',
'requestPath' => '/' . $legacyScriptFile->getRelativePathname(),
'legacyScript' => $legacyScriptFile->getPathname(),
]));
}
return $collection;
}
}
--
<?php
// src/Controller/LegacyController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\StreamedResponse;
class LegacyController
{
public function loadLegacyScript(string $requestPath, string $legacyScript)
{
return StreamedResponse::create(
function () use ($requestPath, $legacyScript) {
$_SERVER['PHP_SELF'] = $requestPath;
$_SERVER['SCRIPT_NAME'] = $requestPath;
$_SERVER['SCRIPT_FILENAME'] = $legacyScript;
chdir(dirname($legacyScript));
include $legacyScript;
}
);
}
}
--
# config/services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Legacy\LegacyRouteLoader\:
tags: ['routing.loader']
--
# config/routes.yaml
#index:
# path: /
# controller: App\Controller\DefaultController::index
app_legacy:
resource: .
type: extra
Thanks to #cerad above, I managed to get it going with some changes.
# config/services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Legacy\LegacyRouteLoader:
tags: ['routing.loader']
--
<?php
// src/Legacy/LegacyRouteLoader.php
namespace App\Legacy;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
class LegacyRouteLoader extends Loader
{
private $webDir = __DIR__.'/../../public/';
public function supports($resource, $type = null)
{
return 'extra' === $type;
}
public function load($resource, $type = null)
{
$collection = new RouteCollection();
$finder = new Finder();
$finder->files()->name('*.php');
/** #var SplFileInfo $legacyScriptFile */
foreach ($finder->in($this->webDir) as $legacyScriptFile) {
// This assumes all legacy files use ".php" as extension
$filename = basename($legacyScriptFile->getRelativePathname(), '.php');
$routeName = sprintf('app.legacy.%s', str_replace('/', '__', $filename));
$collection->add($routeName, new Route($legacyScriptFile->getRelativePathname(), [
'_controller' => 'App\Controller\LegacyController::loadLegacyScript',
'requestPath' => '/' . $legacyScriptFile->getRelativePathname(),
'legacyScript' => $legacyScriptFile->getPathname(),
]));
}
return $collection;
}
}
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.
I'm having the same problem symfony2 is describing here
This comes in handy when you have a bundle but don't want to manually
add the routes for the bundle to app/config/routing.yml. This may be
especially important when you want to make the bundle reusable
TLDR; im trying to implement a custom Route Loader using this part of the symfony2 documentation
http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html#more-advanced-loaders
However it doesn't seem to be working, the route cannot be found;
This is what I've tried so far:
The loader:
<?php
//namespace Acme\DemoBundle\Routing;
namespace Gabriel\AdminPanelBundle\Routing;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\RouteCollection;
class AdvancedLoader extends Loader
{
public function load($resource, $type = null)
{
$collection = new RouteCollection();
$resource = '#GabrielAdminPanelBundle/Resources/config/routing.yml';
$type = 'yaml';
$importedRoutes = $this->import($resource, $type);
$collection->addCollection($importedRoutes);
return $collection;
}
public function supports($resource, $type = null)
{
return $type === 'advanced_extra';
}
}
here is my routing.yml
located in: src/Gabriel/AdminPanelBundle/Resources/config/routing.yml
the routing.yml
gabriel_admin_panel:
resource: "#GabrielAdminPanelBundle/Controller/"
type: annotation
prefix: /superuser
The Routes of the bundle can't be found unless I put the Routes back in the main app/config/routing.yml file, how to fix this?
Edit:
FileLoaderImportCircularReferenceException: Circular reference
detected in "/app/config/routing_dev.yml"
("/app/config/routing_dev.yml" > "/app/config/routing.yml" > "." >
"#GabrielAdminPanelBundle/Controller/" >
"/app/config/routing_dev.yml").
You must also configure service
# src/Gabriel/AdminPanelBundle/Resources/config/services.yml
your_bundle.routing_loader:
class: Gabriel\AdminPanelBundle\Routing\AdvancedLoader
tags:
- { name: routing.loader }
And routing file
# app/config/routing.yml
YourBundle_extra:
resource: .
type: advanced_extra
Is it possible to use global variable from config.yml in translation file in symfony 2?
If yes, please can you give some example or useful link?
For injecting a (or all) twig globals into your translations you need to override the translation service. Check out this answer if you want a detailed explanation. Here is what I did:
Override the translator.class parameter (e.g. in your parameters.yml):
translator.class: Acme\YourBundle\Translation\Translator
Create the new Translator service:
use Symfony\Bundle\FrameworkBundle\Translation\Translator as BaseTranslator;
class Translator extends BaseTranslator
{
}
Finally override both trans and transChoice:
/**
* {#inheritdoc}
*/
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
return parent::trans(
$id,
array_merge($this->container->get('twig')->getGlobals(), $parameters),
$domain,
$locale
);
}
/**
* {#inheritdoc}
*/
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{
return parent::transChoice(
$id,
$number,
array_merge($this->container->get('twig')->getGlobals(), $parameters),
$domain,
$locale
);
}
In this example I am injecting all twig globals. You can only inject one like this:
array_merge(['%your_global%' => $this->container->get('twig')->getGlobals()['your_global']], $parameters)
You can follow those 2 simple steps:
Inject a Global variable in all the templates using the twig configuration:
# app/config/parameters.yml
parameters:
my_favorite_website: www.stackoverflow.com
And
# app/config/config.yml
twig:
globals:
my_favorite_website: "%my_favorite_website%"
Use Message Placeholders to have the ability to place your text in your translation:
# messages.en.yml
I.love.website: "I love %website%!!"
# messages.fr.yml
I.love.website: "J'adore %website%!!"
You now can use the following twig syntax in your templates to get your expected result:
{{ 'I.love.website'|trans({'%website%': my_favorite_website}) }}
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!