Completed
Push — master ( cb73dc...79e5e4 )
by Andreas
07:42
created

objectmanager   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Test Coverage

Coverage 96.55%

Importance

Changes 0
Metric Value
dl 0
loc 220
ccs 140
cts 145
cp 0.9655
rs 10
c 0
b 0
f 0
wmc 29

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A update() 0 13 1
A purge() 0 11 1
B copy_metadata() 0 20 5
A approve() 0 12 1
A unapprove() 0 12 1
A copy_associations() 0 4 2
A unlock() 0 9 1
A new_instance() 0 16 3
A create() 0 19 4
A undelete() 0 8 1
A lock() 0 12 1
B kill_potential_proxies() 0 16 5
A delete() 0 22 2
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 116
    }
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 31
            }
35 116
        }
36
37
        //workaround for possible oid collisions in UnitOfWork
38
        //see http://www.doctrine-project.org/jira/browse/DDC-2785
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 116
    }
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 27
    private function kill_potential_proxies($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 24
            } elseif (!array_key_exists($name, $changed_associations)) {
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;
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ORM\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
91 11
                    continue;
92
                }
93
            }
94 27
        }
95 27
    }
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);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on midgard\portable\api\dbobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
102
103
        //workaround for possible oid collisions in UnitOfWork
104
        //see http://www.doctrine-project.org/jira/browse/DDC-2785
105 27
        if ($this->em->getUnitOfWork()->getEntityState($copy) != UnitOfWork::STATE_DETACHED) {
106 1
            connection::log()->warning('oid collision during delete detected, detaching ' . spl_object_hash($copy));
107 1
            $this->em->detach($copy);
108 1
        }
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 27
    }
120
121 3
    public function undelete(dbobject $entity)
122
    {
123 3
        $entity->metadata_deleted = false;
0 ignored issues
show
Bug Best Practice introduced by
The property metadata_deleted does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
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
    public function approve(dbobject $entity)
145
    {
146 2
        $user = connection::get_user();
147 2
        $ref = $this->em->getReference(get_class($entity), $entity->id);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on midgard\portable\api\dbobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
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
    public function unapprove(dbobject $entity)
159
    {
160 1
        $user = connection::get_user();
161 1
        $ref = $this->em->getReference(get_class($entity), $entity->id);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on midgard\portable\api\dbobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
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
    public function lock(dbobject $entity)
173
    {
174 2
        $user = connection::get_user();
175 2
        $ref = $this->em->getReference(get_class($entity), $entity->id);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on midgard\portable\api\dbobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on midgard\portable\api\dbobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
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 116
    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 116
        $counter = 0;
206 116
        $candidates = [];
207
        do {
208 116
            $entity = new $classname;
209 116
            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 116
            $candidates[] = $entity;
214 116
        } while ($this->em->getUnitOfWork()->getEntityState($entity) !== UnitOfWork::STATE_NEW);
215
        // TODO: Calling $em->getUnitOfWork()->isInIdentityMap($entity) returns false in the same situation. Why?
216 116
        return $entity;
217
    }
218
219 40
    private function copy_metadata($source, $target, $action = 'update')
220
    {
221 40
        if (!$source instanceof metadata) {
222 1
            return;
223
        }
224
225 39
        $target->metadata_revised = $source->metadata_revised;
0 ignored issues
show
Bug introduced by
Accessing metadata_revised on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
226 39
        $target->metadata_revisor = $source->metadata_revisor;
0 ignored issues
show
Bug introduced by
Accessing metadata_revisor on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
227 39
        $target->metadata_revision = $source->metadata_revision;
0 ignored issues
show
Bug introduced by
Accessing metadata_revision on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
228
229 39
        if ($action == 'lock') {
230 2
            $target->metadata_islocked = $source->metadata_islocked;
0 ignored issues
show
Bug introduced by
Accessing metadata_islocked on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
231 2
            $target->metadata_locker = $source->metadata_locker;
0 ignored issues
show
Bug introduced by
Accessing metadata_locker on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
232 2
            $target->metadata_locked = $source->metadata_locked;
0 ignored issues
show
Bug introduced by
Accessing metadata_locked on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
233 39
        } elseif ($action == 'approve') {
234 2
            $target->metadata_isapproved = $source->metadata_isapproved;
0 ignored issues
show
Bug introduced by
Accessing metadata_isapproved on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
235 2
            $target->metadata_approver = $source->metadata_approver;
0 ignored issues
show
Bug introduced by
Accessing metadata_approver on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
236 2
            $target->metadata_approved = $source->metadata_approved;
0 ignored issues
show
Bug introduced by
Accessing metadata_approved on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
237 37
        } elseif ($action == 'delete') {
238 27
            $target->metadata_deleted = $source->metadata_deleted;
0 ignored issues
show
Bug introduced by
Accessing metadata_deleted on the interface midgard\portable\storage\interfaces\metadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
239 27
        }
240 39
    }
241
}
242