问题
I have this controller
Controller1.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class file1Controller extends AbstractController
{
/**
* @Route("/Some/URI", methods={"GET"})
* @param Request $request
* @return JsonResponse
*/
public function list(Request $request)
{
if (empty($request->headers->get('api-key'))) {
return new JsonResponse(['error' => 'Please provide an API_key'], 401);
}
if ($request->headers->get('api-key') !== $_ENV['API_KEY']) {
return new JsonResponse(['error' => 'Invalid API key'], 401);
}
return new JsonResponse($this->getDoctrine()->getRepository('App:Something')->findAll());
}
}
Which works exactly as intended (tested it with Postman and with my browser). I would like to generalize to make a general controller as a Service and use that Service for each controller, Controller1.php
, Controller2.php
and Controller3.php
where everything is the same excpet the @route
and the Something
inside the method getRepository
.
This is my go at it:
GeneralService.php
<?php
namespace Service;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class GeneralService
{
/**
* @param Request $request
* @param String $entity
* @return JsonResponse
*/
public function list(Request $request, String $entity)
{
if (empty($request->headers->get('api-key'))) {
return new JsonResponse(['error' => 'Please provide an API_key'], 401);
}
if ($request->headers->get('api-key') !== $_ENV['API_KEY']) {
return new JsonResponse(['error' => 'Invalid API key'], 401);
}
return new JsonResponse($this->getDoctrine()->getRepository('App:{$entity}')->findAll());
}
}
And Controller1.php
is then changed to SubscriptionController.php
:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Service\GeneralService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class SubscriptionController extends AbstractController
{
/**
* @Route("/Some/Uri", methods={"GET"})
* @param GeneralService $generalService
* @param Request $request
* @return JsonResponse
*/
public function AuthenticateAPI(GeneralService $generalService, Request $request)
{
$AuthenticatorObject = $generalService->list($request ,'Something');
return $AuthenticatorObject;
}
}
This unfortunately does not work and yields the following error:
InvalidArgumentException
Cannot determine controller argument for "App\Controller\Controller1::AuthenticateAPI()": the $generalService argument is type-hinted with the non-existent class or interface: "Service\GeneralService".
I don't see where this error is coming from nor why it's happening. Could somehelp help me understand why is this the case and how to fix it?
回答1:
It looks like your import of the service is wrong and some other things that we spoke about in tchat.
Important :
- The service should be in
src/Service
folder. - The service should not be excluded in services.yml
Final solution for people :
The service :
namespace App\Service;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\ORM\EntityManagerInterface;
class GeneralService
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @param Request $request
* @param String $entity
* @return JsonResponse
*/
public function list(Request $request, String $entity)
{
if (empty($request->headers->get('api-key'))) {
return new JsonResponse(['error' => 'Please provide an API_key'], 401);
}
if ($request->headers->get('api-key') !== $_ENV['API_KEY']) {
return new JsonResponse(['error' => 'Invalid API key'], 401);
}
return new JsonResponse($this->entityManager->getRepository($entity)->findAll());
}
}
And the controller :
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Service\GeneralService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class SubscriptionController extends AbstractController
{
/**
* @Route("/Some/Uri", methods={"GET"})
* @param GeneralService $generalService
* @param Request $request
* @return JsonResponse
*/
public function AuthenticateAPI(GeneralService $generalService, Request $request)
{
$AuthenticatorObject = $generalService->list($request , 'App\Entity\Something');
return $AuthenticatorObject;
}
}
回答2:
Okay. Back to the basics. Start by getting yourself an IDE such a PHPStorm. It will highlight various syntax errors and what not. In particular, the IDE will help with the service namespace issue as well as assorted other problems.
The fundamental concept behind dependency injection is that dependencies needed by a given class should be, well, injected. Your GeneralService class needs the entity manager. In your previous identical question you had GeneralService extending AbstractController because AbstractController has a getDoctrine method. It still would not have have worked because getDoctrine in turn needs the service container. And of course in your current code, GeneralService has no getDoctrine method at all.
In any event, since GeneralService needs the entity manager then inject it:
# src/Service/GeneralService.php
namespace App\Service; # NOTE App\Service not just Service
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class GeneralService
{
private $entityManager;
// Let Symfony inject whatever dependencies are needed
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function list(Request $request, String $entityClass) : JsonResponse
{
if (!$request->headers->has('api-key')) {
return new JsonResponse(['error' => 'Please provide an API_key'], 401);
}
if (!$request->headers->get('api-key') !== $_ENV['API_KEY']) {
return new JsonResponse(['error' => 'Invalid API key'], 401);
}
return new JsonResponse($this->entityManager->getRepository($entityClass)->findAll());
}
}
And do not exclude the GeneralService anymore.
Your controller class is pretty much okay once you use an IDE to get rid of all the little syntax errors:
namespace App\Controller;
use App\Service\GeneralService;
use App\Entity\SomeEntity;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
class SubscriptionClass extends AbstractController
{
public function AuthenticateAPI(Request $request, GeneralService $generalService)
{
$AuthenticatorObject = $generalService->list($request,SomeEntity::class);
return $AuthenticatorObject;
}
}
I did not actually test the above code though it should get you at least one step further.
One final note: Don't access any of the super globals directly i.e. $_ENV['API_KEY'] is a no no. The api_key should be injected along with the entity manager. I'll leave the exact details as an exercise for the student since injecting strings is a little bit different from injecting objects.
来源:https://stackoverflow.com/questions/56543322/symfony-4-how-to-implement-a-general-controller-as-a-service