Inject parameter into Service Symfony 3.3 - symfony

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

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');
// ...
}
}

I can't write logs to a file using a specific channel with Monolog in Symfony 3.4

I try to write logs to a specific file using a specific channel in Monolog (called encuestas_cloud) inside a Command in Symfony 3.4 but I'm not able to do it.
I've read Symfony docs and searched in the web and I think it's well configured but i get an error.
The code is:
In config_dev.yml:
monolog:
handlers:
main:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.log'
level: debug
channels: ['!event']
...
encuestas_cloud_logger:
#type: rotating_file
type: stream
path: 'D:/web/xampp/htdocs/temp/logs/encuestas_cloud.log'
level: info
channels: ['encuestas_cloud']
max_files: 10
In services.yml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\Command\EncuestasCloudCommand\:
resource: '../../src/AppBundle/Command/EncuestasCloudCommand.php'
arguments: ['#logger']
public: true
tags:
- { name: monolog.logger, channel: encuestas_cloud }
The command:
// src/AppBundle/Command/EncuestasCloudCommand.php
namespace AppBundle\Command;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
...
class EncuestasCloudCommand extends ContainerAwareCommand
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
parent::__construct();
}
...
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->logger;
$logger->addInfo('My logger is now ready');
When I execute it I get:
In LoggerChannelPass.php line 98:
Monolog configuration error: The logging channel "encuestas_cloud" assigned to the
"encuestas_cloud_logger" handler does not exist.
In ContainerBuilder.php line 1063:
You have requested a non-existent service "monolog.logger.encuestas_cloud".
If I add channels: ['encuestas_cloud'] in config_dev.yml
monolog:
channels: ['encuestas_cloud']
handlers:
main:
type: stream
...
The error dissappear but the log still goes to the general log file: dev.log
Please, could somebody help me to find out what is wrong with the configuration?
Thanks a lot!!!
Does changing the argument for your Command from #logger to #monolog.logger.encuestas_cloud work? That should inject the specific configured logger and therefor your logging would appear in the correct logger.
monolog:
channels: ['encuestas_cloud']
Should be defined AFAIK, and explicitly excluding the channel for your main logger, to not appear there:
monolog:
handlers:
main:
...
channels: [ '!encuestas_cloud' ]
Thanks a lot evertjes for your answer, it didn't solve the problem but helped me to investigate other paths...
The problem was that the command wasn't defined as a service in the right way, so Symfony was unable to match the channel to the service (and to the command).
I executed :
php bin/console debug:container
and the service of the command didn't appear.
So after investigating how to define a command as a service the configuration worked fine... uffff.
Here I post the final code that works.
In config_dev.yml:
monolog:
channels: ['encuestas_cloud']
handlers:
main:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.log'
level: debug
channels: ['!event','!encuestas_cloud']
...
encuestas_cloud_logger:
type: rotating_file
path: 'D:/web/xampp/htdocs/temp/logs/encuestas_cloud.log'
level: info
channels: ['encuestas_cloud']
max_files: 10
In services.yml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
console.command.encuestas_cloud_command:
class: 'AppBundle\Command\EncuestasCloudCommand'
arguments: ['#logger']
public: true
tags:
- { name: monolog.logger, channel: encuestas_cloud }
The command:
// src/AppBundle/Command/EncuestasCloudCommand.php
namespace AppBundle\Command;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
...
class EncuestasCloudCommand extends ContainerAwareCommand
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
parent::__construct();
}
...
protected function execute(InputInterface $input, OutputInterface $output)
{
$logger = $this->logger;
$logger->addInfo('My logger is now ready');
Now the file is created and the log appears with the configured channel.
Thanks a lot to everybody!!!

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