FOSRestBundle: How to remove {_format} parameter? - symfony

I need to support only single API format which is JSON and I don't like to {_format} in my routes. Is it possible to remove it?

In your config.yml, make sure you have this configured:
fos_rest:
format_listener: true
routing_loader:
default_format: json
include_format: false
Hope that helps
EDIT:
There is an example in the FOSRestBundle Docs that shows how to use the ClassResourceInterface. The biggest difference is that you don't have to manually define your routes at all. The interface will generate your routes based on you class name and the method name. So it is very important what you name your methods (you can override how the class name is used, this is shown in the docs)
for example, something like this:
use FOS\RestBundle\Routing\ClassResourceInterface {
class UserController implements ClassResourceInterface {
public function cgetAction() {
//return a list of all users
}
}
would generate a route that looks like this: [GET] /users. This is how I use the bundle, and it works great. I also don't have to use the {_format} option anywhere because I don't have to define the routes manually anywhere.
note - see my original answer as well, I made an edit that might also help with how you are using the bundle. I haven't tried using the bundle the way you are, so I'm not sure if this will work or not, but the docs make it seem like it will work.

Related

How to set a custom ErrorRenderer in symfony 5?

Problem:
I tried to register a CustomErrorRenderer on Symfony 5 to render my CustomException, but all the time the TwigErrorRenderer is called. I thought probably self-defined renderers might be preferred?
CustomErrorRenderer.php:
namespace App\ErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
class CustomErrorRenderer implements ErrorRendererInterface
{
public function render(\Throwable $exception): FlattenException
{
dd('CustomErrorRenderer.render() called');
// TODO check if CustomException, else refer to preset renderer...
}
}
services.yaml:
services:
App\ErrorRenderer\CustomErrorRenderer:
tags: ['error_renderer.renderer']
# also tried: ['error_renderer.renderer', 'error_renderer.html','error_handler.error_renderer' , 'error_handler.error_renderer.html']
What is wrong with that?
Background:
I have a web application and want to handle exceptions that are related to my internal business logic separately. E.g., a user wants to book a resource but it is currently not available. While current errors/exceptions are handled by the TwigErrorRenderer (or default HtmlErrorRenderer), I would like to add my own Renderer (ideally just extending the TwigErrorRenderer, so that I can use some specific, self-defined twig templates). By that, I aim to have a better UI, e.g., my custom exceptions being rendered while still the menu of the web application is shown. As my exceptions are not related to how the data is accessed (e.g. http), I do not want to use HttpExceptions and their status code.
This was not particularly easy to figure out.
I made a copy of vendor/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php into my app src\CustomerErrorRenderer.php.
Then override the error_renderer in services.yaml:
services:
error_renderer:
class: App\CustomErrorRenderer
arguments: ['#twig', '#error_handler.error_renderer.html','%kernel.debug%']
Of course, that's not exactly how they call in their code when I tried to test using /_error/404 for instance.
They actually use arguments more like:
error_renderer:
class: App\Twig\CustomErrorRenderer
arguments:
- '#twig'
- '#error_handler.error_renderer.html'
- !service
factory: [ 'Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer', 'getAndCleanOutputBuffer' ]
arguments: ['#request_stack']

How to get rid of route-name conflicts when ensuring api works both with and without the api-version?

I want to apply asp.net api-versioning to my web app (which didn't have versioning). However, the tricky issue is that I must ensure that APIs should work both with and without the api-version.
[ApiVersion("1.0")]
[Route("api/products/{productId}/[controller]")]
[Route("api/v{version:apiVersion}/products/{productId}/[controller]")]
[ValidateModel]
[Produces("application/json")]
public partial class ProductController : ControllerBase {
internal const string GetLatestRoute = "GET Product/GetLatestAsync";
[HttpGet(Name = GetLatestRoute)]
public async Task<IActionResult> GetLatestAsync() {
}
}
I have a controller with multiple actions, each of them is defined with a unique route name. When I add two routes (with and without versions) to the controller, there comes a route-name conflict error:
Attribute routes with the same name 'GET Products/GetLatestAsync' must have the same template:
Action: 'Service.Controllers.ProductController.GetLatestAsync (ProductFD)' - Template: 'api/products/{productId}/Product'
Action: 'Service.Controllers.ProductController.GetLatestAsync (ProductFD)' - Template: 'api/v{version:apiVersion}/products/{productId}/Product'
There are several answers on StackOverflow that say the issue can be solved by removing the route names defined for the action methods. However, in my scenario, the route names are used to create Url Links in several places in the project.
Is there an approach that I can get rid of the issue? I'm wondering whether I could append version to the route name variable or mapping the non-version api to the version/1.0 ...? On the other hand, there is a rare case that I update all the methods in a controller. So is it possible that I only define a route-prefix on the top-level of the controller and only apply the api-version on the method-level?
Route names and the route table are not API version aware. In order for this to work, you need to use double route registration like you have because you are versioning by URL segment (not recommended). If clients are properly following the links returned by the server, then always using the route generated with the explicit version in it will do. If the client doesn't honor that and just calls the APIs directly without the API version, the second template will handle that for you. If you are only generating links with the same controller, then I would suggest using CreatedAtAction instead because it will not rely on the route name. If memory serves me correct, you can specify the order of each [Route] for precedence. If unspecified, it will be the first attribute specified - which matters.
You'll also need to enable:
services.AddApiVersioning(options => options.AssumeDefaultVersionWhenUnspecified = true);
If you haven't already.
Last, but not least, beware the known, breaking change: Async suffix trimmed from controller action names. This has snared many people.

Getting Symfony base URL from a service?

I have a service that needs to access the current application base URL (what's returned by app.request.getBaseURL() in Twig views). Currently, my config is like this:
services:
WidgetModel:
class: AppBundle\Model\WidgetModel
scope: prototype
arguments: ['%widgets%']
So as a second argument, I would like to inject the base URL. Is it possible? If not, what would be proper solution?
As far as I know there is no builtin base url service. Which is actually a bit of a red flag that maybe having your component depending on it might not be such a good idea. But I can't think of a good reason why.
So normally, one would just inject the request object. But that has it's own problems as documented here: http://symfony.com/blog/new-in-symfony-2-4-the-request-stack
Instead, inject the #request_stack service and pull the url from it:
class WidgetModel
{
public __construct($widgets,$requestStack)
{
$this->baseUrl = $requestStack->getCurrentRequest()->getBaseUrl();
If you do find yourself needing the baseUrl in multiple services then you could define your own factory type service to generate it. But again, that might mean your design needs rethinking.
You can use the expression language in your service definition.
This example should do what you want:
services:
WidgetModel:
class: AppBundle\Model\WidgetModel
scope: prototype
arguments: [%widgets%, "#=service('request').getBaseUrl()"]
It fetches the request service and then executes the getBaseUrl method.
You will need to add a second parameter in your WigetModel for the base URL.
To complete the answer, in Symfony 3 you can use request_stack to get the base url using expressions languages (updated link) such as:
services:
WidgetModel:
class: AppBundle\Model\WidgetModel
scope: prototype
arguments: [%widgets%,"#=service('request_stack').getCurrentRequest().getBaseUrl()"

add custom logic to internal Symfony classes like SwitchUserListener or TemplateGuesser

I got a problem to add custom logic to some Symfony classes.
SwitchUserListener
I want to add a check, that a user cannot switch to a another user, which have more rights/roles, than the initial user.
First attempt
Overwrite the parameter in the security_listeners.xml with the key:
security.authentication.switchuser_listener.class But where can I overwrite it?
In the security.yml it didn't work:
security:
...
authentication:
switchuser_listener:
class: Symfony\Component\Security\Http\Firewall\SwitchUserListener
Second attempt
Overwrite the service for the SwitchUserListner service id: security.authentication.switchuser_listener
I create the same service in my service.xml of my bundle, but my class was not used / called.
Another idea was to overwrite only the class, but that only works for bundles, but the SwitchUserListener was not in the SecurityBundle, it was in the symfony component directory and that seemed to me as a really bad idea to overwrite the SecurityBundle
Third attempt
Now I get the solution: First time I didn't realize that the dispatcher call listener for the SWTICH_USER event in the SwitchUserListener:
$switchEvent = new SwitchUserEvent($request, $token->getUser());
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent);
So I need only to create a service with the special tag for this event type:
<tag name="kernel.event_listener" event="security.switch_user" method="onSecuritySwitchUser" />
And do the check in the given method.
This seems to be a better solution thatn the other two. But there is still a problem. In my listener for the SwitchUserEvent I need to ignore my custom check if the user wants to exit the switched user.
So I need to check the requested path: ignore if path containts '?switch_user=_exit'
But the path (URL parameter) can be changed:
# app/config/security.yml
security:
firewalls:
main:
# ...
switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
But in my bundle I can't read this parameter, because it will not be passed to the service container. It will be passed to the constructor of the SwitchUserListner class and will be saved there as private attribute, never accessable (without Reflection) from outside. (that happens here: SecurityExtension.php line 591) So what to do? Define the parameter twice go against DRY. Use Reflection?
And the other point is that there aren' every time events that will be fired on which I write a subscriber class. So what would be another / best solution for it?
I ask this question because I will get some similar problem where I want to add or overwrite something of the symfony intern components.
TemplateGuesser
I wanted to modify the TemplateGuesser: For a specific bundle all Templates which has the annotation #Tempalte the tempate file should be located with the controller TestController#showAction at this path:
Resources/views/customDir/Test/show.html.twig
So the guesser should be put and locate everything into a additional folder customDir instead of using only views. When using the render function with a specific template, the guesser should ignore the annotation.
I created my own Guesser and overwrite the service id: sensio_framework_extra.view.guesser and in comparision to the SwitchUserListener this time my class is really called instead of the original guesser. Why it works here but not with the SwitchUserListener?
Is this a good solution at all? I also tried to add a second listener, which calls the TemplateGuesser, its the service sensio_framework_extra.view.listener with the class Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener But that didn't work.
Whenever you need to add custom logic or extend the framework behaviour, you can use and abuse the container configuration. That means you can overwrite pretty much every service Symfony defines by just creating a new class that extends that service – or not, really – and creating the service definition for it with the same key as the original service you wanted to extend or change behaviour.
For instance, Symfony has a base template guesser registered as a service with the sensio_framework_extra.view.guesser id. If you want to extend that or change behaviour, you only need to create your own class and register it with the same id of the original service – remember that the bundles loading order affects the service definitons with the same id, where the last one loaded is the one that will be created.
That should solve both of your problems.

How to pass arguments to controller from route in Symfony2

I'm working on a Symfony2 project and am trying to figure out how to pass parameters from the route configuration to the controller. I know I can configure default values in the route configuration, and retrieve the values in the controller using the appropriate var name in the function declaration, but that isn't exactly what I want.
My use case is the following. I have a standard method in my controller that I want to access from 2 or 3 different routes. Depending on which route is being called, I want to "configure" the method differently. I can accomplish this in a few ways:
In my controller, check the route name using `$this->container->get("request")->get("_route"), but that is ugly, and then I am hardcoded to the route name. Moves configuration to the controller, which should just be logic - not configuration.
Create a base controller class, and subclass each method for my different routes. Each subclassed method would then have the necessary configuration within the method. Cleaner soln than #1, but still "heavy" in the sense of having multiple classes for a simple need and still pushes configuration data into the business logic.
Put configuration data into the route configuration. In the controller, access the configuration data as required. Ideal solution, but don't know how.
I can use the route default array to specify my arguments, but then must make sure to use a regex to ensure that the params are not overridden at the URL level (security risk). This is functional, but still kinda cludgy and not a pretty hack.
I presume that there must a better way to do this, but I can't seem to figure it out. Is there a way to access the routing object from the controller, and access the different configuration parameters?
You can pull the actual route from the router service. Something like:
$routeName = $this->container->get("request")->get("_route");
$router = $this->container->get("router");
$route = $router->getRouteCollection()->get($routeName);
Not sure if this would be such a great design though. Consider passing a $configName to your controller method, adding a parameter with the same name in a config file then using getParameter to access it. That would eliminate the route stuff from the equation.
Something like:
zayso_arbiter_import:
pattern: /import
defaults: { _controller: ZaysoArbiterBundle:Import:index, configName: 'someConfigName' }
public function importAction(Request $request, $configName)

Resources