I am trying to use Sonata Admin and followed the documentation step-by-step: https://symfony.com/bundles/SonataAdminBundle/current/getting_started/creating_an_admin.html
However, as I try to run the project it gives the following error:
In AdminSearchCompilerPass.php line 75: Service
"admin.media" must implement
Sonata\AdminBundle\Admin\AdminInterface.
This is my MediaAdmin Class:
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Symfony\Component\Form\Extension\Core\Type\TextType;
final class MediaAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $form): void
{
$form->add('name', TextType::class);
}
protected function configureDatagridFilters(DatagridMapper $datagrid): void
{
$datagrid->add('name');
}
protected function configureListFields(ListMapper $list): void
{
$list->addIdentifier('name');
}
protected function configureShowFields(ShowMapper $show): void
{
$show->add('name');
}
}
This is my services.yaml
parameters:
locale: 'en'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
admin.media:
class: App\Admin\MediaAdmin
tags:
- { name: sonata.admin, model_class: App\Entity\Media, manager_type: orm, label: Media }
I was just confronted with the same error. It was due to a class name miss-typing in services.yaml.
When encountering this error, one should check that the configured Admin class exists and can be loaded by the auto-loader:
Filename and class name are the same (e.g. App\Admin\MediaAdmin class is in src/Admin/MediaAdmin.php.
Class name in services.yaml matches the Admin class.
The error is miss-leading. I filed a bug report.
Related
I'm asking you for help because I got an error while trying to create a log service for tracing.
I followed this tutorial to perform this task:
https://nehalist.io/logging-events-to-database-in-symfony/
No problem occurred when creating the Log entity and the dbHandler service seems to run.
When I introduce the argument in services.yaml (arguments): ["#doctrine.orm.entity_manager"] and I use it in the MenuController constructor parameters, that's where the problem occurs.
Here is the error message: "It's a requirement to specify a Metadata Driver and pass it to Doctrine\ORM\Configuration::setMetadataDriverImpl()"
Thanks for your help.
Code error
services.yaml
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event
subscribers, etc.
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
arguments: ['#monolog.logger.db']
dbHandler:
class: App\Service\DbHandler
arguments: ["#doctrine.orm.entity_manager"]
monolog.yaml
monolog:
channels: ['db']
handlers:
dbHandler:
type: service
id: dbHandler
DbHandler.php
<?php
namespace App\Service;
use App\Entity\Log;
use Monolog\Handler\AbstractProcessingHandler;
use Doctrine\ORM\EntityManagerInterface;
class DbHandler extends AbstractProcessingHandler {
private $em;
public function __construct(EntityManagerInterface $em) {
parent::__construct();
$this->em = $em;
}
protected function write($record):void {
$log = new Log();
$log->setMessage($record['message']);
$log->setLevel($record['level']);
$log->setLevelName($record['level_name']);
$log->setContext($record['context']);
$this->em->persist($log);
$this->em->flush();
}
}
MenuController.php
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class MenuController extends AbstractController
{
private $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
/**
* #Route("/menu", name="menu")
* #IsGranted("ROLE_USER")
*/
public function menu(LoggerInterface $logger )
{
$logger->info('Test');
}
}
I am upgrading from Symfony 3.3 to Symfony 4.
KNPMenu was running fine under Symfony 3.3, but now I am seeing this exception:
An exception has been thrown during the rendering of a template ("The
menu "main" is not defined.").
services.yaml
App\Menu\MenuBuilder:
public: true
tags:
- { name: app.menu_builder.admin, method: createAdminMenu, alias: admin }
- { name: app.menu_builder.calendar, method: createCalendarMenu, alias: calendar }
- { name: app.menu_builder.main, method: createMainMenu, alias: main }
- { name: app.menu_builder.trailer, method: createTrailerMenu, alias: trailer }
- { name: app.menu_builder.user, method: createUserMenu, alias: user }
MenuBuilder
Namespace App\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\Security\Core\Security;
class MenuBuilder implements ContainerAwareInterface
{
use ContainerAwareTrait;
private $factory;
/**
* #param FactoryInterface $factory
*/
public function __construct( FactoryInterface $factory )
{
$this->factory = $factory;
}
I put a die('here'); in the __construct, it is never executed.
Using php bin/console debug:container menu yields:
Information for Service "App\Menu\MenuBuilder"
==============================================
---------------- -------------------------------------------------------------------------
Option Value
---------------- -------------------------------------------------------------------------
Service ID App\Menu\MenuBuilder
Class App\Menu\MenuBuilder
Tags app.menu_builder.admin (method: createAdminMenu, alias: admin)
app.menu_builder.calendar (method: createCalendarMenu, alias: calendar)
app.menu_builder.main (method: createMainMenu, alias: main)
app.menu_builder.trailer (method: createTrailerMenu, alias: trailer)
app.menu_builder.user (method: createUserMenu, alias: user)
Public yes
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
---------------- -------------------------------------------------------------------------
How can I get MenuBuilder __construct to execute?
Thank you all for your time.
This gist was helpful: https://gist.github.com/lsv/4d8044d21819f28f0dde52a3fb8211a0
This answer was helpful: How to avoid "knp_menu.factory" deprecation?
services.yaml
app.menu_builder:
class: App\Menu\MenuBuilder
app.menu.main:
class: Knp\Menu\MenuItem
factory: ['#app.menu_builder', 'createMainMenu']
arguments: { $options: [] }
tags:
- { name: knp_menu.menu, alias: main }
I used php bin/console debug:autowiring security to get the security authorization checker
Then I updated MenuBuilder.php like so:
Namespace App\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class MenuBuilder
{
private $factory;
private $security;
/**
* #param FactoryInterface $factory
*/
public function __construct( FactoryInterface $factory, AuthorizationCheckerInterface $security )
{
$this->factory = $factory;
$this->security = $security;
}
Adding the SecurityAuthorizationChecker allows me to control the menu items offered with this statement:
if( $this->security->isGranted( 'IS_AUTHENTICATED_FULLY' ) )
I'm following the FOSUserbundle Official documentation in order to change the redirection after a password resetting procedure. I created a new class named PasswordResettingListener and add it to services.yml
The class is executes up to the getSubscribedEvents() method but Symfony never calls my specific method that performs the redirect. Am I misunderstanding something?
src/AppBundle/EventListener/PasswordResettingListener.php:
<?php
// src/AppBundle/EventListener/PasswordResettingListener.php
namespace AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class PasswordResettingListener implements EventSubscriberInterface {
private $router;
public function __construct(UrlGeneratorInterface $router){
$this->router = $router;
}
/**
* CALLED!
*/
public static function getSubscribedEvents() {
return array(
FOSUserEvents::RESETTING_RESET_SUCCESS => 'onPasswordResettingSuccess',
);
}
public function onPasswordResettingSuccess(FormEvent $event) {
die("alone"); //never called
$url = $this->router->generate('tarifs'); //nevercalled
die("die alone !!!!!"); // never called
$event->setResponse(new RedirectResponse($url)); // ""
}
}
The service is recorded here (/app/config/services.yml):
# app/config/services.yml
services:
app.form.registration:
class: AppBundle\Form\RegistrationType
tags:
- { name: form.type, alias: app_user_registration }
app.password_resetting:
class: AppBundle\EventListener\PasswordResettingListener
arguments: [#router]
tags:
- { name: kernel.event_subscriber }
src/AppBundle/EventListener/PasswordResettingListener.php:
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class PasswordResettingListener implements EventSubscriberInterface
{
private $router;
private $security;
public function __construct(UrlGeneratorInterface $router, AuthorizationChecker $security) {
$this->router = $router;
$this->security = $security;
}
public static function getSubscribedEvents() {
return [
FOSUserEvents::RESETTING_RESET_SUCCESS => 'onPasswordResettingSuccess',
];
}
public function onPasswordResettingSuccess(FormEvent $event) {
$url = $this->router->generate('homepage');
$event->setResponse(new RedirectResponse($url));
}
}
and app/config/services.yml services:
acme_user.password_resetting:
class: src/AppBundle/EventListener/PasswordResettingListener
arguments: [ "#router", "#security.authorization_checker" ]
tags:
- { name: kernel.event_subscriber }
You can use FOSUser's routing in order to change the redirect.
When importing it's routes, you have two options: either you add all by adding
fos_user:
resource: "#FOSUserBundle/Resources/config/routing/all.xml"
or each of them, by path. This is the one for the user's profile:
fos_user_profile:
resource: "#FOSUserBundle/Resources/config/routing/profile.xml"
prefix: /profile
Just change it's prefix by your desired path and that's all.
PS: I tried to use Event Listener as well but it didn't worked.
Good luck!
I am able to use the new autowire feature of Symfony2 to inject a service into a service. However, I cannot inject a service into a controller. What am I not doing/doing wrong?
This is my services.yml file:
services:
home_controller:
class: AppBundle\Controller\HomeController
autowire: true
This is my ServiceConsumerDemo:
namespace AppBundle\Services;
class ServiceConsumerDemo {
private $serviceDemo;
public function __construct(ServiceDemo $serviceDemo) {
$this->serviceDemo = $serviceDemo;
}
public function getMessage(){
return $this->serviceDemo->helloWorld();
}
}
This is ServiceDemo:
namespace AppBundle\Services;
class ServiceDemo
{
public function helloWorld(){
return "hello, world!";
}
}
This is HomeController (which works):
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class HomeController extends Controller
{
/**
* #Route("/")
*
*/
public function indexAction(Request $request)
{
$message[0] = $this->get('service_consumer_demo')->getMessage();
return new \Symfony\Component\HttpFoundation\JsonResponse($message);
}
}
This is HomeController which does not work
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Services\ServiceConsumerDemo;
class HomeController extends Controller
{
private $serviceConsumerDemo;
public function __construct(ServiceConsumerDemo $serviceConsumerDemo) {
$this->serviceConsumerDemo = $serviceConsumerDemo;
}
/**
* #Route("/", name="homepage")
*
*/
public function indexAction(Request $request)
{
$message[0] = $this->serviceConsumerDemo->getMessage();
return new \Symfony\Component\HttpFoundation\JsonResponse($message);
}
}
This is the error thrown:
Catchable Fatal Error: Argument 1 passed to
AppBundle\Controller\HomeController::__construct() must be an instance
of AppBundle\Services\ServiceConsumerDemo, none given,
How do I access the Services in the Controller? I understand I need to declare the controller as a service. So I'm mentioning the controller in the services.yml file. Is there anything else that I need to do?
By default, controllers are not considered as services, so you can't use autowiring with them.
However, it's a simple enough fix - add a #Route annotation on the class (i.e. not on an action method in the class) with a service definition.
From the documentation:
The #Route annotation on a controller class can also be used to assign
the controller class to a service so that the controller resolver will
instantiate the controller by fetching it from the DI container
instead of calling new PostController() itself.
For example:
/**
* #Route(service="app_controller")
*/
class AppController extends Controller
{
private $service;
public function __construct(SomeService $service)
{
$this->service = $service;
}
/** #Route("/") */
public function someAction()
{
$this->service->doSomething();
}
}
and in your config:
app_controller:
class: AppBundle\Controller\AppController
autowire: true
Symfony 3.3+ May 2017 UPDATE!
Symfony 3.3 allows native way to approach this.
# app/config/services.yml
services:
_defaults:
autowire: true # all services in this config are now autowired
App\: # no more manual registration of similar groups of services
resource: ../{Controller}
This means App\Controller\SomeController is autowired by default and to be found in app/Controller/SomeController.php file.
You can read:
more about _defaults here
and more about PSR-4 service registration
Thanks to autowiring in Symfony 2.8+ I could create bundle, that adds autowiring even for controllers: Symplify/ControllerAutowire
You can read more about this in the article.
Usage is as simple as:
class YourController
{
public function __construct(SomeDependency $someDependency)
{
// ...
}
}
Nothing more is required.
You do not pass service to your controller in services.yml. Change it to:
services:
service_demo:
class: AppBundle\Services\ServiceDemo
service_consumer_demo:
class: AppBundle\Services\ServiceConsumerDemo
autowire: true
home_controller:
class: AppBundle\Controller\HomeController
autowire: true
arguments:
service: "#service_consumer_demo"
I'm trying to configure Sonata admin bundle, but I'm having this error:
Attempted to load class "ProductAdmin" from namespace
"Admin\AdminBundle\Admin". Did you forget a "use" statement for
another namespace?
protected function getSonata_Admin_ProductService() {
$instance = new \Admin\AdminBundle\Admin\ProductAdmin('sonata.admin.product', 'Admin\AdminBundle\Entity\Product', 'SonataAdminBundle:CRUD');
$instance->setTranslationDomain('AdminAdminBundle');
$instance->setFormTheme(array(0 => 'SonataDoctrineORMAdminBundle:Form:form_admin_fields.html.twig'));
}
Someone have an idea how to sloved this error ?
Thank you
Config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
- { resource: #AdminAdminBundle/Resources/config/admin.yml }
# app/config/config.yml
sonata_block:
default_contexts: [cms]
blocks:
# Enable the SonataAdminBundle block
sonata.admin.block.admin_list:
contexts: [admin]
# Your other blocks
Admin.yml
services:
sonata.admin.Product:
class: Admin\AdminBundle\Admin\ProductAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "Content", label: "Product" }
arguments:
- ~
- Admin\AdminBundle\Entity\Product
- ~
calls:
- [ setTranslationDomain, [AdminAdminBundle]]
ProductAdmin.php
<?php
// src/Acme/DemoBundle/Admin/PostAdmin.php
namespace Admin\AdminBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
class ProductAdmin extends Admin
{
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title', 'text', array('label' => 'Post Title'))
->add('author', 'entity', array('class' => 'Admin\AdminBundle\Entity\Product'))
->add('body') //if no type is specified, SonataAdminBundle tries to guess it
;
}
// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('title')
->add('author')
;
}
// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('title')
->add('slug')
->add('author')
;
}
}
AdminAdminExtension.php
<?php
namespace Admin\AdminBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class AdminAdminExtension extends Extension
{
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$loader->load('admin.yml');
}
}
autoload.php
<?php
use Doctrine\Common\Annotations\AnnotationRegistry;
use Composer\Autoload\ClassLoader;
/**
* #var ClassLoader $loader
*/
$loader = require __DIR__.'/../vendor/autoload.php';
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
return $loader;
The error indicates that your class file could not be autoloaded properly. Check if the namespace matches your path, and when in doubt, add a file to the root of your project loading the class to check whether the autoload config works:
<?php
require_once 'vendor/autoload.php';
var_dump(class_exists('a\b\c'));
The fact that your source file states another path than your namespace structure indicates that your class is in the wrong place.