How to cache routes when using Symfony Routing as a standalone? - php

I'm using the Symfony Routing components standalone, i.e. not with the Symfony framework. Here's my bare-bones code I'm playing with:
<?php
$router = new Symfony\Component\Routing\RouteCollection();
$router->add('name', new Symfony\Component\Routing\Route(/*uri*/));
// more routes added here
$context = new Symfony\Component\Routing\RequestContext();
$context->setMethod(/*method*/);
$matcher = new Symfony\Component\Routing\Matcher\UrlMatcher($router, $context);
$result = $matcher->match(/*requested path*/);
Is there a way to cache the routes, so I don't need to run all the add() calls on every page load? (See for example FastRoute.) I believe there is caching when using the full Symfony framework, can that be implemented easily here?

The Symfony Routing Component documentation contains an example of how to easily enable the cache: The all-in-one Router
Basically your example can be reworked like the following:
// RouteProvider.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('name', new Route(/*uri*/));
// more routes added here
return $collection;
// Router.php
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\RequestContext
use Symfony\Component\Routing\Loader\PhpFileLoader;
$context = new RequestContext();
$context->setMethod(/*method*/);
$locator = new FileLocator(array(__DIR__));
$router = new Router(
new PhpFileLoader($locator),
'RouteProvider.php',
array('cache_dir' => __DIR__.'/cache'), // must be writeable
$context
);
$result = $router->match(/*requested path*/);

Related

Route Not Found In Prefix Root Path

I'm trying to setting up symfony/routing component on my project..
Things go well but when I define prefix for routes, it throw route not found exception for root path of this prefix.
For example, let assume I have bunch of admin routes. Instead of defining "admin" keyword on each route I made a prefix route all of those. So my dashboard path turned into "/" from "/admin". And now it throws route not found error..
When I checked the route collections. Dashboard path seems as "/admin/". And its not matching with REQUEST_URI
Am I setting up the component poorly or there are some cautions that I need to do ?
Here is part of RouteProvider
foreach (scanDirectory(ROOT_PATH . "/routes") as $file) {
$subCollection = new RouteCollection();
$filepath = ROOT_PATH . "/routes/" . $file;
$routes = Yaml::parseFile($filepath);
$prefix = "api";
if (array_key_exists("prefix", $routes)){
$prefix = $routes["prefix"];
unset($routes["prefix"]);
}
foreach ($routes as $name => $route) {
$parameters = (new RouteParser($route))->parse();
$subCollection->add(
$name,
new Route(...$parameters)
);
}
$subCollection->addPrefix($prefix);
$subCollection->addOptions([
"trailing_slash_on_root" => false
]);
$collection->addCollection($subCollection);
}
I poked around a bit in the router component. The trailing_slash_on_root functionality is implemented as part of the loader process. So I think you need to set it in your routes file. You did not provide an example of what your admin route files look like so I'm not positive. Normally I would expect to see only a master routes file loaded which in turn would load individual sets of routes such as your admin routes.
However, using your posted code as an example, we can implement the same process that trailing_slash_on_root uses. Basically we explicitly drop the trailing slash for the dashboard route after all the processing takes place. Here is a complete standalone working example taken mostly from the routing component docs:
$rootCollection = new RouteCollection();
$adminCollection = new RouteCollection();
$route = new Route('/users',['_controller' => 'admin_users_controller']);
$adminCollection->add('admin_users',$route);
$route = new Route('/',['_controller' => 'admin_dashboard_controller']);
$adminCollection->add('admin_dashboard',$route);
$adminCollection->addPrefix('/admin'); # This actually adds the prefix
# *** Explicitly tweak the processed dashboard route ***
$route = $adminCollection->get('admin_dashboard');
$route->setPath('/admin');
$rootCollection->addCollection($adminCollection);
$context = new RequestContext('/');
// Routing can match routes with incoming requests
$matcher = new UrlMatcher($rootCollection, $context);
$parameters = $matcher->match('/admin/users');
var_dump($parameters);
$parameters = $matcher->match('/admin');
var_dump($parameters);

HttpFoundation Session stand alone component and forms renderer

(I deleted previous similar subject, because there was too many changed in the code).
I use Symfony\Component\HttpFoundation\Session\Session and Symfony\Component\HttpFoundation\Request but without all framework.
I have simple index
$request = Request::createFromGlobals();
$session = new Session();
if(!$session->isStarted()){
$session->start();
}
$request->setSession($session);
$kernel = new Kernel(new AppContainer());
$response = $kernel->handle($request);
$response->send();
it works well when I use just twig templates.
When I use any class what implements FormRendererInterface it throw me an error.
I expect that by CsrfTokenManager.
This problem doesn't exists when I use $session = new Session(new PhpBridgeSessionStorage()); in index.php. Unfortunately in such case sessions in the next request are empty (this is logic, because I sessions auto start in php.inii is disabled).
Bellow is code what I use in the controller to use form builder.
...
$form = (new LoginForm($this->formBuilder))->getForm($this->generateUrl('login'));
$form->handleRequest($request);
Match::val($form->isSubmitted() && $form->isValid())
->of(
When::equals(true, function($item) use ($form){
$this->commandBus->dispatch(new UserLogin($form->getData()));
}),
When::other(false)
);
...
Thanks for any hints.
SOLUTION
The issues was because to build form I use class FormBuilder what is abstract for others forms and it was provided as service.
There is creating csrf token and to do it I need to session instance.
I could not fond sessions in this class because it was configured in AppContainer.
So finally I had session what was start in the index.php and next try to start in the SessionTokenStorage. It has been throwed an error.
Now session is create in the AppContainer as public service.
I can set the same instance as parameter for others services and also add to the request by $appContainer->get('sessions').
Little bit of code
services.yaml
...
sessions:
class: Symfony\Component\HttpFoundation\Session\Session
arguments: []
public: true
form.builder:
class: Iaso\Web\Component\Form\FormBuilder
arguments: ['#twig','#config.loader', '#sessions']
public: true
index.php
<?php
...
$request = Request::createFromGlobals();
$appContainer = new AppContainer();
$session = $appContainer->get('sessions');
if(!$session->isStarted()){
$session->start();
}
$request->setSession($session);
$kernel = new Kernel($appContainer);
$response = $kernel->handle($request);
$response->send();

Symfony Routing Component not routing url

I have mvc php cms like this folder structure:
application
---admin
--------controller
--------model
--------view
--------language
---catalog
--------controller
------------------IndexController.php
--------model
--------view
--------language
core
--------controller.php
//...more
public
--------index.php
vendor
I install symfony/router component for help my route url using composer json:
{
"autoload": {
"psr-4": {"App\\": "application/"}
},
"require-dev":{
"symfony/routing" : "*"
}
}
Now with route documents I add this code for routing in index.php:
require '../vendor/autoload.php';
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$route = new Route('/index', array('_controller' => 'App\Catalog\Controller\IndexController\index'));
$routes = new RouteCollection();
$routes->add('route_name', $route);
$context = new RequestContext('/');
$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->match('/index');
In My IndexController I have :
namespace App\Catalog\Controller;
class IndexController {
public function __construct()
{
echo 'Construct';
}
public function index(){
echo'Im here';
}
}
Now in Action I work in this url: localhost:8888/mvc/index and can't see result : Im here IndexController.
How do symfony routing url work and find controller in my mvc structure? thank for any practice And Help.
The request context should be populated with the actual URI that's hitting the application. Instead of trying to do this yourself you can use the HTTP Foundation package from symfony to populate this:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContext;
$context = new RequestContext();
$context->fromRequest(Request::createFromGlobals());
It's also documented here: https://symfony.com/doc/current/components/routing.html#components-routing-http-foundation
After the matching ($parameters = $matcher->match('/index');) you can use the _controller key of the parameters to instantiate the controller and dispatch the action. My suggestion would be to replace the last \ with a different symbol for easy splitting, like App\Controller\Something::index.
You can then do the following:
list($controllerClassName, $action) = explode($parameters['_controller']);
$controller = new $controllerClassName();
$controller->{$action}();
Which should echo the response you have in your controller class.

Symfony3 Hook Up Annotation Routes

I'm writing my own PHP framework built on top of Symfony components as a learning exercise. I followed the tutorial found at http://symfony.com/doc/current/create_framework/index.html to create my framework.
I'd now like to wire up my routes against my controllers using annotations. I currently have the following code to setup the routing:
// Create the route collection
$routes = new RouteCollection();
$routes->add('home', new Route('/{slug}', [
'slug' => '',
'_controller' => 'Controllers\HomeController::index',
]));
// Create a context using the current request
$context = new RequestContext();
$context->fromRequest($request);
// Create the url matcher
$matcher = new UrlMatcher($routes, $context);
// Try to get a matching route for the request
$request->attributes->add($matcher->match($request->getPathInfo()));
I have come across the following class to load the annotations but I'm not sure how to use it:
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php
I'd appreciate it if someone could help.
Thanks
I've finally managed to get this working. First I changed where I included the autoload.php file to the following:
use Doctrine\Common\Annotations\AnnotationRegistry;
$loader = require __DIR__ . '/../vendor/autoload.php';
AnnotationRegistry::registerLoader([$loader, 'loadClass']);
Then I changed the routes collection bit (in the question) to:
$reader = new AnnotationReader();
$locator = new FileLocator();
$annotationLoader = new AnnotatedRouteControllerLoader($reader);
$loader = new AnnotationDirectoryLoader($locator, $annotationLoader);
$routes = $loader->load(__DIR__ . '/../Controllers'); // Path to the app's controllers
Here's the code for the AnnotatedRouteControllerLoader:
class AnnotatedRouteControllerLoader extends AnnotationClassLoader {
protected function configureRoute(Route $route, ReflectionClass $class, ReflectionMethod $method, $annot) {
$route->setDefault('_controller', $class->getName() . '::' . $method->getName());
}
}
This has been taken from https://github.com/sensiolabs/SensioFrameworkExtraBundle/blob/master/Routing/AnnotatedRouteControllerLoader.php. You may wish to modify it to support additional annotations.
I hope this helps.

How to bind route with controller with Symfony2 Routing component

I want to use a standalone Symfony2 Routing component in my small site. I've created this according to documentation and some examples:
$request = Request::createFromGlobals();
$routeTest = new Symfony\Component\Routing\Route('/route-test', array('controller' => 'test'));
$routes = new Symfony\Component\Routing\RouteCollection();
$routes->add('test', $routeTest);
$context = new Symfony\Component\Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Symfony\Component\Routing\Matcher\UrlMatcher($routes, $context);
$matcher->match($request->getPathInfo());
I don't understand how I should call my controller test, that I've passed to the Route constructor. As a result I want to get something like Silex Route matching:
$app->get('/hello/{name}', function($name) use($app) {
return 'Hello '.$app->escape($name);
});
And sorry for my english...
$matcher->match() returns[1] the attributes of the matching route[2] (including a special _route attribute containing the route name [3]).
The controller default is included in the attributes too, so you can easily access it and then use something like call_user_func to call the controller:
// ...
$attributes = $match->match($request->getPathInfo());
$controllerResult = call_user_func($attributes['controller']);

Categories