I tried to use
# app/config/services.yml
services:
project.controller.some:
class: Project\SomeBundle\Controller\SomeController
arguments: ['#templating']
and
namespace Project\SomeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
class SomeController
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function indexAction()
{
return $this->templating->render(
'SomeBundle::template.html.twig',
array(
)
);
}
}
in Symfony 4 flex. Now I get the error
ServiceNotFoundException
The service "project.controller.some" has a dependency on a non-existent service "templating".
Please tell me how to solve this. My composer.json already contains "symfony/templating": "^4.0" but this seems not to be enough.
Symfony 4 doesn't include Twig by default, so you need to install it first:
composer require twig
should do the trick. Also, with service autowiring in Symfony 4 you don't need to manually declare it in the services.yml.
Another solution is to add the configuration under framework as explained in the doc
# app/config/packages/framework.yaml
framework:
# ...
templating: { engines: ['twig'] }
With Symfony 4 you can also use new DI features (already available since Symfony 3.3):
_defaults
class named service
They will simplify all to:
# app/config/services.yml
services:
_defaults:
autowired: true
Project\SomeBundle\Controller\SomeController: ~
If you want to know more with real before/after examples, read How to refactor to new Dependency Injection features in Symfony 3.3
Related
I have a controller
use Doctrine\ORM\EntityManagerInterface:
class ExampleController{
public function someFunction(ExampleService $injectedService){
$injectedService->serviceFunction();
}
}
With a Service
use Doctrine\ORM\EntityManagerInterface;
class ExampleService{
public function __construct(EntityManagerInterface $em){
...
}
}
However, calls to someFunction() fail due to 0 parameters being passed (the EntityManagerInterface is not being injected). I am attempting to use the EntityManager from the Service. Autowiring is on. I've tried the solutions for Symfony3 but they don't seem to work unless I'm missing something.
Edit: Here is my services.yaml:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
Use only in Symfony 4.
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Name; //if you use entity for example Name
class ExampleService{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
function newName($code) // for example with a entity
{
$name = new Name();
$name->setCode($code); // use setter for entity
$this->em->persist($name);
$this->em->flush();
}
}
I know it's an old post, but just in case somebody struggles with this, there's a typo in the use statment:
use Doctrine\ORM\EntityManagerInterface: //<- see that's a colon, not a semicolon
Agree with Yarimadam. Service container, dependency injection and autowiring is not a story about injecting into methods. Dependencies injected into objects we are calling "services".
When application is up, service container is built injecting one services into another ones via class constructor or "set" method invocation.
Your ExampleController::someFunction is intended to be called only by you, so only way how this method will receive $injectedService as an argument, is that you will pass it evidently. This is the wrong way.
A classic symfony service with autowiring uses constructor injection method to inject dependencies. In your case, you don't have a constructor.
You may consider to add a constructor method and set dependency to a private class property. And use accordingly.
Or you can utilize setter injection.
Service Configuration:
services:
app.example_controller:
class: Your\Namespace\ExampleController
calls:
- [setExampleService, ['#exampleService']]
Controller Class:
class ExampleController
{
private $exampleService;
public function someFunction() {
$this->exampleService->serviceFunction();
}
public function setExampleService(ExampleService $exampleService) {
$this->exampleService = $exampleService;
}
}
I have the following class:
EmailNotification
namespace App\Component\Notification\RealTimeNotification;
use Symfony\Bridge\Twig\TwigEngine;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use App\Component\Notification\NotificationInterface;
class EmailNotification implements NotificationInterface
{
private $logNotification;
public function __construct(LogNotification $logNotification, \Swift_Mailer $mailer, EngineInterface $twigEngine)
{
$this->logNotification = $logNotification;
}
public function send(array $options): void
{
$this->logNotification->send($options);
dump('Sent to email');
}
}
I have the following service definition on my yml:
app.email_notification:
class: App\Component\Notification\RealTimeNotification\EmailNotification
decorates: app.log_notification
decoration_inner_name: app.log_notification.inner
arguments: ['#app.log_notification.inner', '#mailer', '#templating']
However, when i tried to run my app it throws an Exception saying:
Cannot autowire service
"App\Component\Notification\RealTimeNotification\EmailNotification":
argument "$twigEngine" of method "__construct()" has type
"Symfony\Bundle\FrameworkBundle\Templating\EngineInterface" but this
class was not found.
Why is that so?
Thanks!
You have to install symfony/templating
composer require symfony/templating
change a little bit config/packages/framework.yaml
framework:
templating:
engines:
- twig
I managed to do it with Twig Environment and HTTP Response
<?php
namespace App\Controller;
use Twig\Environment;
use Symfony\Component\HttpFoundation\Response;
class MyClass
{
private $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
public function renderTemplateAction($msg)
{
return new Response($this->twig->render('myTemplate.html.twig'));
}
}
Most likely you don't have Templating included in your project, in Symfony 4 you have to require it explicitly:
composer require symfony/templating
I am using Symfony 3.4.0, I try to load fixtures with:
php bin/console doctrine:fixtures:load
An error occurred while creating the data, what's wrong?
This command looks for all services tagged with doctrine.fixture.orm.
There is two ways to fix this problem.
First one: any class that implements ORMFixtureInterface will automatically be registered with this tag.
<?php
namespace AppBundle\DataFixtures\ORM;
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
class LoadFixtures implements ORMFixtureInterface
{
public function load(ObjectManager $manager)
{
#your code
}
}
Second one: You need manually tag doctrine.fixture.orm to DataFixtures in sevice.yml configuration.
services:
...
# makes classes in src/AppBundle/DataFixtures available to be used as services
# and have a tag that allows actions to type-hint services
AppBundle\DataFixtures\:
resource: '../../src/AppBundle/DataFixtures'
tags: ['doctrine.fixture.orm']
I tried #Alexander's solution but it's doesn't work for me.
I had resolved the same problem by adding the tag service to the class, Symfony doc on the services.yml file bundle:
BlogBundle/Resources/config/services.yml
Services:
...
# Fixtures services
BlogBundle\DataFixtures\ORM\PostFixture:
tags: [doctrine.fixture.orm]
...
My BlogBundle/DataFixtures/ORM/PostFixture.php class :
...
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
...
class PostFixture implements FixtureInterface
{
public function load(ObjectManager $manager)
{
...
}
}
Source Inspiration : Synfony doc -> Service container -> The autoconfigure Option
Hope it'll an alternative solution
Example for reusable bundle.
src/Acme/Bundle/UserBundle/DataFixtures/ORM/DataFixtures.php
<?php
namespace Acme\Bundle\UserBundle\DataFixtures\ORM;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
class DataFixtures extends Fixture
{
/**
* Load data fixtures with the passed EntityManager
*
* #param ObjectManager $manager
*/
public function load(ObjectManager $manager)
{
#your code
}
}
in app/config/services.yml
Acme\Bundle\UserBundle\DataFixtures\:
resource: '../../src/Acme/Bundle/UserBundle/DataFixtures/'
append your fixtures data:
php bin/console doctrine:fixtures:load --append
In 4.0.1 I have to implement service configuration to show Symfony my DataFixtures folder:
in config/services.yaml
services:
...
App\DataFixtures\:
resource: '%kernel.project_dir%/src/DataFixtures'
tags: [doctrine.fixture.orm]
if my class IMPLEMENTS FixtureInterface and without this config if it is EXTENDS Fixture
~/dev/domain.lan/src/ProductBundle/DataFixtures/ORM/ProductFixture.php
<?php
namespace ProductBundle\DataFixtures\ORM;
use Doctrine\Bundle\FixturesBundle\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use ProductBundle\Entity\Product;
class ProductFixture implements FixtureInterface
{
public function load(ObjectManager $manager)
{
// create 20 products! Bam!
for ($i = 0; $i < 20; $i++) {
$product = new Product();
$product->setName('Product name' . $i);
$manager->persist($product);
}
$manager->flush();
}
}
The problem is solved it was necessary to add a service: (app/config/services.yml)
services:
# Product service
ProductBundle\:
resource: '../../src/ProductBundle/*'
exclude: '../../src/ProductBundle/{Entity,Repository,Tests}'
use Doctrine\Bundle\FixturesBundle\Fixture
class ProductFixture extends Fixture implements FixtureInterface
see documentation: http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
After long research, found a solution.
This work with :
doctrine/doctrine-fixtures-bundle: ^3.0,
Symfony ^3.3
First
Define your Fixture.
<?php
namespace Where\MyFixtureBundle\FileFolder\IsLocated;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Nao\UserBundle\Entity\User;
class LoadData implements FixtureInterface
{
/**
* Load data fixtures with the passed EntityManager
*
* #param ObjectManager $manager
*/
public function load(ObjectManager $manager){
$object = new Entity();
$object->setFoo(bar)
...
$manager->persist($object);
$manager->flush();
}
}
Next, define a service in the bundle's service.yml file or directly in
"app/config/service.yml" (not recommanded)
# Fixtures service
your_service.name:
class: Full\Namespce\With\TheClassFixtureName
tags: [doctrine.fixture.orm] <-- important
Don't forget, just to be sure the following
composer du -o or composer dump-autoload -o
Try to execute your command now for load your data fixtures.
I also had to update the app/AppKernel.php and added the following the the bundles array:
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle()
After read above comment, i found solution inside #GuRu answer's :
"Second one: You need manually tag doctrine.fixture.orm to DataFixtures in sevice.yml configuration".
Then implements ORMFixtureInterface in your fixtures class.
. in fact, we have to add additionnal configuration inside services.yml to solve it.
Important to know, i notice this issue in version ~3.4 of symfony.
Best regard
I'm jumping here after several years just to document for myself the mixed of solution that I found in this thread.
I experienced the same issue and find the solution by using multiple answers and I hope it will help.
This were my code:
namespace App\DataFixtures;
use App\Entity\Book;
use App\Factory\{BookFactory, UserFactory};
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
UserFactory::new()->createOne([
'email' => 'user2#user.hr',
'roles' => ['ROLE_ADMIN']
]);
UserFactory::new()->createOne([
'email' => 'user3#user.hr',
'roles' => ['ROLE_USER']
]);
BookFactory::new()->createMany(25);
$manager->flush();
}
}
so I changed replaced extends Fixture ORMFixtureInterface. So new code looks like this:
namespace App\DataFixtures;
use App\Entity\Book;
use App\Factory\{BookFactory, UserFactory};
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;
use Doctrine\Persistence\ObjectManager;
class AppFixtures implements ORMFixtureInterface
{
public function load(ObjectManager $manager): void
{
UserFactory::new()->createOne([
'email' => 'user2#user.hr',
'roles' => ['ROLE_ADMIN']
]);
UserFactory::new()->createOne([
'email' => 'user3#user.hr',
'roles' => ['ROLE_USER']
]);
BookFactory::new()->createMany(25);
$manager->flush();
}
}
then I went to services.yml and the I inserted this:
App\DataFixtures\:
resource: '%kernel.project_dir%/src/DataFixtures'
tags: ['doctrine.fixture.orm']
so services.yml file looks like this:
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'
App\DataFixtures\:
resource: '%kernel.project_dir%/src/DataFixtures'
tags: ['doctrine.fixture.orm']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
and then, like #Spartacvs1 suggested, in your terminal execute this
composer dump-autoload -o
Following this documentation, I can create many channels which will create services with the following name monolog.logger.<channel_name>
How can I inject these services into my service with DI injection and autowiring ?
class FooService
{
public function __construct(LoggerInterface $loggerInterface) { }
}
Yaml
#existing
foo_service:
class: AppBundle\Services\FooService
arguments: ["#monolog.logger.barchannel"]
# what I want to do
foo_service:
autowire: true # how to inject #monolog.logger.barchannel ?
Starting from MonologBundle 3.5 you can autowire different Monolog
channels by type-hinting your service arguments with the following
syntax: Psr\Log\LoggerInterface $<channel>Logger. For example, to
inject the service related to the app logger channel use this:
public function __construct(LoggerInterface $appLogger)
{
$this->logger = $appLogger;
}
https://symfony.com/doc/current/logging/channels_handlers.html#monolog-autowire-channels
I wrote (maybe more complicated) method. I don't want to tag my autowired services to tell symfony which channel to use.
Using symfony 4 with php 7.1.
I built LoggerFactory with all additional channels defined in monolog.channels.
My factory is in bundle, so in Bundle.php add
$container->addCompilerPass(
new LoggerFactoryPass(),
PassConfig::TYPE_BEFORE_OPTIMIZATION,
1
); // -1 call before monolog
This is important to call this compiler pass before monolog.bundle because monolog after pass removes parameters from container.
Now, LoggerFactoryPass
namespace Bundle\DependencyInjection\Compiler;
use Bundle\Service\LoggerFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class LoggerFactoryPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
* #param ContainerBuilder $container
* #throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* #throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
*/
public function process(ContainerBuilder $container): void
{
if (!$container->has(LoggerFactory::class) || !$container->hasDefinition('monolog.logger')) {
return;
}
$definition = $container->findDefinition(LoggerFactory::class);
foreach ($container->getParameter('monolog.additional_channels') as $channel) {
$loggerId = sprintf('monolog.logger.%s', $channel);
$definition->addMethodCall('addChannel', [
$channel,
new Reference($loggerId)
]);
}
}
}
and LoggerFactory
namespace Bundle\Service;
use Psr\Log\LoggerInterface;
class LoggerFactory
{
protected $channels = [];
public function addChannel($name, $loggerObject): void
{
$this->channels[$name] = $loggerObject;
}
/**
* #param string $channel
* #return LoggerInterface
* #throws \InvalidArgumentException
*/
public function getLogger(string $channel): LoggerInterface
{
if (!array_key_exists($channel, $this->channels)) {
throw new \InvalidArgumentException('You are trying to reach not defined logger channel');
}
return $this->channels[$channel];
}
}
So, now you can inject LoggerFactory, and choose your channel
public function acmeAction(LoggerFactory $factory)
{
$logger = $factory->getLogger('my_channel');
$logger->log('this is awesome!');
}
After some searching I have found some kind of workaround using tags and manually injecting several parameters to autowired service.
My answer looks similar to #Thomas-Landauer. The difference is, I do not have to manually create logger service, as the compiler pass from monolog bundle does this for me.
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '#logger'
tags:
- { name: monolog.logger, channel: barchannel }
You can use the bind parameter:
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: true
bind:
$loggerMyApi: '#monolog.logger.my_api'
Then you can use it in your service's constructor:
use Psr\Log\LoggerInterface;
...
public function __construct(LoggerInterface $loggerMyApi)
{
...
}
I didn't find a way to autowire the very logger channel. However, I found a way to use autowire in principle, and inject just the logger manually. With your class FooService, this is how services.yml could look like (Symfony 3.3):
# services.yml
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '#monolog.logger.barchannel'
So the "trick" is to inject the logger channel explicitly, while still having all other dependencies of this service injected through autowiring.
From the documentation it is now possible to autowire based on the type hinting of the argument name.
// autowires monolog with "foo" channel
public function __construct(\Psr\Log\LoggerInterface $fooLogger);
Essentially, you've got two options:
First, service tagging:
services:
App\Log\FooLogger:
arguments: ['#logger']
tags:
- { name: monolog.logger, channel: foo }
Then you can use your CustomLogger as a dependency elsewhere
Second, you can rely on Monolog to auto-register loggers for each custom channel within the configuration:
# config/packages/prod/monolog.yaml
monolog:
channels: ['foo', 'bar']
You will then have these services available: monolog.logger.foo, 'monolog.logger.bar'
You can then retrieve them from the service container, or wire them in manually, e.g:
services:
App\Lib\MyService:
$fooLogger: ['#monolog.logger.foo']
You can read more here and here.
Recently I was implement single point access to the all registered loggers by MonologBundle.
And also I tried to do some better solution - and did auto-generated logger decorators. Each class decorates one object of one of the registered monolog channel.
Link to the bundle adrenalinkin/monolog-autowire-bundle
For those still struggling with this one.
In Symfony 4.3, I had, on top of that, add an alias for the specific channel, because without that, it was working only on the dev environment : when building, the Unit Tests were all failing because the custom logger was an undefined service.
monolog.logger.my_custom_logger:
alias: Psr\Log\LoggerInterface
public: true
App\Logger\MyLogger:
arguments:
$logger: '#monolog.logger.my_custom_logger'
How can I have a global variable in symfony template?
I did read this
but I prefer to fetch parameter from database, I think this service will be loaded on startup before it can fetch anything from db. Is it possible to do a trick to do so?
EDIT: Update in 2019 with Symfony 3.4+ syntax.
Create a Twig extension where you inject the Entity Manager:
Fuz/AppBundle/Twig/Extension/DatabaseGlobalsExtension.php
<?php
namespace Fuz\AppBundle\Twig\Extension;
use Doctrine\ORM\EntityManager;
use Twig\Extension\AbstractExtension;
use Twig\Extension\GlobalsInterface;
class DatabaseGlobalsExtension extends AbstractExtension implements GlobalsInterface
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function getGlobals()
{
return [
'myVariable' => $this->em->getRepository(FuzAppBundle\Entity::class)->getSomeData(),
];
}
}
Register your extension in your Fuz/AppBundle/Resources/config/services.yml:
services:
_defaults:
autowire: true
autoconfigure: true
Fuz\AppBundle\Twig\Extension\DatabaseGlobalsExtension: ~
Now you can do the requests you want using the entity manager.
Don't forget to replace paths and namespaces to match with your application.
As of this day, the class signature has changed. You must implement \ Twig_Extension_GlobalsInterface, without it, your globals won't show up.
class MyTwigExtension extends \Twig_Extension implements Twig_Extension_GlobalsInterface
{ }
Bye!
you can register a twig extension
services:
twig_extension:
class: Acme\DemoBundle\Extension\TwigExtension
arguments: [#doctrine]
tags:
- { name: twig.extension }
And then in the TwigExtension you can do as follows:
class TwigExtension extends \Twig_Extension
{
public function getGlobals() {
return array(
// your key => values to make global
);
}
}
So you could get a value from the database in this TwigExtension and pass it to the template with the getGlobals() function
Stay away from global variables.
Instead make a custom twig extension then inject the database connection as a parameter.
Something like:
services:
acme.twig.acme_extension:
class: Acme\DemoBundle\Twig\AcmeExtension
arguments: [#doctrine.dbal.default_connection]
tags:
- { name: twig.extension }
Details:
http://symfony.com/doc/current/cookbook/templating/twig_extension.html