It's the first time I try to autowire a service in symfony4, as symfony4 is new i'm never sure if the anwser I find online is working or is outdated..
In my services.yaml:
services:
[...]
smugmug_controller:
class: App\Controller\SmugmugController
arguments:
- '#smugmug_service'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
smugmug_service:
class: App\Services\SmugmugService
arguments:
$consumerKey: "%smugmug.consumer_key%"
$consumerSecret: "%smugmug.consumer_secret%"
$oauthToken: "%smugmug.oauth_token%"
$oauthTokenSecret: "%smugmug.oauth_token_secret%"
$allowedRootId: "%smugmug.allowed_root_id%"
In my Smugmug Service:
class SmugmugService
{
private $consumerKey;
private $consumerSecret;
private $oauthToken;
private $oauthTokenSecret;
private $allowedRootId;
private $galleryNameFromDropbox = "dropbox";
/**
* Constructor.
*
* #param string $consumerKey
* #param string $consumerSecret
* #param string $oauthToken
* #param string $oauthTokenSecret
* #param string $allowedRootId
*/
public function __construct(String $consumerKey, String $consumerSecret, String $oauthToken, String $oauthTokenSecret, String $allowedRootId) {
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
$this->oauthToken = $oauthToken;
$this->oauthTokenSecret = $oauthTokenSecret;
$this->allowedRootId = $allowedRootId;
}
In my Controller:
class SmugmugController extends Controller {
private $smugmugService;
public function __construct(SmugmugService $smugmugService) {
$this->smugmugService = $smugmugService;
}
And when I try to call a route from my controller, I have this error:
Cannot autowire service "App\Services\SmugmugService": argument
"$consumerKey" of method "__construct()" is type-hinted "string", you
should configure its value explicitly.
I know that I call a controller with an injected service, who himself have injected parameters (is it the problem ?). Any help ?
#Cerad answer:
You want to stop using service ids like smugmug_controller. Use the fully qualified class name instead. In fact if you replace the id with the class name then you can remove the class attribute. And anytime you look at an example, always make sure it is for S4 with autowire. It's all in the docs.
Related
I am looking how to add a dynamic variable into an API Platform #ApiProperty annotation.
I found that Symfony allows that but it does not seem to work in API Platform annotations.
For example :
/**
* Redirection URL.
*
* #Groups({"authorization_code_login_write", "authorization_code_logout_write"})
* #ApiProperty(
* attributes={
* "openapi_context"={
* "type"="string",
* "example"="%app.auth.default.redirect%"
* }
* }
* )
*/
protected ?string $redirectionUrl = null;
%app.auth.default.redirect% is not replaced by the container parameter with the same name.
How should I do ?
At first sight, I see here only the one way - to create your own attribute in openapi_context, let's say my_redirect_example.
Smth like this, in example:
"openapi_context"={
"type"="string",
"my_redirect_example"=true
}
Then you need to decorate like in documentation
Smth, like that:
public function normalize($object, $format = null, array $context = [])
{
$docs = $this->decorated->normalize($object, $format, $context);
$redirectUrl = .... # your own logic to get this dynamical value
foreach ($docs['paths'] as $pathName => $path) {
foreach ($path as $operationName => $operation) {
if ($operation['my_redirect_example'] ?? false) {
$docs['paths'][$pathName][$operationName]['example'] = $redirectUrl;
}
}
}
return $docs;
}
It should work. Anyway - it is just an example (I didn't test it), just to understanding how you can handle it.
Sure, you can replace true value with your own and use it inside the if statement to get it depending on some yours own logic.
The way to go is to follow the documentation to decorate the Swagger Open API generator service (https://api-platform.com/docs/core/swagger/#overriding-the-openapi-specification).
Add your own service :
# api/config/services.yaml
services:
'App\Swagger\SwaggerDecorator':
decorates: 'api_platform.swagger.normalizer.api_gateway'
arguments: [ '#App\Swagger\SwaggerDecorator.inner' ]
autoconfigure: false
Then create you service class :
<?php
namespace App\Swagger;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Custom Swagger decorator to remove/edit some API documentation information.
*/
final class SwaggerDecorator implements NormalizerInterface
{
/**
* Decorated base Swagger normalizer.
*
* #var NormalizerInterface
*/
protected NormalizerInterface $decorated;
/**
* SwaggerDecorator constructor.
*
* #param NormalizerInterface $decorated
*/
public function __construct(NormalizerInterface $decorated)
{
$this->decorated = $decorated;
}
/**
* {#inheritDoc}
*/
public function normalize($object, string $format = null, array $context = [])
{
$docs = $this->decorated->normalize($object, $format, $context);
$docs['components']['schemas']['authorization-authorization_code_login_write']['properties']['redirectionUrl']['example'] = 'https://example.com/my-dynamic-redirection';
$docs['components']['schemas']['authorization:jsonld-authorization_code_login_write']['properties']['redirectionUrl']['example'] = 'https://example.com/my-dynamic-redirection';
return $docs;
}
/**
* {#inheritDoc}
*/
public function supportsNormalization($data, string $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
}
You'll just have to find which keys to use, browsing the schemas can help on your Swagger UI. In my example, authorization is the short name of my API resource entity and authorization_code_login_write is the denormalization context value of the operation.
And here you go :
Of course, the ideal solution will iterate over all schemas and replace found configuration parameters with their real values. Maybe this feature could be done in API Platform itself (Follow issue : https://github.com/api-platform/api-platform/issues/1711)
Given a class Publisher like this:
<?php
namespace App\Util\Publisher;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Messenger\MessageBusInterface;
class Publisher
{
protected $topic = null;
protected $bus;
/**
* Publisher constructor.
* #param MessageBusInterface $bus
*/
public function __construct(MessageBusInterface $bus)
{
$this->topic = getenv('TOPIC_MAIN_URL');
$this->bus = $bus;
}
...
}
I would like to autowire it in my controllers like this:
/**
* #Route("/_exp/_exp", name="exp")
*/
public function expAction(Publisher $publisher)
{
...
}
and I added this to my services.yaml:
Symfony\Component\Messenger\MessageBusInterface: ~
App\Util\Publisher\Publisher:
autowire: true
arguments: ['#Symfony\Component\Messenger\MessageBusInterface']
But I get an error:
Cannot instantiate interface Symfony\Component\Messenger\MessageBusInterface
Is that related to the MessageBusInterface or am I doing something wrong with the autowiring. I followed The Symfony docs for autowiring and they seem to be the same?
Thank you!
I believe MessageBusInterface service is already declared by Symfony Messenger component.
Try to remove Symfony\Component\Messenger\MessageBusInterface: ~ from your services.yaml, otherwise you are overriding the default definition.
A note for clarification: MessageBusInterface service does not really exists, it in an alias over the "default bus" service. You can declare other buses, cf documentation
I have created a service in app/services/KDataService.php that looks like this:
class KDataService
{
/** #var string */
private $license;
/** #var string */
private $owner;
/** #var string */
private $accessToken;
public function __construct($owner, $license)
{
$this->owner = $owner;
$this->license = $license;
...
}
...
}
In one of my controller I try to inject this service with the dependency injection pattern but I get the following error:
Unresolvable dependency resolving [Parameter #0 [ $owner ]] in class App\Services\KDataService
My controller:
use App\Services\KDataService;
class DamagePointController extends Controller
{
/** #var KDataService $kDataService */
private $kDataService;
/**
* Instantiate a new controller instance.
*
* #param KDataService $kDataService
*/
public function __construct(KDataService $kDataService)
{
$this->kDataService = $kDataService;
}
...
}
Anyone knows how I can pass my $owner and $license?
The problem is that your service has arguments but you don't specify them. There are several ways to do this.
Using service provider:
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
class kDataServiceServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind(KDataService::class, function ($app) {
return new KDataService(getOwner(), getLicense());
});
}
}
bind could be change to other methods. See Service Container docs.
Using app to make instanse:
/* Controller __construct */
$this->kDataService = \App::make(KDataService::class, [getOwner(), getLicense()]);
Simply create class instance
/* Controller __construct */
$this->kDataService = new KDataService(getOwner(), getLicense());
Note: getOwner and getLicense change to your logic. Usually you can retrieve it within controller or from $app.
Generally what you need to resolve the issue is to read about service container and service providers in docs.
I'm migrating my app from Symfony 2.8 to Symfony 3.3.
From inside a controller of mine I have this:
public function indexAction()
{
$email = new Email();
$form = $this->createForm(GetStartedType::class, $email, [
'action' => $this->generateUrl('get_started_end'),
'method' => 'POST',
]);
return [
'form' => $form->createView(),
];
}
But I receive this exception:
Call to a member function get() on null
My controller extends Symfony\Bundle\FrameworkBundle\Controller\Controller:
/**
* {#inheritdoc}
*/
class DefaultController extends Controller
{
...
}
So I have access to the container.
Putting some dumps around in the Symfony's code, I see that the container is correctly set:
namespace Symfony\Component\DependencyInjection;
/**
* ContainerAware trait.
*
* #author Fabien Potencier <fabien#symfony.com>
*/
trait ContainerAwareTrait
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* Sets the container.
*
* #param ContainerInterface|null $container A ContainerInterface instance or null
*/
public function setContainer(ContainerInterface $container = null)
{
dump('Here in the ContainerAwareTrait');
dump(null === $container);
$this->container = $container;
}
}
This dumps
Here in the ContainerAwareTrait
false
So the autowiring works well and sets the container.
But in the ControllerTrait I have this:
trait ControllerTrait
{
/**
* Generates a URL from the given parameters.
*
* #param string $route The name of the route
* #param mixed $parameters An array of parameters
* #param int $referenceType The type of reference (one of the constants in UrlGeneratorInterface)
*
* #return string The generated URL
*
* #see UrlGeneratorInterface
*/
protected function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
dump('Here in the ControllerTrait');
die(dump(null === $this->container));
return $this->container->get('router')->generate($route, $parameters, $referenceType);
}
...
this is the dump:
Here in the ControllerTrait
true
So here the container is null and this causes the error.
Anyone can help me solve this issue?
Why is the container null?
If may help, this is the services.yml configuration (the default that cames with Symfony):
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
This question is posted as issue on the Symfony's issue tracker.
The S3.3 autowire capability makes it a bit easier to define controllers as services.
The usual motivation behind defining controllers as services is to avoid injecting the container. In other words you should be explicitly injecting each service a controller uses. The autowire capability allows you to use action method injection so you don't have to inject a bunch of stuff in the constructor.
However, the base Symfony controller class provides a number of helper function which use about 12 different services. It would be painful indeed to inject these one at a time. I had sort of thought that the autowire capability might take care of this for you but I guess not.
So you basically need to add a call to setContainer in your service definition. Something like:
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
[[setContainer, ['#service_container']]]
tags: ['controller.service_arguments']
The autowire capability is very much a work in progress so I would not be surprised if this changes for 3.4/4.0.
This problem is fixed by PR #23239 and is relased in Symfony 3.3.3.
I'm trying to inject my repository service into EventListener but that leads me to following exception, which, with my basic knowledge of Symfony2, I have no idea how to resolve. Exception is:
ServiceCircularReferenceException in bootstrap.php.cache line 2129:
Circular reference detected for service "doctrine.orm.default_entity_manager", path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> person.connect.listener -> tag.repository.service".
And here is how I've declared repository and listener:
tag.repository.service:
class: Application\Bundle\PersonBundle\Entity\TagRepository
factory: ["#doctrine", getRepository]
arguments: [ Application\Bundle\PersonBundle\Entity\Tag ]
person.connect.listener:
class: Application\Bundle\PersonBundle\EventListener\ConnectListener
arguments:
tokenStorage: "#security.token_storage"
tagRepo: "#tag.repository.service"
tags:
- { name: doctrine.event_listener, event: postPersist, connection: default }
Most answers, that I've able to find, suggest injecting service container, but I really don't want do that. Is there any way to resolve this properly?
UPD: Here is the code of the listener. Everything worked fine until I've tried to inject TagRepository
class ConnectListener
{
/**
* #var TokenStorage
*/
private $tokenStorage;
/**
* #var TagRepository
*/
private $tagRepo;
/**
* #param TokenStorage $tokenStorage
* #param TagRepository $tagRepo
*/
public function __construct(TokenStorage $tokenStorage, TagRepository $tagRepo)
{
$this->tokenStorage = $tokenStorage;
}
/**
* #param LifecycleEventArgs $args
* #return void
*/
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Person) {
$user = $this->tokenStorage->getToken()->getUser();
$visibility = new PersonVisibility($entity, $user);
$visibility->setVisibilityType(PersonVisibility::VT_CREATED);
$entityManager->persist($visibility);
$entityManager->flush();
}
}
}
As far as TagRepository is descendant of EntityRepository try obtaining its instance in postPersist event. Like this:
// using full classname:
$tagRepo = $entityManager->getRepository("Application\Bundle\PersonBundle\Entity\TagRepository");
// alternatively:
$tagRepo = $entityManager->getRepository("ApplicationPersonBundle:Tag");
Yo can also change your declaration of your repository, don't use the factory and use one of these 2 methods.
This will avoid the circular reference and will be cleaner than use the EntityManager class.