Cannot inject Templating on Symfony 4 Service - symfony

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

Related

symfony6 - defining and using services.yaml in third part bundle

I'm making simple "MyCoreBundle" (MystertyCoreBundle) using symfony6.1 how to make bundle's doc.
I defined my bundle class vendor/mysterty/core-bundle/CoreBundle.class
<?php
namespace Mysterty\CoreBundle;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
class MystertyCoreBundle extends AbstractBundle
{
}
I defined some parameters and configuration in vendor/mysterty/core-bundle/config/services.yaml as defaults :
services:
Mysterty\CoreBundle\Controller\CoreController:
public: true
calls:
- method: setContainer
arguments: ["#service_container"]
parameters:
app.admin_email: "mymailATserver.com"
Then I made simple controller in vendor/mysterty/core-bundle/src/Controller/CoreController.php:
<?php
namespace Mysterty\CoreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
class CoreController extends AbstractController
{
#[Route('/', name: 'mty_default')]
public function indexNoLocale(): Response
{
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
$supportedLangs = explode('|', $this->getParameter('app.supported_locales'));
$lang = in_array($lang, $supportedLangs) ? $lang : $supportedLangs[0];
return $this->redirectToRoute('mty_home', ['_locale' => $lang]);
}
Finally, i added the bundle's routes to \config\routes.yaml
mysterty_core:
resource: "../vendor/mysterty/core-bundle/src/Controller/CoreController.php"
type: annotation
prefix: /
Here is the error i have on http://127.0.0.1:8000/ :
"Mysterty\CoreBundle\Controller\CoreController" has no container set, did you forget to define it as a service subscriber?
I try to make a shared bundle with default actions and components for all my symfony projects.
Solution (thx to helpers)
define loadExtension function in MyOwnBundle.php :
<?php
namespace MyOwn\MyOwnBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
class MyOwnBundle extends AbstractBundle
{
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
// load an XML, PHP or Yaml file
$container->import('../config/services.yaml');
}
}
It looks like Symfiony could not autoconfigure your controller. Try adding the #[AsController] attribute to your controller classes or add autoconfigure: true to your controller service definition in your services.yaml

how make services in symfony3

i have this controller
namespace InicioBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use EntidadesBundle\Entity\Usuarios;
use Symfony\Component\HttpFoundation\Session\Session;
class DefaultController extends Controller
{
private $session;
public function __construct(){
$this->session = new Session();
}
.....
public function ver_rol($rol){
if($this->sacarRol() === $rol){
return true;
}else{
return false;
}
}
}
and in the services.yml , i got this:
parameters:
#parameter_name: value
services:
app.rolSession:
class: InicioBundle\Controller\DefaultController
arguments: ["i dont know how get paramets"]
the problem is that it doesnt work, symfony return an error FileLoaderLoadException, that the services.yml does not caontain valid YAML
There is a space before parameters: in your services.yml file, maybe remove that and your yaml should be valid.
Also if you are passing no arguments to constructor you can just delete arguments: ["null"]
One more thing, IIRC you need to add FQCN as class name, so class: InicioBundle\Controller\Default => class: InicioBundle\Controller\DefaultController
While we are at the subject, you can type hint Request in your action and use it to getSession() or maybe inject #session service to your controller

Is there a way to inject EntityManager into a service

While using Symfony 3.3, I am declaring a service like this:
class TheService implements ContainerAwareInterface
{
use ContainerAwareTrait;
...
}
Inside each action where I need the EntityManager, I get it from the container:
$em = $this->container->get('doctrine.orm.entity_manager');
This is a bit annoying, so I'm curious whether Symfony has something that acts like EntityManagerAwareInterface.
Traditionally, you would have created a new service definition in your services.yml file set the entity manager as argument to your constructor
app.the_service:
class: AppBundle\Services\TheService
arguments: ['#doctrine.orm.entity_manager']
More recently, with the release of Symfony 3.3, the default symfony-standard-edition changed their default services.yml file to default to using autowire and add all classes in the AppBundle to be services. This removes the need for adding the custom service and using a type hint in your constructor will automatically inject the right service.
Your service class would then look like the following:
use Doctrine\ORM\EntityManagerInterface;
class TheService
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
// ...
}
For more information about automatically defining service dependencies, see https://symfony.com/doc/current/service_container/autowiring.html
The new default services.yml configuration file is available here: https://github.com/symfony/symfony-standard/blob/3.3/app/config/services.yml
Sometimes I inject the EM into a service on the container like this in services.yml:
application.the.service:
class: path\to\te\Service
arguments:
entityManager: '#doctrine.orm.entity_manager'
And then on the service class get it on the __construct method.
Hope it helps.
I ran into the same issue and solved it by editing the migration code.
I replaced
$this->addSql('ALTER TABLE user ADD COLUMN name VARCHAR(255) NOT NULL');
by
$this->addSql('ALTER TABLE user ADD COLUMN name VARCHAR(255) NOT NULL DEFAULT "-"');
I don't know why bin/console make:entity doesn't prompt us to provide a default in those cases. Django does it and it works well.
So I wanted to answer your subquestion:
This is a bit annoying, so I'm curious whether Symfony has something
that acts like EntityManagerAwareInterface.
And I think there is a solution to do so (I use it myself).
The idea is that you slightly change your kernel so tha it checks for all services which implement the EntityManagerAwareInterface and injects it for them.
You can also add write an EntityManagerAwareTrait that implements the $entityManager property and the setEntityManager()setter. The only thing left after that is to implement/use the interface/trait couple the way you would do for the Logger for example.
(you could have done this through a compiler pass as well).
<?php
// src/Kernel.php
namespace App;
use App\Entity\EntityManagerAwareInterface;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use function array_key_exists;
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
public function process(ContainerBuilder $container): void
{
$definitions = $container->getDefinitions();
foreach ($definitions as $definition) {
if (!$this->isAware($definition, EntityManagerAwareInterface::class)) {
continue;
}
$definition->addMethodCall('setEntityManager', [$container->getDefinition('doctrine.orm.default_entity_manager')]);
}
}
private function isAware(Definition $definition, string $awarenessClass): bool
{
$serviceClass = $definition->getClass();
if ($serviceClass === null) {
return false;
}
$implementedClasses = #class_implements($serviceClass, false);
if (empty($implementedClasses)) {
return false;
}
if (array_key_exists($awarenessClass, $implementedClasses)) {
return true;
}
return false;
}
}
The interface:
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\EntityManagerInterface;
interface EntityManagerAwareInterface
{
public function setEntityManager(EntityManagerInterface $entityManager): void;
}
The trait:
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\EntityManagerInterface;
trait EntityManagerAwareTrait
{
/** #var EntityManagerInterface */
protected $entityManager;
public function setEntityManager(EntityManagerInterface $entityManager): void
{
$this->entityManager = $entityManager;
}
}
And now you can use it:
<?php
// src/SomeService.php
declare(strict_types=1);
namespace App;
use Exception;
use App\Entity\EntityManagerAwareInterface;
use App\Entity\Entity\EntityManagerAwareTrait;
use App\Entity\Entity\User;
class SomeService implements EntityManagerAwareInterface
{
use EntityManagerAwareTrait;
public function someMethod()
{
$users = $this->entityManager->getRepository(User::Class)->findAll();
// ...
}
}

How to have a global variable coming from db in symfony template?

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

Fetching data through a custom repository in a Twig extension

I'd like to display new notifications on every page of my symfony 2 webapplication.
I was advised to use a Twig Extension for this. I've created a function getFriendRequests in that extension, but I don't know if it's good practice to get data through my custom repository in the twig extension: Right now it's giving me the error, that it can't find the getDoctrine method.
<?php
namespace Tennisconnect\DashboardBundle\Extension;
class NotificationTwigExtension extends \Twig_Extension
{
public function getFriendRequests($user)
{
$users = $this->getDoctrine()
->getRepository('TennisconnectUserBundle:User')
->getFriendRequests();
return count($users);
}
public function getName()
{
return 'notification';
}
public function getFunctions()
{
return array(
'getFriendRequests' => new \Twig_Function_Method($this, 'getFriendRequests'));
}
}
I don't think it is so bad to fetch your data directly from your twig extension. After all, if you don't do it here, you will need to fetch those records before and then pass them to the extension for display anyway.
The important point is to do the DQL/SQL stuff in the repository like you are already doing. This is important to separate database statements from other part of your project.
The problem you having is that the method getDoctrine does not exist in this class. From what I understand, you took this code from a controller which extends the FrameworkBundle base controller. The base controller of the FrameworkBundle defines this method.
To overcome this problem, you will have to inject the correct service into your extension. This is based on the dependency injection container. You certainly defined a service for your twig extension, something like this definition:
services:
acme.twig.extension.notification:
class: Acme\WebsiteBundle\Twig\Extension\NotificationExtension
tags:
- { name: twig.extension }
The trick is now to inject the dependencies you need like this:
services:
acme.twig.extension.notification:
class: Acme\WebsiteBundle\Twig\Extension\NotificationExtension
arguments:
doctrine: "#doctrine"
tags:
- { name: twig.extension }
And then, in you extension, you define a constructor that receives the doctrine dependency:
use Symfony\Bridge\Doctrine\RegistryInterface;
class NotificationTwigExtension extends \Twig_Extension
{
protected $doctrine;
public function __construct(RegistryInterface $doctrine)
{
$this->doctrine = $doctrine;
}
// Now you can do $this->doctrine->getRepository('TennisconnectUserBundle:User')
// Rest of twig extension
}
This is the concept of dependency injection. You can see another question I answered sometime ago about accessing services outside controller: here
Hope this helps.
Regards,
Matt
The same but with mongo:
in config.yml
services:
user.twig.extension:
class: MiProject\CoreBundle\Twig\Extension\MiFileExtension
arguments:
doctrine: "#doctrine.odm.mongodb.document_manager"
tags:
- { name: twig.extension }
and in your Twig\Extensions\MiFile.php
<?php
namespace MiProject\CoreBundle\Twig\Extension;
use Symfony\Component\HttpKernel\KernelInterface;
class MiFileExtension extends \Twig_Extension
{
protected $doctrine;
public function __construct( $doctrine){
$this->doctrine = $doctrine;
}
public function getTransactionsAmount($user_id){
return $results = $this->doctrine
->createQueryBuilder('MiProjectCoreBundle:Transaction')
->hydrate(false)
->getQuery()
->count();
}
Rest of mi code ...
}

Resources