I'm working with Symfony2 and I'm trying to setup a service definition overriding.
The extension is correctly loaded but the service definition not changed.
Here is the code:
class AppExtension extends Extension
{
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$container->setParameter('app.comment.delay_between', $config['comment']['delay_between']);
$def = $container->getDefinition('app.comment.manager')
->replaceArgument(0, $config['comment']);
$container->setDefinition('app.comment.manager2', $def);
}
/**
* {#inheritdoc}
*/
public function getAlias() {
return 'app';
}
}
The service definition:
app.comment.manager:
class: AppBundle\Comment\CommentManager
arguments:
- []
- "#doctrine.orm.entity_manager"
And the constructor of the class CommentManager:
/**
* CommandManager constructor.
* #param array $config
* #param EntityManagerInterface $em
*/
public function __construct(array $config, EntityManagerInterface $em) {
$this->config = $config;
var_dump($config);
$this->em = $em;
}
And in the controller I call:
$this->get('app.comment.manager');
$this->get('app.comment.manager2');
And I get this result:
/home/nathan/Dev/click-tube/src/symfony/src/AppBundle/Comment/CommentManager.php:37:
array (size=0)
empty
/home/nathan/Dev/click-tube/src/symfony/src/AppBundle/Comment/CommentManager.php:37:
array (size=1)
'delay_between' => int 60
As you can see, I can't modify the app.commment.manager service definition (which is what I want to do). But I can reflect the changes on a extension-created service.
What is the solution to apply the changes on app.comment.manager?
Try inject in the service the parameter defined in the extension, as example:
app.comment.manager:
class: AppBundle\Comment\CommentManager
arguments:
- "%app.comment.delay_between%"
- "#doctrine.orm.entity_manager"
Hope this help
I have had the same issue where the replaceArgument call didn't really modify the arguments given to the constructor of my service. The reason was that my services.yml was already imported in my /app/config/config.yml like:
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
- { resource: "#MyMightyBundle/Resources/config/services.yml" }
This would override the service definition loaded in met bundle extension file. Removing the service.yml import in my config.yml solved my problem.
Related
I'm trying to create a config for an extension which will give me 1 service with all dependencies injected but so far I was unable to do it. My steps were this to follow those steps in this web page https://symfony.com/doc/current/bundles/extension.html but I still get an error that I don't have service
Cannot autowire service "App\Command\ParserConfigAutoCreationCommand": argument "$nominatimGeocode" of method "__construct()" references class "XYZ\Service\NominatimGeocode" but no such service exists.
my services.yml file:
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:
Geocoder\Provider\Provider $nominatimProvider: '#bazinga_geocoder.provider.nominatim'
Geocoder\Provider\Provider $googleProvider: '#bazinga_geocoder.provider.google'
XYZ\Service\NominatimGeocode:
arguments: [ '#bazinga_geocoder.provider.nominatim' ]
and my GeoCodeExtension.php file:
<?php
declare(strict_types=1);
namespace XYZ\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* This is the class that loads and manages your bundle configuration.
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class GeoCodeExtension extends Extension
{
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
// $configuration = new Configuration();
// $config = $this->processConfiguration($configuration, $configs);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
do I need to add additional files or something?
EDIT:
class NominatimGeocode
{
/**
* #var \Geocoder\Provider\Provider
*/
private $nominatimProvider;
private $checkPostCode = false;
/**
* Geo Nominatim constructor.
*
* #param \Geocoder\Provider\Provider $nominatimProvider
*/
public function __construct(Provider $nominatimProvider)
{
$this->nominatimProvider = $nominatimProvider;
}
.
.
.
so I'm injecting it with right param name.
Your class should inject the Geocoder\Provider\Provider $nominatimProvider dependency if you want type binding to work.
Mind the variable's name must match the one in the bind section too.
Your is totally different:
XYZ\Service\NominatimGeocode $nominatimGeocode
I have Symfony 4 project.
And here is the service. It is marked as public and it is in separate cooperation.yml which is connected to project:
services:
App\Matcher\CooperationSiteConfigMatcher:
public: true
Here is class itself
<?php
namespace App\Matcher;
use App\Factory\CooperationOptionsFactory;
use App\Options\Cooperation\CooperationOptions;
use App\Resolver\SiteResolver;
use App\Factory\Exception\MissingCooperationOptionsException;
use Assert\AssertionFailedException;
class CooperationSiteConfigMatcher
{
const TYPE_PARTNERSHIP = 'partnership';
const TYPE_FRANCHISE = 'franchise';
/**
* #var CooperationOptionsFactory
*/
private $optionsFactory;
/**
* #var SiteResolver
*/
private $siteResolver;
/**
* CooperationSiteConfigMatcher constructor.
* #param CooperationOptionsFactory $optionsFactory
* #param SiteResolver $siteResolver
*/
public function __construct(CooperationOptionsFactory $optionsFactory, SiteResolver $siteResolver)
{
$this->optionsFactory = $optionsFactory;
$this->siteResolver = $siteResolver;
}
/**
* #param $group
* #return CooperationOptions
* #throws AssertionFailedException
* #throws MissingCooperationOptionsException
*/
public function matchOptionsByGroupAndSite($group)
{
$domainAliasKey = $this->siteResolver->resolve();
return $this->optionsFactory->createForSite($group, $domainAliasKey);
}
}
Global service.yml base configuration:
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 # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
App\:
resource: '%kernel.root_dir%/*'
exclude: '%kernel.root_dir%/{Entity,Migrations,Tests,Kernel.php,Form/EventListener,Request}'
But I receive an error:
The "App\Matcher\CooperationSiteConfigMatcher" service or alias has
been removed or inlined when the container was compiled. You should
either make it public or stop using the container directly and use
dependency injection instead.
The problem disappears when I move this service to global services.yml.
My custom services.yml is loaded via CooperationExtension class:
class CooperationExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$config = $this->processConfiguration(new CooperationConfiguration(), $configs);
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__ . '/../Resources/config/services')
);
$loader->load('cooperation.yaml');
$this->generateCooperationServices($config, $container);
}
public function getAlias()
{
return 'cooperation';
}
public function getXsdValidationBasePath()
{
return false;
}
public function getNamespace()
{
return "cooperation/schema";
}
}
Extension registered in Kernel.php configureContainer method:
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
$cooperationExtension = new CooperationExtension();
$container->registerExtension($cooperationExtension);
$container->loadFromExtension($cooperationExtension->getAlias());
$container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
// Feel free to remove the "container.autowiring.strict_mode" parameter
// if you are using symfony/dependency-injection 4.0+ as it's the default behavior
$container->setParameter('container.autowiring.strict_mode', true);
$container->setParameter('container.dumper.inline_class_loader', true);
$confDir = $this->getProjectDir().'/config';
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{packages}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
}
Symfony sees cooperation.yml. I checked it when removed some other services from it and exception appeared.
But somehow it do not merge it correctly with global services.yml and I don't see this service as public when load it from cooperation.yml
I am creating a normalizer and im not sure now it should be applied
"Custom normalizers and/or encoders can also be loaded by tagging them as serializer.normalizer and serializer.encoder. It's also possible to set the priority of the tag in order to decide the matching order."
https://symfony.com/doc/current/serializer.html#adding-normalizers-and-encoders
services.yml
datetime_normalizer:
class: App\Normalizer\DateTimeNormalizer
public: true
tags: [serializer.normalizer]
class
<?php
namespace App\Normalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Class DateTimeNormalizer
*/
class DateTimeNormalizer implements NormalizerInterface
{
/**
* {#inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
return $object->format(\DateTime::ISO8601);
}
/**
* {#inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \DateTime;
}
}
call
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer([$normalizer]);
$user = $serializer->normalize($token->getUser());
output
"datetime":{"timezone":{"name":"UTC","transitions":[{"ts":-9223372036854775808,"time":"-292277022657-01-27T08:29:52+0000","offset":0,"isdst":false,"abbr":"UTC"}],"location":{"country_code":"??","latitude":0,"longitude":0,"comments":""}},"offset":0,"timestamp":1527033600}
full code at github
https://github.com/ricardosaracino/symfony-pull-list/blob/master/config/services.yaml
Instead of creating your own serializer, you need to rely on the serializer created by Symfony by injecting it where you need. An example of this is in the doc: https://symfony.com/doc/current/serializer.html#using-the-serializer-service.
Have a look at https://symfony.com/doc/current/controller.html#fetching-services for controllers to learn more.
So, this is not the first time I am creating the service but I just can't resolve the error
You have requested a non-existent service "global_settings".
Steps I took to ensure service is properly setup:
My AppBundleExtension.php
namespace AppBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader;
class AppBundleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('settings.xml');
}
}
My settings.xml
<?xml version="1.0" encoding="UTF-8" ?>
<container
xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="global_settings" class="AppBundle\Services\GlobalSettings">
<call method="setEntityManager">
<argument type="service" id="doctrine.orm.default_entity_manager" />
</call>
</service>
</services>
</container>
My GlobalSettings service
namespace AppBundle\Services;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
class GlobalSettings
{
/**
* #var EntityManager
*/
protected $em;
/**
* #var EntityRepository
*/
protected $repo;
public function setEntityManager(EntityManager $em) {
$this->em = $em;
$this->repo = null;
}
/**
* #return array with name => value
*/
public function all() {
return $this->$this->getRepo()->findAll();
}
/**
* #param string $name Name of the setting.
* #return string|null Value of the setting.
* #throws \RuntimeException If the setting is not defined.
*/
public function get($name) {
$setting = $this->$this->getRepo()->findOneBy(array(
'name' => $name,
));
if ($setting === null) {
throw $this->createNotFoundException($name);
}
return $setting->getValue();
}
/**
* #param string $name Name of the setting to update.
* #param string|null $value New value for the setting.
* #throws \RuntimeException If the setting is not defined.
*/
public function set($name, $value) {
$setting = $this->$this->getRepo()->findOneBy(array(
'name' => $name,
));
if ($setting === null) {
throw $this->createNotFoundException($name);
}
$setting->setValue($value);
$this->em->flush($setting);
}
/**
* #return EntityRepository
*/
protected function getRepo() {
if ($this->repo === null) {
$this->repo = $this->em->getRepository('AppBundle:Settings');
}
return $this->repo;
}
/**
* #param string $name Name of the setting.
* #return \RuntimeException
*/
protected function createNotFoundException($name) {
return new \RuntimeException(sprintf('Setting "%s" couldn\'t be found.', $name));
}
}
Then inside my controller I trying to access the service using the following code
$data = $this->get('global_settings')->get('paypal_email');
What am I doing wrong? Any help will be really appreciate as I am out of idea.
The reason why I kept getting this error was that my default setting for services was public: false
So to fix that I needed to set the public property to true for my service
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
my_service:
class: AppBundle\Service\MyService
public: true
You wrote:
Steps I took to ensure service is properly setup
My AppBundleExtension.php
And:
I know AppBundleExtension is not loading, what do I need to do to load it? What am I missing?
So it was clear that the AppBundleExtension class was not loaded.
According to the official documentation you should remove the Bundle in the file name and class name:
The name is equal to the bundle name with the Bundle suffix replaced by Extension (e.g. the Extension class of the AppBundle would be called AppExtension and the one for AcmeHelloBundle would be called AcmeHelloExtension).
You can update your config.yml file:
imports:
- { resource: "#AppBundle/Resources/config/services.yml" }
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.