Test Failed
Push — master ( 421470...12a314 )
by Andreas
08:33
created

subscriber::postLoad()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
ccs 5
cts 5
cp 1
crap 2
rs 10
c 1
b 0
f 0
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\storage\interfaces\metadata;
11
use midgard\portable\api\dbobject;
12
use midgard\portable\api\repligard;
13
use Doctrine\Common\EventSubscriber;
14
use Doctrine\Persistence\Mapping\ClassMetadata;
15
use Doctrine\ORM\Events;
16
use Doctrine\ORM\EntityManagerInterface;
17
use Doctrine\ORM\Event\OnFlushEventArgs;
18
use Doctrine\DBAL\Schema\Column;
19
use Doctrine\DBAL\Types\Type;
20
use Doctrine\DBAL\Events as dbal_events;
21
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
22
use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
23
use Doctrine\ORM\Tools\ToolEvents;
24
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
25
use Doctrine\DBAL\Types\Types;
26
use Doctrine\Persistence\Event\LifecycleEventArgs;
27
28
class subscriber implements EventSubscriber
29
{
30
    const ACTION_NONE = 0;
31
    const ACTION_DELETE = 1;
32
    const ACTION_PURGE = 2;
33
    const ACTION_CREATE = 3;
34
    const ACTION_UPDATE = 4;
35 116
36
    public function onFlush(OnFlushEventArgs $args)
37 116
    {
38 116
        $em = $args->getEntityManager();
39
        $uow = $em->getUnitOfWork();
40 116
41 116
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
42
            $this->on_create($entity, $em);
43
        }
44 116
45 40
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
46
            $this->on_update($entity, $em);
47
        }
48 116
49 18
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
50
            $this->on_remove($entity, $em);
51 116
        }
52
    }
53 116
54
    public function postLoad(LifecycleEventArgs $args)
55 116
    {
56 116
        $entity = $args->getObject();
57 116
        if ($entity instanceof dbobject) {
58 103
            $om = $args->getObjectManager();
59 103
            $entity->injectObjectManager($om, $om->getClassMetadata(get_class($entity)));
60
        }
61
    }
62 116
63 116
    private function on_create(dbobject $entity, EntityManagerInterface $em)
64 116
    {
65 116
        $cm = $em->getClassMetadata(get_class($entity));
66 116
        if (!($entity instanceof repligard)) {
67 116
            if (empty($entity->guid)) {
0 ignored issues
show
Bug Best Practice introduced by
The property $guid is declared protected in midgard\portable\api\dbobject. Since you implement __get, consider adding a @property or @property-read.
Loading history...
68 116
                $entity->set_guid(connection::generate_guid());
0 ignored issues
show
Bug introduced by
The method set_guid() does not exist on midgard\portable\api\dbobject. It seems like you code against a sub-type of midgard\portable\api\dbobject such as midgard\portable\api\mgdobject. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

68
                $entity->/** @scrutinizer ignore-call */ 
69
                         set_guid(connection::generate_guid());
Loading history...
69 116
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
70
            }
71
72 116
            $om = new objectmanager($em);
73 98
            $repligard_cm = $em->getClassMetadata('midgard:midgard_repligard');
74
            $repligard_entry = $om->new_instance($repligard_cm->getName());
75
            $repligard_entry->typename = $cm->getReflectionClass()->getShortName();
0 ignored issues
show
Bug Best Practice introduced by
The property typename does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
76 98
            $repligard_entry->guid = $entity->guid;
77 98
            $repligard_entry->object_action = self::ACTION_CREATE;
0 ignored issues
show
Bug Best Practice introduced by
The property object_action does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
78 7
            $em->persist($repligard_entry);
79 7
            $em->getUnitOfWork()->computeChangeSet($repligard_cm, $repligard_entry);
80
        }
81 98
82 98
        if ($entity instanceof metadata) {
83
            $entity->metadata->created = new \midgard_datetime();
0 ignored issues
show
Bug Best Practice introduced by
The property metadata does not exist on midgard\portable\api\repligard. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property metadata does not exist on midgard\portable\api\dbobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
84 116
            // we copy here instead of creating a new, because otherwise we might have
85
            // a one second difference if the code runs at the right millisecond
86 40
            $entity->metadata->revised = $entity->metadata->created;
87
            if ($user = connection::get_user()) {
88 40
                $entity->metadata_creator = $user->person;
0 ignored issues
show
Bug Best Practice introduced by
The property metadata_creator does not exist on midgard\portable\api\repligard. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property metadata_creator does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
89
                $entity->metadata_revisor = $user->person;
0 ignored issues
show
Bug Best Practice introduced by
The property metadata_revisor does not exist on midgard\portable\api\repligard. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property metadata_revisor does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
90
            }
91 40
            $entity->metadata->size = $this->calculate_size($cm, $entity);
92 40
            $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
93 40
        }
94 39
    }
95 39
96
    private function on_update(dbobject $entity, EntityManagerInterface $em)
97
    {
98 39
        if ($entity instanceof repligard) {
99 39
            return;
100 4
        }
101
        $check_repligard = true;
102
        $deleted = false;
103 39
        if ($entity instanceof metadata) {
104 39
            $deleted = $entity->{metadata::DELETED_FIELD};
105 2
            $cs = $em->getUnitOfWork()->getEntityChangeSet($entity);
106 2
            // We only need to update repligard if we're coming from create (revision 0)
107
            // or if we delete/undelete
108
            if (   !array_key_exists('metadata_deleted', $cs)
109 39
                && $entity->metadata_revision > 0)  {
0 ignored issues
show
Bug Best Practice introduced by
The property metadata_revision does not exist on midgard\portable\api\dbobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
110 37
                $check_repligard = false;
111 37
            }
112 37
113 37
            $create_revision = true;
114 5
            if (array_key_exists('metadata_islocked', $cs)) {
115
                $lock_fields = array_flip(['metadata_locked', 'metadata_islocked', 'metadata_locker']);
116 37
                $create_revision = !empty(array_diff_key($cs, $lock_fields));
117 37
            }
118
119
            if ($create_revision) {
120
                $cm = $em->getClassMetadata(get_class($entity));
121 40
                $entity->metadata_revised = new \midgard_datetime();
0 ignored issues
show
Bug Best Practice introduced by
The property metadata_revised does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
122 40
                $entity->metadata_revision++;
123
                if ($user = connection::get_user()) {
124 40
                    $entity->metadata_revisor = $user->person;
0 ignored issues
show
Bug Best Practice introduced by
The property metadata_revisor does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
125 27
                }
126
                $entity->metadata->size = $this->calculate_size($cm, $entity);
0 ignored issues
show
Bug Best Practice introduced by
The property metadata does not exist on midgard\portable\api\dbobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
127 21
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
128
            }
129 40
        }
130 40
131
        if ($check_repligard) {
132 40
            $repligard_entry = $em->getRepository('midgard:midgard_repligard')->findOneBy(['guid' => $entity->guid]);
0 ignored issues
show
Bug Best Practice introduced by
The property $guid is declared protected in midgard\portable\api\dbobject. Since you implement __get, consider adding a @property or @property-read.
Loading history...
133
134 18
            if ($deleted) {
135
                $repligard_entry->object_action = self::ACTION_DELETE;
136 18
            } else {
137 18
                $repligard_entry->object_action = self::ACTION_UPDATE;
138 18
            }
139
            $em->persist($repligard_entry);
140
            $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata('midgard:midgard_repligard'), $repligard_entry);
141 18
        }
142 18
    }
143 18
144
    private function on_remove(dbobject $entity, EntityManagerInterface $em)
145
    {
146 18
        if (!($entity instanceof repligard)) {
147
            $repligard_entry = $em->getRepository('midgard:midgard_repligard')->findOneBy(['guid' => $entity->guid]);
0 ignored issues
show
Bug Best Practice introduced by
The property $guid is declared protected in midgard\portable\api\dbobject. Since you implement __get, consider adding a @property or @property-read.
Loading history...
148 98
            if (empty($repligard_entry)) {
149
                connection::log()->error('No repligard entry found for GUID ' . $entity->guid);
150 98
            } else {
151 98
                $repligard_entry->object_action = self::ACTION_PURGE;
152 94
                $em->persist($repligard_entry);
153
                $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata('midgard:midgard_repligard'), $repligard_entry);
154 98
            }
155 98
        }
156
    }
157 98
158
    private function calculate_size(ClassMetadata $cm, metadata $entity) : int
159
    {
160 6
        $size = 0;
161
        foreach ($cm->getAssociationNames() as $name) {
162 6
            $size += strlen($entity->$name);
163 6
        }
164 6
        foreach ($cm->getFieldNames() as $name) {
165
            $size += strlen($entity->$name);
166 6
        }
167 6
        return $size;
168 6
    }
169 6
170 1
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
171 1
    {
172
        $platform = $args->getPlatform();
173
        $columns = $args->getColumns();
174 6
        $modified = false;
175
176
        foreach ($columns as $name => &$config) {
177
            if ($platform->getName() === 'sqlite') {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Platforms\...ractPlatform::getName() has been deprecated: Identify platforms by their class. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

177
            if (/** @scrutinizer ignore-deprecated */ $platform->getName() === 'sqlite') {

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.

Loading history...
178
                if (   !empty($config['comment'])
179
                    && $config['comment'] == 'BINARY') {
180
                    $modified = true;
181
                    $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' COLLATE BINARY' . $platform->getDefaultValueDeclarationSQL($config);
182
                }
183
            }
184
            if ($platform->getName() === 'mysql') {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Platforms\...ractPlatform::getName() has been deprecated: Identify platforms by their class. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

184
            if (/** @scrutinizer ignore-deprecated */ $platform->getName() === 'mysql') {

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.

Loading history...
185
                if (!empty($config['comment'])) {
186
                    if ($config['comment'] == 'BINARY') {
187
                        $modified = true;
188
                        $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' CHARACTER SET utf8 COLLATE utf8_bin' . $platform->getDefaultValueDeclarationSQL($config);
189
                    }
190
                    if (substr(strtolower(trim($config['comment'])), 0, 3) == 'set') {
191 6
                        $modified = true;
192 5
                        $config['columnDefinition'] = $config['comment'] . $platform->getDefaultValueDeclarationSQL($config);
193
                    }
194
                    if (!empty($config['columnDefinition']) && $platform->supportsInlineColumnComments()) {
195 1
                        $config['columnDefinition'] .=  " COMMENT " . $platform->quoteStringLiteral($config['comment']);
196
                    }
197
                }
198
            }
199 1
        }
200 1
201
        if (!$modified) {
202 1
            return;
203
        }
204 1
205
        $args->preventDefault();
206
207
        //The following is basically copied from the respective Doctrine function, since there seems to be no way
208
        //to just modify columns and pass them back to the SchemaManager
209
        $table = $args->getTable();
210 1
        $options = $args->getOptions();
211
212
        $queryFields = $platform->getColumnDeclarationListSQL($columns);
213
214
        if (!empty($options['uniqueConstraints'])) {
215
            foreach ($options['uniqueConstraints'] as $name => $definition) {
216 1
                $queryFields .= ', ' . $platform->getUniqueConstraintDeclarationSQL($name, $definition);
217 1
            }
218
        }
219 1
220
        if (!empty($options['foreignKeys'])) {
221
            foreach ($options['foreignKeys'] as $foreignKey) {
222
                $queryFields .= ', ' . $platform->getForeignKeyDeclarationSQL($foreignKey);
223 1
            }
224
        }
225
226
        $name = str_replace('.', '__', $table->getName());
227
        $args->addSql('CREATE TABLE ' . $name . ' (' . $queryFields . ')');
228
229 1
        if (isset($options['alter']) && true === $options['alter']) {
230
            return;
231
        }
232
233
        if (!empty($options['indexes'])) {
234 1
            foreach ($options['indexes'] as $indexDef) {
235
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
236
            }
237
        }
238
239
        if (!empty($options['unique'])) {
240
            foreach ($options['unique'] as $indexDef) {
241 2
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
242
            }
243 2
        }
244 2
    }
245
246 2
    /**
247
     * This function contains workarounds for reading existing Midgard databases
248
     *
249
     * ENUM fields are converted to string for now (Like in the XML reader)
250
     */
251
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args)
252
    {
253
        $column = array_change_key_case($args->getTableColumn(), CASE_LOWER);
254
        $type = strtok($column['type'], '()');
255
256
        if ($type == 'enum') {
257 2
            $options = [
258
                'length' => 255,
259
                'default' => $column['default'] ?? null,
260
                'notnull' => $column['null'] != 'YES',
261
                'comment' => $column['type']
262
            ];
263
264
            $args->preventDefault();
265
            $args->setColumn(new Column($column['field'], Type::getType(Types::STRING), $options));
266 4
        }
267
    }
268 4
269 4
    /**
270 4
     * This is mostly a workaround for the fact that SchemaTool wants to create FKs on
271
     * each run since it doesn't detect that MyISAM tables don't support them
272
     *
273 4
     * @see https://github.com/doctrine/orm/issues/4270
274 3
     * @param GenerateSchemaTableEventArgs $args
275
     */
276 4
    public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $args)
277
    {
278 9
        $table = $args->getClassTable();
279
        if (   !$table->hasOption('engine')
280
            || $table->getOption('engine') !== 'MyISAM') {
281 9
            return;
282 9
        }
283 9
        foreach ($table->getForeignKeys() as $key) {
284
            $table->removeForeignKey($key->getName());
285
        }
286
    }
287
288
    public function getSubscribedEvents()
289
    {
290
        return [
291
            Events::postLoad,
292
            Events::onFlush,
293
            dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
294
            ToolEvents::postGenerateSchemaTable
295
        ];
296
    }
297
}
298