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
|
116 |
|
public function __construct(EntityManager $em) |
24
|
|
|
{ |
25
|
116 |
|
$this->em = $em; |
26
|
|
|
} |
27
|
|
|
|
28
|
116 |
|
public function create(dbobject $entity) |
29
|
|
|
{ |
30
|
116 |
|
foreach ($this->em->getClassMetadata(get_class($entity))->getAssociationNames() as $name) { |
31
|
103 |
|
if (!empty($entity->$name)) { |
32
|
|
|
//This makes sure that we don't have stale references |
33
|
31 |
|
$entity->$name = $entity->$name; |
34
|
|
|
} |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
//workaround for possible oid collisions in UnitOfWork |
38
|
|
|
//see https://github.com/doctrine/orm/issues/3037 |
39
|
116 |
|
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
|
116 |
|
$this->em->persist($entity); |
45
|
116 |
|
$this->em->flush($entity); |
46
|
116 |
|
$this->em->detach($entity); |
47
|
|
|
} |
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
|
|
|
} |
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(dbobject $source, dbobject $target) |
70
|
|
|
{ |
71
|
15 |
|
foreach ($this->em->getClassMetadata(get_class($source))->getAssociationNames() as $name) { |
72
|
13 |
|
$target->$name = $source->$name; |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
|
76
|
27 |
|
private function kill_potential_proxies(dbobject $entity) |
77
|
|
|
{ |
78
|
27 |
|
$classname = ClassUtils::getRealClass(get_class($entity)); |
79
|
27 |
|
$cm = $this->em->getClassMetadata($classname); |
80
|
27 |
|
$changed_associations = $entity->__get_changed_associations(); |
81
|
|
|
|
82
|
27 |
|
foreach ($cm->getAssociationNames() as $name) { |
83
|
24 |
|
if ($entity->$name === 0) { |
84
|
|
|
//This is necessary to kill potential proxy objects pointing to purged entities |
85
|
21 |
|
$entity->$name = 0; |
86
|
11 |
|
} elseif (!isset($changed_associations[$name])) { |
87
|
11 |
|
$value = $cm->getReflectionProperty($name)->getValue($entity); |
88
|
11 |
|
if ($value instanceof Proxy) { |
89
|
|
|
//This makes sure that the associated entity doesn't end up in the changeset calculation |
90
|
11 |
|
$value->__isInitialized__ = false; |
|
|
|
|
91
|
11 |
|
continue; |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|
97
|
27 |
|
public function delete(dbobject $entity) |
98
|
|
|
{ |
99
|
|
|
//we might deal with a proxy here, so we translate the classname |
100
|
27 |
|
$classname = ClassUtils::getRealClass(get_class($entity)); |
101
|
27 |
|
$copy = new $classname($entity->id); |
|
|
|
|
102
|
|
|
|
103
|
|
|
//workaround for possible oid collisions in UnitOfWork |
104
|
|
|
//see https://github.com/doctrine/orm/issues/3037 |
105
|
27 |
|
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
|
27 |
|
$copy = $this->em->merge($copy); |
|
|
|
|
111
|
27 |
|
$this->kill_potential_proxies($copy); |
112
|
27 |
|
$copy->metadata_deleted = true; |
113
|
|
|
|
114
|
27 |
|
$this->em->persist($copy); |
115
|
27 |
|
$this->em->flush($copy); |
116
|
27 |
|
$this->em->detach($copy); |
117
|
27 |
|
$this->em->detach($entity); |
118
|
27 |
|
$this->copy_metadata($copy, $entity, 'delete'); |
119
|
|
|
} |
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
|
|
|
} |
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
|
|
|
} |
139
|
18 |
|
$this->em->remove($entity); |
140
|
18 |
|
$this->em->flush($entity); |
141
|
18 |
|
$this->em->detach($entity); |
142
|
|
|
} |
143
|
|
|
|
144
|
2 |
|
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
|
|
|
} |
157
|
|
|
|
158
|
1 |
|
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
|
|
|
} |
171
|
|
|
|
172
|
2 |
|
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
|
|
|
} |
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
|
|
|
} |
196
|
|
|
|
197
|
116 |
|
public function new_instance(string $classname) : dbobject |
198
|
|
|
{ |
199
|
|
|
//workaround for possible oid collisions in UnitOfWork |
200
|
|
|
//see https://github.com/doctrine/orm/issues/3037 |
201
|
116 |
|
$counter = 0; |
202
|
116 |
|
$candidates = []; |
203
|
|
|
do { |
204
|
116 |
|
$entity = new $classname; |
205
|
116 |
|
if ($counter++ > 100) { |
206
|
|
|
throw new exception('Failed to create fresh ' . $classname . ' instance (all tried oids are already known to UoW)'); |
207
|
|
|
} |
208
|
|
|
//we keep the entity in memory to make sure we get a different oid during the next iteration |
209
|
116 |
|
$candidates[] = $entity; |
210
|
116 |
|
} while ($this->em->getUnitOfWork()->getEntityState($entity) !== UnitOfWork::STATE_NEW); |
211
|
|
|
// TODO: Calling $em->getUnitOfWork()->isInIdentityMap($entity) returns false in the same situation. Why? |
212
|
116 |
|
return $entity; |
213
|
|
|
} |
214
|
|
|
|
215
|
40 |
|
private function copy_metadata(dbobject $source, dbobject $target, string $action = 'update') |
216
|
|
|
{ |
217
|
40 |
|
if (!$source instanceof metadata) { |
218
|
1 |
|
return; |
219
|
|
|
} |
220
|
|
|
|
221
|
39 |
|
$target->metadata_revised = $source->metadata_revised; |
|
|
|
|
222
|
39 |
|
$target->metadata_revisor = $source->metadata_revisor; |
|
|
|
|
223
|
39 |
|
$target->metadata_revision = $source->metadata_revision; |
|
|
|
|
224
|
|
|
|
225
|
39 |
|
if ($action == 'lock') { |
226
|
2 |
|
$target->metadata_islocked = $source->metadata_islocked; |
|
|
|
|
227
|
2 |
|
$target->metadata_locker = $source->metadata_locker; |
|
|
|
|
228
|
2 |
|
$target->metadata_locked = $source->metadata_locked; |
|
|
|
|
229
|
37 |
|
} elseif ($action == 'approve') { |
230
|
2 |
|
$target->metadata_isapproved = $source->metadata_isapproved; |
|
|
|
|
231
|
2 |
|
$target->metadata_approver = $source->metadata_approver; |
|
|
|
|
232
|
2 |
|
$target->metadata_approved = $source->metadata_approved; |
|
|
|
|
233
|
35 |
|
} elseif ($action == 'delete') { |
234
|
27 |
|
$target->metadata_deleted = $source->metadata_deleted; |
|
|
|
|
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.