i'm new to Symfony. I'm trying to create dynamic universal route that will pick required controller based on part of url. I've found in docs that there is special param {_controller} that can be used in route pattern, but could not find any examples of usage.
// config/routes.yaml
api:
path: /api/{_controller}
So for example for route /api/product i expect ProductController to be initiated.
But as a result i get error "The controller for URI "/api/product" is not callable: Controller "product" does neither exist as service nor as class."
Can somebody please help me understanding how {_controller] param works? Or maybe there is a better way for specifying universal route that can dynamically chose controller without listing controller names in routes.yaml.
Thanks in advance
This isn't really the cleanest way to do what I think you're trying to do. If I am correct in assuming you want to have a /api/product/ point to methods in your product controller, then something like this is more "symfonyish"
// src/Controller/ProductController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/api/product", name="product_")
*/
class ProductController extends AbstractController
{
/**
* #Route("/", name="index")
* --Resolves to /api/product/
*/
public function index(): Response
{
// ...
}
/**
* #Route("/list", name="list")
* --Resolves to /api/product/list
*/
public function list(): Response
{
// ...
}
/**
* #Route("/{productID}", name="get")
* --Resolves to /api/product/{productID}
*/
public function get(string $productID): Response
{
// $productID contains the string from the url
// ...
}
}
Note that this really just scratches the surface of Symfony routing. You can also specify things like methods={"POST"} on the routing directive; so you could have the same path do different things depending on the type of request (e.g. you could have a route /product/{productID} that GETs the product on that request but updates the product on a PATCH request.
Regardless, the takeaway here is that it is unwieldy to have all of your routes defined in routes.yml rather you should define your routes as directives in the controller itself.
Related
I am currently developing an API and am using FOSRestBundle. I've gone wrong somewhere with the annotation side of my controller.
Please see my code below:
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
class DefaultController extends FOSRestController
{
/**
* #Rest\("/default/{string})
* #param string $string
*/
public function defaultAction($string)
{}
}
I am trying to to pass a parameter to the default action and do something with it. However, the parameter I include in the URL isn't getting passed to the action. Any help will be appreciated.
You're just missing the Get from your first annotation, it should be: #Rest\Get("/default/{string}")
As said in the title, I actually need to create a validation process with Symfony.
I'm using YAML file, everything is okay.
But in some cases, I need to check the database before saying that the data is validated.
I was searching in the Callback method, but it actually only allows me to basically check the values. I searched to make dependency injection, or even passing a defined service as a Callback, but it does not help too.
So the question, in short is: is it possible to achieve it? In which way?
With what #dragoste said in comments, I searched how to made it with my own constraint.
The solution is so to use a Custom Constraint. It is a bit messy to know what file to make and what to do, so here is what I have done.
To explain you what are my files, the goal was to validate a rent, not by how it is made but just check that there is no rent at the same moment. That's why I have to use a constraint with Doctrine inside it.
Creating the Validator folder inside the root of your bundle. Then, adding a Constraints folder inside the Validator folder.
Creating a file RentDatesConstraint.php in Validaor/Constraints folder.
Here is how it looks:
<?php
namespace ApiBundle\Validator\Constraints;
use ApiBundle\Validator\RentDatesValidator;
use Symfony\Component\Validator\Constraint;
class RentDatesConstraint extends Constraint
{
public $message = 'The beginning and ending date of the rent are not available for this vehicle.'; // note that you could use parameters inside it, by naming it with % to surround it
/**
* #inheritdoc
*/
public function validatedBy()
{
return RentDatesValidator::class; // this is the name of the class that will be triggered when you need to validate this constraint
}
/**
* #inheritdoc
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT; // says that this constraints is a class constraint
}
}
Now you have created your own class constraint, you have to create your own validator.
Create a file RentDatesValidator.php in Validator folder.
<?php
namespace ApiBundle\Validator;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RentDatesValidator extends ConstraintValidator
{
/**
* #var Registry $doctrine
*/
private $doctrine;
/**
* RentDatesValidator constructor.
* #param Registry $_doctrine
*/
public function __construct(Registry $_doctrine)
{
$this
->setDoctrine($_doctrine)
;
}
/**
* #param Registry $_doctrine
* #return $this
*/
public function setDoctrine(Registry $_doctrine)
{
$this->doctrine = $_doctrine;
return $this;
}
/**
* #inheritdoc
* #param Rent $_value
*/
public function validate($_value, Constraint $_constraint)
{
//do your stuff here
if ($testFails) {
$this
->context
->buildViolation($_constraint->message) // here you can pass an array to set the parameters of the string, surrounded by %
->addViolation()
;
}
}
}
We are almost finished, we have to declare it as a service, so here we edit services.yml in Resources/config
services:
# [...]
validator.rent_dates:
class: ApiBundle\Validator\RentDatesValidator
tags:
- { name: validator.constraint_validator }
arguments: [ "#doctrine" ]
You can notice here that I passed #doctrine service, but you can actually pass any service you want, even many, as long as you are defining the RentDatesValidator class properly to accept those services in its constructor.
And now, all you have to do is to use this in your validation.
Here we edit Rent.yml in Resource/config/validation to add this only line:
ApiBundle\Entity\Rent:
constraints:
- ApiBundle\Validator\Constraints\RentDatesConstraint: ~
We are done! The validation will work when passing your object to the validator service.
You can notice that this is made with YAML, I personally prefer this way of doing things as it separate each parts (entity-definition, database schema, validation files, ...) but you can do it with annotation, XML or even pure PHP. It's up to you, so if you want to see more syntax, you can still go on the link to Symfony Documentation to know how to do this.
I'm teaching myself Symfony. And the routing doesn't make any sense.
I have a postController class with a few actions. Originally the crud generator from the command line gave me this;
/**
* Post controller.
*
* #Route("/post")
*/
class PostController extends Controller
{
/**
* Lists all Post entities.
*
* #Route("/", name="post_index")
* #Method("GET")
*/
public function indexAction()
{
//
}
//
}
What I want to achieve is to remove the #Route from the class itself. Thus I want my indexAction to be the the homepage, and all other actions in my class to still start with /post. For example, this is what I want;
class PostController extends Controller
{
/**
* Lists all Post entities.
*
* #Route("/", name="homepage")
* #Method("GET")
*/
public function indexAction()
{
//
}
/**
* Finds and displays a Post entity.
*
* #Route("post/{id}", name="post_show")
* #Method("GET")
*/
public function showAction(Post $post)
{
//
}
// what I want for the showAction should count for all other Actions as well
}
When I make the change I get an error;
No route found for "GET /post/"
Can somebody please explain to me what I'm doing wrong and how to fix this. I don't think it is something major, it's probably something small that I just don't see. I want to make that indexAction my main action, the action when the website opens after a user logged in. Thank you
Your route specification requires {id} parameter which is obligatory so there's no "GET /post/" route indeed. You need to do one of the following
Pass id value and access /post/1 for example
Remove {id} from route specification and then you can access /post/
Pass default value for id so you can access both /post/ and /post/1. To do that your route specification should look like #Route("post/{id}", name="post_show", defaults={"id" = 1})
No route found for "/post/" is correct
because there is no route "/post/"
in your example theres only "/" and "/post/{id}"
so it makes perfect sense, i prefer to use the yml notation and prefixes so its not bound to a class
check the "YAML" tabs in the DOCS
In my routes files I have a bunch or routes for testing purposes:
/** testing controllers */
Route::get('viewemail', 'TestController#viewemail');
Route::get('restore', 'TestController#restore');
Route::get('sendemail', 'TestController#send_email');
Route::get('socket', 'TestController#socket');
Route::get('colors', 'TestController#colors');
Route::get('view', 'TestController#view_test');
Route::get('numbers', 'TestController#numbers');
Route::get('ncf', 'TestController#ncf');
Route::get('dates', 'TestController#dates');
Route::get('print', 'TestController#printer');
Route::get('{variable}', 'TestController#execute');
/** End of testing controllers */
I want to eliminate all those routes and simple use the name of the given URL to call and return the method:
I have accomplished that in this way:
Route::get('{variable}', 'TestController#execute');
And in my testing controller:
public function execute($method){
return $this->$method();
}
Basically what I want to know if Laravel has a built in solution to do this, I was reading the documentation but couldn't find any way to accomplish this.
From official documentation:
http://laravel.com/docs/5.1/controllers#implicit-controllers
Laravel allows you to easily define a single route to handle every
action in a controller class. First, define the route using the
Route::controller method. The controller method accepts two
arguments. The first is the base URI the controller handles, while the
second is the class name of the controller:
Route::controller('users', 'UserController');
Next, just add methods to your controller. The method names should begin with the
HTTP verb they respond to followed by the title case version of the
URI:
<?php
namespace App\Http\Controllers;
class UserController extends Controller
{
/**
* Responds to requests to GET /users
*/
public function getIndex()
{
//
}
/**
* Responds to requests to GET /users/show/1
*/
public function getShow($id)
{
//
}
/**
* Responds to requests to GET /users/admin-profile
*/
public function getAdminProfile()
{
//
}
/**
* Responds to requests to POST /users/profile
*/
public function postProfile()
{
//
}
}
As you can see in the example above, index methods will respond to the
root URI handled by the controller, which, in this case, is users.
You could add a route pattern for the endpoints you want to listen for. Route them to a controller action, and then inspect the request:
class TestController extends Controller
{
public function handle(Request $request)
{
$method = $request->segment(1); // Gets first segment of URI
// Do something…
}
}
And in your route service provider:
$router->pattern('{variable}', 'foo|bar|qux|baz');
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 :>