Symfony 4,如何将通用控制器实现为服务?
Symfony 4, How to implement a general controller as a service?
我有这个控制器
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());
}
}
这完全符合预期(使用 Postman 和我的浏览器进行了测试)。我想概括一下,将通用控制器作为服务并为每个控制器使用该服务,Controller1.php
、Controller2.php
和 Controller3.php
,其中一切都相同,除了 @route
以及方法 getRepository
.
中的 Something
这是我的做法:
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());
}
}
然后Controller1.php
改为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;
}
}
不幸的是,这不起作用并产生以下错误:
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".
我不知道这个错误是从哪里来的,也不知道为什么会发生。有人可以帮助我理解为什么会这样以及如何解决它吗?
看来您导入的服务有误,还有我们在 tchat 中谈到的一些其他问题。
重要:
- 该服务应该在
src/Service
文件夹中。
- 不应在 services.yml
中排除该服务
人的最终解决方案:
服务:
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());
}
}
和控制器:
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;
}
}
好的。回到基础。首先让自己拥有一个 IDE 这样的 PHPStorm。它将突出显示各种语法错误以及其他错误。特别是,IDE 将有助于解决服务命名空间问题以及其他各种问题。
依赖注入背后的基本概念是给定 class 所需的依赖应该被注入。您的 GeneralService class 需要实体管理器。在您之前的相同问题中,您有 GeneralService 扩展 AbstractController 因为 AbstractController 有一个 getDoctrine 方法。它仍然无法工作,因为 getDoctrine 反过来需要服务容器。当然,在您当前的代码中,GeneralService 根本没有 getDoctrine 方法。
无论如何,由于 GeneralService 需要实体管理器然后注入它:
# 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());
}
}
并且不要再排除一般服务。
你的控制器 class 在你使用 IDE 消除了所有小的语法错误后就非常好了:
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;
}
}
我实际上并没有测试上面的代码,但它至少能让你更进一步。
最后一个注意事项:不要直接访问任何超级全局变量,即 $_ENV['API_KEY'] 是不行的。 api_key 应该与实体管理器一起注入。由于注入字符串与注入对象有点不同,我会将确切的细节留给学生作为练习。
我有这个控制器
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());
}
}
这完全符合预期(使用 Postman 和我的浏览器进行了测试)。我想概括一下,将通用控制器作为服务并为每个控制器使用该服务,Controller1.php
、Controller2.php
和 Controller3.php
,其中一切都相同,除了 @route
以及方法 getRepository
.
Something
这是我的做法:
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());
}
}
然后Controller1.php
改为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;
}
}
不幸的是,这不起作用并产生以下错误:
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".
我不知道这个错误是从哪里来的,也不知道为什么会发生。有人可以帮助我理解为什么会这样以及如何解决它吗?
看来您导入的服务有误,还有我们在 tchat 中谈到的一些其他问题。
重要:
- 该服务应该在
src/Service
文件夹中。 - 不应在 services.yml 中排除该服务
人的最终解决方案:
服务:
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());
}
}
和控制器:
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;
}
}
好的。回到基础。首先让自己拥有一个 IDE 这样的 PHPStorm。它将突出显示各种语法错误以及其他错误。特别是,IDE 将有助于解决服务命名空间问题以及其他各种问题。
依赖注入背后的基本概念是给定 class 所需的依赖应该被注入。您的 GeneralService class 需要实体管理器。在您之前的相同问题中,您有 GeneralService 扩展 AbstractController 因为 AbstractController 有一个 getDoctrine 方法。它仍然无法工作,因为 getDoctrine 反过来需要服务容器。当然,在您当前的代码中,GeneralService 根本没有 getDoctrine 方法。
无论如何,由于 GeneralService 需要实体管理器然后注入它:
# 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());
}
}
并且不要再排除一般服务。
你的控制器 class 在你使用 IDE 消除了所有小的语法错误后就非常好了:
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;
}
}
我实际上并没有测试上面的代码,但它至少能让你更进一步。
最后一个注意事项:不要直接访问任何超级全局变量,即 $_ENV['API_KEY'] 是不行的。 api_key 应该与实体管理器一起注入。由于注入字符串与注入对象有点不同,我会将确切的细节留给学生作为练习。