Skip to content

Commit 32bc087

Browse files
committed
[Tree] Add tree hydrator for objects
Until now, one could only build a fully mapped tree using arrays. This hydrator is an attempt to change that. 🙂
1 parent 58dfe52 commit 32bc087

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
3+
namespace Gedmo\Tree\Hydrator\ORM;
4+
5+
use Doctrine\ORM\Internal\Hydration\ObjectHydrator as BaseObjectHydrator;
6+
use Doctrine\ORM\EntityManagerInterface;
7+
use Gedmo\Tool\Wrapper\EntityWrapper;
8+
use Gedmo\Tree\TreeListener;
9+
10+
class ObjectHydrator extends BaseObjectHydrator
11+
{
12+
/**
13+
* @var array
14+
*/
15+
private $config;
16+
17+
/**
18+
* @var string
19+
*/
20+
private $parentField;
21+
22+
/**
23+
* @var string
24+
*/
25+
private $childrenField;
26+
27+
/**
28+
* We hook into the `hydrateAllData` to map the children collection of the entity
29+
*
30+
* {@inheritdoc}
31+
*/
32+
protected function hydrateAllData()
33+
{
34+
$data = parent::hydrateAllData();
35+
36+
if (count($data) === 0) {
37+
return $data;
38+
}
39+
40+
$listener = $this->getTreeListener($this->_em);
41+
$entityClass = $this->getEntityClassFromHydratedData($data);
42+
$this->config = $listener->getConfiguration($this->_em, $entityClass);
43+
$this->parentField = $this->getParentField();
44+
$this->childrenField = $this->getChildrenField($entityClass);
45+
46+
$childrenHashmap = $this->buildChildrenHashmap($data);
47+
$this->populateChildrenArray($data, $childrenHashmap);
48+
49+
// Only return root elements
50+
// The sub-nodes will be accessible via the `children` property
51+
return isset($childrenHashmap[null])
52+
? $childrenHashmap[null]
53+
: array();
54+
}
55+
56+
/**
57+
* Creates a hashmap to quickly find the children of a node
58+
*
59+
* ```
60+
* [parentId => [child1, child2, ...], ...]
61+
* ```
62+
*
63+
* @param array $nodes
64+
* @return array
65+
*/
66+
protected function buildChildrenHashmap($nodes)
67+
{
68+
$r = array();
69+
70+
foreach ($nodes as $node) {
71+
$wrapper = new EntityWrapper($node, $this->_em);
72+
$parentProxy = $wrapper->getPropertyValue($this->config['parent']);
73+
74+
$parentId = $parentProxy !== null
75+
? $parentProxy->getId()
76+
: null;
77+
78+
$r[$parentId][] = $node;
79+
}
80+
81+
return $r;
82+
}
83+
84+
/**
85+
* @param array $nodes
86+
* @param array $childrenHashmap
87+
*/
88+
protected function populateChildrenArray($nodes, $childrenHashmap)
89+
{
90+
foreach ($nodes as $node) {
91+
$wrapper = new EntityWrapper($node, $this->_em);
92+
$childrenCollection = $wrapper->getPropertyValue($this->childrenField);
93+
94+
// Mark all children collections as initialized to avoid select queries
95+
$childrenCollection->setInitialized(true);
96+
97+
if (!isset($childrenHashmap[$node->getId()])) { continue; }
98+
99+
$childrenCollection->clear();
100+
101+
foreach ($childrenHashmap[$node->getId()] as $child) {
102+
$childrenCollection->add($child);
103+
}
104+
}
105+
}
106+
107+
/**
108+
* @return string
109+
*/
110+
protected function getParentField()
111+
{
112+
if (!isset($this->config['parent'])) {
113+
throw new \Gedmo\Exception\InvalidMappingException('The `parent` property is required for the TreeHydrator to work');
114+
}
115+
116+
return $this->config['parent'];
117+
}
118+
119+
/**
120+
* @return string
121+
*/
122+
protected function getChildrenField($entityClass)
123+
{
124+
$meta = $this->getClassMetadata($entityClass);
125+
126+
foreach ($meta->getReflectionProperties() as $property) {
127+
128+
// Skip properties that have no association
129+
if (!$meta->hasAssociation($property->getName())) { continue; }
130+
$associationMapping = $meta->getAssociationMapping($property->getName());
131+
132+
// Make sure the association is mapped by the parent property
133+
if ($associationMapping['mappedBy'] !== $this->parentField) { continue; }
134+
135+
return $associationMapping['fieldName'];
136+
}
137+
138+
throw new \Gedmo\Exception\InvalidMappingException('The children property could not found. It is identified through the `mappedBy` annotation to your parent property.');
139+
}
140+
141+
/**
142+
* @param EntityManagerInterface $em
143+
* @return TreeListener
144+
*/
145+
protected function getTreeListener(EntityManagerInterface $em)
146+
{
147+
foreach ($em->getEventManager()->getListeners() as $listeners) {
148+
foreach ($listeners as $listener) {
149+
if ($listener instanceof TreeListener) {
150+
return $listener;
151+
}
152+
}
153+
}
154+
155+
throw new \Gedmo\Exception\InvalidMappingException('Tree listener was not found on your entity manager, it must be hooked into the event manager');
156+
}
157+
158+
/**
159+
* @param array $data
160+
* @return string
161+
*/
162+
protected function getEntityClassFromHydratedData($data)
163+
{
164+
$firstMappedEntity = array_values($data);
165+
$firstMappedEntity = $firstMappedEntity[0];
166+
return get_class($firstMappedEntity);
167+
}
168+
}

0 commit comments

Comments
 (0)