Sylius/Symfony 3 inject service in a service - php

I created a service to extend the menu in admin of Sylius. It's work well ;)
I follow the official doc
I try to inject the router service in, but I've this following error :
Type error: Too few arguments to function
XXMenuListener::__construct(), 0 passed in
appDevDebugProjectContainer.php on line 1542 and exactly 1 expected
The declaration of this service :
services:
app.listener.admin.menu_builder:
class: XXX\Menu\AdminMenuListener
autowire: true
arguments:
- '#router'
tags:
- { name: kernel.event_listener, event: sylius.menu.admin.main, method: addAdminMenuItems }
and the service himself :
<?php
namespace XXX\Menu;
use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
final class AdminMenuListener
{
private $router;
public function __construct(Router $router){
$this->router = $router;
}
/**
* #param MenuBuilderEvent $event
*/
public function addAdminMenuItems(MenuBuilderEvent $event){
$menu = $event->getMenu();
$newSubmenu = $menu
->addChild('new')
->setLabel('XXX')
;
$newSubmenu
->addChild('new-subitem')
->setLabel('XXX')
//->setUri('https://www.google.com');
->setUri($this->router->generate('foo'))
;
}
}
What is wrong in ? Thanks for your help!

I think you need to clear cache if not helped to clean the cache directory manually.
In any case, you don't need a router service because menubuilder already has it.
For example:
for uri
$newSubmenu
->addChild('new-subitem')
->setLabel('XXX')
->setUri('https://www.google.com')
;
for route
$newSubmenu
->addChild('new-subitem', ['route' => 'foo'])
->setLabel('XXX')
;

If you use autowire to true you don't need to specify the router service. Something like this should be enough :
services:
app.listener.admin.menu_builder:
class: XXX\Menu\AdminMenuListener
autowire: true
tags:
- { name: kernel.event_listener, event: sylius.menu.admin.main, method: addAdminMenuItems }
In any case, your error indicates that you don't have any arguments. May be it's a caching issue or may be you have another service declaration for the same class XXX\Menu\AdminMenuListener without autowire to true and without arguments.

Related

How to pass dynamic values to container in Symfony 5

How passing a dynamic variable from controller to a service? I want manage some istance in the constructor of my service that depend by json value. My service take two parameters in the construct: a service and a variable with the JSON.
For the first one, i have passed it directly in the service.yaml. For the second one, i have some difficult.
In the controller, i get from a API the JSON. But this json it can be null.
class IndexController extends AbstractController
{
/**
* #Route("/converter-hl7", name="converter", methods={"POST"})
*/
public function index(Request $request, $myjson = null) {
$myjson = $request->getContent();
global $kernel;
$converter = $kernel->getContainer()->get('app.converter');
$xml = $converter->outputXML();
$response = new Response($xml);
$response->headers->set('Content-Type', 'xml');
return $response;
}
I stock the JSON in my .env file, MYJSON=null.
This is my service.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:
# 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/'
- '../src/Kernel.php'
- '../src/Tests/'
# 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']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
# explicitly configure the service
app.error:
class: App\Service\Error
public: true
autowire: true
app.converter:
class: App\Service\ConverterHl7Refacto
public: true
autowire: true
arguments:
$error: '#app.error'
$json: '%env(MYJSON)'
So, in my service called ConverterHl7Refacto.php, i have the two parameters in the constructor. I would like manage the istances if the json is empty or non. If i do a dd() of $json, i get '%env(MYJSON)' instead JSON. Why?
class ConverterHl7Refacto
{
private $ricetta;
private $identificativiDocumento;
private $codiceDocumento;
private $infoDocumento;
private $assistiti;
private $partecipanti;
private $relatedDoc;
private $structuredBody;
private $root;
private $error;
public function __construct(string $json,Error $error) {
$this->error = $error;
if ($json){
$this->ricetta = new Ricetta(json_decode($json));
$this->root = new Root();
$this->identificativiDocumento = new Identificativi();
$this->codiceDocumento = new CodiceDocumento($this->ricetta);
$this->infoDocumento = new InfoDocumento($this->ricetta);
$this->assistiti = new Assistiti($this->ricetta);
$this->partecipanti = new Partecipanti($this->ricetta);
$this->relatedDoc = new RelatedDocument($this->ricetta);
$this->structuredBody = new StructuredBody($this->ricetta);
}
}
Nothing particularly tricky about the factory concept. A factory is basically used to create an instance of a given type. I did not test the following code so apologies for typos:
class Error {}
class Converter {
public function __construct(string $json, Error $error)
{
// Whatever
...
class ConverterFactory {
private $error;
public function __construct(Error $error) {
$this->error = $error;
}
public function create(string $json) : Converter {
return new Converter($json,$this->error);
}
}
class MyController {
public function action(ConverterFactory $converterFactory)
{
$json = $request->getContent();
$converter = $converterFactory->create($json);
You will need to exclude your Converter class from autowire in your services.yaml file. But that should be all you need. No need to explicitly define things like app.converter as autowire will take care of all that.

Autowire String parameter in symfony

How can I wire a String parameter in Symfony 3.4?
I have simple service and I want to wire a url parameter specified in parameters.yml:
namespace AppBundle\Service;
use Psr\Log\LoggerInterface;
class PythonService {
private $logger;
private $url;
/**
* #param LoggerInterface $logger
* #param String $url
*/
public function __construct(LoggerInterface $logger, String $url) {
$this->logger = $logger;
$this->url = $url;
}
}
My service.yml looks like:
AppBunde\Services\PythonService:
arguments: ['#logger', '%url%']
But I am getting error:
Cannot autowire service "AppBundle\Service\PythonService": argument "$url" of method "__construct()" is type-hinted "string", you should configure its value explicitly.
I tried also manually specify parameters:
AnalyticsDashboardBunde\Services\PythonService:
arguments:
$logger: '#logger'
$url: '%session_memcached_host%'
This gives me following error:
Invalid service "AppBundle\Services\PythonService": class "AppBundle\Services\PythonService" does not exist.
First, you have a typo in AppBundle\Services\PythonService (Services <> Service).
Then, string <> String. No uppercase in php.
You can bind an argument to a certain parameter/service:
service.yml:
services:
_defaults:
bind:
$memcacheHostUri: '%session_memcached_host%'
Service class: (have to be the same var name as specified ^)
public function __construct(LoggerInterface $logger, string $memcacheHostUri)
Controller action:
public function myAwesomeAction(PythonService $pythonService)
{
$pythonService->doPythonStuffs();
}
With this solution, if you create others services which need the memecacheHostUri, it will be autowired for these services too.
Resources:
Argument binding
// services.yml
app.python_service:
class: AppBundle\Service\PythonService
arguments:
$logger: '#monolog.logger.request'
$url: 'link'
public: true
// in controller
//use container:
$pS = $this->container->get('app.python_service');

Symfony KnpMenuBundle showing error [MenuBuilder As a Service]

I'm using sumfony 3.3.10, I have installed a fresh project of symfony and I added knpMenuBundle using this command,
composer require knplabs/knp-menu-bundle "^2.0"
Now I followed everything exactly as mentioned here http://symfony.com/doc/master/bundles/KnpMenuBundle/menu_builder_service.html
and added this line {{ knp_menu_render('main') }} in default/index.html.twig file.
Now when I execute the project, its showing me this error,
[InvalidArgumentException]
Menu builder services must be public but "app.menu_builder" is a private service.
config.yml
knp_menu:
# use "twig: false" to disable the Twig extension and the TwigRenderer
twig:
template: KnpMenuBundle::menu.html.twig
# if true, enables the helper for PHP templates
templating: false
# the renderer to use, list is also available by default
default_renderer: twig
MenuBuilder.php
<?php
namespace AppBundle\Menu;
use Knp\Menu\FactoryInterface;
class MenuBuilder
{
private $factory;
/**
* #param FactoryInterface $factory
*
* Add any other dependency you need
*/
public function __construct(FactoryInterface $factory)
{
$this->factory = $factory;
}
public function createMainMenu(array $options)
{
$menu = $this->factory->createItem('root');
$menu->addChild('Home', array('route' => 'homepage'));
// ... add more children
return $menu;
}
}
services.yml
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
arguments: ["#knp_menu.factory"]
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main } # The alias is what is used to retrieve the menu
How can I resolve it. Any help is much appreciated. Thanks
I added public: true to the app.menu_builder service in services.php,
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
public: true
arguments: ["#knp_menu.factory"]
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main } # The alias is what is used to retrieve the menu
And everything is working fine now.
Refer : https://symfony.com/doc/current/service_container/alias_private.html#marking-services-as-public-private

How to inject a repository into a service in Symfony?

I need to inject two objects into ImageService. One of them is an instance of Repository/ImageRepository, which I get like this:
$image_repository = $container->get('doctrine.odm.mongodb')
->getRepository('MycompanyMainBundle:Image');
So how do I declare that in my services.yml? Here is the service:
namespace Mycompany\MainBundle\Service\Image;
use Doctrine\ODM\MongoDB\DocumentRepository;
class ImageManager {
private $manipulator;
private $repository;
public function __construct(ImageManipulatorInterface $manipulator, DocumentRepository $repository) {
$this->manipulator = $manipulator;
$this->repository = $repository;
}
public function findAll() {
return $this->repository->findAll();
}
public function createThumbnail(ImageInterface $image) {
return $this->manipulator->resize($image->source(), 300, 200);
}
}
Here is a cleaned up solution for those coming from Google like me:
Update: here is the Symfony 2.6 (and up) solution:
services:
myrepository:
class: Doctrine\ORM\EntityRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments:
- MyBundle\Entity\MyClass
myservice:
class: MyBundle\Service\MyService
arguments:
- "#myrepository"
Deprecated solution (Symfony 2.5 and less):
services:
myrepository:
class: Doctrine\ORM\EntityRepository
factory_service: doctrine.orm.entity_manager
factory_method: getRepository
arguments:
- MyBundle\Entity\MyClass
myservice:
class: MyBundle\Service\MyService
arguments:
- "#myrepository"
I found this link and this worked for me:
parameters:
image_repository.class: Mycompany\MainBundle\Repository\ImageRepository
image_repository.factory_argument: 'MycompanyMainBundle:Image'
image_manager.class: Mycompany\MainBundle\Service\Image\ImageManager
image_manipulator.class: Mycompany\MainBundle\Service\Image\ImageManipulator
services:
image_manager:
class: %image_manager.class%
arguments:
- #image_manipulator
- #image_repository
image_repository:
class: %image_repository.class%
factory_service: doctrine.odm.mongodb
factory_method: getRepository
arguments:
- %image_repository.factory_argument%
image_manipulator:
class: %image_manipulator.class%
In case if do not want to define each repository as a service, starting from version 2.4 you can do following, (default is a name of the entity manager):
#=service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')
Symfony 3.3, 4 and 5 makes this much simpler.
Check my post How to use Repository with Doctrine as Service in Symfony for more general description.
To your code, all you need to do is use composition over inheritance - one of SOLID patterns.
1. Create own repository without direct dependency on Doctrine
<?php
namespace MycompanyMainBundle\Repository;
use Doctrine\ORM\EntityManagerInterface;
use MycompanyMainBundle\Entity\Image;
class ImageRepository
{
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Image::class);
}
// add desired methods here
public function findAll()
{
return $this->repository->findAll();
}
}
2. Add config registration with PSR-4 based autoregistration
# app/config/services.yml
services:
_defaults:
autowire: true
MycompanyMainBundle\:
resource: ../../src/MycompanyMainBundle
3. Now you can add any dependency anywhere via constructor injection
use MycompanyMainBundle\Repository\ImageRepository;
class ImageService
{
public function __construct(ImageRepository $imageRepository)
{
$this->imageRepository = $imageRepository;
}
}
In my case bases upon #Tomáš Votruba answer and this question I propose the following approaches:
Adapter Approach
Without Inheritance
Create a generic Adapter Class:
namespace AppBundle\Services;
use Doctrine\ORM\EntityManagerInterface;
class RepositoryServiceAdapter
{
private $repository=null;
/**
* #param EntityManagerInterface the Doctrine entity Manager
* #param String $entityName The name of the entity that we will retrieve the repository
*/
public function __construct(EntityManagerInterface $entityManager,$entityName)
{
$this->repository=$entityManager->getRepository($entityName)
}
public function __call($name,$arguments)
{
if(empty($arrguments)){ //No arguments has been passed
$this->repository->$name();
} else {
//#todo: figure out how to pass the parameters
$this->repository->$name(...$argument);
}
}
}
Then foreach entity Define a service, for examplein my case to define a (I use php to define symfony services):
$container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class)
->serArguments([new Reference('doctrine'),AppBundle\Entity\ContactEmail::class]);
With Inheritance
Same step 1 mentioned above
Extend the RepositoryServiceAdapter class for example:
namespace AppBundle\Service\Adapters;
use Doctrine\ORM\EntityManagerInterface;
use AppBundle\Entity\ContactEmail;
class ContactEmailRepositoryServiceAdapter extends RepositoryServiceAdapter
{
public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct($entityManager,ContactEmail::class);
}
}
Register service:
$container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class)
->serArguments([new Reference('doctrine')]);
Either the case you have a good testable way to function tests your database beavior also it aids you on mocking in case you want to unit test your service without the need to worry too much on how to do that. For example, let us suppose we have the following service:
//Namespace definitions etc etc
class MyDummyService
{
public function __construct(RepositoryServiceAdapter $adapter)
{
//Do stuff
}
}
And the RepositoryServiceAdapter adapts the following repository:
//Namespace definitions etc etc
class SomeRepository extends \Doctrine\ORM\EntityRepository
{
public function search($params)
{
//Search Logic
}
}
Testing
So you can easily mock/hardcode/emulate the behavior of the method search defined in SomeRepository by mocking aither the RepositoryServiceAdapter in non-inheritance approach or the ContactEmailRepositoryServiceAdapter in the inheritance one.
The Factory Approach
Alternatively you can define the following factory:
namespace AppBundle\ServiceFactories;
use Doctrine\ORM\EntityManagerInterface;
class RepositoryFactory
{
/**
* #param EntityManagerInterface $entityManager The doctrine entity Manager
* #param String $entityName The name of the entity
* #return Class
*/
public static function repositoryAsAService(EntityManagerInterface $entityManager,$entityName)
{
return $entityManager->getRepository($entityName);
}
}
And then Switch to php service annotation by doing the following:
Place this into a file ./app/config/services.php (for symfony v3.4, . is assumed your ptoject's root)
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$definition = new Definition();
$definition->setAutowired(true)->setAutoconfigured(true)->setPublic(false);
// $this is a reference to the current loader
$this->registerClasses($definition, 'AppBundle\\', '../../src/AppBundle/*', '../../src/AppBundle/{Entity,Repository,Tests,Interfaces,Services/Adapters/RepositoryServiceAdapter.php}');
$definition->addTag('controller.service_arguments');
$this->registerClasses($definition, 'AppBundle\\Controller\\', '../../src/AppBundle/Controller/*');
And cange the ./app/config/config.yml (. is assumed your ptoject's root)
imports:
- { resource: parameters.yml }
- { resource: security.yml }
#Replace services.yml to services.php
- { resource: services.php }
#Other Configuration
Then you can clace the service as follows (used from my example where I used a Dummy entity named Item):
$container->register(ItemRepository::class,ItemRepository::class)
->setFactory([new Reference(RepositoryFactory::class),'repositoryAsAService'])
->setArguments(['$entityManager'=>new Reference('doctrine.orm.entity_manager'),'$entityName'=>Item::class]);
Also as a generic tip, switching to php service annotation allows you to do trouble-free more advanced service configuration thin one above. For code snippets use a special repository I made using the factory method.
For Symfony 5 it is really simple, without need of services.yml to inject the dependency:
inject the Entity Manager in the service constructor
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
Then get the repository :
$this->em->getRepository(ClassName::class)
by replacing ClassName with your entity name.

How to inject a service in another service in Symfony?

I am trying to use the logging service in another service in order to trouble shoot that service.
My config.yml looks like this:
services:
userbundle_service:
class: Main\UserBundle\Controller\UserBundleService
arguments: [#security.context]
log_handler:
class: %monolog.handler.stream.class%
arguments: [ %kernel.logs_dir%/%kernel.environment%.jini.log ]
logger:
class: %monolog.logger.class%
arguments: [ jini ]
calls: [ [pushHandler, [#log_handler]] ]
This works fine in controllers etc. however I get no out put when I use it in other services.
Any tips?
You pass service id as argument to constructor or setter of a service.
Assuming your other service is the userbundle_service:
userbundle_service:
class: Main\UserBundle\Controller\UserBundleService
arguments: [#security.context, #logger]
Now Logger is passed to UserBundleService constructor provided you properly update it, e.G.
protected $securityContext;
protected $logger;
public function __construct(SecurityContextInterface $securityContext, Logger $logger)
{
$this->securityContext = $securityContext;
$this->logger = $logger;
}
For Symfony 3.3, 4.x, 5.x and above, the easiest solution is to use Dependency Injection
You can directly inject the service into another service, (say MainService)
// AppBundle/Services/MainService.php
// 'serviceName' is the service we want to inject
public function __construct(\AppBundle\Services\serviceName $injectedService) {
$this->injectedService = $injectedService;
}
Then simply, use the injected service in any method of the MainService as
// AppBundle/Services/MainService.php
public function mainServiceMethod() {
$this->injectedService->doSomething();
}
And viola! You can access any function of the Injected Service!
For older versions of Symfony where autowiring does not exist -
// services.yml
services:
\AppBundle\Services\MainService:
arguments: ['#injectedService']
More versatile option, is to once create a trait for the class you would want to be injected. For instance:
Traits/SomeServiceTrait.php
Trait SomeServiceTrait
{
protected SomeService $someService;
/**
* #param SomeService $someService
* #required
*/
public function setSomeService(SomeService $someService): void
{
$this->someService = $someService;
}
}
And where you need some service:
class AnyClassThatNeedsSomeService
{
use SomeServiceTrait;
public function getSomethingFromSomeService()
{
return $this->someService->something();
}
}
The class will autoload due to #required annotation. This generaly makes it much faster to implement when you want to inject services into numerous classes (like event handlers).

Categories