I'm building a Symfony 4 application using locale.
My locale is binded in services.yaml :
parameters:
locale: 'en'
app_locales: en|fr|de|es|pt|
services:
_defaults:
autowire: true
autoconfigure: true
bind:
$locale: "#=service('request_stack').getCurrentRequest().getLocale()"
$listLocales: '%app_locales%'
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
App\Repository\:
esource: '../src/Repository'
Also, locale is injected and can be used in my repositories (used for elastic search request).
I need to add a command that uses one of my repositories to retrieve some data but I get this error:
In getSomeRepositoryService.php line 12:
Call to a member function getLocale() on null
var/cache/dev/ContainerJyByO4D/getSomeRepositoryService.php:
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the private 'App\Repository\SomeRepository' shared autowired service.
include_once $this->targetDirs[3].'/src/Repository/AbstractRepository.php';
include_once $this->targetDirs[3].'/src/Repository/SomeRepository.php';
return $this->privates['App\Repository\SomeRepository'] = new \App\Repository\SomeRepository(($this->privates['Elastica\Client'] ?? $this->load('getClientService.php')), ($this->services['request_stack'] ?? ($this->services['request_stack'] = new \Symfony\Component\HttpFoundation\RequestStack()))->getCurrentRequest()->getLocale());
Locale is injected in AbstractRepository constructor.
Here's my command (simplified version) :
classFooCommand extends Command
{
protected $someRepository;
public function __construct(SomeRepository $someRepository)
{
$this->someRepository = $someRepository;
parent::_construct();
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$demoData = $this->someRepository->getFoo();
var_dump($demoData);
}
}
How can the locale get injected in my command correctly?
Related
I want to migrate a symfony2.8 api to symfony5.4. I transferred all my Symfony commands, changed containerAwareCommand to \Symfony\Component\Console\Command\Command and changed the command configuration to match the configuration of the new version .
However, my commands are still not visible in the bin/console list and I cannot use them without declaring them in the services.yml. I have more than thirty commands to transfer and adding a paragraph in the service.yml seems to me tedious and not necessary. Did I forget something?
Second question, how can I access the service container directly in the commands? On Symfony 2.8 I was accessing it with $this->getContainer()->get('toto') but it doesn't seem to work anymore on version 5.
services.yml:
tags:
- { name: 'console.command', command: 'word:manager:expiration_email' }
arguments:
- '#service_container'
- '#logger'
Command:
protected function configure()
{
$this
->setName('word:manager:expiration_email')
->setDescription('Alert email : Send an email x days before the expiration.')
->addOption(
'days',
'd',
InputOption::VALUE_REQUIRED,
'How many days before the expiration',
1
);
}
/**
* {#inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$days = intval($input->getOption('days'));
if ($days > 0) {
try {
$this->getManager()->sendExpirationEmail($days);
} catch (\Exception $e) {
$this->container->get('logger')->error(sprintf(
'aemCommand: an error occurred in file "%s" (L%d): "%s"',
$e->getFile(),
$e->getLine(),
$e->getMessage()
));
}
}
return 0;
}
/**
* #return aem
*/
private function getManager()
{
return $this->container->get('word.manager.alert_employee');
}
services.yml:
imports:
- { resource: "#WORDCoreBundle/Resources/config/services.yml" }
- { resource: "#WORDAlertBundle/Resources/config/services.yml" }
- { resource: "#WORDEmployeeBundle/Resources/config/services.yml" }
- { resource: "#WORDTimeManagementBundle/Resources/config/services.yml" }
- { resource: "#WORDUserBundle/Resources/config/services.yml" }
- { resource: "#WORDFileBundle/Resources/config/services.yml" }
- { resource: "#WORDLocalisationBundle/Resources/config/services.yml" }
- { resource: "#WORDPlatformBundle/Resources/config/services.yml" }
- { resource: "#WORDCompanyBundle/Resources/config/services.yml" }
- { resource: "#WORDContractBundle/Resources/config/services.yml" }
- { resource: "#WORDFolderBundle/Resources/config/services.yml" }
- { resource: "#WORDTrainingBundle/Resources/config/services.yml" }
- { resource: "#WORDExpenseReportBundle/Resources/config/services.yml" }
- { resource: "#WORDBookStoreBundle/Resources/config/services.yml" }
- { resource: "#WORDPaymentBundle/Resources/config/services.yml" }
- { resource: "#WORDInvoiceBundle/Resources/config/services.yml" }
- { resource: "#WORDAgendaBundle/Resources/config/services.yml" }
- { resource: "#WORDAlertBundle/Resources/config/services.yml" }
- { resource: "#WORDAnnualReviewBundle/Resources/config/services.yml" }
- { resource: "#WORDProReviewBundle/Resources/config/services.yml" }
- { resource: "#WORDWidgetBundle/Resources/config/services.yml" }
- { resource: "#WORDCommentBundle/Resources/config/services.yml"}
- { resource: "#WORDFrontBundle/Resources/config/services.yml" }
- { resource: "#WORDNotificationBundle/Resources/config/services.yml" }
services:
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
_defaults:
autowire: true
autoconfigure: true
WORD\CoreBundle\Controller\ApiController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\PlatformBundle\Controller\PlatformRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\UserBundle\Controller\UserRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\AlertBundle\Controller\AlertRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\EmployeeBundle\Controller\EmployeeRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\CompanyBundle\Controller\CompanyRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\CompanyBundle\Controller\ContactRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\CompanyBundle\Controller\ServiceRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\CompanyBundle\Controller\ChartRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\BookStoreBundle\Controller\InfoRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD.user.manager.user:
class: 'WORD\UserBundle\Service\UserManager'
public: false
lazy: true
arguments:
- '#fos_user.util.password_updater'
- '#fos_user.util.canonical_fields_updater'
- '#fos_user.object_manager'
- 'WORD\UserBundle\Entity\User'
WORD\AgendaBundle\DataFixtures\AgendaFixtures:
tags: [doctrine.fixture.orm]
WORD\UserBundle\DataFixtures\UserFixtures:
tags: [doctrine.fixture.orm]
arguments:
$fos_manager: '#fos_user.user_manager'
WORD\LocalisationBundle\DataFixtures\CountryFixtures:
tags: [doctrine.fixture.orm]
App\DataFixtures\Processor\UserProcessor:
tags: [ { name: fidry_alice_data_fixtures.processor } ]
WORD\AlertBundle\Command\AlertExpirationEmailCommand:
tags:
- { name: 'console.command', command: 'WORD:alert:expiration_email' }
arguments:
- '#service_container'
- '#logger'
Composer.json
"autoload": {
"psr-4": {
"App\\": "src/",
"WORD\\": "WORD"
}
},
Namespace PSR-4 mapping
In order for composer to properly map out your files to their associated namespaces, the namespace and path must be declared in the autoload.psr-4 and/or autoload.classmap composer.json configuration.
composer.json autoload mappings
"autoload": {
"psr-4": {
"App\\": "src/",
"W2D\\": "W2D/",
"WORD\\": "WORD/"
},
"classmap": {
"src/",
"W2D/",
"WORD/"
}
},
Convert Commands to be Lazy Loaded
First to reduce configuration and instantiation overhead, make all of your commands lazy loaded by declaring the name using protected static $defaultName = '.....'.
Repeat the changes for each command in the namespace.
Command changes
// /WORD/AlertBundle/Command/AlertExpirationEmailCommand.php
namespace WORD\AlertBundle\Command;
class AlertExpirationEmailCommand extends Command
{
protected static $defaultName = 'WORD:alert:expiration_email';
protected function configure()
{
$this
/* ->setName('WORD:alert:expiration_email') */
// ...
}
}
Fix Service Loading
All services in Symfony 3.4+ are public: false by default (including the Logger) making them inaccessible by using container->get('service'). Additionally if a service is not explicitly injected in a service configuration or by autowire, the services are removed from the Container to reduce overhead. Because of this, you should never inject the entire container into a service - since the service you may be looking for may have inadvertently been removed.
Due to the way services are handled, _defaults should be the first line of any services declaration. It is also important to note, that _defaults will not be inherited/cascaded by any other file or configuration imports.
Since App\ is the default namespace being autowired, you will need to replace it with yours to ensure that the appropriate namespace(s) are being loaded as services for autowire.
Application services autowire
# /config/services.yaml
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
# makes classes in App/ 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'
# makes classes in WORD/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
WORD\: # <--- root namespace to autowire eg: WORD\Command\MyCommand
resource: '../WORD/' # location of the namespace
exclude:
- '../WORD/DependencyInjection/'
- '../WORD/Entity/'
- '../WORD/*Bundle/' # Never autowire Bundles as they should be self-contained
# declare manually wired and overridden services below this line
# ...
Fix Bundle Configurations to be self-contained
With the introduction of autowiring and changes to order of operations in Symfony Flex (4.0+), loading Bundle services in the main application config/services.yaml will often cause conflicts in loading. Because of this it is best to use an Extension for each bundle to load the services configurations, until you migrate away from the Bundle file hierarchy in favor of using a flat application configuration.
Again repeat the changes for each registered Bundle.
Ultimately it is best-practice to explicitly define all services
within the Bundles and NOT to use autowire: true. However, considering that this is a migration from
2.8 to 5.x, you will want to refactor away from using Bundles.
Bundle services.yaml and autowire
# /WORD/AlertBundle/Resources/config/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.
# makes classes in WORD/AlertBundle/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
WORD\AlertBundle\:
resource: '../../*'
exclude:
- '../../DependencyInjection/'
- '../../Entity/'
# Bundle specific services below this line
# ...
Bundle Extension
/* /WORD/AlertBundle/DependencyInjection/WORDAlertExtension.php */
namespace WORD\AlertBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class WORDAlertExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__ . '/../Resources/config')
);
$loader->load('services.yml');
}
}
Manual Bundle Extension Loading
When not using the Bundle file hierarchy naming conventions, you may need to manually instantiate the Bundle's extension in the Bundle::getContainerExtension() method.
/* /WORD/AlertBundle/WordAlertBundle.php */
namespace WORD\AlertBundle;
// ...
use WORD\AlertBundle\DependencyInjection\AlertExtension;
class WordAlertBundle extends Bundle
{
public function getContainerExtension()
{
return new AlertExtension();
}
}
Container Access and Autowire
As mentioned above, as of Symfony 3.4+ you should never inject the entire container directly into a service.
Instead, the desired services should be explicitly type-hinted in the __construct() method of the class, which will be injected with the autowire: true services configuration.
# EXAMPLE ONLY
#
# /WORD/AlertBundle/Resources/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true
# example autowire and autoconfig
WORD\AlertBundle\:
resource: '../../*'
# EXAMPLE - Service Override Declaration
WORD\AlertBundle\Service\EmployeeManager:
arguments:
- '#fos_user.util.password_updater'
- '#fos_user.util.canonical_fields_updater'
- '#fos_user.object_manager'
- 'WORD\UserBundle\Entity\User'
# EXAMPLE - declaration of alias
word.manager.alert_employee:
alias: WORD\AlertBundle\Service\EmployeeManager
public: true # to prevent deletion of service when not used
# EXAMPLE OVERRIDE - manually declare all files under Command as a command service
WORD\AlertBundle\Command\:
resource: '../../Command/*'
tags:
- { name: 'console.command' } # force as a command
// /WORD/AlertBundle/Command/AlertExpirationEmailCommand.php
namespace WORD\AlertBundle\Command;
// ...
use Psr\Log\LoggerInterface;
use WORD\AlertBundle\Service\EmployeeManager;
class AlertExpirationEmailCommand extends Command
{
protected static $defaultName = 'WORD:alert:expiration_email';
private LoggerInterface $logger;
private EmployeeManager $manager;
public function __construct(LoggerInterface $logger, EmployeeManager $manager)
{
$this->logger = $logger;
$this->manager = $manager;
}
/**
* {#inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$days = intval($input->getOption('days'));
if ($days > 0) {
try {
$this->getManager()->sendExpirationEmail($days);
} catch (\Exception $e) {
$this->logger->error(sprintf(
'aemCommand: an error occurred in file "%s" (L%d): "%s"',
$e->getFile(),
$e->getLine(),
$e->getMessage()
));
}
}
return 0;
}
/**
* #return EmployeeManager
*/
private function getManager(): EmployeeManager
{
return $this->manager;
}
}
Moving Forward
Go through each of the service classes that uses the container directly and refactor them to use the explicit service type-hints or manual wiring in their respective services.yml configurations.
You should aim to migrate away from the 2.x - 3.4 Bundle file hierarchy and naming conventions and port to a flat configuration such as moving WORD\AlertBundle to WORD\Alert\... or WORD\Command\Alert\.... This way you have a single cohesive application, as opposed to several mini-applications as Bundles, significantly reducing code complexity, overhead, page loads, and cache warm-up times.
Alternatively, if each Bundle provides functionality shared across several of your applications, move each Bundle to their own repositories, following the Bundle best-practices, so they are segregated from the main application. Which will require another directory refactoring.
I have a problem autowiring some parameters in services config.
I can't clear:cache because it triggers the error so I don't think it is a cache issue.
And I have copy/paste all my files into a whole new Symfony project and still got the same problem.
This is my services.yaml
#config/services.yaml
parameters:
app.medipim_api_key_value: '%env(resolve:MEDIPIM_API_KEY)%'
app.medipim_api_key_id: 326
services:
[6 lines ...]
# 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}'
App\Services\MedipimApi:
arguments:
$medipim_api_key_id: '%app.medipim_api_key_id%'
$medipim_api_key_value: '%app.medipim_api_key_value%'
[10 lines ...]
This is my services :
#src/Services/MedipimApi.php
class MedipimApi
{
const FILTER_CNK_AND_ACTIVE = 'filter_cnk_and_active';
/**
* #var MedipimApiV3Client
*/
private MedipimApiV3Client $medipimApiV3Client;
private TranslatorInterface $translator;
/**
* MedipimApi constructor.
* #param string $medipim_api_key_id API key id for Medipim API call
* #param string $medipim_api_key_value Api key (secret) value for Medipim API call
* #param TranslatorInterface $translator TranslatorInterface object for I18N purpose
*/
public function __construct(string $medipim_api_key_id, string $medipim_api_key_value, TranslatorInterface $translator)
{
$this->translator = $translator;
$this->medipimApiV3Client = new MedipimApiV3Client($medipim_api_key_id, $medipim_api_key_value);
}
....
When I try to call this service, I got this error :
Cannot autowire service "App\Services\MedipimApi": argument
"$medipim_api_key_id" of method "__construct()" is type-hinted
"string", you should configure its value explicitly.
It is working only when I copy the value of services.yaml and paste it into my services_env.yaml (where env is my current environment).
#config/services_dev.yaml
parameters:
app.medipim_api_key_value: '%env(resolve:MEDIPIM_API_KEY)%'
app.medipim_api_key_id: 326
services:
[6 lines ...]
# 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}'
App\Services\MedipimApi:
arguments:
$medipim_api_key_id: '%app.medipim_api_key_id%'
$medipim_api_key_value: '%app.medipim_api_key_value%'
[10 lines ...]
Now it works!
Why did that work?
Symfony is suppose to load file as described here :
For the dev environment, Symfony loads the following config files and
directories and in this order:
config/packages/*
config/packages/dev/*
config/services.yaml
config/services_dev.yaml
What did I miss?
EDIT
My kernel class looks right. It is loading services.yaml and then services_env.yaml as it should be :
class Kernel extends BaseKernel
{
[1 line...]
protected function configureContainer(ContainerConfigurator $container): void
{
$container->import('../config/{packages}/*.yaml');
$container->import('../config/{packages}/'.$this->environment.'/*.yaml');
if (is_file(\dirname(__DIR__).'/config/services.yaml')) {
$container->import('../config/{services}.yaml');
$container->import('../config/{services}_'.$this->environment.'.yaml');
} elseif (is_file($path = \dirname(__DIR__).'/config/services.php')) {
(require $path)($container->withPath($path), $this);
}
}
[14 lines...]
Why you configure param xx_id as int in your yml file ? In your service you type-hinted id as string !
I am trying to use the container.service_subscriber tag on my Controller to make some services available without injecting them through the constructor. In our project we don't want to use the autowiring and also can't use the autoconfigure option.
The structure of the Controller is as follow:
I have a base BaseController which extends from the AbstractFOSRestController of FOSRestBundle which has some common used methods for all my Controllers. That service will be used as parent for my other Controllers.
The service definition looks like this:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "#service1"
- "#service2"
- ...
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
#autowire: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
The class looks like this:
/**
* User controller.
*/
class UserController extends AbstractCRUDController implements ClassResourceInterface
{
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
'servicexyz' => ServiceXYZ::class,
]);
}
.......
}
The problem I have is, if I set autowire: false, it always automatically sets the full container and with this the appropriate deprecation message (as I am not setting it myself):
User Deprecated: Auto-injection of the container for "WM\ApiBundle\Controller\UserController" is deprecated since Symfony 4.2. Configure it as a service instead.
When setting autowire: true Symfony does respect the container.service_subscriber tag and only sets the partial container (ServiceLocator), which also would solve the deprecation message. I would have expected that autowiring should not make any differences in this case because I am explicitly telling the service which other services it should have.
Am I using the tags wrong or do I have a general problem in understanding how to subscribe a service to a Controller?
The basic issue is that the builtin service subscriber functionality will only inject the service locator into the constructor. A conventional controller which extends AbstractController uses autoconfigure to basically override this and uses setContainer instead of the constructor.
# ApiBundle/Resources/config/services.yaml
services:
_defaults:
autowire: false
autoconfigure: false
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
class UserController extends AbstractController
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// ...
'logger' => LoggerInterface::class,
]);
}
public function index()
{
$url = $this->generateUrl('user'); // Works as expected
// $signer = $this->get('uri_signer'); // Fails as expected
$logger = $this->get('logger'); // Works as expected
return new Response('API Index Controller ' . get_class($this->container));
}
}
Results in:
API Index Controller Symfony\Component\DependencyInjection\Argument\ServiceLocator
Indicating that a service locator (as opposed to the global container is being injected).
You can also configure your service to use the setContainer method and eliminate the need for a constructor. Either approach will work.
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
calls: [['setContainer', ['#Psr\Container\ContainerInterface']]]
Solution to the problem is to extend the service definition of the Controller with a call to setContainer to inject the '#Psr\Container\ContainerInterface' service:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "#service1"
- "#service2"
- ...
calls:
- ['setContainer', ['#Psr\Container\ContainerInterface']]
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
This will give me a ServiceLocator as container containing only the regiestered services instead of the full container without using the autowire option.
Sidenote: Setting the #service_container would inject the full container.
For completeness, there was already an issue on the symfony project where this was discussed.
I can’t get my Mailer working when creating a service.
I’ve been following a few tutorials.
I’ve been trying to inject my dependencies but there is no way to get my $this->container->render() working
I’m getting the following error message
ServiceNotFoundException:
The service "App\Services\Mailer" has a dependency on a non-existent service "templating".
What would be a good way to inject the templating service in my Mailer service? I know this is called dependency injection but I can’t have it work properly.
I tried to follow this but no luck: RenderView in My service
My Controller:
use App\Services\Mailer;
class ScriptController extends AbstractController
{
private function getThatNotifSent($timeframe, Mailer $mailer)
{
// Some code
foreach ( $users as $user ) {
$mailer->sendNotification($user, $cryptos, $news, $timeframe);
$count++;
}
$response = new JsonResponse(array('Mails Sent' => $count));
return $response;
}
}
My service:
<?php
// src/Service/Mailer.php
namespace App\Services;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Mailer
{
private $mailer;
private $templating;
public function __construct(\Swift_Mailer $mailer ,ContainerInterface $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
public function sendNotification($user, $cryptos, $news, $timeframe)
{
$message = (new \Swift_Message('Your Daily Digest Is Here!'))
->setFrom('no-reply#gmail.com')
->setTo($user->getEmail())
->setBody(
$this->templating->render(
'emails/notification.html.twig',
array(
'timeframe' => $timeframe,
'cryptos' => $cryptos,
'user' => $user,
'news' => $news,
)
),
'text/html'
)
->setCharset('utf-8');
$this->mailer->send($message);
return true;
}
}
My service.yaml
# 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:
locale: 'en'
images_directory: '%kernel.project_dir%/public/images/blog'
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/{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
App\EventListener\LoginListener:
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login' }
App\Services\Mailer:
arguments: ["#mailer", "#templating”]
UPDATE:
I have updated my code to follow Taylan answer. My Mailer service now looks like the following (no change to sendNotification Made)
<?php
// src/Service/Mailer.php
namespace App\Services;
use Symfony\Bundle\TwigBundle\TwigEngine;
class Mailer
{
private $mailer;
private $templating;
public function __construct(\Swift_Mailer $mailer ,TwigEngine $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
I still had the same error message. But after doing research online, I’ve updated my framework.yaml to the following after reading on this helpful link:
https://github.com/whiteoctober/BreadcrumbsBundle/issues/85
framework:
templating: { engines: [twig] }
It worked
Thanks for your help.
ContainerInterface typehint gives you the container, yet you named it $templating. You're supposed to get templating from container like this $this->templating = $container->get('templating').
But do you really need the container in the first place? You should be able to inject templating directly by typehinting like this Symfony\Bundle\TwigBundle\TwigEngine $templating instead of Symfony\Component\DependencyInjection\ContainerInterface $container.
P.S: You can search for services via php bin/console debug:container command.
I followed the example on Symfony 3.4 docs in order to load a Twig extension at runtime but it doesn't load: what I'am doing wrong?
IN: src/PlotlyBundle/Twig/AppRuntime.php
<?php
namespace PlotlyBundle\Twig;
class AppRuntime
{
public function __construct()
{
}
public function biDraw()
{
return 'awesome text here';
}
}
IN: src/PlotlyBundle/Resources/config/services.yml
services:
plotly.twig_runtime:
class: PlotlyBundle\Twig\AppRuntime
public: true
tags:
- { name: twig.runtime }
IN: src/PlotlyBundle/Twig/AppExtension.php
<?php
namespace PlotlyBundle\Twig;
use PlotlyBundle\Twig\AppRuntime;
class AppExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction(
'bi_draw',
array(AppRuntime::class, 'biDraw')
),
];
}
}
IN: src/AppBundle/Controller/DashboardController.php
$twig = $this->get('plotly.twig_runtime');
return $this->render(
'dashboard/index.html.twig'
);
IN: app/Resources/views/dashboard/index.html.twig
{{ bi_draw() }}
Thanks to #Federkun comments I fixed it by autowiring the Twig extension:
IN: src/PlotlyBundle/Resources/config/services.yml
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
# this creates a service per class whose id is the fully-qualified class name
PlotlyBundle\Twig\:
resource: '../../../../src/PlotlyBundle/Twig/*'
tags:
- { name: twig.runtime }
The example on the Symfony docs (Creating Lazy-Loaded Twig Extensions) need an update to mention that auto-wiring MUST be enabled (as explained in the autoconfigure Option) in order for the example to work.
I submitted a PR to the Symfony docs.