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!
I have a problem with injecting RabbitMq producer from RabbitMqBundle into my service.
Service:
namespace App\Service;
use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;
use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
class RatingPositionRecalculateService
{
protected $entityManager;
protected $positionProducer;
public function __construct(EntityManagerInterface $entityManager, ProducerInterface $producer)
{
$this->entityManager = $entityManager;
$this->positionProducer = $producer;
}
public function recalculate(Carbon $day)
{
// do stuff
}
}
old_sound_rabbit_mq.yml:
old_sound_rabbit_mq:
connections:
default:
url: ''
producers:
rating_position:
connection: default
exchange_options: { name: 'rating_position', type: direct }
consumers:
rating_position:
connection: default
exchange_options: {name: 'rating_position', type: direct}
queue_options: {name: 'rating_position'}
callback: rating_position_service
and I get:
Cannot autowire service "App\Service\RatingPositionRecalculateService": argument "$producer" of method "__construct()" references interface "OldSound\RabbitMqBundle\RabbitMq\ProducerInterface" but no such service exists. You should maybe alias this interface to the existing "old_sound_rabbit_mq.rating_position_producer" service. Did you create a class that implements this interface?
I've tried wiring using services.yml:
rating_position_recalculate_service:
class: App\Service\RatingPositionRecalculateService
autowire: false
arguments:
$entityManager: '#doctrine.orm.entity_manager'
$producer: '#old_sound_rabbit_mq.rating_position_producer'
but I still get the same exception.
If you only have one producer, you can define an alias like this in your services.yaml:
OldSound\RabbitMqBundle\RabbitMq\ProducerInterface: '#old_sound_rabbit_mq.rating_position_producer'
But i suggest you to create an empty class for your producer:
// src/Producer/RatingPositionProducer.php
<?php
namespace App\Producer;
class RatingPositionProducer extends \OldSound\RabbitMqBundle\RabbitMq\Producer
{
}
Then, in your old_sound_rabbit_mq.yml file:
old_sound_rabbit_mq:
...
producers:
rating_position:
class: App\Producer\RatingPositionProducer
...
In your services.yaml file:
App\Producer\RatingPositionProducer: '#old_sound_rabbit_mq.rating_position_producer'
And finally, in your RatingPositionRecalculateService class
// src/Service/RatingPositionRecalculateService.php
namespace App\Service;
use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;
use App\Producer\RatingPositionProducer;
class RatingPositionRecalculateService
{
...
public function __construct(EntityManagerInterface $entityManager, RatingPositionProducer $producer)
{
...
}
I don't think your problem is related to the bundle. Your service definition looks ok. They wrote it in the documentation:
Here we configure the connection service and the message endpoints that our application will have. In this example your service container will contain the service old_sound_rabbit_mq.upload_picture_producer and old_sound_rabbit_mq.upload_picture_consumer. The later expects that there's a service called upload_picture_service.
Which means you should have a service old_sound_rabbit_mq.rating_position_producer. You can verify it by using the command bin/console debug:cont --show-private | grep old_sound_rabbit_mq.
But the problem is probably somewhere else.
Here is what I guess:
You have a configuration file that registers all your classes as service
You have another config file for your RatingPositionRecalculateService
If it's true, then the error is due to the duplicated registration of your service. While registering all the services, Symfony DIC tries to register your RatingPositionRecalculateService but can't link it to the producer (because the producer is not registered in the same file). You may have a look at another problem which has a similar issue: External service configuration in Symfony 4
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
{
//...
I am wondering if this is even a good practice. But for my project i need to get a parameter from parameters.yml and use it inside EntityRepository.
So for this I created a service but still the call is not executed.
services:
xxx_repository:
class: XXX\DatabaseBundle\Repository\CitiesRepository
calls:
- [setTheParameter, ["%the_parameter%"]]
parameters.yml
...
the_parameter: 14400
...
And inside the CitiesRepository.php I am doing the following:
class CitiesRepository extends EntityRepository
{
/**
* #var
*/
protected $theParameter;
public function setTheParameter($theParameter)
{
$this->theParameter = $theParameter;
}
....
}
But $this->theParameter is always null.
SO i have 2 questions: Is this a healthy habit? And why is the result always null?
You need to use getRepository method of the doctrine service as factory:
xxx_repository:
class: XXX\DatabaseBundle\Repository\CitiesRepository
factory: ["#doctrine", "getRepository"]
arguments: ["DatabaseBundle:City"]
calls:
- ["setTheParameter", ["%the_parameter%"]]
And then you can access to this repository as service in your controller:
$this->get('xxx_repository');
I have class ModelsRepository:
class ModelsRepository extends EntityRepository
{}
And service
container_data:
class: ProjectName\MyBundle\Common\Container
arguments: [#service_container]
I want get access from ModelsRepository to service container_data. I can't transmit service from controller used constructor.
Do you know how to do it?
IMHO, this shouldn't be needed since you may easily break rules like SRP and Law of Demeter
But if you really need it, here's a way to do this:
First, we define a base "ContainerAwareRepository" class which has a call "setContainer"
services.yml
services:
# This is the base class for any repository which need to access container
acme_bundle.repository.container_aware:
class: AcmeBundle\Repository\ContainerAwareRepository
abstract: true
calls:
- [ setContainer, [ #service_container ] ]
The ContainerAwareRepository may looks like this
AcmeBundle\Repository\ContainerAwareRepository.php
abstract class ContainerAwareRepository extends EntityRepository
{
protected $container;
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
}
}
Then, we can define our Model Repository.
We use here, the doctrine's getRepository method in order to construct our repository
services.yml
services:
acme_bundle.models.repository:
class: AcmeBundle\Repository\ModelsRepository
factory_service: doctrine.orm.entity_manager
factory_method: getRepository
arguments:
- "AcmeBundle:Models"
parent:
acme_bundle.repository.container_aware
And then, just define the class
AcmeBundle\Repository\ModelsRepository.php
class ModelsRepository extends ContainerAwareRepository
{
public function findFoo()
{
$this->container->get('fooservice');
}
}
In order to use the repository, you absolutely need to call it from the service first.
$container->get('acme_bundle.models.repository')->findFoo(); // No errors
$em->getRepository('AcmeBundle:Models')->findFoo(); // No errors
But if you directly do
$em->getRepository('AcmeBundle:Models')->findFoo(); // Fatal error, container is undefined
I tried some versions. Problem was solved follows
ModelRepository:
class ModelRepository extends EntityRepository
{
private $container;
function __construct($container, $em) {
$class = new ClassMetadata('ProjectName\MyBundle\Entity\ModelEntity');
$this->container = $container;
parent::__construct($em, $class);
}
}
security.yml:
providers:
default:
id: model_auth
services.yml
model_auth:
class: ProjectName\MyBundle\Repository\ModelRepository
argument
As a result I got repository with ability use container - as required.
But this realization can be used only in critical cases, because she has limitations for Repository.
Thx 4all.
You should never pass container to the repository, just as you should never let entities handle heavy logic. Repositories have only one purpose - retrieving data from the database. Nothing more (read: http://docs.doctrine-project.org/en/2.0.x/reference/working-with-objects.html).
If you need anything more complex than that, you should probably create a separate (container aware if you wish) service for that.
I would suggest using a factory service:
http://symfony.com/doc/current/components/dependency_injection/factories.html
//Repository
class ModelsRepositoryFactory
{
public static function getRepository($entityManager,$entityName,$fooservice)
{
$em = $entityManager;
$meta = $em->getClassMetadata($entityName);
$repository = new ModelsRepository($em, $meta, $fooservice);
return $repository;
}
}
//service
AcmeBundle.ModelsRepository:
class: Doctrine\ORM\EntityRepository
factory: [AcmeBundle\Repositories\ModelsRepositoryFactory,getRepository]
arguments:
- #doctrine.orm.entity_manager
- AcmeBundle\Entity\Models
- #fooservice
Are you sure that is a good idea to access service from repo?
Repositories are designed for custom SQL where, in case of doctrine, doctrine can help you with find(),findOne(),findBy(), [...] "magic" methods.
Take into account to inject your service where you use your repo and, if you need some parameters, pass it directly to repo's method.
I strongly agree that this should only be done when absolutely necessary. Though there is a quite simpler approach possible now (tested with Symfony 2.8).
Implement in your repository "ContainerAwareInterface"
Use the "ContainerAwareTrait"
adjust the services.yml
RepositoryClass:
namespace AcmeBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use AcmeBundle\Entity\User;
class UserRepository extends EntityRepository implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function findUserBySomething($param)
{
$service = $this->container->get('my.other.service');
}
}
services.yml:
acme_bundle.repository.user:
lazy: true
class: AcmeBundle\Repository\UserRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments:
- "AcmeBundle:Entity/User"
calls:
- method: setContainer
arguments:
- '#service_container'
the easiest way is to inject the service into repository constructor.
class ModelsRepository extends EntityRepository
{
private $your_service;
public function __construct(ProjectName\MyBundle\Common\Container $service) {
$this->your_service = $service;
}
}
Extending Laurynas Mališauskas answer, to pass service to a constructor make your repository a service too and pass it with arguments:
models.repository:
class: ModelsRepository
arguments: ['#service_you_want_to_pass']