Skip to content

Commit 2dc052b

Browse files
committed
New mapper system for dependent entity mapping
1 parent ca5cdfe commit 2dc052b

15 files changed

+366
-12
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace Sherlockode\AdvancedFormBundle\Controller;
4+
5+
use Sherlockode\AdvancedFormBundle\DependentEntity\DependentMapperPool;
6+
use Sherlockode\AdvancedFormBundle\Event\GetResponseDependentResultEvent;
7+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
8+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
9+
use Symfony\Component\HttpFoundation\JsonResponse;
10+
use Symfony\Component\HttpFoundation\Request;
11+
use Symfony\Component\HttpFoundation\Response;
12+
use Symfony\Component\HttpKernel\Kernel;
13+
14+
class DependentEntityController extends AbstractController
15+
{
16+
/**
17+
* @var EventDispatcherInterface
18+
*/
19+
private $eventDispatcher;
20+
21+
/**
22+
* @var DependentMapperPool
23+
*/
24+
private $mapperPool;
25+
26+
/**
27+
* @param EventDispatcherInterface $eventDispatcher
28+
* @param DependentMapperPool $mapperPool
29+
*/
30+
public function __construct(EventDispatcherInterface $eventDispatcher, DependentMapperPool $mapperPool)
31+
{
32+
$this->eventDispatcher = $eventDispatcher;
33+
$this->mapperPool = $mapperPool;
34+
}
35+
36+
/**
37+
* @param Request $request
38+
*
39+
* @return Response
40+
*/
41+
public function getDependentResultsAction(Request $request)
42+
{
43+
$id = (int) $request->get('id');
44+
$mapperName = $request->get('mapper');
45+
$mapper = $this->mapperPool->getMapper($mapperName);
46+
47+
$entity = $this->getDoctrine()->getRepository($mapper->getSubjectClass())->find($id);
48+
if ($entity === null) {
49+
throw $this->createNotFoundException();
50+
}
51+
52+
$event = new GetResponseDependentResultEvent($mapper, $entity);
53+
$this->eventDispatcher->dispatch($event);
54+
if (Kernel::VERSION_ID < 40300) {
55+
$this->eventDispatcher->dispatch(get_class($event), $event);
56+
} else {
57+
$this->eventDispatcher->dispatch($event);
58+
}
59+
if ($event->getResponse()) {
60+
return $event->getResponse();
61+
}
62+
63+
$data = $mapper->getDependentResults($entity);
64+
65+
return new JsonResponse($data);
66+
}
67+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Sherlockode\AdvancedFormBundle\DependencyInjection\Compiler;
4+
5+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
6+
use Symfony\Component\DependencyInjection\ContainerBuilder;
7+
use Symfony\Component\DependencyInjection\Reference;
8+
9+
class DependentEntityMapperPass implements CompilerPassInterface
10+
{
11+
public function process(ContainerBuilder $container)
12+
{
13+
$definition = $container->getDefinition('sherlockode_afb.dependent_entity.mapper_pool');
14+
$taggedServices = $container->findTaggedServiceIds('sherlockode_afb.dependent_entity_mapper');
15+
foreach ($taggedServices as $id => $tags) {
16+
$definition->addMethodCall('addMapper', [new Reference($id)]);
17+
}
18+
}
19+
}

DependencyInjection/SherlockodeAdvancedFormExtension.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Sherlockode\AdvancedFormBundle\DependencyInjection;
44

5+
use Sherlockode\AdvancedFormBundle\DependentEntity\DependentMapperInterface;
56
use Sherlockode\AdvancedFormBundle\Storage\FilesystemStorage;
67
use Symfony\Component\Config\FileLocator;
78
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -51,6 +52,9 @@ public function load(array $configs, ContainerBuilder $container)
5152
$definition->addMethodCall('addHandler', [new Reference($id), $tag['alias'] ?? $id]);
5253
}
5354
}
55+
56+
$container->registerForAutoconfiguration(DependentMapperInterface::class)
57+
->addTag('sherlockode_afb.dependent_entity_mapper');
5458
}
5559

5660
/**
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Sherlockode\AdvancedFormBundle\DependentEntity;
4+
5+
interface DependentMapperInterface
6+
{
7+
/**
8+
* @return string
9+
*/
10+
public function getName();
11+
12+
/**
13+
* @return string
14+
*/
15+
public function getSubjectClass();
16+
17+
/**
18+
* @param object $entity
19+
*
20+
* @return array
21+
*/
22+
public function getMapping($entity);
23+
24+
/**
25+
* @param object $entity
26+
*
27+
* @return array
28+
*/
29+
public function getDependentResults($entity);
30+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Sherlockode\AdvancedFormBundle\DependentEntity;
4+
5+
class DependentMapperPool
6+
{
7+
/**
8+
* @var DependentMapperInterface[]
9+
*/
10+
private $mappers = [];
11+
12+
public function addMapper(DependentMapperInterface $mapper)
13+
{
14+
$this->mappers[$mapper->getName()] = $mapper;
15+
16+
return $this;
17+
}
18+
19+
/**
20+
* @param string $name
21+
*
22+
* @return DependentMapperInterface
23+
* @throws \Exception
24+
*/
25+
public function getMapper($name)
26+
{
27+
if (!$this->hasMapper($name)) {
28+
throw new \Exception(sprintf('Unknown dependent entity mapper %s', $name));
29+
}
30+
31+
return $this->mappers[$name];
32+
}
33+
34+
/**
35+
* @param string $name
36+
*
37+
* @return bool
38+
*/
39+
public function hasMapper($name)
40+
{
41+
return isset($this->mappers[$name]);
42+
}
43+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Sherlockode\AdvancedFormBundle\Event;
4+
5+
use Sherlockode\AdvancedFormBundle\DependentEntity\DependentMapperInterface;
6+
use Symfony\Component\HttpFoundation\Response;
7+
8+
class GetResponseDependentResultEvent
9+
{
10+
/**
11+
* @var Response
12+
*/
13+
private $response;
14+
15+
/**
16+
* @var object
17+
*/
18+
private $entity;
19+
20+
/**
21+
* @var DependentMapperInterface
22+
*/
23+
private $mapper;
24+
25+
/**
26+
* @param DependentMapperInterface $mapper
27+
* @param object $entity
28+
*/
29+
public function __construct(DependentMapperInterface $mapper, $entity)
30+
{
31+
$this->mapper = $mapper;
32+
$this->entity = $entity;
33+
}
34+
35+
/**
36+
* @return Response
37+
*/
38+
public function getResponse()
39+
{
40+
return $this->response;
41+
}
42+
43+
/**
44+
* @param Response $response
45+
*/
46+
public function setResponse(Response $response)
47+
{
48+
$this->response = $response;
49+
}
50+
51+
public function getEntity()
52+
{
53+
return $this->entity;
54+
}
55+
56+
public function getMapper()
57+
{
58+
return $this->mapper;
59+
}
60+
}

Form/Type/DependentEntityType.php

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Sherlockode\AdvancedFormBundle\Form\Type;
44

55
use Doctrine\ORM\EntityManagerInterface;
6+
use Sherlockode\AdvancedFormBundle\DependentEntity\DependentMapperPool;
67
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
78
use Symfony\Component\Form\AbstractType;
89
use Symfony\Component\Form\FormBuilderInterface;
@@ -12,6 +13,7 @@
1213
use Symfony\Component\Form\FormInterface;
1314
use Symfony\Component\Form\FormView;
1415
use Symfony\Component\OptionsResolver\OptionsResolver;
16+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
1517
use Symfony\Contracts\Translation\TranslatorInterface;
1618

1719
/**
@@ -29,16 +31,34 @@ class DependentEntityType extends AbstractType
2931
*/
3032
private $translator;
3133

34+
/**
35+
* @var DependentMapperPool
36+
*/
37+
private $mapperPool;
38+
39+
/**
40+
* @var UrlGeneratorInterface
41+
*/
42+
private $router;
43+
3244
/**
3345
* DependentEntityType constructor.
3446
*
3547
* @param EntityManagerInterface $em
3648
* @param TranslatorInterface $translator
49+
* @param DependentMapperPool $mapperPool
50+
* @param UrlGeneratorInterface $router
3751
*/
38-
public function __construct(EntityManagerInterface $em, TranslatorInterface $translator)
39-
{
52+
public function __construct(
53+
EntityManagerInterface $em,
54+
TranslatorInterface $translator,
55+
DependentMapperPool $mapperPool,
56+
UrlGeneratorInterface $router
57+
) {
4058
$this->em = $em;
4159
$this->translator = $translator;
60+
$this->mapperPool = $mapperPool;
61+
$this->router = $router;
4262
}
4363

4464
public function getParent()
@@ -59,18 +79,29 @@ public function configureOptions(OptionsResolver $resolver)
5979

6080
public function buildView(FormView $view, FormInterface $form, array $options)
6181
{
62-
parent::buildView($view, $form, $options);
6382
$depend = $this->getDependentElement($view, $options['dependOnElementName']);
6483

6584
$mapping = $this->processMapping($options, $form);
6685

6786
$class = isset($view->vars['attr']['class']) ? $view->vars['attr']['class'] : '';
6887
$class = $class . ' ' . 'dependent-entity';
88+
89+
$ajaxUrl = $options['ajax_url'];
90+
if ($ajaxUrl === true) {
91+
if (!is_string($options['mapping'])) {
92+
throw new \Exception(
93+
'In order to use ajax for dependent dropdown, '
94+
.'you need to use a mapper or provide the URL explicitly in ajax_url'
95+
);
96+
}
97+
$ajaxUrl = $this->router->generate('sherlockode_afb_dependent_results', ['mapper' => $options['mapping']]);
98+
}
99+
69100
$view->vars['attr'] = array_merge($view->vars['attr'], [
70101
'class' => $class,
71102
'data-depend-on-element' => $depend->vars['id'],
72103
'data-mapping' => json_encode($mapping),
73-
'data-dependent-ajax-url' => $options['ajax_url'],
104+
'data-dependent-ajax-url' => $ajaxUrl,
74105
]);
75106
}
76107

@@ -95,14 +126,19 @@ public function buildForm(FormBuilderInterface $builder, array $options)
95126

96127
private function processMapping(array $options, FormInterface $form)
97128
{
98-
if ($options['mapping'] === null) {
129+
if ($options['mapping'] === null || $options['ajax_url'] !== null) {
99130
return [];
100131
}
101132

102-
if (is_array($options['mapping'])) {
103-
$mapping = $options['mapping'];
133+
$dependForm = $this->getDependentForm($form, $options['dependOnElementName']);
134+
$mapping = [];
135+
if (is_string($options['mapping'])) {
136+
$mapper = $this->mapperPool->getMapper($options['mapping']);
137+
foreach ($dependForm->getConfig()->getAttribute('choice_list')->getChoices() as $choice) {
138+
list($k, $v) = $mapper->getMapping($choice);
139+
$mapping[$k] = $v;
140+
}
104141
} elseif (is_callable($options['mapping'])) {
105-
$dependForm = $this->getDependentForm($form, $options['dependOnElementName']);
106142
$mapping = [];
107143
foreach ($dependForm->getConfig()->getAttribute('choice_list')->getChoices() as $choice) {
108144
list($k, $v) = $options['mapping']($choice);
@@ -111,7 +147,7 @@ private function processMapping(array $options, FormInterface $form)
111147
} else {
112148
throw new \InvalidArgumentException(
113149
sprintf(
114-
'The "%s" option only supports array or callable, %s received.',
150+
'The "%s" option only supports string or callable, %s received.',
115151
'mapping',
116152
gettype($options['mapping'])
117153
)

Resources/config/controller.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,10 @@ services:
1313
- '@sherlockode_afb.upload_manager'
1414
- '%sherlockode_afb.tmp_uploaded_file_class%'
1515
- '@sherlockode_afb.storage.tmp_storage'
16+
17+
sherlockode_afb.dependent_entity.controller:
18+
class: Sherlockode\AdvancedFormBundle\Controller\DependentEntityController
19+
public: true
20+
arguments:
21+
- '@event_dispatcher'
22+
- '@sherlockode_afb.dependent_entity.mapper_pool'

Resources/config/form.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ services:
3333
arguments:
3434
- '@doctrine.orm.entity_manager'
3535
- '@translator'
36+
- '@sherlockode_afb.dependent_entity.mapper_pool'
37+
- '@router'
3638
tags: ['form.type']

Resources/config/manager.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,6 @@ services:
4040
- '@sherlockode_afb.upload_manager'
4141
tags:
4242
- { name: twig.extension }
43+
44+
sherlockode_afb.dependent_entity.mapper_pool:
45+
class: Sherlockode\AdvancedFormBundle\DependentEntity\DependentMapperPool

0 commit comments

Comments
 (0)