Completed
Push — master ( 90ce09...e781d5 )
by Andreas
05:35
created

subscriber   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Test Coverage

Coverage 80.88%

Importance

Changes 18
Bugs 2 Features 1
Metric Value
eloc 152
c 18
b 2
f 1
dl 0
loc 278
ccs 127
cts 157
cp 0.8088
rs 4.5599
wmc 58

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 26 4
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 midgard\portable\storage\type\datetime;
14
use Doctrine\Common\EventSubscriber;
15
use Doctrine\Persistence\Mapping\ClassMetadata;
16
use Doctrine\ORM\Events;
17
use Doctrine\ORM\EntityManagerInterface;
18
use Doctrine\ORM\Event\OnFlushEventArgs;
19
use Doctrine\DBAL\Schema\Column;
20
use Doctrine\DBAL\Types\Type;
21
use Doctrine\DBAL\Events as dbal_events;
22
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
23
use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
24
use Doctrine\ORM\Tools\ToolEvents;
25
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
26
use Doctrine\DBAL\Types\Types;
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
36 116
    public function onFlush(OnFlushEventArgs $args)
37
    {
38 116
        $em = $args->getEntityManager();
39 116
        $uow = $em->getUnitOfWork();
40
41 116
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
42 116
            $this->on_create($entity, $em);
43
        }
44
45 116
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
46 40
            $this->on_update($entity, $em);
47
        }
48
49 116
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
50 18
            $this->on_remove($entity, $em);
51
        }
52 116
    }
53
54 116
    private function on_create(dbobject $entity, EntityManagerInterface $em)
55
    {
56 116
        $cm = $em->getClassMetadata(get_class($entity));
57 116
        if (!($entity instanceof repligard)) {
58 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...
59 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

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