Symfony 3 - FOSRestBundle - Inject Dependency into Service - symfony

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

Related

How to log response time for all Guzzle requests

I'm using Guzzle as a http client in a Symfony app. With the following code I'm able to log all requests and responses. Is there a way to log also response time?
I tried on_stats but it should be added to each client call and it is not an option.
services:
app.client.default:
class: 'GuzzleHttp\Client'
lazy: true
arguments:
- handler: '#app.handler_stack.default'
app.handler_stack.default:
class: 'GuzzleHttp\HandlerStack'
factory: [ GuzzleHttp\HandlerStack, create ]
calls:
- [ push, [ '#app.middleware.log_default', 'log_default' ] ]
app.middleware.log_default:
class: callback
factory: [ GuzzleHttp\Middleware, log ]
arguments:
- '#logger'
- '#app.log_formatter.default'
app.log_formatter.default:
class: 'GuzzleHttp\MessageFormatter'
arguments:
- ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"
app.service:
class: 'App\Service'
arguments:
- '#app.client.default'
use GuzzleHttp\Client;
class Service {
private $httpClient;
public function __construct(Client $httpClient) {
$this->httpClient = $httpClient;
}
public function apiRequest() {
$response = $this->httpClient->get('https://www.google.com');
// ...
}
}

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$) ]

JMS Serializer Annotation Group not working on Entity using Symfony 4

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"

Inject parameter into Service Symfony 3.3

I have a service which injects string values from my parameters.yml.
I declared the service like so:
parameters:
url: '%cms_url%'
client_id: '%cms_client_id%'
client_secret: '%cms_client_secret%'
services:
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository}'
api:
class: AppBundle\Service\Api
arguments: ['#session', '%cms_url%']
autowire: false
access_token_generator:
class: AppBundle\Security\AccessTokenGenerator
arguments: [cms_url, cms_client_id, cms_client_secret]
autowire: false
When I ran the app it AccessTokenGenerator throws an error saying:
Cannot autowire service argument $apiUrl must have typehint or given
value directly.
This is my AccessTokenGenrator class:
class AccessTokenGenerator
{
private $apiUrl;
private $clientId;
private $clientSecret;
public function __construct(string $apiUrl, string $clientId, string $clientSecret)
{
$this->apiUrl = $apiUrl;
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
}
public function generateAccessToken(string $username, string $password) : ?AccessToken
{
$queryParams = [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'grant_type' => 'password',
'username' => $username,
'password' => $password
];
$requestUrl = $this->apiUrl . '/oauth/v2/token?' . http_build_query($queryParams);
$client = new Client();
$request = new Request('GET', $requestUrl);
$response = $client->send($request);
$contents = json_decode($response->getBody()->getContents(), true);
if (isset($contents['access_token'])) {
$accessToken = new AccessToken();
$accessToken->setAccessToken($contents['access_token']);
$accessToken->setExpiresIn($contents['expires_in']);
$accessToken->setTokenType($contents['token_type']);
$accessToken->setScope($contents['scope']);
$accessToken->setRefreshToken($contents['refresh_token']);
return $accessToken;
}
}
}
I dont know why it proceeds to autowiring even my configuration is set to false.
Thanks!
#Arno is right you can autowire a scalar if you specify exaclty the name of paramaters.
https://symfony.com/doc/current/service_container.html#service-parameters
sorry i haven't see it part before.
The AppBundle\Security\AccessTokenGenerator service is automatically loaded with autowire: true by this declaration :
AppBundle\:
resource: '../../src/AppBundle/*'
You can override any service that's imported by using its id (class name) below
AppBundle\Security\AccessTokenGenerator:
arguments: ['%cms_url%', '%cms_client_id%', '%cms_client_secret%']
Warning, you have forgottent the percentage '%' for arguments.
If you need a access_token_generator service, you can make an alias :
access_token_generator: '#AppBundle\Security\AccessTokenGenerator'
Final file could be :
services:
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository}'
api: '#AppBundle\Service\Api'
AppBundle\Service\Api:
arguments: {$apiUrl: '%cms_url%'}
access_token_generator: '#AppBundle\Security\AccessTokenGenerator'
AppBundle\Security\AccessTokenGenerator:
arguments: ['%cms_url%', '%cms_client_id%', '%cms_client_secret%']
Documentation : manually wiring arguments
Documentation : explicitly configuring services and arguments
You can not autowire scalar parameter just object
see documentation
https://symfony.com/doc/current/service_container/autowiring.html#fixing-non-autowireable-arguments

Resources