Answer the question
In order to leave comments, you need to log in
What is the best way to implement the code for processing an AJAX request from different entities to the same controller?
Help me figure out the necessary code structure. There is a clear understanding of what I have done ... crooked. It works, but the code is cumbersome and repetitive.
I have many entities with relationships. To manage these links in the admin, I use tetranz/select2entity-bundle . Accordingly, in the form class, a field with a link is described with an indication of the Select2EntityType type. One of the parameters for this field type is remote_route, which specifies the route for processing the Ajax request, which returns a list of potential entities to create a connection.
Next, I will give an example of a specific connection and code. There are such entities as Category (category), Tag (tag) and Faq (question / answer). Both Category and Questions have a relationship with the Tag entity. When creating/editing a connection with the Tag entity, the search is performed by the name field of the Tag entity (by the tag name).
The code of the CategoryType and FaqType form, as well as the controller that processes the Ajax request from all entities that have a connection with the tag (there are more of them, not only Category and Faq):
class CategoryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
->add('tags', Select2EntityType::class, [
'multiple' => true,
'remote_route' => 'admin_tag_ajax_searching',
'class' => Tag::class,
'remote_params' => [
'entityClass' => urlencode(Category::class),
'entityId' => $options['data']->getId(),
],
]);
}
}
class FaqType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
->add('tags', Select2EntityType::class, [
'multiple' => true,
'remote_route' => 'admin_tag_ajax_searching',
'class' => Tag::class,
'remote_params' => [
'entityClass' => urlencode(Faq::class),
'entityId' => $options['data']->getId(),
],
]);
}
}
class TagController extends Controller
{
/**
* @Route("/tag_ajax_searching", methods={"GET"}, name="admin_tag_ajax_searching")
*/
public function search(Request $request, TagRepository $tagRepository): Response
{
$query = $request->query->get('q', '');
$limit = $request->query->get('limit', 20);
$entityId = $request->query->get('entityId');
$entityClass = urldecode($request->query->get('entityClass'));
$conditions = [];
// исключить из поиска уже назначенные Теги
if ($entityId) {
$entityRepository = $this->getDoctrine()->getRepository($entityClass);
$entity = $entityRepository->findOneBy(['id' => $entityId]);
/** @var ArrayCollection|Tag[] */
$conditions['excludeTags'] = $entity->getTags();
}
$foundTags = $tagRepository->findBySearchQuery($query, $limit, $conditions);
$results = [];
foreach ($foundTags as $tag) {
$results[] = [
'id' => htmlspecialchars($tag->getId()),
'text' => htmlspecialchars($tag->getTitle()),
];
}
return $this->json($results);
}
}
'remote_params' => [
'entityClass' => urlencode(Faq::class),
'entityId' => $options['data']->getId(),
]
$entityClass = urldecode($request->query->get('entityClass'));
$entityRepository = $this->getDoctrine()->getRepository($entityClass);
$entity = $entityRepository->findOneBy(['id' => $entityId]);
/** @var ArrayCollection|Tag[] */
$conditions['excludeTags'] = $entity->getTags();
$foundTags = $tagRepository->findBySearchQuery($query, $limit, $conditions);
I will not give the entire code of the method that generates a query to the database, only the important part - this part just adds the condition 'NOT IN (id, id, id, id)' to the query to the database:if (isset($conditions['excludeTags'])) {
/** @var $tag Tag */
foreach ($conditions['excludeTags'] as $tag) {
$ids[] = $tag->getId();
}
if (isset($ids)) {
$qb->andWhere($qb->expr()->notIn('tag.id', $ids));
}
}
$entity = $entityRepository->findOneBy(['id' => $entityId]);
and make a separate method in the tag repository (TagRepository), like getAssignedTagsForEntity($entityType, $entityId), which should return ... what ... just id's or all the same tag objects (Tag)? True, I have not yet thought about how to implement binding inside this method - apparently through switch ($ entityType).Answer the question
In order to leave comments, you need to log in
Depending on the real needs of your application, you can look towards using entities inheritance . For example, perhaps your "category, faq, article" are special cases of a common logical entity Content
, in which case it would be possible to request a repository specifically for the base entity and then you will not have separation of the logic of working with the same tags.
In addition, nothing prevents you from moving the code for working with tags into a separate service class (let's call it TagSearchService
), for each type "category, faq, article" separate actions in their controllers that will refer to the service class with different (and in this case, beforehand known) parameters. That is, in other words, in some CategoryController::search()
you could call TagSearchService
and pass to itCategory::class
instead of relying on input from outside. If you switch to such a scheme, then different classes for form elements ( CategoryType
and FaqType
in your example) are also naturally replaced by one class (any one TagSearchType
). there is no difference between them. In addition, it will not be necessary to pass the class name from the outside - in my opinion this is a bad idea in any case. If we develop this idea further, then an interface for entities that can have tags
logically emerges . TaggableInterface
This naturally leads to the possibility in the compiler pass to collect a list of such entities and pass them to TagSearchService
. Might be needed for some purposes :)
Next, about filtering tags. Still, when building queries, we are talking about DQL, so there is a non-zero chance (although I can’t prove it, I have to try) that you don’t need to pull out the id from the tags, it’s enough to pass the array of the entities themselves to notIn()
. If this is not the case, the code can be rewritten using array_map()
, perhaps this will make it clearer. It also findOneBy(['id' => $entityId])
obviously changes to EntityManager::find()
something that looks a little simpler.
As for the idea of pulling exactly the id tags - it hardly makes any special sense unless you have a very loaded application.
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question