JMS Serializer Annotation Group not working on Entity using Symfony 4 - symfony

I have been using the Group annotation for years on SF2 and SF3.
I'm trying SF4.1. And I'm getting an empty JSON when I send a GET to my endpoint.
The interesting parts of my composer.json:
"friendsofsymfony/rest-bundle": "^2.3",
"jms/serializer-bundle": "^2.3",
"sensio/framework-extra-bundle": "^5.1",
"symfony/serializer-pack": "^1.0"
The config:
framework:
serializer:
enabled: true
enable_annotations: true
sensio_framework_extra:
view: { annotations: true }
fos_rest:
routing_loader:
default_format: json
view:
view_response_listener: 'force'
format_listener:
rules:
- { path: ^/, prefer_extension: true, fallback_format: json, priorities: [ json,xml, html ] }
The Entity
use JMS\Serializer\Annotation\Groups;
class User implements UserInterface, \Serializable
{
private $id;
/**
* #Groups({"api"})
*/
private $username;
And the endpoint API Controller:
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Context\Context;
use FOS\RestBundle\View\View;
class UserController extends FOSRestController {
public function getUserAction(Request $request, EntityManagerInterface $em)
{
$user = $em->getReference('App:User', 1);
$view = View::create();
$context = new Context();
$context->setGroups(['api']);
$view->setContext($context);
$view->setData($user);
return $this->handleView($view);
}
}
If I remove `$context->setGroups(['api']), the JSON has all the User attributes.
Any idea? Thanks!
Debug Info:
bin/console debug:container jms
Select one of the following services to display its information [fos_rest.serializer.jms]:
[0] fos_rest.serializer.jms
0
Information for Service "fos_rest.serializer.jms"
=================================================
Option Value
Service ID fos_rest.serializer.jms
Class FOS\RestBundle\Serializer\JMSSerializerAdapter
Tags -
Public no
Synthetic no
Lazy yes
Shared yes
Abstract no
Autowired no
Autoconfigured no

By default FOSRest prefers the JMSSerializer if it is installed. So first check if the service defined by the JMSSerializerBundle is defined:
./bin/console debug:container jms_serializer.serializer
If this command displays an error message (ServiceNotFound) then the bundle is not correctly installed. Check the config/bundles.php and add the following line if it's missing:
JMS\SerializerBundle\JMSSerializerBundle::class => ['all' => true],
If it actually is installed, you can check the fos_rest configuration, if it maybe changes the serializer service. You can configure it like that:
fos_rest:
service:
serializer: "fos_rest.serializer.jms"

Related

Symfony 4 with translations, no route to / without _locale

I created a Symfony 4.4.8 project with translations to English and French languages, so I followed the steps:
https://symfony.com/doc/current/translation.html
and set:
config/packages/translation.yaml
framework:
default_locale: 'fr'
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks: ['en', 'fr']
config/routes/annotations.yaml
controllers:
resource: ../../src/Controller/
type: annotation
prefix: /{_locale}/
requirements:
_locale: fr|en
and a src/Controller/HomeController.php with
class HomeController extends AbstractController
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
/**
* #Route("/", name="home")
*/
public function index() {
return $this->render('home/index.html.twig', [
'controller_name' => 'HomeController',
]);
}
I have the translation files in translations folder and when I run localhost:8000/fr or localhost:8000/en, it works fine, my home/index.html.twig is shown.
The issue is when I run localhost:8000/, it shows me the default Welcome to Symfony page with "You're seeing this page because you haven't configured any homepage URL."
Is this solution for sf2 will work on sf4?
Is anybody know how to solve it ?
I tested also to change the route in HomeController to:
#Route("/{_locale}", name="home", defaults={"_locale"="fr"}
but it returns exception:
Route pattern "/{_locale}/{_locale}" cannot reference variable name "_locale" more than once.
workaround, added:
RedirectMatch ^/$ /fr
in my virtualhost (with apache2 server)
pure symfony solution shoud be better !
I would guess a bad routing configuration. I couldn't recall a default behaviour where we can set the locale by adding the locale code in the URL so I would guess it is something your added ;)
Symfony resolves the route in the same order as you declare the route. So one solution would be to import 2 times the same route but with the prefix:
# config/route.yaml
app_front:
type: annotation
resource: '../src/Controller/*.php'
prefix: /
options:
expose: true
app_localized_front:
type: annotation
resource: '../src/Controller/*.php'
name_prefix: localized_
prefix: /{locale}
options:
expose: true
I know the previous config works, but I don't know if this is the more elegant way to do it :)

Symfony annotation routing order

I'm currently stuck with routing in my Symfony4 (4.3) project. My problem is pretty simple, I want to use route annotations in my controllers but I want to defined the order of these one.
For example, if I have two controllers with following routing :
class BarController extends AbstractController
{
/**
* #Route("/test/{data}", name="app_bar")
*/
public function index($data)
{
// ...
return $this->render('index.html.twig', [
'data' => $data,
]);
}
}
and
class FooController extends AbstractController
{
/**
* #Route("/test/my_value", name="app_foo")
*/
public function index()
{
// ...
return $this->render('index.html.twig', [
'data' => 'my_value',
]);
}
}
In config/routes/annotations.yaml I define my route like this
app_controllers:
resource: ../../src/Controller/
type: annotation
Then if I call /test/my_value I would like to be redirected to FooController since his index action define #Route("/test/my_value", name="app_foo") but like routes is loaded alphabetically the index action from BarController with app_bar route is called first.
So I have tried to defined following routing :
app_foo_controller:
resource: ../../src/Controller/FooController.php
type: annotation
app_controllers:
resource: ../../src/Controller/
type: annotation
But this didn't work, BarController and his app_bar route still called before app_foo route from FooController.
Also, I don't understand the purpose of config/routes/annotations.yaml vs config/routes.yaml since both could contains any type of routes... I miss something ?
Nevermind I found the solution. I just miss the fact that I override my specifically app_foo_controller routing when I define app_controllers the solution is to defined each controllers like that :
app_controllers:
resource: ../../src/Controller/
type: annotation
app_foo_controller:
resource: ../../src/Controller/FooController.php
type: annotation
app_bar_controller:
resource: ../../src/Controller/BarController.php
type: annotation

How to hide Api-plaform Docs from Nelmio Docs

I hope someone could help me to use Api-platorm with Nelmio.
I use Api-plaform and Nelmio. I need to hide the Api-platform docs from Nelmio.
I need to have 3 routes:
/internal -> API-Platform Docs
/external -> NELMIO-Docs
/admin -> NELMIO-Docs
My config of Nelmio:
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
documentation:
info:
title: ...
description: ...
version: 0.2.0
areas: # to filter documented areas
default:
path_patterns: [ ^/external ]
external:
path_patterns: [ ^/external ]
admin:
path_patterns: [ ^/admin ]
My config of Nelmio (routes):
# config/routes/nelmio_api_doc.yaml
app.swagger:
path: /{area}/json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger, area: default }
app.swagger_ui:
path: /{area}
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui, area: default }
My config of API-Platform:
# config/routes/api_platform.yaml
api_platform:
resource: .
type: api_platform
prefix: /internal/
But if I go to http://localhost/external or http://localhost/admin I see always not only needed routes, but also the routes from API-Platform:
I know this question is old by now, but I am facing the same situation and I found a workaround that may help some one, so I am posting it.
API Platform lets you decorate Swagger so you can customize the final documentation output. I took advantage of this to get rid of the entire api platform documentation when not asking for it.
<?php
namespace App\Swagger;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class SwaggerDecorator implements NormalizerInterface
{
private $decorated;
private $requestStack;
public function __construct(NormalizerInterface $decorated, RequestStack $requestStack)
{
$this->decorated = $decorated;
$this->requestStack = $requestStack;
}
public function normalize($object, $format = null, array $context = [])
{
if ('/internal/docs' !== $this->requestStack->getCurrentRequest()->getPathInfo()) {
// request is not for internal docs (maybe it is for external or admin one) so get rid of api platform docs
return null;
}
$docs = $this->decorated->normalize($object, $format, $context);
// here you can customize documentation
return $docs;
}
public function supportsNormalization($data, $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
}
I hope this helps someone, happy coding!
UPDATE
In order to enable that decorator, you must declare it as so in your services file:
App\Swagger\SwaggerDecorator:
decorates: 'api_platform.swagger.normalizer.api_gateway'
arguments: [ '#App\Swagger\SwaggerDecorator.inner' ]
autoconfigure: false
Then, in the class itself, replace '/internal/docs' with the actual URL you are using for your API Platform documentation.
Hope this helps.
On your nelmio configuration yaml file, use a regex to exclude the docs. For instance, for excluding the /external/doc you should:
external:
path_patterns: [ ^/external(?!/doc$) ]

Symfony 3 - FOSRestBundle - Inject Dependency into Service

This must be a simple oversight - why isn't my entity_menus parameter being injected into the StoreController?
This is the error I am seeing:
Catchable Fatal Error: Argument 1 passed to AppBundle\Controller\Api\Admin\Common\StoreController::__construct() must be of the type array, none given
The relevant parts of services.yml
parameters:
entity_menus: [ 'manufacturers', 'vendors' ]
services:
app.admin.store_controller:
class: AppBundle\Controller\Api\Admin\Common\StoreController
arguments:
- '%entity_menus%'
The controller:
class StoreController extends FOSRestController
{
private $entityMenus;
public function __construct( Array $entityMenus )
{
$this->entityMenus = $entityMenus;
}
I updated services.yml like so to set the service container:
app.admin.store_controller:
class: AppBundle\Controller\Api\Admin\Common\StoreController
arguments: ['%entity_menus%']
calls:
- [setContainer, ["#service_container"]]
Thanks to: https://stackoverflow.com/a/19283476/2182349
I updated routing_rest.yml to use the service name and the class:
app_admin_common_store_menu:
type: rest
class: AppBundle\Controller\Api\Admin\Common\StoreController
resource: app.admin.store_controller
name_prefix: app_admin_api_store_
prefix: /api/store
Thanks to: https://github.com/FriendsOfSymfony/FOSRestBundle/issues/990

How to configure http_basic firewall in Symfony to return JSON in response body?

By default, when you configure a http_basic firewall in Symfony, the firewall will return "401 Unauthorized" and an empty body for requests that fail.
I'd like to have it return a custom JSON (eg: {success: false, error: 401}). Is this possible?
Here's my configuration:
security:
firewalls:
api:
http_basic:
provider: myprovider
You need to use a custom AuthenticationEntryPoint. Create a class implementing the AuthenticationEntryPointInterface:
<?php
namespace AppBundle;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
class CustomBasicAuthenticationEntryPoint implements AuthenticationEntryPointInterface {
private $realmName;
public function __construct($realmName) {
$this->realmName = $realmName;
}
public function start(Request $request, AuthenticationException $authException = null) {
$content = array('success' => false, 'error' => 401);
$response = new Response();
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName));
$response->headers->set('Content-Type', 'application/json');
$response->setContent(json_encode($content))
->setStatusCode(401);
return $response;
}
}
The class needs to be accessible as a service so add it to services.yml. Pass the realm as an argument.
custom_basic_authentication_entry_point:
class: AppBundle\CustomBasicAuthenticationEntryPoint
arguments: [ main ]
You can then use it in security.yml:
firewalls:
main:
anonymous: ~
http_basic: ~
entry_point: custom_basic_authentication_entry_point

Resources