Symfony 5.1 creating extension with services - php

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

Related

Symfony 4: Service shown as private when loaded from additional services yaml during being marked as public

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

Symfony Flex - How to create a database mysql connection

I am a beginner on Symfony.
I am trying to create a class to use my database (mysql). But I can not succeed. Can you help me ?
Here is my current class:
<?php
namespace App\Database;
use Doctrine;
use Doctrine\DBAL\Connection;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DatabaseApi extends AbstractController
{
/**
* #var Connection
*/
protected $database = null;
protected static $instance = null;
/**
* DatabaseApi constructor.
*/
protected function __construct()
{
$database = $this->container->get('database');
$this->database = $database;
}
public static function getInstance()
{
self::$instance = new self();
return self::$instance;
}
private function __clone()
{
}
/**
* #param $authentication_key
* #return mixed
* #throws Doctrine\DBAL\DBALException
*/
public function select_authentication_key($authentication_key)
{
$auth_key_query = $this->database->prepare('CALL Verify_authentication_key(:authentication_key)');
$auth_key_query->bindValue('authentication_key', $authentication_key);
$auth_key_query->execute();
$results = $auth_key_query->fetchAll(\PDO::FETCH_ASSOC);
return $results[0];
}
}
I tried to create it with everything I could find on the Internet. Unfortunately, I get an error "Call to a member function get() on null".
There is my services.yaml file :
parameters:
database_connection: default
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.
# 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']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
database:
alias: doctrine.dbal.%database_connection%_connection
And is is how I try to call the fonction :
$database = DatabaseApi::getInstance();
$result = null;
try{
$result = $database->select_authentication_key("1111-1111-1111-1111");
}catch (Exception $e){
print($e);
}
print_r($result);

Symfony 3: cannot access the container from inside a controller

I'm migrating my app from Symfony 2.8 to Symfony 3.3.
From inside a controller of mine I have this:
public function indexAction()
{
$email = new Email();
$form = $this->createForm(GetStartedType::class, $email, [
'action' => $this->generateUrl('get_started_end'),
'method' => 'POST',
]);
return [
'form' => $form->createView(),
];
}
But I receive this exception:
Call to a member function get() on null
My controller extends Symfony\Bundle\FrameworkBundle\Controller\Controller:
/**
* {#inheritdoc}
*/
class DefaultController extends Controller
{
...
}
So I have access to the container.
Putting some dumps around in the Symfony's code, I see that the container is correctly set:
namespace Symfony\Component\DependencyInjection;
/**
* ContainerAware trait.
*
* #author Fabien Potencier <fabien#symfony.com>
*/
trait ContainerAwareTrait
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* Sets the container.
*
* #param ContainerInterface|null $container A ContainerInterface instance or null
*/
public function setContainer(ContainerInterface $container = null)
{
dump('Here in the ContainerAwareTrait');
dump(null === $container);
$this->container = $container;
}
}
This dumps
Here in the ContainerAwareTrait
false
So the autowiring works well and sets the container.
But in the ControllerTrait I have this:
trait ControllerTrait
{
/**
* Generates a URL from the given parameters.
*
* #param string $route The name of the route
* #param mixed $parameters An array of parameters
* #param int $referenceType The type of reference (one of the constants in UrlGeneratorInterface)
*
* #return string The generated URL
*
* #see UrlGeneratorInterface
*/
protected function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
dump('Here in the ControllerTrait');
die(dump(null === $this->container));
return $this->container->get('router')->generate($route, $parameters, $referenceType);
}
...
this is the dump:
Here in the ControllerTrait
true
So here the container is null and this causes the error.
Anyone can help me solve this issue?
Why is the container null?
If may help, this is the services.yml configuration (the default that cames with Symfony):
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
This question is posted as issue on the Symfony's issue tracker.
The S3.3 autowire capability makes it a bit easier to define controllers as services.
The usual motivation behind defining controllers as services is to avoid injecting the container. In other words you should be explicitly injecting each service a controller uses. The autowire capability allows you to use action method injection so you don't have to inject a bunch of stuff in the constructor.
However, the base Symfony controller class provides a number of helper function which use about 12 different services. It would be painful indeed to inject these one at a time. I had sort of thought that the autowire capability might take care of this for you but I guess not.
So you basically need to add a call to setContainer in your service definition. Something like:
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
[[setContainer, ['#service_container']]]
tags: ['controller.service_arguments']
The autowire capability is very much a work in progress so I would not be surprised if this changes for 3.4/4.0.
This problem is fixed by PR #23239 and is relased in Symfony 3.3.3.

__construct() must implement interface error

I use symfony2 and SonataAdminBundle, SonataMediaBundle and SonataClassificationBundle
Now I want custmize setting for admin panel, but I have this error.
[Symfony\Component\Config\Exception\FileLoaderLoadException]
Catchable Fatal Error: Argument 5 passed to Sonata\MediaBundle\Admin\BaseMe
diaAdmin::__construct() must implement interface Sonata\ClassificationBundl
e\Model\CategoryManagerInterface, none given, called in /Users/whitebear/Codin
gWorks/httproot/myapp/app/cache/de_/appDevDebugProjectContaine_.php on l
ine 9494 and defined in . (which is being imported from "/Users/whitebear/Codi
ngWorks/httproot/myapp/app/config/routing.yml").
What I have done is two things.
made DependencyInjection file
Application/Sonata/MediaBundle/DependencyInjection/ApplicationSonataMediaExtension.php
<?php
namespace Application\Sonata\MediaBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* 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 ApplicationSonataMediaExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
then made services.yml for admin
Application/Sonata/MediaBundle/Resources/config/services.yml
#Disable gallery & media menu from admin panel
services:
sonata.media.admin.media:
class: %sonata.media.admin.media.class%
tags:
- { name: sonata.admin, manager_type: orm, show_in_dashboard: false, label_catalogue: %sonata.media.admin.media.translation_domain% , label_translator_strategy: sonata.admin.label.strategy.underscore }
arguments:
- ~
- %sonata.media.admin.media.entity%
- %sonata.media.admin.media.controller%
- "#sonata.media.pool"
- %sonata.classification.manager.category% # add here.
calls:
- [setModelManager, ["#sonata.media.admin.media.manager"]]
- [setTranslationDomain, [%sonata.media.admin.media.translation_domain%]]
- [setTemplates, [{ inner_list_row: SonataMediaBundle:MediaAdmin:inner_row_media.html.twig , base_list_field: SonataAdminBundle:CRUD:base_list_flat_field.html.twig , list: SonataMediaBundle:MediaAdmin:list.html.twig , edit: SonataMediaBundle:MediaAdmin:edit.html.twig }]]
sonata.media.admin.gallery:
class: %sonata.media.admin.gallery.class%
tags:
- { name: sonata.admin, manager_type: orm, show_in_dashboard: false, label_catalogue: %sonata.media.admin.media.translation_domain% , label_translator_strategy: sonata.admin.label.strategy.underscore }
arguments:
- ~
- %sonata.media.admin.gallery.entity%
- %sonata.media.admin.gallery.controller%
- "#sonata.media.pool"
calls:
- [setTranslationDomain, [%sonata.media.admin.media.translation_domain%]]
- [setTemplates, [{ list: SonataMediaBundle:GalleryAdmin:list.html.twig }]]
in Sonata\MediaBundle\Admin\BaseMediaAdmin
abstract class BaseMediaAdmin extends AbstractAdmin
{
/**
* #var Pool
*/
protected $pool;
/**
* #var CategoryManagerInterface
*/
protected $categoryManager;
/**
* #param string $code
* #param string $class
* #param string $baseControllerName
* #param Pool $pool
* #param CategoryManagerInterface $categoryManager
*/
public function __construct($code, $class, $baseControllerName, Pool $pool, CategoryManagerInterface $categoryManager)
{
parent::__construct($code, $class, $baseControllerName);
$this->pool = $pool;
$this->categoryManager = $categoryManager;
}
Thanks to #mdma
I figured out I must path 5th parameters as CategoryManagerInterface to BaseMediaAdmin constructor.
then I updated the service.yml but I have error like this.
[Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException]
The service "sonata.media.admin.media" has a dependency on a non-existent p
arameter "sonata.classification.manager.category". Did you mean one of thes
e: "sonata.classification.manager.category.class", "sonata.classification.m
anager.tag.class", "sonata.classification.manager.context.class", "sonata.c
lassification.manager.tag.entity", "sonata.classification.manager.category.
entity", "sonata.classification.manager.context.entity", "sonata.classifica
tion.admin.category.class"?
It solved.
I changed this sentence inservices.yml
- %sonata.classification.manager.category% to "#sonata.classification.manager.category"
The error say : Argument 5 doesn't exist in Sonata\MediaBundle\Admin\BaseMediaAdmin::__construct()
So, look at arguments in you sonata.media.admin.media service configuration. There are only 4 arguments. You need to add the 5th.
In bundle config (https://github.com/sonata-project/SonataMediaBundle/blob/master/Resources/config/doctrine_orm_admin.xml), there are 5 arguments :
<argument/>
<argument>%sonata.media.admin.media.entity%</argument>
<argument>%sonata.media.admin.media.controller%</argument>
<argument type="service" id="sonata.media.pool"/>
<argument type="service" id="sonata.media.manager.category" on-invalid="null"/>
So, I think you can add #sonata.media.manager.category as 5th argument.

Symfony 3 - You have requested a non-existent service is driving me crazy

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" }

Categories