Passed
Branch master (4b377c)
by Andreas
03:22
created

subscriber::on_update()   B

Complexity

Conditions 10
Paths 40

Size

Total Lines 45
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 10.0036

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 30
c 1
b 0
f 0
nc 40
nop 2
dl 0
loc 45
ccs 29
cts 30
cp 0.9667
crap 10.0036
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use Doctrine\DBAL\Platforms\SqlitePlatform;
28
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
29
30
class subscriber implements EventSubscriber
31
{
32
    const ACTION_NONE = 0;
33
    const ACTION_DELETE = 1;
34
    const ACTION_PURGE = 2;
35
    const ACTION_CREATE = 3;
36
    const ACTION_UPDATE = 4;
37
38 115
    public function onFlush(OnFlushEventArgs $args)
39
    {
40 115
        $em = $args->getEntityManager();
41 115
        $uow = $em->getUnitOfWork();
42
43 115
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
44 115
            $this->on_create($entity, $em);
45
        }
46
47 115
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
48 39
            $this->on_update($entity, $em);
49
        }
50
51 115
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
52 17
            $this->on_remove($entity, $em);
53
        }
54
    }
55
56 81
    public function postLoad(LifecycleEventArgs $args)
57
    {
58 81
        $entity = $args->getObject();
59 81
        if ($entity instanceof dbobject) {
60 81
            $om = $args->getObjectManager();
61 81
            $entity->injectObjectManager($om, $om->getClassMetadata(get_class($entity)));
62
        }
63
    }
64
65 115
    private function on_create(dbobject $entity, EntityManagerInterface $em)
66
    {
67 115
        $cm = $em->getClassMetadata(get_class($entity));
68 115
        if (!($entity instanceof repligard)) {
69 115
            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...
70 102
                $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

70
                $entity->/** @scrutinizer ignore-call */ 
71
                         set_guid(connection::generate_guid());
Loading history...
71 102
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
72
            }
73
74 115
            $om = new objectmanager($em);
75 115
            $repligard_cm = $em->getClassMetadata(connection::get_fqcn('midgard_repligard'));
76 115
            $repligard_entry = $om->new_instance($repligard_cm->getName());
77 115
            $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...
78 115
            $repligard_entry->guid = $entity->guid;
79 115
            $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...
80 115
            $em->persist($repligard_entry);
81 115
            $em->getUnitOfWork()->computeChangeSet($repligard_cm, $repligard_entry);
82
        }
83
84 115
        if ($entity instanceof metadata) {
85 97
            $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...
86
            // we copy here instead of creating a new, because otherwise we might have
87
            // a one second difference if the code runs at the right millisecond
88 97
            $entity->metadata->revised = $entity->metadata->created;
89 97
            if ($user = connection::get_user()) {
90 7
                $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...
91 7
                $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...
92
            }
93 97
            $entity->metadata->size = $this->calculate_size($cm, $entity);
94 97
            $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
95
        }
96
    }
97
98 39
    private function on_update(dbobject $entity, EntityManagerInterface $em)
99
    {
100 39
        if ($entity instanceof repligard) {
101
            return;
102
        }
103 39
        $check_repligard = true;
104 39
        $deleted = false;
105 39
        if ($entity instanceof metadata) {
106 38
            $deleted = $entity->{metadata::DELETED_FIELD};
107 38
            $cs = $em->getUnitOfWork()->getEntityChangeSet($entity);
108
            // We only need to update repligard if we're coming from create (revision 0)
109
            // or if we delete/undelete
110 38
            if (   !array_key_exists('metadata_deleted', $cs)
111 38
                && $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...
112 4
                $check_repligard = false;
113
            }
114
115 38
            $create_revision = true;
116 38
            if (array_key_exists('metadata_islocked', $cs)) {
117 2
                $lock_fields = array_flip(['metadata_locked', 'metadata_islocked', 'metadata_locker']);
118 2
                $create_revision = !empty(array_diff_key($cs, $lock_fields));
119
            }
120
121 38
            if ($create_revision) {
122 36
                $cm = $em->getClassMetadata(get_class($entity));
123 36
                $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...
124 36
                $entity->metadata_revision++;
125 36
                if ($user = connection::get_user()) {
126 5
                    $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...
127
                }
128 36
                $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...
129 36
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
130
            }
131
        }
132
133 39
        if ($check_repligard) {
134 39
            $repligard_entry = $em->getRepository(connection::get_fqcn('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...
135
136 39
            if ($deleted) {
137 26
                $repligard_entry->object_action = self::ACTION_DELETE;
138
            } else {
139 21
                $repligard_entry->object_action = self::ACTION_UPDATE;
140
            }
141 39
            $em->persist($repligard_entry);
142 39
            $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata(connection::get_fqcn('midgard_repligard')), $repligard_entry);
143
        }
144
    }
145
146 17
    private function on_remove(dbobject $entity, EntityManagerInterface $em)
147
    {
148 17
        if (!($entity instanceof repligard)) {
149 17
            $repligard_entry = $em->getRepository(connection::get_fqcn('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...
150 17
            if (empty($repligard_entry)) {
151
                connection::log()->error('No repligard entry found for GUID ' . $entity->guid);
152
            } else {
153 17
                $repligard_entry->object_action = self::ACTION_PURGE;
154 17
                $em->persist($repligard_entry);
155 17
                $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata(connection::get_fqcn('midgard_repligard')), $repligard_entry);
156
            }
157
        }
158
    }
159
160 97
    private function calculate_size(ClassMetadata $cm, metadata $entity) : int
161
    {
162 97
        $size = 0;
163 97
        foreach ($cm->getAssociationNames() as $name) {
164 93
            $size += strlen($entity->$name);
165
        }
166 97
        foreach ($cm->getFieldNames() as $name) {
167 97
            $size += strlen($entity->$name);
168
        }
169 97
        return $size;
170
    }
171
172 6
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
173
    {
174 6
        $platform = $args->getPlatform();
175 6
        $columns = $args->getColumns();
176 6
        $modified = false;
177
178 6
        foreach ($columns as $name => &$config) {
179 6
            if ($platform instanceof SqlitePlatform) {
180 6
                if (   !empty($config['comment'])
181 6
                    && $config['comment'] == 'BINARY') {
182 1
                    $modified = true;
183 1
                    $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' COLLATE BINARY' . $platform->getDefaultValueDeclarationSQL($config);
184
                }
185
            }
186 6
            if ($platform instanceof AbstractMySQLPlatform) {
187
                if (!empty($config['comment'])) {
188
                    if ($config['comment'] == 'BINARY') {
189
                        $modified = true;
190
                        $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' CHARACTER SET utf8 COLLATE utf8_bin' . $platform->getDefaultValueDeclarationSQL($config);
191
                    }
192
                    if (substr(strtolower(trim($config['comment'])), 0, 3) == 'set') {
193
                        $modified = true;
194
                        $config['columnDefinition'] = $config['comment'] . $platform->getDefaultValueDeclarationSQL($config);
195
                    }
196
                    if (!empty($config['columnDefinition']) && $platform->supportsInlineColumnComments()) {
197
                        $config['columnDefinition'] .=  " COMMENT " . $platform->quoteStringLiteral($config['comment']);
198
                    }
199
                }
200
            }
201
        }
202
203 6
        if (!$modified) {
204 5
            return;
205
        }
206
207 1
        $args->preventDefault();
208
209
        //The following is basically copied from the respective Doctrine function, since there seems to be no way
210
        //to just modify columns and pass them back to the SchemaManager
211 1
        $table = $args->getTable();
212 1
        $options = $args->getOptions();
213
214 1
        $queryFields = $platform->getColumnDeclarationListSQL($columns);
215
216 1
        if (!empty($options['uniqueConstraints'])) {
217
            foreach ($options['uniqueConstraints'] as $name => $definition) {
218
                $queryFields .= ', ' . $platform->getUniqueConstraintDeclarationSQL($name, $definition);
219
            }
220
        }
221
222 1
        if (!empty($options['foreignKeys'])) {
223
            foreach ($options['foreignKeys'] as $foreignKey) {
224
                $queryFields .= ', ' . $platform->getForeignKeyDeclarationSQL($foreignKey);
225
            }
226
        }
227
228 1
        $name = str_replace('.', '__', $table->getName());
229 1
        $args->addSql('CREATE TABLE ' . $name . ' (' . $queryFields . ')');
230
231 1
        if (isset($options['alter']) && true === $options['alter']) {
232
            return;
233
        }
234
235 1
        if (!empty($options['indexes'])) {
236
            foreach ($options['indexes'] as $indexDef) {
237
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
238
            }
239
        }
240
241 1
        if (!empty($options['unique'])) {
242
            foreach ($options['unique'] as $indexDef) {
243
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
244
            }
245
        }
246
    }
247
248
    /**
249
     * This function contains workarounds for reading existing Midgard databases
250
     *
251
     * ENUM fields are converted to string for now (Like in the XML reader)
252
     */
253 2
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args)
254
    {
255 2
        $column = array_change_key_case($args->getTableColumn(), CASE_LOWER);
256 2
        $type = strtok($column['type'], '()');
257
258 2
        if ($type == 'enum') {
259
            $options = [
260
                'length' => 255,
261
                'default' => $column['default'] ?? null,
262
                'notnull' => $column['null'] != 'YES',
263
                'comment' => $column['type']
264
            ];
265
266
            $args->preventDefault();
267
            $args->setColumn(new Column($column['field'], Type::getType(Types::STRING), $options));
268
        }
269
    }
270
271
    /**
272
     * This is mostly a workaround for the fact that SchemaTool wants to create FKs on
273
     * each run since it doesn't detect that MyISAM tables don't support them
274
     *
275
     * @see https://github.com/doctrine/orm/issues/4270
276
     * @param GenerateSchemaTableEventArgs $args
277
     */
278 4
    public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $args)
279
    {
280 4
        $table = $args->getClassTable();
281 4
        if (   !$table->hasOption('engine')
282 4
            || $table->getOption('engine') !== 'MyISAM') {
283
            return;
284
        }
285 4
        foreach ($table->getForeignKeys() as $key) {
286 3
            $table->removeForeignKey($key->getName());
287
        }
288
    }
289
290 9
    public function getSubscribedEvents()
291
    {
292
        return [
293 9
            Events::postLoad,
294
            Events::onFlush,
295
            dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
296
            ToolEvents::postGenerateSchemaTable
297
        ];
298
    }
299
}
300