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
{
//...
Related
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.
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 am having trouble accessing an injected service in the constructor of one of my controllers.
Per http://symfony.com/doc/current/service_container/injection_types.html I believe I have done the injection correctly, however when I try to load a view from the controller, I get the following error:
Argument 1 passed to Regions\AnalyticsBundle\Controller\PatternsController::__construct()
must be an instance of Regions\AnalyticsBundle\Controller\PatternCacheService, instance of
Regions\AnalyticsBundle\Service\PatternCacheService given, called
in /var/tmp/symfony/cache/dev/ContainerLoHUcSH/getPatternsControllerService.php on line 9
It seems like the error indicates that the type hinting in the constructor is trying making it look for an instance in the *\Controller\* namespace instead of the *\Services\* namespace - what am I doing wrong or not seeing here?
Details of my setup are as follows...
Symfony 4.1.0, PHP 7.2.5
services.yaml
services:
...
pattern_cache_service:
class: Regions\AnalyticsBundle\Service\PatternCacheService
public: true
Regions\AnalyticsBundle\Controller\PatternsController:
arguments: ['#pattern_cache_service']
Controller:
namespace Regions\AnalyticsBundle\Controller;
class PatternsController extends BaseController
{
private $pcs;
public function __construct(PatternCacheService $pcs)
{
$this->pcs = $pcs;
}
}
You forgot a use in your Controller, making PHP think that your service is in the same namespace as your controller.
<?php
namespace Regions\AnalyticsBundle\Controller;
use Regions\AnalyticsBundle\Service\PatternCacheService;
class PatternsController extends BaseController
{
private $pcs;
public function __construct(PatternCacheService $pcs)
{
$this->pcs = $pcs;
}
}
This was actually raised as part of your error message
Argument 1 passed to Regions\AnalyticsBundle\Controller\PatternsController::__construct()
must be an instance of Regions\AnalyticsBundle\Controller\PatternCacheService
When what you expected was your controller to need an instance of Regions\AnalyticsBundle\Service\PatternCacheService
The class PatternCacheService cannot be found in the namespace Regions\AnalyticsBundle\Controller.
Add an import:
<?php
namespace Regions\AnalyticsBundle\Controller;
use Regions\AnalyticsBundle\Service\PatternCacheService;
class PatternsController extends BaseController
{
private $pcs;
public function __construct(PatternCacheService $pcs)
{
$this->pcs = $pcs;
}
}
For reference, see
http://php.net/manual/en/language.namespaces.importing.php
You don't need a service definition for pattern_cache_service. It should autowire your service if autowire: true is set.
PatternCacheService should be private as you don't want to access it from within container. Suggested practise!
You don't need a service definition for PatternsController either.
Note: You should not use "bundles" anymore in Symfony 4 so I would get rid of AnalyticsBundle.
Note: Better organise your configuration files as show here: Organising route, service and parameter configuration files in symfony 4 applications.
This should suffice:
services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\:
resource: '../src/*'
exclude: '../src/{Entity,....so on .....,Kernel.php}'
App\Controller\:
resource: '../../src/Regions/AnalyticsBundle/Controller'
tags: ['controller.service_arguments']
PatternsController
namespace Regions\AnalyticsBundle\Controller;
use Regions\AnalyticsBundle\Service\PatternCacheService;
class PatternsController
{
private $pcs;
public function __construct(PatternCacheService $pcs)
{
$this->pcs = $pcs;
}
}
according to How to inject a repository into a service in Symfony2?
it's like
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments:
- 'Acme\FileBundle\Model\File'
but I get an Exception
Invalid service "acme.custom_repository": class
"EntityManager5aa02de170f88_546a8d27f194334ee012bfe64f629947b07e4919__CG__\Doctrine\ORM\EntityManager"
does not exist.
How can I do this in Symfony 3.4?
update:
EntityClass is actually a valid class FQCN (also used copy reference on phpstorm to be sure) , just renamed it because a companies name is in it :). updated it anyway.
solution
BlueM's solution works perfectly.
In case you are not using autowiring here's the service defintion:
Acme\AcmeBundle\Respository\MyEntityRepository:
arguments:
- '#Doctrine\Common\Persistence\ManagerRegistry'
- Acme\AcmeBundle\Model\MyEntity # '%my_entity_class_parameter%'
As you are using Symfony 3.4, you can use a much simpler approach, using ServiceEntityRepository. Simply implement your repository, let it extend class ServiceEntityRepository and you can simply inject it. (At least when using autowiring – I haven’t used this with classic DI configuration, but would assume it should also work.)
In other words:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class ExampleRepository extends ServiceEntityRepository
{
/**
* #param ManagerRegistry $managerRegistry
*/
public function __construct(ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry, YourEntity::class);
}
}
Now, without any DI configuration, you can inject the repository wherever you want, including controller methods.
One caveat (which equally applies to the way you try to inject the repository): if the Doctrine connection is reset, you will have a reference to a stale repository. But IMHO, this is a risk I accept, as otherwise I won’t be able to inject the repository directly..
Create the custom repository properly
First, you need to create the repository custom class that extends the default repository from doctrine:
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
// your own methods
}
Then you need this annotation in the entity class:
/**
* #ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
Then you define the repository in the .yml file:
custom_repository:
class: MyDomain\Model\UserRepository
factory: ["#doctrine", getRepository]
arguments:
- Acme\FileBundle\Model\File
Make sure that in the definition of your repository class points to your custom repository class and not to Doctrine\ORM\EntityRepository.
Inject custom services into your custom repository:
On your custom repository create custom setters for your services
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
protected $paginator;
public function setPaginator(PaginatorInterface $paginator)
{
$this->paginator = $paginator;
}
}
Then inject them like this:
custom_repository:
class: MyDomain\Model\UserRepository
factory: ["#doctrine", getRepository]
arguments:
- Acme\FileBundle\Model\File
calls:
- [setPaginator, ['#knp_paginator']]
Inject your repository into a service:
my_custom_service:
class: Acme\FileBundle\Services\CustomService
arguments:
- "#custom_repository"
Check the arguments is a valid class (with FQCN or with a bundle simplification) as example:
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory:
- '#doctrine.orm.entity_manager'
- getRepository
arguments:
- Acme\MainBundle\Entity\MyEntity
or
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory:
- '#doctrine.orm.entity_manager'
- getRepository
arguments:
- AcmeMainBundle:MyEntity
Hope this help
Solutions I could see here so far are not bad. I looked at it from a different angle. So my solution allows you to keep clean repositories, sorta enforces consistent project structure and you get to keep autowiring!
This is how I would solve it in Symfony 5.
GOAL
We want to have autowired Repositories and we want to keep them as clean as possible. We also want them to be super easy to use.
PROBLEM
We need to figure out a way to tell Repository about the entity it should use.
SOLUTION
The solution is simple and consists of a few things:
We have custom Repository class which extends Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository class.
Our custom class has public string $entity property on it.
When we create our new repository and extend our custom repository class we have two choices: on our new repository we can just point to the class like this
namespace App\Database\Repository\Post;
use App\Database\Repository\Repository;
use App\Entity\Blog\Post;
/**
* Class PostRepository
* #package App\Database\Repository
*/
class PostRepository extends Repository
{
public string $entity = Post::class;
public function test()
{
dd(99999, $this->getEntityName());
}
}
or we could omit that property and let our new base Repository class find it automatically! (More about that later.)
CODE
So let's start with the code and then I will explain it:
<?php
namespace App\Database\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Laminas\Code\Reflection\ClassReflection;
use Symfony\Component\Finder\Finder;
/**
* Class Repository
* #package App\Database\Repository
*/
abstract class Repository extends ServiceEntityRepository
{
/** #var string */
private const REPOSITORY_FILE = 'repository';
/** #var string */
public string $entity = '';
/** #var string */
public string $defaultEntitiesLocation;
/** #var string */
public string $defaultEntitiesNamespace;
/**
* Repository constructor.
*
* #param ManagerRegistry $registry
* #param $defaultEntitiesLocation
* #param $defaultEntitiesNamespace
* #throws \Exception
*/
public function __construct(
ManagerRegistry $registry,
$defaultEntitiesLocation,
$defaultEntitiesNamespace
) {
$this->defaultEntitiesLocation = $defaultEntitiesLocation;
$this->defaultEntitiesNamespace = $defaultEntitiesNamespace;
$this->findEntities();
parent::__construct($registry, $this->entity);
}
/**
* Find entities.
*
* #return bool
* #throws \ReflectionException
*/
public function findEntities()
{
if (class_exists($this->entity)) {
return true;
}
$repositoryReflection = (new ClassReflection($this));
$repositoryName = strtolower(preg_replace('/Repository/', '', $repositoryReflection->getShortName()));
$finder = new Finder();
if ($finder->files()->in($this->defaultEntitiesLocation)->hasResults()) {
foreach ($finder as $file) {
if (strtolower($file->getFilenameWithoutExtension()) === $repositoryName) {
if (!empty($this->entity)) {
throw new \Exception('Entity can\'t be matched automatically. It looks like there is' .
' more than one ' . $file->getFilenameWithoutExtension() . ' entity. Please use $entity
property on your repository to provide entity you want to use.');
}
$namespacePart = preg_replace(
'#' . $this->defaultEntitiesLocation . '#',
'',
$file->getPath() . '/' . $file->getFilenameWithoutExtension()
);
$this->entity = $this->defaultEntitiesNamespace . preg_replace('#/#', '\\', $namespacePart);
}
}
}
}
}
Ok, so what is happening here? I have bound some values to the container in services.yml:
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.
bind:
$defaultEntitiesLocation: '%kernel.project_dir%/src/Entity'
$defaultEntitiesNamespace: 'App\Entity'
Then in our new extension class, I know where by default to look for my Entities (this enforces some consistency).
VERY IMPORTANT BIT - I assume that we will name Repositories and Entities with exactly the same so for example: Post will be our Entity and PostRepository is our repository. Just note that the word Repository is not obligatory. If it is there it will be removed.
Some clever logic will create namespaces for you - I assume that you will follow some good practices and that it will all be consistent.
It's done! To have your repository autowired all you need to do is extend your new base repository class and name Entity the same as the repository. so End result looks like this:
<?php
namespace App\Database\Repository\Post;
use App\Database\Repository\Repository;
use App\Entity\Blog\Post;
/**
* Class PostRepository
* #package App\Database\Repository
*/
class PostRepository extends Repository
{
public function test()
{
dd(99999, $this->getEntityName());
}
}
It is CLEAN, AUTOWIRED, SUPER EASY AND QUICK TO CREATE!
What about the drawbacks about the ServiceEntityRepository?
https://symfony.com/doc/current/doctrine/multiple_entity_managers.html
One entity can be managed by more than one entity manager. This
however results in unexpected behavior when extending from
ServiceEntityRepository in your custom repository. The
ServiceEntityRepository always uses the configured entity manager for
that entity.
In order to fix this situation, extend EntityRepository instead and no
longer rely on autowiring:
In an own project I've seen that using:
$repository = $entityManager->getRepository(MyEntity:class)
The $repository->_em is not equals to $entityManager (with both using the same connection), causing problems like:
$entity = $entityManager->getRepository(MyEntity:class)->find($id);
$entityManager->refresh($entity); // throws 'entity is not managed'
That's why the entity is fetched with $repository->_em and the refresh (or persist, flush, etc.) is using $entityManager.
This problem is described here:
https://github.com/symfony/symfony-docs/issues/9878
So... You can't rely in ServiceEntityRepository using multiple entity managers, but the EntityRepository doesn't allow autowire, so, what?
My two cents (I believe this should be works in every scenario):
Manually set the class metadata (something like you need to do in the constructor of the ServiceEntityManager), so I can:
Remove the autowire of repositories in services.yaml:
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Repository,Tests,Kernel.php,Client}'
(you also add the repositories below in services.yaml)
And create another /config/packages/repositories.yaml and add:
my.entity.metadata:
class: App\Entity\Metadata
arguments:
$entityName: App\Entity\MyEntity
App\Repository\MyEntityRepository:
arguments:
[$class: my.entity.metadata]
Now you have a EntityRepository that is capable of being autowireable. You can make a repositories.yaml file in the config and keep updated when you create/edit/delete your repositories. Is not cleanest but it should be works.
I'm upgrading a project from Symfony 3 to Symfony 4
(https://github.com/symfony/symfony/blob/master/UPGRADE-4.0.md) and I have many repository/services like this:
namespace App\Entity;
use App\Entity\Activation;
use Doctrine\ORM\EntityRepository;
use Predis\Client;
class ActivationRepository extends EntityRepository
{
// ...
}
And when I try to run the project in the browser like this:
http://localhost:8000/login
I get this error:
(1/1) RuntimeException
Cannot autowire service "App\Entity\ActivationRepository":
argument "$class" of method
"Doctrine\ORM\EntityRepository::__construct()"
references class "Doctrine\ORM\Mapping\ClassMetadata"
but no such service exists.
Does this mean you have to create a service for "Doctrine\ORM\Mapping\ClassMetadata" in your services.yaml file?
Thanks to autowiring my new services.yaml file is fairly small compared to the old one, which had 2000+ lines.
The new services.yaml just has several of these (so far):
App\:
resource: '../src/*'
# Controllers
App\Controller\:
resource: '../src/Controller'
autowire: true
public: true
tags: ['controller.service_arguments']
# Models
App\Model\:
resource: '../src/Model/'
autowire: true
public: true
// etc
Question:
Do you really need to add service definitions to services.yaml for third party vendor classes? And if so, can I get an example of how to do that please?
Any advice from anyone who has already upgraded from Symfony 3 to Symfony 4 would be great.
PHP 7.2.0-2+ubuntu16.04.1+deb.sury.org+2 (cli) (built: Dec 7 2017 20:14:31) ( NTS )
Linux Mint 18, Apache2 Ubuntu.
EDIT / FYI:
This is the "Doctrine\ORM\EntityRepository::__construct()" which the ActivationRepository extends:
/**
* Initializes a new <tt>EntityRepository</tt>.
*
* #param EntityManager $em The EntityManager to use.
* #param Mapping\ClassMetadata $class The class descriptor.
*/
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
$this->_class = $class;
}
which is located here:
/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php
Starting from the 1.8 version of DoctrineBundle, you can extend your class using Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository instead of Doctrine\ORM\EntityRepository. The result will be the same, but this does support the autowire.
Example:
use App\Entity\Activation;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class ActivationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Activation::class);
}
// ...
}
Do you really need to add service definitions to services.yaml for third party vendor classes?
No, don't do that. My personal suggestion is: don't extend EntityRepository. Ever. You don't want your repository's interface to have method like createQuery or flush. At least, you don't want that if you consider a repository just like a collection of objects. If you extend EntityRepository you will have a leaky abstraction.
Instead you can inject the EntityManager inside your repository, and that's it:
use App\Entity\Activation;
use App\Repository\ActivationRepository;
use Doctrine\ORM\EntityManagerInterface;
final class DoctrineActivationRepository implements ActivationRepository
{
private $entityManager;
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $this->entityManager->getRepository(Activation::class);
}
public function store(Activation $activation): void
{
$this->entityManager->persist($activation);
$this->entityManager->flush();
}
public function get($id): ?Activation
{
return $this->repository->find($id);
}
// other methods, that you defined in your repository's interface.
}
No other steps are required.
My issue was a wrong namespace.
File real position was App\Infrastructure\MySQL\Rubric\Specification
But namespace was set to App\Infrastructure\Rubric\Specification
Result "[blah blah] but no such service exists".
I got this issue. My service had a private constructor. I had to change:
private function __construct(
to
public function __construct(