Passed
Push — master ( bd537c...93b551 )
by Andreas
03:32
created

subscriber   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Test Coverage

Coverage 80.13%

Importance

Changes 20
Bugs 2 Features 1
Metric Value
eloc 154
c 20
b 2
f 1
dl 0
loc 277
ccs 125
cts 156
cp 0.8013
rs 4.5599
wmc 58

10 Methods

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

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
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->getObjectManager();
41 115
        $uow = $em->getUnitOfWork();
0 ignored issues
show
Bug introduced by
The method getUnitOfWork() does not exist on Doctrine\Persistence\ObjectManager. It seems like you code against a sub-type of said class. However, the method does not exist in Doctrine\Persistence\ObjectManagerDecorator. Are you sure you never get one of those? ( Ignorable by Annotation )

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

41
        /** @scrutinizer ignore-call */ 
42
        $uow = $em->getUnitOfWork();
Loading history...
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
            switch ($cm->fieldMappings[$name]['type']) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
168 97
                case 'datetime':
169 97
                    $size += 19; // Y-m-d H:i:s
170 97
                    break;
171 97
                case 'date':
172
                    $size += 10; // Y-m-d
173
                    break;
174
                default:
175 97
                    $size += strlen($entity->$name);
176 97
                    break;
177
            }
178
        }
179 97
        return $size;
180
    }
181
182 6
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
183
    {
184 6
        $platform = $args->getPlatform();
185 6
        $columns = $args->getColumns();
186 6
        $modified = false;
187
188 6
        foreach ($columns as $name => &$config) {
189 6
            if ($platform instanceof SqlitePlatform) {
190 6
                if (   !empty($config['comment'])
191 6
                    && $config['comment'] == 'BINARY') {
192 1
                    $modified = true;
193 1
                    $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' COLLATE BINARY' . $platform->getDefaultValueDeclarationSQL($config);
194
                }
195
            }
196 6
            if ($platform instanceof AbstractMySQLPlatform) {
197
                if (!empty($config['comment'])) {
198
                    if ($config['comment'] == 'BINARY') {
199
                        $modified = true;
200
                        $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' CHARACTER SET utf8 COLLATE utf8_bin' . $platform->getDefaultValueDeclarationSQL($config);
201
                    }
202
                    if (substr(strtolower(trim($config['comment'])), 0, 3) == 'set') {
203
                        $modified = true;
204
                        $config['columnDefinition'] = $config['comment'] . $platform->getDefaultValueDeclarationSQL($config);
205
                    }
206
                    if (!empty($config['columnDefinition']) && $platform->supportsInlineColumnComments()) {
207
                        $config['columnDefinition'] .=  " COMMENT " . $platform->quoteStringLiteral($config['comment']);
208
                    }
209
                }
210
            }
211
        }
212
213 6
        if (!$modified) {
214 5
            return;
215
        }
216
217 1
        $args->preventDefault();
218
219
        //The following is basically copied from the respective Doctrine function, since there seems to be no way
220
        //to just modify columns and pass them back to the SchemaManager
221 1
        $table = $args->getTable();
222 1
        $options = $args->getOptions();
223
224 1
        $queryFields = $platform->getColumnDeclarationListSQL($columns);
225
226 1
        if (!empty($options['uniqueConstraints'])) {
227
            foreach ($options['uniqueConstraints'] as $name => $definition) {
228
                $queryFields .= ', ' . $platform->getUniqueConstraintDeclarationSQL($name, $definition);
229
            }
230
        }
231
232 1
        if (!empty($options['foreignKeys'])) {
233
            foreach ($options['foreignKeys'] as $foreignKey) {
234
                $queryFields .= ', ' . $platform->getForeignKeyDeclarationSQL($foreignKey);
235
            }
236
        }
237
238 1
        $name = str_replace('.', '__', $table->getName());
239 1
        $args->addSql('CREATE TABLE ' . $name . ' (' . $queryFields . ')');
240
241 1
        if (isset($options['alter']) && true === $options['alter']) {
242
            return;
243
        }
244
245 1
        if (!empty($options['indexes'])) {
246
            foreach ($options['indexes'] as $indexDef) {
247
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
248
            }
249
        }
250
251 1
        if (!empty($options['unique'])) {
252
            foreach ($options['unique'] as $indexDef) {
253
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
254
            }
255
        }
256
    }
257
258
    /**
259
     * This function contains workarounds for reading existing Midgard databases
260
     *
261
     * ENUM fields are converted to string for now (Like in the XML reader)
262
     */
263 2
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args)
264
    {
265 2
        $column = array_change_key_case($args->getTableColumn(), CASE_LOWER);
266 2
        $type = strtok($column['type'], '()');
267
268 2
        if ($type == 'enum') {
269
            $options = [
270
                'length' => 255,
271
                'default' => $column['default'] ?? null,
272
                'notnull' => $column['null'] != 'YES',
273
                'comment' => $column['type']
274
            ];
275
276
            $args->preventDefault();
277
            $args->setColumn(new Column($column['field'], Type::getType(Types::STRING), $options));
278
        }
279
    }
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
    }
299
300 9
    public function getSubscribedEvents()
301
    {
302 9
        return [
303 9
            Events::postLoad,
304 9
            Events::onFlush,
305 9
            dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
0 ignored issues
show
introduced by
The constant Doctrine\DBAL\Events::onSchemaColumnDefinition has been deprecated. ( Ignorable by Annotation )

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

305
            dbal_events::onSchemaCreateTable, /** @scrutinizer ignore-deprecated */ dbal_events::onSchemaColumnDefinition,
Loading history...
introduced by
The constant Doctrine\DBAL\Events::onSchemaCreateTable has been deprecated. ( Ignorable by Annotation )

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

305
            /** @scrutinizer ignore-deprecated */ dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
Loading history...
306 9
            ToolEvents::postGenerateSchemaTable
307 9
        ];
308
    }
309
}
310