Passed
Push — master ( dd2b6c...573650 )
by Andreas
04:29
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 3
Bugs 0 Features 0
Metric Value
cc 10
eloc 30
c 3
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
27
class subscriber implements EventSubscriber
28
{
29
    const ACTION_NONE = 0;
30
    const ACTION_DELETE = 1;
31
    const ACTION_PURGE = 2;
32
    const ACTION_CREATE = 3;
33
    const ACTION_UPDATE = 4;
34
35 116
    public function onFlush(OnFlushEventArgs $args)
36
    {
37 116
        $em = $args->getEntityManager();
38 116
        $uow = $em->getUnitOfWork();
39
40 116
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
41 116
            $this->on_create($entity, $em);
42
        }
43
44 116
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
45 40
            $this->on_update($entity, $em);
46
        }
47
48 116
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
49 18
            $this->on_remove($entity, $em);
50
        }
51 116
    }
52
53 116
    private function on_create(dbobject $entity, EntityManagerInterface $em)
54
    {
55 116
        $cm = $em->getClassMetadata(get_class($entity));
56 116
        if (!($entity instanceof repligard)) {
57 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...
58 103
                $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

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