Using getParameter() in admin class (not controller class) - symfony

namespace Acme\AdminBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use FOS\UserBundle\Model\UserManagerInterface;
class LessonAdmin extends Admin
{
public function
{
//I have tried these, but in vain.
$items = $this->container->getParameter('items');
or
$items = $this->getContainer()->getParameter('items');
I think this problem is related with Dependency Injection though, still unclear for me.
How can I inject getContainer item here??

In SonataAdmin, the DI container can be fetched from the Admin configuration pool:
<?php
use Sonata\AdminBundle\Admin\Admin;
class YourAdmin extends Admin
{
protected function yourAdminMethod()
{
$this->getConfigurationPool()->getContainer()->getParameter('your_parameter');
}
}
`

You can also inject parameters through setter in admin service definition:
services:
sonata.admin.lesson:
class: Acme\AdminBundle\Admin\LessonAdmin
tags:
- { name: sonata.admin, manager_type: orm, ...}
arguments:
- ~
- Acme\AppBundle\Entity\Lesson
- ~
calls:
- [ setItems, ["%items%"]]
And in your admin class:
class LessonAdmin extends Admin
{
public function setItems($items)
{
$this->items = $items;
}
In this way your parameter is accessible in your whole admin class.

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

Create Service implementing ContainerAwareInterface

The service don't invoke setContainer when I extend ContainerAware or implement ContainerAwareInterface.
class CustomService implements ContainerAwareInterface
{
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
How can I use the container from my service without injection one?
Is it required to pass the container object to the constructor or a setter?
Make a definition in your services.yml file
services:
bundle.service_name:
class: ...
calls:
- [ setContainer, [ #service_container ] ]
Only implementing the ContainerAware or ContainerAwareInterface is not enough. You have to call a setter injection with the service_container as argument. But injecting the complete container is not recommended. Better inject only the services you really need.
You have to put the service name inside quotes:
services:
bundle.service_name:
class: ...
calls:
- [ setContainer, [ '#service_container' ]]
There is also ContainerAwareTrait that you can use to implement ContainerAwareInterface.
This is an example of a full implementaion of a container aware service.
But be warned that injecting the whole container should be avoided. It is best practice to inject only the needed components. See the Law of Demeter - Wikipedia for more info on the subject.
To this end, this command will help you find all the available services :
# symfony < 3.0
php app/console debug:container
# symfony >= 3.0
php bin/console debug:container
Anyway, here's the full example.
The app/config/services.yml file:
app.my_service:
class: AppBundle\Service\MyService
calls:
- [setContainer, ['#service_container']]
The service class in src/AppBundle/Service/MyService.php:
<?php
namespace AppBundle\Service;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
class MyService implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function useTheContainer()
{
// do something with the container
$container = $this->container;
}
}
And finaly your controller in src/AppBundle/Controller/MyController.php:
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
/**
* My controller.
*/
class MyController extends Controller
{
/**
* #Route("/", name="app_index")
* #Method("GET")
*/
public function indexAction(Request $request)
{
$myService = $this->get('app.my_service');
$myService->useTheContainer();
return new Response();
}
}
You might want to automatically call setContainer on all classes that have the ContainerAwareInterface (based on this answer):
# services.yaml
services:
_instanceof:
Symfony\Component\DependencyInjection\ContainerAwareInterface:
calls:
- [setContainer, ['#service_container']]

How to make bundles communicate in Symfony2

I am making an administration menu bundle. I want other bundles to be able to add menu items to the bundle, but I also want the menu items to be deleted when a bundle is removed. What would be the best way to do this?
I could create a 'regenerate admin menu' action that scans all bundles for a certain YML and then store that in cache or database.
Is there a better way to do this ?
You could use tags to find services from other bundles (or even the same) and use them to build menu from them in compiler pass.
In this example I will assume you have your menu defined as a service (I will use service id acme_menu.menu).
// src/Acme/MenuBundle/DependencyInjection/Compiler/BuildMenuCompilerPass.php
namespace Acme\MenuBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class BuildMenuCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('acme_menu.menu')) {
return;
}
$definition = $container->getDefinition('acme_menu.menu');
$taggedServices = $container->findTaggedServiceIds('acme_menu.item');
foreach ($taggedServices as $id => $attributes) {
$definition->addMethodCall(
'addMenuItem',
array(new Reference($id))
);
}
}
}
Register it with your menu bundle:
// src/Acme/MenuBundle/AcmeMenuBundle.php
namespace Acme\MenuBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Acme\MenuBundle\DependencyInjection\Compiler\BuildMenuCompilerPass;
class AcmeMenuBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new BuildMenuCompilerPass());
}
}
And every service tagged as acme_menu.item will be added to the menu - them method addMenuItem will be called on the menu on it's creation with the tagged service as a parameter. So simply define:
# services.yml
services:
acme_demo.menu.item1:
# ...
tags:
- { name: acme_menu.item }
acme_demo.menu.item2:
# ...
tags:
- { name: acme_menu.item }

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