I have a controller, ApiBundle\Controller\UserController, and it has the following contents:
<?php
namespace ApiBundle\Controller;
use Doctrine\ORM\EntityManager;
use FOS\RestBundle\Controller\Annotations\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity\User;
use ApiBundle\Form\RegisterType;
use ApiBundle\Utility\ApiUtility;
class UserController extends ApiController
{
/**
* #var EntityManager $em
*/
private $em;
/**
* UserController constructor.
* #param EntityManager $entityManager
*/
function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* #Route("register")
* #Method({"POST"})
* #return \Symfony\Component\HttpFoundation\Response
* #param Request $request
*/
public function registerUser(Request $request)
{
$data = ApiUtility::getContentAsArray($request);
$user = new User();
$form = $this->createForm(new RegisterType(), $user);
$form->submit($data);
if($form->isValid()) {
$user->setUsername($request->get('email'));
$this->em->persist($user);
$this->em->flush();
}else{
return ApiUtility::returnResponse($form->getErrors());
}
}
}
It extends API Controller, seen here:
<?php
namespace ApiBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
class ApiController extends FOSRestController
{
}
And it has the following services:
services:
api.controller.user:
class: ApiBundle\Controller\UserController
public: false
parent: api.controller
arguments: ["#doctrine.orm.entity_manager"]
api.controller:
class: ApiBundle\Controller\ApiController
calls:
- [setContainer, ['#service_container']]
No matter what I do, I can't get the container to inject. Right now, when I just try to get doctrine into the container through the constructor, I get Type error: Too few arguments to function ApiBundle\Controller\UserController::__construct(), 0 passed in app/cache/dev/classes.php on line 2230 and exactly 1 expected, if I switch it back to $this->container->get('doctrine.orm.entity_manager') or getEntityManager, I get container is null. How can I fix this?
Related
I want to refactoring my code, but now i have error and don't understand what.
Objectif : Don't need to pass parameters when call TokenService, and use autowiring to autowiring EntityManager & Request, and don't set it when controller call service.
Cannot resolve argument $tokenService of App\Controller\TokenController::showTokens()
Cannot autowire service App\Service\TokenService
argument $request of method __construct() references class Symfony\Component\HttpFoundation\Request but no such service exists.
Before :
/src/Controller/TokenController.php
<?php
namespace App\Controller;
use App\Service\TokenService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* #Route("/v1")
*/
class TokenController
{
/** #var EntityManagerInterface $em */
private $em;
/** #var Request $request */
private $request;
/**
* TokenService constructor.
*
* #param Request $request
* #param EntityManagerInterface $em
*/
public function __construct(Request $request, EntityManagerInterface $em)
{
$this->request = $request;
$this->em = $em;
}
public function showTokens(Request $request, EntityManagerInterface $em): JsonResponse
{
$tokenService = new TokenService($request, $em);
return $tokenService->getTokens();
}
}
/src/Service/TokenService.php
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Class TokenService
* #package App\Service
*/
class TokenService
{
/** #var EntityManagerInterface $em */
private $em;
/** #var Request $request */
private $request;
/**
* TokenService constructor.
*
* #param Request $request
* #param EntityManagerInterface $em
*/
public function __construct(Request $request, EntityManagerInterface $em)
{
$this->request = $request;
$this->em = $em;
}
public function getTokens()
{
return true;
}
}
After :
/config/services.yaml
parameters:
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
App\Service\TokenService: ~
/src/Controller/TokenController.php
<?php
namespace App\Controller;
use App\Service\TokenService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* #Route("/v1")
*/
class TokenController
{
public function showTokens(Request $request, EntityManagerInterface $em, TokenService $tokenService): JsonResponse
{
return $tokenService->getTokens();
}
/src/Service/TokenService.php
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Class TokenService
* #package App\Service
*/
class TokenService
{
/** #var EntityManagerInterface $em */
private $em;
/** #var Request $request */
private $request;
/**
* TokenService constructor.
*
* #param Request $request
* #param EntityManagerInterface $em
*/
public function __construct(Request $request, EntityManagerInterface $em)
{
$this->request = $request;
$this->em = $em;
}
public function getTokens()
{
return true;
}
}
Thanks !
I guess it's been awhile since we had a good request stack question. I did a bit of a search and did not find any answer that was directly applicable and provided a decent explanation.
The basic issue is that the Symfony framework supports nested requests. You get these, for example, when using embedded controllers. So there is no actual request service. There actually used to be when Symfony 2.0 was first released but it was a real mess. Supporting nested request services was done at the container level and it was not fun.
So a big hammer known as the request stack was introduced to solve the problem once and for all. You inject the request stack instead of the request and then access the request when you actually need it.
class TokenService
{
private $em;
private $requestStack;
public function __construct(RequestStack $requestStack, EntityManagerInterface $em)
{
$this->requestStack = $requestStack;
$this->em = $em;
}
public function getTokens()
{
$request = $this->requestStack->getMasterRequest(); // or possibly getCurrentRequest depending on where the tokens are
return true;
}
Having said that, I would personally just pass the request from the controller. Doing so gets rid of that 'it depends' comment of mine. I also thinks it reduces the 'magic' involved just a bit. Both approaches will work.
class TokenService
{
public function getTokens(Request $request)
{
return true;
}
...
class TokenController
{
public function showTokens(Request $request, TokenService $tokenService): JsonResponse
{
return $tokenService->getTokens($request);
}
I want to make some operations before controller load and I have problem with include interfaces or classes into function.
My question is how should I do it to start working?
There is a code:
~/src/Controller/ControllerListener.php
<?php
namespace App\EventListener;
use App\Controller\DailyWinController;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ControllerListener implements DailyWinController
{
public function onKernelController(FilterControllerEvent $event, LoggerInterface $logger) {
$logger->alert('Working');
}
}
~/src/Controller/DailyWinController.php
<?php
namespace App\Controller;
interface DailyWinController {
// maybe there something?
}
~/src/Controller/UserController.php
<?php
namespace App\Controller;
use App\Entity\User;
use App\Entity\DailyWin;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class UserController extends Controller implements DailyWinController
{
/**
* #Route("/user", name="user")
* #param AuthorizationCheckerInterface $authChecker
* #param UserInterface $user
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function user(AuthorizationCheckerInterface $authChecker, UserInterface $user = null, LoggerInterface $logger) {
if ($authChecker->isGranted('ROLE_USER') === false) {
return $this->redirectToRoute('logowanie');
}
$logger->warning('Logger is working');
$em = $this->getDoctrine()->getManager();
$DWrep = $em->getRepository(DailyWin::class);
$userId = $user->getId();
$dailyWin = $DWrep->findOneBy(['userId' => $userId]);
return $this->render('andprize/user/index.html.twig', array(
'dailyWin' => $dailyWin,
'userId' => $userId
));
}
}
I have the following problem:
FatalThrowableError Type error: Argument 2 passed to
App\EventListener\ControllerListener::onKernelController() must
implement interface Psr\Log\LoggerInterface, string given
You have to inject the logger to the listener.
<?php
namespace App\EventListener;
use App\Controller\DailyWinController;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ControllerListener implements DailyWinController
{
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger=$logger;
}
public function onKernelController(FilterControllerEvent $event) {
$this->logger->alert('Working');
}
}
I got this error:
Warning: Missing argument 1 for Doctrine\ORM\EntityRepository::__construct()
I used phpstorm for coding and the line in TestController.php
new ProductRepository();
is underlined with a message:
Required parameter $em missing less.
Invocation parameter types are not compatible with declared.
But I don't use the $em parameter yet.
I use 3 files:
AppBundle
|__Controller
| |__ TestController.php
|__Entity
|_______ Product.php
|_______ ProductRepository.php
TestController.php:
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Doctrine\ORM\EntityRepository;
use AppBundle\Entity\ProductRepository;
use Symfony\Component\HttpFoundation\Response;
class TestController extends Controller
{
/**
* #Route("/test", name="test")
*/
public function indexAction()
{
$pr = new ProductRepository();
return new Response('OK '.$pr->test());
}
}
Product.php:
<?php
// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="AppBundle\Entity\ProductRepository")
*/
class Product
{ /* ......CODE ...*/}
ProductRepository.php:
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function test()
{
return 'hello';
}
}
Get repository through doctrine service, controller returns doctrine service by getDoctrine method
public function indexAction()
{
$pr = $this->getDoctrine()->getRepository('AppBundle:Product');
return new Response('OK '.$pr->test());
}
You shouldn't create repositories directly. Use EntityManager for this purpose. You can try the following code:
class TestController extends Controller
{
private $entityManager;
public function __construct(\Doctrine\ORM\EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #Route("/test", name="test")
*/
public function indexAction()
{
$pr = $this->entityManager->getRepository('AppBundle\Entity\ProductRepository');
return new Response('OK '.$pr->test());
}
}
I'm trying to implement events on FOSUserBundle
<?php
namespace EasyApp\UserBundle\Service;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\Bundle\DoctrineBundle\Registry as Doctrine;
use EasyApp\UserBundle\Entity\UserLogin;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserBundle;
class LoginManager implements EventSubscriberInterface
{
/** #var \Symfony\Component\Security\Core\SecurityContext */
private $securityContext;
/** #var \Doctrine\ORM\EntityManager */
private $em;
/**
* Constructor
*
* #param SecurityContext $securityContext
* #param Doctrine $doctrine
*/
public function __construct(SecurityContext $securityContext, Doctrine $doctrine)
{
$this->securityContext = $securityContext;
$this->em = $doctrine->getEntityManager();
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::SECURITY_IMPLICIT_LOGIN => 'onSecurityImplicitLogin',
FOSUserEvents::REGISTRATION_COMPLETED=> 'onRegistrationCompleted'
);
}
public function onSecurityImplicitLogin(UserEvent $event)
{
die;
$user = $event->getAuthenticationToken()->getUser();
$request = $event->getRequest();
if ($this->securityContext->isGranted('IS_AUTHENTICATED_FULLY')) {
// user has just logged in
$this->saveLogin($request, $user);
}
}
public function onRegistrationCompleted(FilterUserResponseEvent $event){
$user = $event->getAuthenticationToken()->getUser();
$request = $event->getRequest();
saveLogin($request, $user);
}
public function saveLogin($request, $user){
$login = new UserLogin();
$login->setIp($request->getClientIp());
$login->setUser($user);
$this->em->persist($login);
$this->em->flush();
}
}
And my service
services:
user_login_manager:
class: 'EasyApp\UserBundle\Service\LoginManager'
arguments: ['#security.context', '#doctrine']
tags:
- { name: 'kernel.event_subscriber', event: 'fos_user.security.interactive_login'}
- { name: 'kernel.event_subscriber', event: 'fos_user.registration.completed'}
But I have problems. When I login nothing happen, the getSubscribedEvents() is called but not onSecurityImplicitLogin(UserEvent $event)
And the other problem is on register. Following error occurs on onSecurityImplicitLogin(UserEvent $event)
Catchable Fatal Error: Argument 1 passed to EasyApp\UserBundle\Service\LoginManager::onSecurityImplicitLogin() must be an instance of EasyApp\UserBundle\Service\UserEvent, instance of FOS\UserBundle\Event\UserEvent given in /Users/antoine/Documents/projects/easyApp/application/src/EasyApp/UserBundle/Service/LoginManager.php line 46
and if I comment this line I got the same error on onRegistrationCompleted(FilterUserResponseEvent $event)
Edit
services:
user_login_manager:
class: 'EasyApp\UserBundle\Service\LoginManager'
arguments: ['#security.context', '#doctrine']
tags:
- { name: 'kernel.event_subscriber'}
- { name: 'kernel.event_listener', event: 'security.interactive_login' }
Two points:
In the service definition - { name: 'kernel.event_subscriber'} is enough, no event entry. This the difference between subscriber and listener. A listener can only listen to one event, defined trough event and method params in the service definition. A subscriber can listen to multiple events, defined trough the method getSubscribedEvents(), so no further params in the service definition needed.
Second point, you are type hinting against the event classes (UserEvent andFilterUserResponseEvent), but you never imported them, so PHP assumes them in the same namespace (as said in the error message).
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\Event\FilterUserResponseEvent;
in my services constructor
public function __construct(
EntityManager $entityManager,
SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
$this->entityManager = $entityManager;
I pass entityManager and securityContext as argument.
also my services.xml is here
<service id="acme.memberbundle.calendar_listener" class="Acme\MemberBundle\EventListener\CalendarEventListener">
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="security.context" />
but now,I want to use container in services such as
$this->container->get('router')->generate('fos_user_profile_edit')
how can I pass the container to services?
It's easy, if service extends ContainerAware
use \Symfony\Component\DependencyInjection\ContainerAware;
class YouService extends ContainerAware
{
public function someMethod()
{
$this->container->get('router')->generate('fos_user_profile_edit')
...
}
}
service.yml
your.service:
class: App\...\YouService
calls:
- [ setContainer,[ #service_container ] ]
Add:
<argument type="service" id="service_container" />
And in your listener class:
use Symfony\Component\DependencyInjection\ContainerInterface;
//...
public function __construct(ContainerInterface $container, ...) {
It's 2016, you can use trait which will help you extend same class with multiple libraries.
<?php
namespace iBasit\ToolsBundle\Utils\Lib;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Symfony\Component\DependencyInjection\ContainerInterface;
trait Container
{
private $container;
public function setContainer (ContainerInterface $container)
{
$this->container = $container;
}
/**
* Shortcut to return the Doctrine Registry service.
*
* #return Registry
*
* #throws \LogicException If DoctrineBundle is not available
*/
protected function getDoctrine()
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application.');
}
return $this->container->get('doctrine');
}
/**
* Get a user from the Security Token Storage.
*
* #return mixed
*
* #throws \LogicException If SecurityBundle is not available
*
* #see TokenInterface::getUser()
*/
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application.');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
return $user;
}
/**
* Returns true if the service id is defined.
*
* #param string $id The service id
*
* #return bool true if the service id is defined, false otherwise
*/
protected function has ($id)
{
return $this->container->has($id);
}
/**
* Gets a container service by its id.
*
* #param string $id The service id
*
* #return object The service
*/
protected function get ($id)
{
if ('request' === $id)
{
#trigger_error('The "request" service is deprecated and will be removed in 3.0. Add a typehint for Symfony\\Component\\HttpFoundation\\Request to your controller parameters to retrieve the request instead.', E_USER_DEPRECATED);
}
return $this->container->get($id);
}
/**
* Gets a container configuration parameter by its name.
*
* #param string $name The parameter name
*
* #return mixed
*/
protected function getParameter ($name)
{
return $this->container->getParameter($name);
}
}
Your object, which will be service.
namespace AppBundle\Utils;
use iBasit\ToolsBundle\Utils\Lib\Container;
class myObject
{
use Container;
}
Your service settings
myObject:
class: AppBundle\Utils\myObject
calls:
- [setContainer, ["#service_container"]]
Call your service in controller
$myObject = $this->get('myObject');
If all your services are ContainerAware, I suggest to create a BaseService class that will contain all common code with your other services.
1) Create the Base\BaseService.php class:
<?php
namespace Fuz\GenyBundle\Base;
use Symfony\Component\DependencyInjection\ContainerAware;
abstract class BaseService extends ContainerAware
{
}
2) Register this service as abstract in your services.yml
parameters:
// ...
geny.base.class: Fuz\GenyBundle\Base\BaseService
services:
// ...
geny.base:
class: %geny.base.class%
abstract: true
calls:
- [setContainer, [#service_container]]
3) Now, in your other services, extends your BaseService class instead of ContainerAware:
<?php
namespace Fuz\GenyBundle\Services;
use Fuz\GenyBundle\Base\BaseService;
class Loader extends BaseService
{
// ...
}
4) Finally, you can use the parent option in your services declaration.
geny.loader:
class: %geny.loader.class%
parent: geny.base
I prefer this way for several reasons:
there is consistency between the code and the config
this avoids duplicating too much config for each service
you have a base class for each services, very helpful for common code