Symfony4. ParamConverter annotation conflicts injecting a service by autowire - php

When I try to use #ParamConverter annotation in a Controller action, I get an error
"Cannot resolve argument $company of \"App\\Controller\\ProfileController::top()\": Cannot autowire service \".service_locator.0CrkHeS\": it references class \"App\\Document\\Company\" but no such service exists."
I know that such a service does not exist, because I've excluded Document path in services.yaml. I just need to find a Company document object from Repostiroy.
Here is my controller code:
<?php
// src/Controller/ProfileController.php
namespace App\Controller;
use App\Document\Company;
use App\Service\DocumentManager\CompanyManager;
use FOS\RestBundle\Controller\FOSRestController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Swagger\Annotations as SWG;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/profile")
*/
class ProfileController extends FOSRestController
{
/**
* #Route("/top/{id}")
* #Method("GET")
* #SWG\Response(
* response=200,
* description="Returns top profiles",
* )
* #SWG\Tag(name="profile")
*
* #ParamConverter("company", class="App\Document\Company")
* #param CompanyManager $companyManager
* #return Response
*/
public function top(CompanyManager $companyManager, Company $company)
{
dump($company->getId());exit;
return $this->handleView($this->view($companyManager->getTopProfiles(), Response::HTTP_OK));
}
}
services.yaml configuration:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\:
resource: '../src/*'
exclude: '../src/{Entity,Document,Migrations,Tests,Kernel.php,Exception,DataFixtures}'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']

In case somebody else will get same problem.
The problem had nothing to do with autowire conflict. #ParamConverter didn't work, because I used mongoDB and Doctrine ODM, not ORM. By default Doctrine ParamConverter won't work for mongo documents.
So I found some information here
https://matthiasnoback.nl/2012/10/symfony2-mongodb-odm-adding-the-missing-paramconverter/
Define a new service in services.yaml file:
doctrine_mongo_db_param_converter:
class: Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter
tags:
- { name: request.param_converter, converter: doctrine.odm }
arguments: ['#doctrine_mongodb']
Then #ParamConverter should work OK now.

Related

DependencyInjection in AbstractEntity

i started working on a new Symfony 6.0 project.
I created a new Entity called Project. In this entity I want to set the created_by property automaticlly on PrePersist (hook) call...
Therefore I created an AbstractEntity to extend the original Project entity.
In AbstractEntity I want to automatically inject Symfony\Component\Security\Core\Security service.
BUT the autowire stuff just doesn't work.
# config/services.yaml
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/' # --> i removed that line (doesnt work)
- '../src/Kernel.php'
#this also does not work
App\Entity\AbstractEntity:
autowire: true
#this also does not work
App\Entity\AbstractEntity:
arguments:
- '#security.helper'
// src/Entity/AbstractEntity.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Security;
#[ORM\MappedSuperclass]
#[ORM\HasLifecycleCallbacks]
abstract class AbstractEntity
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
}
The entity should not have any dependencies and contain logic. If you want to do something, consider creating Doctrine Lifecycle Listeners prePersist or Doctrine Entity Listeners.
Lifecycle listeners are defined as PHP classes that listen to a single
Doctrine event on all the application entities.
Add to services.yaml file
App\EventListener\CreatedByLifecycleEvent:
tags:
-
name: 'doctrine.event_listener'
event: 'prePersist'
And create a listener
namespace App\EventListener;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Security;
class CreatedByLifecycleEvent
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function prePersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
if(method_exists($entity,'setCreatedBy') and !empty($user = $this->security->getUser())){
$entity->setCreatedBy($user);
}
}
}
Thus, when saving any entity, provided that the setCreatedBy method exists, our listener will set the current user.

Symfony Listener gone missing after minor changes in Controller

I am encountering some weir behaviour with Symfony listener. I have a working configuration, but when I change or add something to the controller, Expected to find class "App\EventListener\AuthenticationSuccessListener" shows up. The change could be only something inside route path string. All code is here.
Remove cache and server restart doesn't help.
Listener
namespace AppBundle\EventListener;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
use Symfony\Component\Serializer\SerializerInterface;
class AuthenticationListener
{
/**
* #var SerializerInterface
*/
private $serializer;
public function __construct(SerializerInterface $serializer)
{
$this->serializer = $serializer;
}
/**
* #param AuthenticationSuccessEvent $event
*/
public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $event)
{
$event->setData([
'user' => $this->serializer->normalize($event->getUser(), null, ['groups' => ['basic']]),
'token' => $event->getData()['token'],
]);
}
}
services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
services:
app.event.authentication_success_response:
class: AppBundle\EventListener\AuthenticationListener
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccessResponse }
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Controller
<?php
namespace App\Controller;
use App\Entity\User;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
class ApiUserController extends AbstractController
{
/**
* #Route("/api/verify_token", methods="POST", name="api_verify_token")
* #IsGranted("ROLE_USER")
*/
public function verifyToken(User $user = null){
return new JsonResponse([], 200);
}
}
Solved by #Jakumi answer. The error was caused by namespace AppBundle\EventListener. After the change to App\EventListener error disappear.

Problem with autowiring RegistryInterface in ServiceEntityRepository

I was following Symfony docs and wanted to use ServiceEntityRepository (link).
I created entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\BetRepository")
*/
class Bet
{
....
Repository was automatically created:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
use App\Entity\Bet;
class BetRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Bet::class);
}
}
I wanted to create BetService which uses BetRepository:
namespace App\Service;
use App\Repository\BetRepository;
final class BetService
{
private $betRepository;
public function __construct(BetRepository $betRepository)
{
$this->betRepository = $betRepository;
}
finally I created a controller and wanted to inject BetService into it:
namespace App\Controller;
use App\Service\BetService;
final class BetController extends AbstractController
{
private $betService;
public function __construct(BetService $betService)
{
$this->betService = $betService;
}
}
Problem is I keep getting error:
Cannot autowire service "App\Repository\BetRepository": argument
"$registry" of method "__construct()" references interface
"Symfony\Bridge\Doctrine\RegistryInterface" but no such service
exists. Did you create a class that implements this interface?
my services.yaml is default one from installation:
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
I am using Symfony 4.2
I tried autowiring EntityManager to BetRepository however I get error that EntityManager constructor is not public and tbh I feel this is not the right approach as docs say it should work out of box.
I can provide composer.json (didn't initially as already question is quite long)
Thanks everyone in advance!

Symfony 4 base controller class for all other API controllers

In my Symfony 4 application I need to have a multiple Controllers, each to mount/group various API endpoints in different prefix's.
Each controller will need to initialize first and set in a class property the API client and set the credentials. To avoid code repetition across all of them, I'd like to create a BaseController so the others can extend and directly access or have available the client property with all he needs.
The base controller:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Vendor\ApiClient;
class BaseApiController extends Controller
{
/**
* #var ApiClient
*/
protected $apiClient;
public function __construct( array $apiClientCredentials, ApiClient $apiClient )
{
$this->apiClient = $apiClient;
$this->apiClient->credentials($apiClientCredentials['id'], $apiClientCredentials['main_password']);
}
}
One of the many similar controllers that I want to have the API property ready to be called/used:
<?php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* Account endpoints
* #Route("/account")
*/
class AccountApiController extends BaseApiController
{
/**
* Get the balance.
* #Route("/balance", name="balance")
*/
public function balance()
{
return new JsonResponse($this->apiClient->getBalance());
}
}
This is what I have but still doesn't work as expected, wondering how is the best practice to put this setup together?
Edit: This is the error message I get.
Cannot autowire service "App\Controller\AccountApiController": argument "$apiClientCredentials" of method "App\Controller\BaseApiController::__construct()" must have a type-hint or be given a value explicitly.
Edit: Adding my services.yaml
# config/services.yaml
parameters:
app.creds:
id: '%env(ACCOUNT_ID)%'
main_password: '%env(ACCOUNT_MAIN_PASSWORD)%'
# ...
services:
_defaults:
autowire: true
# ...
App\Controller\BaseApiController:
arguments:
$apiClientCredentials: '%app.creds%'
The scope of your APiClient needs to be protected
/**
* #var ApiClient
*/
protected $apiClient;
Since i'm going to use it in multiple controllers seems for now it's fair just binding the argument.
services:
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$walletCreds: '%app.wallet_creds%'
Also the base class has to be an abstract:
abstract class BaseApiController extends Controller
{
//...

Doctrine manager class injection to controller doesn't work

In my bundle I need to initialize my doctrine manager class (as a service and using ManagerRegistry) in constructor of controller, but symfony still throws this exception:
Type error: Too few arguments to function AdminBundle\Controller\RegistraceController::__construct(), 0 passed in C:\apache\htdocs\mujProjekt\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Controller\ControllerResolver.php on line 198 and exactly 1 expected
Controller:
namespace AdminBundle\Controller;
use AdminBundle\Manager\AdminManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* Class DefaultController
* #package AdminBundle\Controller
* #Route("/registrace")
*/
class RegistraceController extends Controller
{
/**
* #var AdminManager
*/
private $manager;
public function __construct(AdminManager $manager)
{
$this->manager = $manager;
}
...
AdminManager:
namespace AdminBundle\Manager;
use AdminBundle\Entity\Uzivatel;
use Doctrine\Common\Persistence\ManagerRegistry;
class AdminManager
{
private $em;
public function __construct(ManagerRegistry $Doctrine)
{
$this->em = $Doctrine->getManager('default');
}
...
AdminBundle\Resources\config\services.yml :
services:
# admin.example:
# class: AdminBundle\Example
# arguments: ["#service_id", "plain_value", "%parameter%"]
admin.admin_manager:
class: AdminBundle\Manager\AdminManager
arguments:
["#doctrine"]
I tried to clear cache, but no success. The services.yml from AdminBundle is correctly included in config.yml.
orm config in config.yml:
orm:
auto_generate_proxy_classes: '%kernel.debug%'
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
AdminBundle: ~
I'm using Symfony 3.3 and PHP 7.1.
if you want to inject your AdminManager in your RegistraceController, you have to define the RegistraceController as a service. Look at https://symfony.com/doc/current/controller/service.html. There are some drawbacks of this approach, because you do not inherit from Symfony‘s base Controller. So, you have to inject the Router and the Template Engine too, if you need them. But I like defining my Controller as services. It‘s much cleaner when you see dependencies.
Instead of this, you can use the Symfony Container inside your controller as an Inversion Of Controll Container and get your service with $this->get('admin.admin_manager'); from inside your action.
So i think your service yml need to look like that:
services:
admin.admin_manager:
class: AdminBundle\Manager\AdminManager
arguments: ["#doctrine"]
admin.admin_controller:
class: AdminBundle\Controller\RegistraceController
arguments: ["#admin.admin_manager"]
Look up here Symfony Service Container
Hope it will help!
Greetings :)
Thank you all for your replies. Fortunately I solved my problem by adding this to services in app/config/services.yml.
AdminBundle\Controller\:
resource: '%kernel.project_dir%/src/AdminBundle/Controller'
public: true
tags: ['controller.service_arguments']

Categories