I use annotations to define routes in my project. I have a controller method that has such a route:
/**
* #Route("/{slug}", name="showcompany")
*/
public function show(Company $company): Response
{
...
}
This is done to catch requests like website.com/company1, website.com/company2, etc...
I get this error:
"App\Entity\Company object not found by the #ParamConverter
annotation."
Since obviously all other routes such as website.com/signup are considered as slugs of the Company entity.
The question is how to avoid this behavior without moving all routes to routes.yaml and ordering them manually? I want all routes to be prioritized over this /{slug} route.
Related
With Slim I group my controllers and generally have an abstract BaseController I extend for each group. I use class based routing:
/* SLIM 2.0 */
// Users API - extends BaseApiController
$app->post('/users/insert/' , 'Controller\Api\UserApiController:insert');
.
.
// Campaigns - extends BaseAdminController
$app->get('/campaigns/', 'Controller\CampaignController:index')->name('campaigns');
and needed to password protect some routes, at other times I needed to have a slightly different configuration. BaseApiController, BaseAdminController... etc. There were times I needed to know which route I was in so I could execute a certain behavior for just that route. In those cases I would have a helper function like so:
/* SLIM 2.0 */
// returns the current route's name
function getRouteName()
{
return Slim\Slim::getInstance()->router()->getCurrentRoute()->getName();
}
This would give me the route name that is currently being used. So I could do something like...
namespace Controller;
abstract class BaseController
{
public function __construct()
{
/* SLIM 2.0 */
// Do not force to login page if in the following routes
if(!in_array(getRouteName(), ['login', 'register', 'sign-out']))
{
header('Location: ' . urlFor('login'));
}
}
}
I cannot find a way to access the route name being executed. I found this link
Slim 3 get current route in middleware
but I get NULL when I try
$request->getAttribute('routeInfo');
I have also tried the suggested:
'determineRouteBeforeAppMiddleware' => true
I've inspected every Slim3 object for properties and methods, I can't seem to find the equivalent for Slim3, or get access to the named route. It doesn't appear that Slim3 even keeps track of what route it executed, it just... executes it.
These are the following methods the router class has and where I suspect this value would be:
//get_class_methods($container->get('router'));
setBasePath
map
dispatch
setDispatcher
getRoutes
getNamedRoute
pushGroup
popGroup
lookupRoute
relativePathFor
pathFor
urlFor
I was hoping someone has done something similar. Sure, there are other hacky ways I could do this ( some I'm already contemplating now ) but I'd prefer using Slim to give me this data. Any Ideas?
NOTE: I'm aware you can do this with middleware, however I'm looking for a solution that will not require middleware. Something that I can use inside the class thats being instantiated by the triggered route. It was possible with Slim2, was hoping that Slim3 had a similar feature.
It's available via the request object, like this:
$request->getAttribute('route')->getName();
Some more details available here
The methods in your controller will all accept request and response as parameters - slim will pass them through for you, so for example in your insert() method:
use \Psr\Http\Message\ServerRequestInterface as request;
class UserApiController {
public function insert( request $request ) {
// handle request here, or pass it on to a getRouteName() method
}
}
After playing around I found a way to do it. It may not be the most efficient way but it works, and although it uses Middleware to accomplish this I think there are other applications for sharing data in the Middleware with controller classes.
First you create a middleware but you use a "Class:Method" string just like you would in a route. Name it whatever you like.
//Middleware to get route name
$app->add('\Middleware\RouteMiddleware:getName');
Then your middleware:
// RouteMiddleware.php
namespace Middleware;
class RouteMiddleware
{
protected $c; // container
public function __construct($c)
{
$this->c = $c; // store the instance as a property
}
public function getName($request, $response, $next)
{
// create a new property in the container to hold the route name
// for later use in ANY controller constructor being
// instantiated by the router
$this->c['currentRoute'] = $request->getAttribute('route')->getName();
return $next($request, $response);
}
}
Then in your routes you create a route with a route name, in this case I'll use "homePage" as the name
// routes.php
$app->get('/home/', 'Controller\HomeController:index')->setName('homePage');
And in your class controller
// HomeController.php
namespace Controller;
class HomeController
{
public function __construct($c)
{
$c->get('currentRoute'); // will give you "homePage"
}
}
This would allow you to do much more then just get a route name, you can also pass values from the middleware to your class constructors.
If anyone else has a better solution please share!
$app->getCurrentRoute()->getName();
$request->getAttribute('route')->getName();
i have read some articles about routing and annotions.
http://mattstauffer.co/blog/laravel-5.0-route-annotations
But since routes.php is removed in Laravel 5 and annotations became the favourite routing part. how can i bind a model to a route using annotation?
In Laravel 5, route model bindings are stored in the before method of the App\Providers\RouteServiceProvider class:
public function before(Router $router, UrlGenerator $url)
{
$router->model('user', 'App\User');
}
This gets called before any of the routes are loaded.
Just imagine, I have a controller with several actions in it.
And to reach each action I have to define it strictly in routing.yml file like this:
admin_edit_routes:
pattern: /administrator/edituser
defaults: { _controller: MyAdminBundle:Default:edituser }
admin_add_routes:
pattern: /administrator/adduser
defaults: { _controller: MyAdminBundle:Default:adduser }
And I can have plenty of such pages. What I want to achieve is to define necessary action in my URI, just like Kohana routes do.
I mean, I want simply to use one route for all actions:
(code below is not valid, just for example)
admin_main_route:
pattern: /administrator/{action}
defaults: { _controller: MyAdminBundle:Default:{action} action:index}
It will pass all requests to /administrator/{anything} to this route, and after that, according to the action, stated in the uri, the needed action is gonna to be called.
If action is not stated in the uri, then we got thrown to index action.
Is it possible in Symfony 2 in any ways and if yes, then how?
Thanking in advance.
This doesn't feel right to do so. By exposing your controller API (methods) "on the fly" directly via GET method, you open your application to security vulnerabilities. This can also easily lead to unwanted behaviours.
Moreover, how would you generate routes in twig for example ? By giving explicit constants tied to the method names?
{{ path('dynamic_route', { 'method': 'addUser' }) }}
This is not how Symfony works nor what it recommands. Symfony is not CodeIgniter nor Kohana. You don't need a second "Front Controller"
If this is a laziness matter, you can switch your routing configurations to annotations. And let Symfony auto-generate the routes you need, with minimal efforts.
namespace Acme\FooBundle\Controller;
/**
* #Route("/administrator")
*/
class DefaultController extends Controller
{
/**
* #Route
*/
public function indexAction(Request $request);
/**
* #Route("/adduser")
*/
public function addUserAction(Request $request);
/**
* #Route("/edituser")
*/
public function editUserAction(Request $request);
/**
* #Route("/{tried}")
*/
public function fallbackAction($tried, Request $request)
{
return $this->indexAction($request);
}
}
The fallbackAction methods, returns any /administrator/* route which does not exist to the indexAction content.
To sum up, I advise not to use dynamic routing "on the fly" directly to method names.
An alternative would be to create a command which will generate routes once executed.
Well, maybe that's not the best way to do that, but it works. Your dynamicAction accepts action string parameter and checks if such action exists. If so, it executes given action with params.
/**
* Default Controller.
*
* #Route("/default")
*/
class DefaultController extends Controller
{
/**
* #Route("/{action}/{params}", name="default_dynamic")
*/
public function dynamicAction($action, $params = null)
{
$action = $action . 'Action';
if (method_exists($this, $action)) {
return $this->{$action}($params);
}
return $this->indexAction();
}
}
I'm curious if someone have any other ideas :>
In Symfony is it possible to access a controller through a route that potentially can contain indefinite number of slug, taking advantage from internal routing system?
e.g.
my_route:
pattern: /{slug_parent}/{slug_child}/{slug_nephew}/{slug_...}/...
as
www.mydomain.com/math/arithmetic/fractions
but also
www.mydomain.com/tech/android
It can be done, but instead of creating routes in config YML I would recommend to use #Route annotations. Add annotations in the controller class, like this:
/**
* #Route("/{slug_parent}/{slug_child}")
* #Route("/{slug_parent}/{slug_child}/{slug_nephew}/")
* #Route("/{slug_parent}/{slug_child}/{slug_nephew}/{slug_...}/")
*/
public function yourControllerAction()
{
...
}
http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html
I'm going to create a modules system in my Symfony 2 app. Each module will be a bundle.
I don't know how to I can dynamically (in my service code) load routes from file (eg. AcmeSomeModuleBundle/Resources/config/routing.yml) and apply them with some prefix (or host). Like it's done by embedding code below in app/config/routing.yml:
berg_applications:
resource: "#AcmeSomeModuleBundle/Resources/config/routing.yml"
host: foobar.com
Any solutions?
You need custom route loader IMO: http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html
For one project, I had to build route loader which loaded routes by fetching them from remote URL via CURL and it worked perfectly.
Documentation is very clear and it's silly easy to build one yourself when you look at the example. Basically, key things are:
"type" when you're defining a route resource. You should make your custom type so that your route loader recognizes it and takes it for processing.
::load() method.
If you have any concrete problems you stumble upon don't hesitate to post question in comment. Basically, your RouteLoader will receive "resource" in it's load method and should do whatever it needs to do with it to add new Route to Router.
If you do a true bundle approach for each module, then the easiest way to accomplish what your trying to do is use the JMS Security-Extra bundle with attribute-based routing.
To your composer.json file, add this:
"require": {
...
"jms/security-extra-bundle": "1.5.*",
Update your composer file with
php composer.phar update
Then in your BundleName/Resources/config/routing.yml file do this:
some_name:
type: annotation
resource: "#SomeBundle/Controller"
Finally, for each action in your controller, decorate it with #Route attributes:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* #Route("/SomeBundle/SomeController")
*/
class SomeController extends Controller {
/**
* #Route("someAction", name="myAction")
* #Method("GET") OR
* #Method({"GET", "POST"})
*/
public function someAction() {
}
}
Some of the other attributes in the JMS bundle make things really nice as well. For example, I use the #Template attribute on my actions quite a bit. This means that I no longer have to do:
public function recentListAction() {
...
return $this->render(
'AcmeArticleBundle:Article:recentList.html.twig',
array('articles' => $articles)
);
}
I can simply do:
/**
* #Route("/Articles/List")
* #Template()
*/
public function recentListAction() {
...
return array('articles' => $articles);
}
And as long as I have a Resources/views/ControllerName/recentList.html.twig file, everything will be weaved together for me automatically.