我正在使用Symfony2构建一个测验应用程序。对于这个例子,假设我有两个问题实体都扩展了一个问题抽象类
我想构建服务以检查用户响应是否正确。我将使用TrueFalseQuestionChecker和MultipleSelectQuestionChecker。
选择加载哪个服务的更好方法是什么? 在我的控制器中,我可以做到:
if($question instance of MultipleSelectQuestion::class){
$checker = $this->get('multiple_select_question_checker');
} else if($question instance of TrueFalseQuestion::class){
$checker = $this->get('true_false_question_checker');
}
$checker->verify($question);
但我觉得在我的控制器中执行此操作非常“丑陋”,因为我将有一个类似10个问题类型的列表,我将不得不为序列化服务,答案检查服务以及其他服务执行此操作。 是否有正确的方法来处理服务和实体之间的关联。
我正在考虑实现我自己的注释,类似的东西:
/**
* @MongoDB\Document
* @Question(serializer="AppBundle\Services\TrueFalseQuestionSerializer",
* responseChecker="AppBundle\Services\TrueFalseQuestionChecker"
*/
public class TrueFalseQuestion extends Question
{
...
}
我是否遗漏了已经包含在Symfony中的内容?或者我的想法是按问题类型做一个服务不好?
编辑:工作解决方案归功于@Tomasz Madeyski
的src /的appbundle /文档/ TrueFalseQuestion
/**
* Class TrueFalseQuestion
*
* @MongoDB\Document(repositoryClass="AppBundle\Repository\TrueFalseQuestionRepository")
* @QuestionServices(serializer="app.true_false_question_serializer")
*/
class TrueFalseQuestion extends Question
{
...
的src /应用/捆绑/注解/ QuestionServices.php
<?php
namespace AppBundle\Annotation;
/**
* @Annotation
*/
class QuestionServices
{
private $checker;
private $serializer;
public function __construct($options)
{
foreach ($options as $key => $value) {
if (!property_exists($this, $key)) {
throw new \InvalidArgumentException(sprintf('Property "%s" does not exist', $key));
}
$this->$key = $value;
}
}
public function getService($serviceName)
{
if (isset($this->$serviceName)) {
return $this->$serviceName;
}
throw new \InvalidArgumentException(sprintf('Property "%s" does not exist', $serviceName));
}
}
的src /的appbundle /服务/ QuestionServicesFactory.php
<?php
namespace AppBundle\Services;
use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
use AppBundle\Annotation\QuestionServices;
use Symfony\Component\DependencyInjection\ContainerInterface;
class QuestionServicesFactory
{
const SERVICE_SERIALIZER = 'serializer';
const SERVICE_CHECKER = 'checker';
public function __construct(Reader $reader, ContainerInterface $container)
{
$this->reader = $reader;
$this->container = $container;
}
/**
* @param string $questionClass
* @param $serviceName
*
* @return object
* @throws \Exception
*/
public function getQuestionService($questionClass, $serviceName)
{
if (!class_exists($questionClass)) {
throw new \Exception(sprintf("The class %s is not an existing class name", $questionClass));
}
$reflectionClass = new ReflectionClass($questionClass);
$classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
foreach ($classAnnotations as $annotation) {
if ($annotation instanceof QuestionServices) {
$serviceName = $annotation->getService($serviceName);
return $this->container->get($serviceName);
}
}
throw new \Exception(sprintf("Annotation QuestionServices does not exist in %s", $questionClass));
}
}
服务声明
<service id="app.question_services_factory"
class="App\Services\QuestionServicesFactory">
<argument type="service" id="doctrine_mongodb.odm.metadata.annotation_reader"/>
<argument type="service" id="service_container"/>
</service>
控制器中的
$questionServiceFactory = $this->get('app.question_services_factory');
$questionSerializer = $questionServiceFactory->getQuestionService(
TrueFalseQuestion::class,
QuestionServicesFactory::SERVICE_SERIALIZER
);
答案 0 :(得分:1)
我建议你使用一个类来获得这样的服务:
$serviceClass = new ServiceClassSearcher();
$serviceName = $serviceClass->getServiceName($question);
$checker = $this->get($serviceName);
在课堂上你可以做这样的事情:
class ServiceClassSearcher
{
private $service = [
'MultipleSelectQuestion' => 'multiple_select_question_checker',
'TrueFalseQuestion' => 'true_false_question_checker'
];
public function __construct()
{
}
public function getServiceName($serviceInstance)
{
foreach ($this->service as $instance => $serviceName) {
$className = instance . '::class';
if($question instance of $className){
return $value;
}
}
return null; //is better to throw an exception for a class not found
}
}
答案 1 :(得分:1)
这基本上是一个基于意见的问题,但是:@Allesandro Minoccheri答案会起作用,但它有一个缺点:添加新类型的问题和问题检查器将需要为每个新对修改ServiceClassSearcher
- 所以这是一个反对SOLID规则。
我认为Question
应该知道哪种类型的检查器应该检查它,所以理想情况下我会将QuestionChecker
注入Question
。由于Question
是一个实体而DI在这里是不可能的,我会说使用注释来指定哪种类型的检查器负责检查给定的问题是一个很好的方法。一旦你有注释,你只需要添加将解析它的类并获取你的检查器的实例。这样,一旦您想要添加新类型的问题,您就不需要修改任何现有代码。
另外我建议使用问题检查服务名称而不是类名,因为它更容易使用。
解析注释并获取问题检查器的类可能类似于:
use Doctrine\Common\Annotations\Reader;
class QuestionCheckerFactory
{
public function __construct(Reader $reader, ContainerInterface $container)
{
$this->reader = $reader;
$this->container = $container;
}
public function getQuestionChecker(Question $question)
{
$reflectionClass = new ReflectionClass(get_class($question));
$classAnnotations = $annotationReader->getClassAnnotations($reflectionClass);
foreach($classAnnotations as $annotation) {
if ($annotation instanceof \Your\Annotation\Class) {
//now depending on how your Annotation is defined you need to get question checker service name
$serviceName = ...
return $this->container->get($serviceName);
}
}
throw new Exception();
}
}
注意:我从头脑中编写了这段代码,因此可能存在一些解析错误,但总体思路就在这里。
您可以查看有关使用自定义注释的this blog post
答案 2 :(得分:0)
我相信你正在寻找的是Factory Pattern
工厂:
class QuestionCheckerFactory
{
public function __construct() // inject what you need to created the services
{
}
public function get(Question $q)
{
if($q instance of TrueFalseQuestion)
{
return new TrueFalseQuestionChecker(...);
}
throw new \Exception('Not implemented ...');
}
}
$checker = $this->get('question.checker.factory')->get($question);
$checker->verify($question);