Base class controller with global twig service - symfony

First of all, I have to say that I have been seeing answers and documentation for several days but none of them answer my question.
The only and simple thing I want to do is to use the twig service as a global service in a BaseController.
This is my code:
<?php
namespace App\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use App\Service\Configuration;
use App\Utils\Util;
abstract class BaseController extends Controller
{
protected $twig;
protected $configuration;
public function __construct(\Twig_Environment $twig,Configuration $configuration)
{
$this->twig = $twig;
$this->configuration = $configuration;
}
}
Then in all my controllers extend the twig and configuration service, without having to inject it again & again.
//...
//......
/**
* #Route("/configuration", name="configuration_")
*/
class ConfigurationController extends BaseController
{
public function __construct()
{
//parent::__construct();
$this->twig->addGlobal('menuActual', "config");
}
As you can see the only thing I want is to have some services global to have everything more organized and also to create some global shortcuts for all my controllers. In this example I am assigning a global variable to make a link active in the menu of my template and in each controller I have to add a new value for menuActual, for example in the UserController the variable would be addGlobal('menuActual', "users").
I think this should be in the good practices of symfony which I don't find :(.
Having to include the \Twig_Environment in each controller to assign a variable to the view seems very repetitive to me. This should come by default in the controller.
Thanks

I've had that problem as well - trying to not have to repeat a bit of code for every controller / action.
I solved it using an event listener:
# services.yaml
app.event_listener.controller_action_listener:
class: App\EventListener\ControllerActionListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
#src/EventListener/ControllerActionListener.php
namespace App\EventListener;
use App\Controller\BaseController;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
/**
* Class ControllerActionListener
*
* #package App\EventListener
*/
class ControllerActionListener
{
public function onKernelController(FilterControllerEvent $event)
{
//fetch the controller class if available
$controllerClass = null;
if (!empty($event->getController())) {
$controllerClass = $event->getController()[0];
}
//make sure your global instantiation only fires if the controller extends your base controller
if ($controllerClass instanceof BaseController) {
$controllerClass->getTwig()->addGlobal('menuActual', "config");
}
}
}

Related

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();
// ...
}
}

Getting a Logger in Symfony WheNot in a Controller

I'm working on an EntityRepository class, and I need to dump some data to my log file. I can't use dump() because this isn't going to build a page; it's just going to return some JSON. Eventually.
Normally, in a Controller, I'd use:
$logger = $this->getLogger();
But I'm not in a Controller.
Thx for your help.
UPDATE: this is for forensic logging. I'm just using it for debugging purposes. It'll be removed afterwards.
I looked into this a bit. My first hunch is "Well, if you could define EntityRepositories as services, then that would make this easy because you could then just inject the logger"
But how do you inject the logger into repositories that doctrine is creating? It turns out you can specify your own repository factory
I'm going to assume all it needs is to implement the Doctrine\ORM\Repository\RepositoryFactory interface, but you'll probably want to subclass Doctrine\ORM\Repository\DefaultRepositoryFactory.
You will also need to create your own, base repository class that can hold a logger. Let's start there
src/AppBundle/Doctrine/EntityRepository.php
<?php
namespace AppBundle\Doctrine;
use Doctrine\ORM\EntityRepository;
use Psr\Log\LoggerInterface;
class LoggerAwareEntityRepository extends EntityRepository
{
protected $logger;
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
Now, the factory
src/AppBundle/Doctrine/LoggerAwareRepositoryFactory.php
<?php
namespace AppBundle\Doctrine;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use AppBundle\Doctrine\LoggerAwareEntityRepository;
class LoggerAwareRepositoryFactory extends DefaultRepositoryFactory
{
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
protected function createRepository(EntityManagerInterface $entityManager, $entityName)
{
$repository = parent::createRepository($entityManager, $entityName);
if ($repository instanceof LoggerAwareEntityRepository) {
$repository->setLogger($this->logger);
}
return $repository;
}
}
Now for the confguration glue to make it all work
app/config/services.yml
services:
logger_aware_repository_factory:
class: AppBundle\Doctrine\LoggerAwareRepositoryFactory
arguments: ['#logger']
app/config/config.yml
doctrine:
orm:
entity_managers:
default:
repository_factory: "#logger_aware_repository_factory"
Lastly, for the actual implementation
src/AppBundle/Entity/SomeCustomRepository.php
<?php
namespace AppBundle\Entity;
use AppBundle\Doctrine\LoggerAwareEntityRepository;
class SomeCustomRepository extends LoggerAwareEntityRepository
{
public function findSomethingCustom()
{
// Log something
$this->logger->log('message');
}
}
Full disclosure: this is untested code - there might be bugs!
Depending on what you want to log the most neat solution would be to create either a doctrine or doctrine entity listener, probably on post load. Inject the logger in the listener.
Once you decide you don't need it, just remove the listener.

Call function in another class Symfony2

I have a function that I need a few controllers. Can i call it in some other way than to extend the class in which the function is?
Question:
class CategoryController extends Controller
{
public function getCategoriesAction()
{
$categories = $this->getDoctrine()->getRepository('ModelBundle:Category')->findAll();
return $categories;
}
}
How call this function in PostController?
You can define a service and inject entity manager in it, for retrieving data:
dummy.manager:
class: AppBundle\Model\DummyManager
arguments:
entityManager: "#doctrine.orm.entity_manager"
In your service, call findAll() method:
class DummyManager {
/** #var EntityManager */
protected $entityManager;
public function __construct($entityManager) {
$this->entityManager = $entityManager;
}
public function getCategories(){
return $this->entityManager->getRepository('ModelBundle:Category')->findAll();
}
}
And last step, in your controller:
class CategoryController extends Controller
{
public function getCategoriesAction()
{
$categories = $this->container->get('dummy.manager')->getCategories();
//...
}
}
I hope this helps.
You can also use pure PHP with traits
trait CategoryProvider {
public function getCategoriesAction() {
$categories = $this->container->get('dummy.manager')>getCategories();
//...
}
}
After that you can use this trait in your controllers
class Controller1 {
use CategoryProvider;
public function indexAction(){
//...
$this->getCategoriesAction();
//...
}
}
class Controller2 {
use CategoryProvider;
public function anotherAction(){
//...
$this->getCategoriesAction();
//...
}
}
You should not define model-style getters on a controller. The notion of a "getCategoriesAction" method for a controller should be vacated from your brain.
What you want to do is define this fetching behavior in the model layer. This can be done a bunch of different ways. Cristian just posted one way so I'll offer another.
You can define the Entity Repository itself as a service.
app/config/services.yml
services:
model.repository.category:
class: ModelBundle\Entity\CategoryRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments: ['ModelBundle:Category']
Then in your controller (assuming it's a container-aware controller) you can do this
$categories = $this->get('model.repository.category')->findAll();
Another approach, how #Med mentioned, is to declare your controller as service:
services:
categoryController:
class: AppBundle\Controller\CategoryController
arguments:
entityManager: #entityManager

Create a Base-Controller Class which implements the ContainerAwareInterface

I followed the tutorial of Fabien Potiencier, about how to create your own Framework on top of the Symfony Components. Now i need a way. And I want to inject the Dependency Container to all my Controllers, without defining every single Controller as a Service.
In the orginal Symfony2 Framework all Controllers extends the Controller Class located in Symfony\Bundle\FrameworkBundle\Controller\Controller.php:
namespace Symfony\Bundle\FrameworkBundle\Controller;
class Controller extends ContainerAware
{
// ...
}
The Controller Class extends the ControllerAware Class, so you can do something like this in your Controller:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function someAction()
{
$this->container->get('dependencie_xyz);
}
}
So my question is: How can I accomplish the same in my Framework?
It took me a while, but i finally figured out how the Symfony2 Framework does it.
In the SymfonyFrameworkBundle is a custom ControllerResolver, which call the setContainer Method on the resolved controller. The controller has to be a instance of the ContainerAwareInterface.
Simplified version:
class ContainerAwareControllerResolver extends ControllerResolver
{
private $container;
public __construct(ContainerInterface $container)
{
$this->container = $container;
parent::__construct();
}
public function getController(Request $request)
{
$controller = parent::getController($request);
if($controller instanceof ContainerAware ){
$controller->setContainer($this->container);
}
}
}
Source:
https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerResolver.php
It is too simply. The next code will help you
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Symfony\Component\DependencyInjection\ContainerAware as ContainerAware;
class TestService extends ContainerAware
{
public function __construct(Container $container) {
// in your example from official doc 'dependencie_xyz' is a name of service
$this->setContainer($container); // call parent setContainer() method, for identifying container variable, from now you can access to ServiceContainer using $this->container variable
$test_param = $this->container->getParameter('test_param'); // get test_param from config.yml
}
}
in service.yml
write smthing like this
services:
test_service:
class: Symfony\Bundle\FrameworkBundle\TestService
arguments: ['#service_container']
and post service container as argument
If you are not implementing any interface on controller you can add the this way and it will work. This is a small modification to c4pone implementation.
/**
* Description of ContainerAwareControllerResolver
*
* #author sbc
*/
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
class ContainerAwareControllerResolver extends ControllerResolver {
private $container;
public function __construct(LoggerInterface $logger = null, ContainerInterface $container = null) {
parent::__construct($logger);
$this->container = $container;
}
protected function instantiateController($class) {
$new_class = new $class();
$new_class->setContainer($this->container);
return $new_class;
}
The Controller Class extends the ControllerAware Class, so you can do something like this in your Controller:
Well, this is not true. If we take a look at the signature of the ContainerAware class, we see that this added a setContainer method so we can set the container. Symfony2 has created the Controller::get method to make some live easier.
We can see how they do it in the source code:
/**
* Gets a service by id.
*
* #param string $id The service id
*
* #return object The service
*/
public function get($id)
{
return $this->container->get($id);
}
You can put this in your own Controller class and let all your controllers extend that controller class.

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