|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* @author CONTENT CONTROL http://www.contentcontrol-berlin.de/ |
|
4
|
|
|
* @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/ |
|
5
|
|
|
* @license http://www.gnu.org/licenses/gpl.html GNU General Public License |
|
6
|
|
|
*/ |
|
7
|
|
|
|
|
8
|
|
|
namespace midgard\portable\storage; |
|
9
|
|
|
|
|
10
|
|
|
use midgard\portable\api\dbobject; |
|
11
|
|
|
use midgard\portable\api\error\exception; |
|
12
|
|
|
use midgard\portable\storage\interfaces\metadata; |
|
13
|
|
|
use Doctrine\ORM\EntityManager; |
|
14
|
|
|
use Doctrine\ORM\Proxy\Proxy; |
|
15
|
|
|
use Doctrine\ORM\UnitOfWork; |
|
16
|
|
|
use Doctrine\Common\Util\ClassUtils; |
|
17
|
|
|
use midgard_datetime; |
|
18
|
|
|
|
|
19
|
|
|
class objectmanager |
|
20
|
|
|
{ |
|
21
|
|
|
private $em; |
|
22
|
|
|
|
|
23
|
114 |
|
public function __construct(EntityManager $em) |
|
|
|
|
|
|
24
|
|
|
{ |
|
25
|
114 |
|
$this->em = $em; |
|
26
|
114 |
|
} |
|
27
|
|
|
|
|
28
|
114 |
|
public function create(dbobject $entity) |
|
29
|
|
|
{ |
|
30
|
114 |
|
foreach ($this->em->getClassMetadata(get_class($entity))->getAssociationNames() as $name) { |
|
31
|
101 |
|
if (!empty($entity->$name)) { |
|
32
|
|
|
//This makes sure that we don't have stale references |
|
33
|
30 |
|
$entity->$name = $entity->$name; |
|
34
|
30 |
|
} |
|
35
|
114 |
|
} |
|
36
|
|
|
|
|
37
|
|
|
//workaround for possible oid collisions in UnitOfWork |
|
38
|
|
|
//see http://www.doctrine-project.org/jira/browse/DDC-2785 |
|
39
|
114 |
View Code Duplication |
if ($this->em->getUnitOfWork()->getEntityState($entity) != UnitOfWork::STATE_NEW) { |
|
|
|
|
|
|
40
|
|
|
connection::log()->warning('oid collision during create detected, detaching ' . spl_object_hash($entity)); |
|
41
|
|
|
$this->em->detach($entity); |
|
42
|
|
|
} |
|
43
|
|
|
|
|
44
|
114 |
|
$this->em->persist($entity); |
|
45
|
114 |
|
$this->em->flush($entity); |
|
46
|
114 |
|
$this->em->detach($entity); |
|
47
|
114 |
|
} |
|
48
|
|
|
|
|
49
|
15 |
|
public function update(dbobject $entity) |
|
50
|
|
|
{ |
|
51
|
|
|
// if entities are loaded by querybuilder, they are managed at this point already, |
|
52
|
|
|
// which can in very rare (and very unreproducable) circumstances lead to the update |
|
53
|
|
|
// getting lost silently (because originalEntityData somehow is empty), so we detach |
|
54
|
|
|
// before doing anything else |
|
55
|
15 |
|
$this->em->detach($entity); |
|
56
|
15 |
|
$merged = $this->em->merge($entity); |
|
57
|
15 |
|
$this->copy_associations($entity, $merged); |
|
58
|
15 |
|
$this->em->persist($merged); |
|
59
|
15 |
|
$this->em->flush($merged); |
|
60
|
15 |
|
$this->em->detach($merged); |
|
61
|
15 |
|
$this->copy_metadata($merged, $entity); |
|
62
|
15 |
|
} |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* This is basically a workaround for some quirks when merging detached entities with changed associations |
|
66
|
|
|
* |
|
67
|
|
|
* @todo: This may or may not be a bug in Doctrine |
|
68
|
|
|
*/ |
|
69
|
15 |
|
private function copy_associations($source, $target) |
|
70
|
|
|
{ |
|
71
|
15 |
|
foreach ($this->em->getClassMetadata(get_class($source))->getAssociationNames() as $name) { |
|
72
|
13 |
|
$target->$name = $source->$name; |
|
73
|
15 |
|
} |
|
74
|
15 |
|
} |
|
75
|
|
|
|
|
76
|
26 |
|
private function kill_potential_proxies($entity) |
|
77
|
|
|
{ |
|
78
|
26 |
|
$classname = ClassUtils::getRealClass(get_class($entity)); |
|
79
|
26 |
|
$cm = $this->em->getClassMetadata($classname); |
|
80
|
26 |
|
$changed_associations = $entity->__get_changed_associations(); |
|
81
|
|
|
|
|
82
|
26 |
|
foreach ($cm->getAssociationNames() as $name) { |
|
83
|
23 |
|
if ($entity->$name === 0) { |
|
84
|
|
|
//This is necessary to kill potential proxy objects pointing to purged entities |
|
85
|
20 |
|
$entity->$name = 0; |
|
86
|
23 |
|
} elseif (!array_key_exists($name, $changed_associations)) { |
|
87
|
10 |
|
$value = $cm->getReflectionProperty($name)->getValue($entity); |
|
88
|
10 |
|
if ($value instanceof Proxy) { |
|
89
|
|
|
//This makes sure that the associated entity doesn't end up in the changeset calculation |
|
90
|
10 |
|
$value->__isInitialized__ = false; |
|
|
|
|
|
|
91
|
10 |
|
continue; |
|
92
|
|
|
} |
|
93
|
|
|
} |
|
94
|
26 |
|
} |
|
95
|
26 |
|
} |
|
96
|
|
|
|
|
97
|
26 |
|
public function delete(dbobject $entity) |
|
98
|
|
|
{ |
|
99
|
|
|
//we might deal with a proxy here, so we translate the classname |
|
100
|
26 |
|
$classname = ClassUtils::getRealClass(get_class($entity)); |
|
101
|
26 |
|
$copy = new $classname($entity->id); |
|
|
|
|
|
|
102
|
|
|
|
|
103
|
|
|
//workaround for possible oid collisions in UnitOfWork |
|
104
|
|
|
//see http://www.doctrine-project.org/jira/browse/DDC-2785 |
|
105
|
26 |
View Code Duplication |
if ($this->em->getUnitOfWork()->getEntityState($copy) != UnitOfWork::STATE_DETACHED) { |
|
|
|
|
|
|
106
|
|
|
connection::log()->warning('oid collision during delete detected, detaching ' . spl_object_hash($copy)); |
|
107
|
|
|
$this->em->detach($copy); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
26 |
|
$copy = $this->em->merge($copy); |
|
111
|
26 |
|
$this->kill_potential_proxies($copy); |
|
112
|
26 |
|
$copy->metadata_deleted = true; |
|
113
|
|
|
|
|
114
|
26 |
|
$this->em->persist($copy); |
|
115
|
26 |
|
$this->em->flush($copy); |
|
116
|
26 |
|
$this->em->detach($copy); |
|
117
|
26 |
|
$this->em->detach($entity); |
|
118
|
26 |
|
$this->copy_metadata($copy, $entity, 'delete'); |
|
119
|
26 |
|
} |
|
120
|
|
|
|
|
121
|
3 |
|
public function undelete(dbobject $entity) |
|
122
|
|
|
{ |
|
123
|
3 |
|
$entity->metadata_deleted = false; |
|
|
|
|
|
|
124
|
3 |
|
$this->kill_potential_proxies($entity); |
|
125
|
|
|
|
|
126
|
3 |
|
$this->em->persist($entity); |
|
127
|
3 |
|
$this->em->flush($entity); |
|
128
|
3 |
|
$this->em->detach($entity); |
|
129
|
3 |
|
} |
|
130
|
|
|
|
|
131
|
18 |
|
public function purge(dbobject $entity) |
|
132
|
|
|
{ |
|
133
|
18 |
|
$this->em->getFilters()->disable('softdelete'); |
|
134
|
|
|
try { |
|
135
|
18 |
|
$entity = $this->em->merge($entity); |
|
136
|
18 |
|
} finally { |
|
137
|
18 |
|
$this->em->getFilters()->enable('softdelete'); |
|
138
|
18 |
|
} |
|
139
|
18 |
|
$this->em->remove($entity); |
|
140
|
18 |
|
$this->em->flush($entity); |
|
141
|
18 |
|
$this->em->detach($entity); |
|
142
|
18 |
|
} |
|
143
|
|
|
|
|
144
|
2 |
View Code Duplication |
public function approve(dbobject $entity) |
|
|
|
|
|
|
145
|
|
|
{ |
|
146
|
2 |
|
$user = connection::get_user(); |
|
147
|
2 |
|
$ref = $this->em->getReference(get_class($entity), $entity->id); |
|
|
|
|
|
|
148
|
2 |
|
$ref->metadata_isapproved = true; |
|
149
|
2 |
|
$ref->metadata_approver = $user->person; |
|
150
|
2 |
|
$ref->metadata_approved = new midgard_datetime; |
|
151
|
|
|
|
|
152
|
2 |
|
$this->em->persist($ref); |
|
153
|
2 |
|
$this->em->flush($ref); |
|
154
|
2 |
|
$this->em->detach($entity); |
|
155
|
2 |
|
$this->copy_metadata($ref, $entity, 'approve'); |
|
156
|
2 |
|
} |
|
157
|
|
|
|
|
158
|
1 |
View Code Duplication |
public function unapprove(dbobject $entity) |
|
|
|
|
|
|
159
|
|
|
{ |
|
160
|
1 |
|
$user = connection::get_user(); |
|
161
|
1 |
|
$ref = $this->em->getReference(get_class($entity), $entity->id); |
|
|
|
|
|
|
162
|
1 |
|
$ref->metadata_isapproved = false; |
|
163
|
1 |
|
$ref->metadata_approver = $user->person; |
|
164
|
1 |
|
$ref->metadata_approved = new midgard_datetime; |
|
165
|
|
|
|
|
166
|
1 |
|
$this->em->persist($ref); |
|
167
|
1 |
|
$this->em->flush($ref); |
|
168
|
1 |
|
$this->em->detach($entity); |
|
169
|
1 |
|
$this->copy_metadata($ref, $entity, 'approve'); |
|
170
|
1 |
|
} |
|
171
|
|
|
|
|
172
|
2 |
View Code Duplication |
public function lock(dbobject $entity) |
|
|
|
|
|
|
173
|
|
|
{ |
|
174
|
2 |
|
$user = connection::get_user(); |
|
175
|
2 |
|
$ref = $this->em->getReference(get_class($entity), $entity->id); |
|
|
|
|
|
|
176
|
2 |
|
$ref->metadata_islocked = true; |
|
177
|
2 |
|
$ref->metadata_locker = $user->person; |
|
178
|
2 |
|
$ref->metadata_locked = new midgard_datetime; |
|
179
|
|
|
|
|
180
|
2 |
|
$this->em->persist($ref); |
|
181
|
2 |
|
$this->em->flush($ref); |
|
182
|
2 |
|
$this->em->detach($entity); |
|
183
|
2 |
|
$this->copy_metadata($ref, $entity, 'lock'); |
|
184
|
2 |
|
} |
|
185
|
|
|
|
|
186
|
1 |
|
public function unlock(dbobject $entity) |
|
187
|
|
|
{ |
|
188
|
1 |
|
$ref = $this->em->getReference(get_class($entity), $entity->id); |
|
|
|
|
|
|
189
|
1 |
|
$ref->metadata_islocked = false; |
|
190
|
|
|
|
|
191
|
1 |
|
$this->em->persist($ref); |
|
192
|
1 |
|
$this->em->flush($ref); |
|
193
|
1 |
|
$this->em->detach($entity); |
|
194
|
1 |
|
$this->copy_metadata($ref, $entity, 'lock'); |
|
195
|
1 |
|
} |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* @param string $classname |
|
199
|
|
|
* @return dbobject |
|
200
|
|
|
*/ |
|
201
|
114 |
|
public function new_instance($classname) |
|
202
|
|
|
{ |
|
203
|
|
|
//workaround for possible oid collisions in UnitOfWork |
|
204
|
|
|
//see http://www.doctrine-project.org/jira/browse/DDC-2785 |
|
205
|
114 |
|
$counter = 0; |
|
206
|
114 |
|
$candidates = []; |
|
207
|
|
|
do { |
|
208
|
114 |
|
$entity = new $classname; |
|
209
|
114 |
|
if ($counter++ > 100) { |
|
210
|
|
|
throw new exception('Failed to create fresh ' . $classname . ' instance (all tried oids are already known to UoW)'); |
|
211
|
|
|
} |
|
212
|
|
|
//we keep the entity in memory to make sure we get a different oid during the next iteration |
|
213
|
114 |
|
$candidates[] = $entity; |
|
214
|
114 |
|
} while ($this->em->getUnitOfWork()->getEntityState($entity) !== UnitOfWork::STATE_NEW); |
|
215
|
|
|
// TODO: Calling $em->getUnitOfWork()->isInIdentityMap($entity) returns false in the same situation. Why? |
|
216
|
114 |
|
return $entity; |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
39 |
|
private function copy_metadata($source, $target, $action = 'update') |
|
220
|
|
|
{ |
|
221
|
39 |
|
if (!$source instanceof metadata) { |
|
222
|
1 |
|
return; |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
38 |
|
$target->metadata_revised = $source->metadata_revised; |
|
|
|
|
|
|
226
|
38 |
|
$target->metadata_revisor = $source->metadata_revisor; |
|
|
|
|
|
|
227
|
38 |
|
$target->metadata_revision = $source->metadata_revision; |
|
|
|
|
|
|
228
|
|
|
|
|
229
|
38 |
|
if ($action == 'lock') { |
|
230
|
2 |
|
$target->metadata_islocked = $source->metadata_islocked; |
|
|
|
|
|
|
231
|
2 |
|
$target->metadata_locker = $source->metadata_locker; |
|
|
|
|
|
|
232
|
2 |
|
$target->metadata_locked = $source->metadata_locked; |
|
|
|
|
|
|
233
|
38 |
|
} elseif ($action == 'approve') { |
|
234
|
2 |
|
$target->metadata_isapproved = $source->metadata_isapproved; |
|
|
|
|
|
|
235
|
2 |
|
$target->metadata_approver = $source->metadata_approver; |
|
|
|
|
|
|
236
|
2 |
|
$target->metadata_approved = $source->metadata_approved; |
|
|
|
|
|
|
237
|
36 |
|
} elseif ($action == 'delete') { |
|
238
|
26 |
|
$target->metadata_deleted = $source->metadata_deleted; |
|
|
|
|
|
|
239
|
26 |
|
} |
|
240
|
38 |
|
} |
|
241
|
|
|
} |
|
242
|
|
|
|
The
EntityManagermight become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:If that code throws an exception and the
EntityManageris closed. Any other code which depends on the same instance of theEntityManagerduring this request will fail.On the other hand, if you instead inject the
ManagerRegistry, thegetManager()method guarantees that you will always get a usable manager instance.