Passed
Push — master ( dd2b6c...573650 )
by Andreas
04:29
created

subscriber   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 266
Duplicated Lines 0 %

Test Coverage

Coverage 84%

Importance

Changes 18
Bugs 2 Features 1
Metric Value
eloc 144
c 18
b 2
f 1
dl 0
loc 266
ccs 126
cts 150
cp 0.84
rs 5.5199
wmc 56

9 Methods

Rating   Name   Duplication   Size   Complexity  
F onSchemaCreateTable() 0 81 24
A onFlush() 0 15 4
A on_create() 0 30 5
B on_update() 0 45 10
A on_remove() 0 10 3
A calculate_size() 0 10 3
A onSchemaColumnDefinition() 0 15 2
A getSubscribedEvents() 0 6 1
A postGenerateSchemaTable() 0 9 4

How to fix   Complexity   

Complex Class

Complex classes like subscriber often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use subscriber, and based on these observations, apply Extract Interface, too.

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