symfony 3.3 custom router - php

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

Symfony 4 LegacyRouteLoader and LegacyController

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;
}
}

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.

Symfony2 can't find Route on custom Route Loader

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

How to use config value in symfony2 translation?

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}) }}

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

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!

Categories