I am building a quiz app with Symfony2. For this example, let's say I have two question Entities both extending a Question abstract class
- TrueFalseQuestion
- MultipleSelectQuestion
I want to build services to check if a user response is correct or not. I will have TrueFalseQuestionChecker and a MultipleSelectQuestionChecker.
What is the better way to choose which one of the service to load ? In my controller I could do:
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);
But I find it very "ugly" to do this in my controller because I will have a list of like 10 question types and I will have to do this for a serializer service, for the answer checker service and maybe for other services. Is there a proper way to deal with an association between a service and an Entity.
I'm thinking of implementing my own annotation, something like that:
/**
* @MongoDB\Document
* @Question(serializer="AppBundle\Services\TrueFalseQuestionSerializer",
* responseChecker="AppBundle\Services\TrueFalseQuestionChecker"
*/
public class TrueFalseQuestion extends Question
{
...
}
Am I missing something already include in Symfony ? Or is my idea of doing one service by question type bad ?
Edit: Working solution thanks to @Tomasz Madeyski
src/AppBundle/Document/TrueFalseQuestion
/**
* Class TrueFalseQuestion
*
* @MongoDB\Document(repositoryClass="AppBundle\Repository\TrueFalseQuestionRepository")
* @QuestionServices(serializer="app.true_false_question_serializer")
*/
class TrueFalseQuestion extends Question
{
...
src/App/Bundle/Annotation/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/Services/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 declaration
<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>
in controller
$questionServiceFactory = $this->get('app.question_services_factory');
$questionSerializer = $questionServiceFactory->getQuestionService(
TrueFalseQuestion::class,
QuestionServicesFactory::SERVICE_SERIALIZER
);