I'm getting an error with using the Symfony service container. This is the error I'm getting:
Catchable Fatal Error: Argument 3 passed to housesBundle\Model\PropertyDetailsModel::__construct() must be an instance of
housesBundle\Model\AuditModel, instance of sharedBundle\Model\AuditModel given,
called in /var/www/anildave/symfony/app/cache/dev/appDevDebugProjectContainer.php
on line 1876 and defined
500 Internal Server Error - ContextErrorException
services.yml
audit_model_service:
class: sharedBundle\Model\AuditModel
arguments: [#doctrine.dbal.default_connection]
property_details_model_service:
class: housesBundle\Model\PropertyDetailsModel
arguments: [#doctrine.dbal.default_connection,#request_stack,#audit_model_service]
calls:
- [ setContainer, [ #service_container ]]
PropertyDetailsModel.php
namespace housesBundle\Model;
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use sharedBundle\Model;
use sharedBundle\Helper;
class PropertyDetailsModel extends Controller
{
private $connection;
private $requestStack;
private $auditModel;
public function __construct(Connection $connection, RequestStack $requestStack, AuditModel $auditModel)
{
$this->connection = $connection;
$this->requestStack = $requestStack;
$this->auditModel = $auditModel;
}
I'm not sure what I'm doing wrong.
Thanks
Look at your uses :
use Doctrine\DBAL\Connection;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use sharedBundle\Model;
use sharedBundle\Helper;
use sharedBundle\Model\AuditModel; // <---- Look here, add this
Related
The application that I am building is not going to work in a traditional way. All the routes ar going to be stored in the database. And based on the route provided I need to get the correct controller and action to be executed.
As I understand this can be achieved using the "kernel.controller" event listener: https://symfony.com/doc/current/reference/events.html#kernel-controller
I am trying to use the docs provided, but the example here does not exacly show how to set up a new callable controller to be passed. And I have a problem here, because I dont know how to inject the service container to my newly called controller.
At first the setup:
services.yaml
parameters:
db_i18n.entity: App\Entity\Translation
developer: '%env(DEVELOPER)%'
category_directory: '%kernel.project_dir%/public/uploads/category'
temp_directory: '%kernel.project_dir%/public/uploads/temp'
product_directory: '%kernel.project_dir%/public/uploads/product'
app.supported_locales: 'lt|en|ru'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
App\Translation\DbLoader:
tags:
- { name: translation.loader, alias: db }
App\Extension\TwigExtension:
arguments:
- '#service_container'
tags:
- { name: twig.extension }
App\EventListener\RequestListener:
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onControllerRequest }
The listener:
RequestListener.php
<?php
namespace App\EventListener;
use App\Controller\Shop\HomepageController;
use App\Entity\SeoUrl;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
class RequestListener
{
public ManagerRegistry $doctrine;
public RequestStack $requestStack;
public function __construct(ManagerRegistry $doctrine, RequestStack $requestStack)
{
$this->doctrine = $doctrine;
$this->requestStack = $requestStack;
}
/**
* #throws Exception
*/
public function onControllerRequest(ControllerEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
if(str_contains($this->requestStack->getMainRequest()->getPathInfo(), '/admin')) {
return;
}
$em = $this->doctrine->getManager();
$pathInfo = $this->requestStack->getMainRequest()->getPathInfo();
;
$route = $em->getRepository(SeoUrl::class)->findOneBy(['keyword' => $pathInfo]);
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = new HomepageController();
$event->setController([$controller, $route->getAction()]);
break;
default:
break;
}
} else {
throw new Exception('Route not found');
}
}
}
So this is the most basic example. I get the route from the database, if it a "homepage" route, I create the new HomepageController and set the action. However I am missing the container interface that I dont know how to inject. I get this error:
Call to a member function has() on null
on line: vendor\symfony\framework-bundle\Controller\AbstractController.php:216
which is:
/**
* Returns a rendered view.
*/
protected function renderView(string $view, array $parameters = []): string
{
if (!$this->container->has('twig')) { // here
throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
}
return $this->container->get('twig')->render($view, $parameters);
}
The controller is as basic as it gets:
HomepageController.php
<?php
namespace App\Controller\Shop;
use App\Repository\CategoryRepository;
use App\Repository\Shop\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomepageController extends AbstractController
{
#[Route('/', name: 'index', methods: ['GET'])]
public function index(): Response
{
return $this->render('shop/index.html.twig', [
]);
}
}
So basically the container is not set. If I dump the $event->getController() I get this:
RequestListener.php on line 58:
array:2 [▼
0 => App\Controller\Shop\HomepageController {#417 ▼
#container: null
}
1 => "index"
]
I need to set the container by doing $controller->setContainer(), but what do I pass?
Do not inject the container, controllers are services too and manually instanciating them is preventing you from using constructor dependency injection. Use a service locator which contains only the controllers:
Declared in config/services.yaml:
# config/services.yaml
services:
App\EventListener\RequestListener:
arguments:
$serviceLocator: !tagged_locator { tag: 'controller.service_arguments' }
Then in the event listener, add the service locator argument and fetch the fully configured controllers from it:
# ...
use App\Controller\Shop\HomepageController;
use Symfony\Component\DependencyInjection\ServiceLocator;
class RequestListener
{
# ...
private ServiceLocator $serviceLocator;
public function __construct(
# ...
ServiceLocator $serviceLocator
) {
# ...
$this->serviceLocator = $serviceLocator;
}
public function onControllerRequest(ControllerEvent $event)
{
# ...
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = $this->serviceLocator->get(HomepageController::class);
# ...
break;
default:
break;
}
}
# ...
}
}
If you dump any controller you will see that the container is set. Same will go for additionnal service that you autowire from the constructor.
I need to decorate an Symfony EntityManager and access a property that I inject in it from ouside, but it always says that this is Undefined Property.
This is config:
app.decorated.doctrine.orm.default_entity_manager:
class: App\Decorator\EntityManager
public: false
decorates: "doctrine.orm.default_entity_manager"
arguments:
- "#app.decorated.doctrine.orm.default_entity_manager.inner"
- '%photo_directory_absolute%'
So for example Im passing value '%photo_directory_absolute%'
This is part of decorator:
namespace App\Decorator;
class EntityManager extends EntityManagerDecorator
{
public $photoDir;
public function __construct(EntityManagerInterface $wrapped, string $photoDir)
{
parent::__construct($wrapped);
$this->photoDir = $photoDir;
}
Then I want to get this 'photoDir' value from Hydrator
namespace App\Hydrator;
class UserAppHydrator extends ObjectHydrator
{
protected function hydrateRowData(array $data, array &$result)
{
$hydrated_result = array();
parent::hydrateRowData($data, $hydrated_result);
die(var_dump($this->_em->photoDir));
And this shows me an error :
"Notice: Undefined property: Doctrine\\ORM\\EntityManager::$photoDir"
What I am missing? Thanks
You have to override the method newHydrator of the EntityManagerDecorator to get your EntityManager injected into a Hydrator. If that is missing, the $wrapped instance will be used.
I was using Authentication success handler to populate some values on session on every success login. I wanted some database operation to be done so i pass #doctrine.dbal.default_connection from my config file. Here is my config file where i override success_handler function.
services:
security.authentication.success_handler:
class: XYZ\UserBundle\Handler\AuthenticationSuccessHandler
arguments: ["#security.http_utils", {}, #doctrine.dbal.default_connection]
tags:
- { name: 'monolog.logger', channel: 'security' }
In AuthenticationSuccessHandler.php my code is like this...
namespace Sourcen\UserBundle\Handler;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\Security\Http\HttpUtils;
use Doctrine\DBAL\Connection;
class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler {
private $connection;
public function __construct( HttpUtils $httpUtils, array $options, Connection $dbalConnection ) {
$this->connection = $dbalConnection;
parent::__construct( $httpUtils, $options );
}
public function onAuthenticationSuccess( Request $request, TokenInterface $token ) {
$response = parent::onAuthenticationSuccess( $request, $token );
// DB CODE GOES
return $response;
}
}
This is working when i execute some controller URL directly. But when i execute my app home url like "www.xyz.com/web" it throws following error...
Catchable fatal error: Argument 3 passed to XYZ\UserBundle\Handler\AuthenticationSuccessHandler::__construct() must be an instance of Doctrine\DBAL\Connection, none given, called in /opt/lampp/xyz/app/cache/prod/appProdProjectContainer.php on line 1006 and defined in /opt/lampp/xyz/src/Sourcen/UserBundle/Handler/AuthenticationSuccessHandler.php on line 18
Any idea how it can be solved ?
You don't need to extends the DefaultAuthenticationSuccessHandlerclass.
Try to define your service class like:
namespace XYZ\UserBundle\Handler;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Doctrine\DBAL\Connection;
class AuthenticationSuccessHandler {
private $connection;
public function __construct( Connection $dbalConnection ) {
$this->connection = $dbalConnection;
}
public function onAuthenticationSuccess( InteractiveLoginEvent $event ) {
$user = $event->getAuthenticationToken()->getUser();
// DB CODE GOES
return $response;
}
}
and configure your service tagged to the event listener component security.interactive_login
services:
security.authentication.success_handler:
class: XYZ\UserBundle\Handler\AuthenticationSuccessHandler
arguments: [#doctrine.dbal.default_connection]
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login'. method:'onAuthenticationSuccess' }
PS: Why don't you use the doctrine.orm.entity_managerinstead of doctrine.dbal.default_connection? (In my sf i haven't this service dumped in the php app/console container:debug command)
I am trying to create a service in Symfony2 to automatically pass Doctrine\ORM\EntityManager to __construct to avoid having to pass it each time I instantiate the class, i.e.
// use this
$TestClass= new TestClass;
// instead of this
$entityManager = $this->getDoctrine()->getEntityManager();
$TestClass= new TestClass($entityManager);
I created a class EntityManagerUser, tried to register it as a service and TestClass extends that.
services.yml is included, as another service works, and I've double-checked by adding (then removing) a syntax error.
I read the docs, this, this and this and I've ended up with the code below, which doesn't pass #doctrine.orm.entity_manager. However, the controller_listener service does receive #templating.
I've cleared cache via the console and manually deleted app/cache but I still see this error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Test\TestBundle\ServiceUser\EntityManagerUser::__construct() must be an instance of Test\TestBundle\ServiceUser\Doctrine\ORM\EntityManager, none given, called in D:\Documents\www\Test\live\src\Test\TestBundle\Controller\MyController.php on line 84 and defined in D:\Documents\www\Test\live\src\Test\TestBundle\ServiceUser\EntityManagerUser.php line 14
services.yml
services:
# this one doesn't throw an error and passes #templating to __construct
test.eventlistener.before_controller_listener:
class: Test\TestBundle\Eventlistener\BeforeControllerListener
arguments: [ #templating ]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
# the following one doesn't pass #doctrine.orm.entity_manager
test.service_user.entity_manager_user:
class: Test\TestBundle\ServiceUser\EntityManagerUser
arguments: [ #doctrine.orm.entity_manager ]
src/Test/TestBundle/ServiceUser/EntityManagerUser.php
namespace Test\TestBundle\ServiceUser;
use Doctrine\ORM\EntityManager;
class EntityManagerUser{
protected $entityManager;
public function __construct(EntityManager $entityManager){
$this->entityManager = $entityManager;
// N.B. it's not possible to do it this way:
// $this->entityManager = new EntityManager;
}
// also tried public function __construct($entityManager){
// and public function __construct(Doctrine\ORM\EntityManager $entityManager){
}
src/Test/TestBundle/Classes/TestClass.php
namespace Test\TestBundle\Classes\TestClass;
use Test\TestBundle\ServiceUser\EntityManagerUser;
class TestClass extends EntityManagerUser{
/* currently no functions */
}
In my controller, line 84
$test= new TestClass;
// I tested that this throws the same error, it does // $test= new EntityManagerUser;
What have I missed?
Services only get their arguments if they are called through the service constructor:
$this->get('test.service_user.entity_manager_user');
Declaring the class as a service doenst make a difference if you create a new class and extend the original.
What you could do is also declare this new class as a service and still have it extend the base class.
test.classes.test_class:
class: Test\TestBundle\Classes\TestClass\TestClass
arguments: [ #doctrine.orm.entity_manager ]
then you dont have to define the constructor in the extended class because it is the parent.
then get the class by doing:
$testClass = $this->get('test.classes.test_class');
//will be instanceof Test\TestBundle\Classes\TestClass\TestClass
I worked out how to do what I wanted, which was not define entityManager each time I was required in an instanciated class.
It made sense to rename EntityManagerUser to ContainerListener, a static class, and inject #service_container into it through services, so it can then also return other classes.
namespace Test\TestBundle\EventListener;
class ContainerListener{
static $container;
// knock out the parent::onKernelRequest function that we don't want
public function onKernelRequest($event){
return;
}
public function __construct($container){
self::$container = $container;
}
static function twig(){
return self::$container->get('twig');
}
static function entityManager(){
return self::$container->get('doctrine')->getEntityManager();
}
static function entityManagerConnection(){
$entityManager = self::$container->get('doctrine')->getEntityManager();
return $entityManager->getConnection();
}
}
services.yml
services:
test.event_listener.container_listener:
class: Test\TestBundle\EventListener\ContainerListener
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
BaseClass.php gets entityManager
namespace Test\TestBundle\Class;
use Test\TestBundle\EventListener\ContainerListener;
class BaseClass{
public function __construct(){
$this->entityManager = ContainerListener::entityManager();
}
}
TestClass.php extends BaseClass as do others
class TestClass extends BaseClass(){
function someFunction(){
// etc etc
// $this->entityManager exists with no construct and without passing it
$stmt = $this->entityManager->getConnection()->prepare( $some_sql );
// etc etc
}
}
Somewhere in DefaultController.php
# nope # $entityManager = $this->getDoctrine()->getEntityManager();
# nope # $TestClass= new TestClass($entityManager);
$TestClass= new TestClass; # win!
I'm trying to build a custom validator running as a service (mainly for getting the entity manager).
I followed the doc and some blog posts but can't make it working. I have this error
Catchable Fatal Error: Argument 1 passed to
D\AjaxBundle\Validator\Constraints\SelectTypeValidator::__construct() must implement
interface Doctrine\Common\Persistence\ObjectManager, none given, called in
/AJAX/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Validator/ConstraintValidatorFactory.php on line 67 and defined in
/AJAX/src/D/AjaxBundle/Validator/Constraints/SelectTypeValidator.php line 14
services.yml
validator.selectType:
class: D\AjaxBundle\Validator\Constraints\SeletTypeValidator
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: validator.selectType, alias: selectType }
SelectTypeValidator:
namespace D\AjaxBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\Common\Persistence\ObjectManager;
class SelectTypeValidator extends ConstraintValidator
{
private $om;
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
public function validate($value, Constraint $constraint)
{
$fieldOne = $this->om->getRepository('DAjaxBundle:City')->findOneBy(array('id' =>
$value->getId()));
if ($fieldOne == null) {
$this->context->addViolation($constraint->message, array('%string%' => $value->
getId()));
}
}
}
SelectType
namespace D\AjaxBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class SelectType extends Constraint
{
public $message ='jakis text';
public function validateBy()
{
return 'selectType';
}
}
The service tag name should be validator.constraint_validator instead of validator.selectType
I'm not sure if this'll make a difference but it's worth a try...I never wrap service arguments with quotes:
arguments: [#doctrine.orm.entity_manager]