subscriber   D
last analyzed

Complexity

Total Complexity 58

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Test Coverage

Coverage 80.52%

Importance

Changes 23
Bugs 2 Features 1
Metric Value
eloc 151
dl 0
loc 274
ccs 124
cts 154
cp 0.8052
rs 4.5599
c 23
b 2
f 1
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
A on_remove() 0 10 3
B on_update() 0 44 11
F onSchemaCreateTable() 0 72 22
A onSchemaColumnDefinition() 0 15 2
A getSubscribedEvents() 0 7 1
A calculate_size() 0 18 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
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 118
    public function onFlush(OnFlushEventArgs $args)
39
    {
40 118
        $em = $args->getObjectManager();
41 118
        $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 118
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
44 118
            $this->on_create($entity, $em);
45
        }
46
47 118
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
48 40
            $this->on_update($entity, $em);
49
        }
50
51 118
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
52 18
            $this->on_remove($entity, $em);
53
        }
54
    }
55
56 83
    public function postLoad(LifecycleEventArgs $args)
57
    {
58 83
        $entity = $args->getObject();
59 83
        if ($entity instanceof dbobject) {
60 83
            $om = $args->getObjectManager();
61 83
            $entity->injectObjectManager($om, $om->getClassMetadata(get_class($entity)));
62
        }
63
    }
64
65 118
    private function on_create(dbobject $entity, EntityManagerInterface $em)
66
    {
67 118
        $cm = $em->getClassMetadata(get_class($entity));
68 118
        if (!($entity instanceof repligard)) {
69 118
            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 105
                $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 105
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
72
            }
73
74 118
            $om = new objectmanager($em);
75 118
            $repligard_cm = $em->getClassMetadata(connection::get_fqcn('midgard_repligard'));
76 118
            $repligard_entry = $om->new_instance($repligard_cm->getName());
77 118
            $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 118
            $repligard_entry->guid = $entity->guid;
79 118
            $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 118
            $em->persist($repligard_entry);
81 118
            $em->getUnitOfWork()->computeChangeSet($repligard_cm, $repligard_entry);
82
        }
83
84 118
        if ($entity instanceof metadata) {
85 100
            $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 100
            $entity->metadata->revised = $entity->metadata->created;
89 100
            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 100
            $entity->metadata->size = $this->calculate_size($cm, $entity);
94 100
            $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
95
        }
96
    }
97
98 40
    private function on_update(dbobject $entity, EntityManagerInterface $em)
99
    {
100 40
        if ($entity instanceof repligard) {
101
            return;
102
        }
103 40
        $check_repligard = true;
104 40
        $deleted = false;
105 40
        if ($entity instanceof metadata) {
106 39
            $deleted = $entity->{metadata::DELETED_FIELD};
107 39
            $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 39
            if (   !array_key_exists('metadata_deleted', $cs)
111 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...
112 4
                $check_repligard = false;
113
            }
114
115 39
            $create_revision = true;
116 39
            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 39
            if ($create_revision) {
122 37
                $cm = $em->getClassMetadata(get_class($entity));
123 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...
124 37
                $entity->metadata_revision++;
125 37
                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 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...
129 37
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
130
            }
131
        }
132
133 40
        if ($check_repligard) {
134 40
            $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 40
            if (empty($repligard_entry)) {
137
                connection::log()->error('No repligard entry found for GUID ' . $entity->guid);
138
            } else {
139 40
                $repligard_entry->object_action = $deleted ? self::ACTION_DELETE : self::ACTION_UPDATE;
140 40
                $em->persist($repligard_entry);
141 40
                $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata(connection::get_fqcn('midgard_repligard')), $repligard_entry);
142
            }
143
        }
144
    }
145
146 18
    private function on_remove(dbobject $entity, EntityManagerInterface $em)
147
    {
148 18
        if (!($entity instanceof repligard)) {
149 18
            $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 18
            if (empty($repligard_entry)) {
151
                connection::log()->error('No repligard entry found for GUID ' . $entity->guid);
152
            } else {
153 18
                $repligard_entry->object_action = self::ACTION_PURGE;
154 18
                $em->persist($repligard_entry);
155 18
                $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata(connection::get_fqcn('midgard_repligard')), $repligard_entry);
156
            }
157
        }
158
    }
159
160 100
    private function calculate_size(ClassMetadata $cm, metadata $entity) : int
161
    {
162 100
        $size = 0;
163 100
        foreach ($cm->getAssociationNames() as $name) {
164 95
            if ($cm->midgard['links_as_entities']) {
0 ignored issues
show
Bug introduced by
Accessing midgard on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
165 1
                $size += strlen($entity->$name?->id ?? '');
166
            } else {
167 94
                $size += strlen($entity->$name);
168
            }
169
        }
170 100
        foreach ($cm->getFieldNames() as $name) {
171 100
            match ($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...
172 100
                Types::DATETIME_MUTABLE => $size += 19, // Y-m-d H:i:s
173 100
                Types::DATE_MUTABLE => $size += 10, // Y-m-d
174 100
                default => $size += strlen($entity->$name),
175 100
            };
176
        }
177 100
        return $size;
178
    }
179
180 7
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
181
    {
182 7
        $platform = $args->getPlatform();
183 7
        $columns = $args->getColumns();
184 7
        $modified = false;
185
186 7
        foreach ($columns as $name => &$config) {
187 7
            if ($platform instanceof SqlitePlatform) {
188 7
                if (   !empty($config['comment'])
189 7
                    && $config['comment'] == 'BINARY') {
190 1
                    $modified = true;
191 1
                    $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' COLLATE BINARY' . $platform->getDefaultValueDeclarationSQL($config);
192
                }
193
            }
194 7
            if ($platform instanceof AbstractMySQLPlatform) {
195
                if (!empty($config['comment'])) {
196
                    if ($config['comment'] == 'BINARY') {
197
                        $modified = true;
198
                        $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' CHARACTER SET utf8 COLLATE utf8_bin' . $platform->getDefaultValueDeclarationSQL($config);
199
                    }
200
                    if (str_starts_with(strtolower(trim($config['comment'])), 'set')) {
201
                        $modified = true;
202
                        $config['columnDefinition'] = $config['comment'] . $platform->getDefaultValueDeclarationSQL($config);
203
                    }
204
                    if (!empty($config['columnDefinition']) && $platform->supportsInlineColumnComments()) {
205
                        $config['columnDefinition'] .=  " COMMENT " . $platform->quoteStringLiteral($config['comment']);
206
                    }
207
                }
208
            }
209
        }
210
211 7
        if (!$modified) {
212 6
            return;
213
        }
214
215 1
        $args->preventDefault();
216
217
        //The following is basically copied from the respective Doctrine function, since there seems to be no way
218
        //to just modify columns and pass them back to the SchemaManager
219 1
        $table = $args->getTable();
220 1
        $options = $args->getOptions();
221
222 1
        $queryFields = $platform->getColumnDeclarationListSQL($columns);
223
224 1
        if (!empty($options['uniqueConstraints'])) {
225
            foreach ($options['uniqueConstraints'] as $name => $definition) {
226
                $queryFields .= ', ' . $platform->getUniqueConstraintDeclarationSQL($name, $definition);
227
            }
228
        }
229
230 1
        if (!empty($options['foreignKeys'])) {
231
            foreach ($options['foreignKeys'] as $foreignKey) {
232
                $queryFields .= ', ' . $platform->getForeignKeyDeclarationSQL($foreignKey);
233
            }
234
        }
235
236 1
        $name = str_replace('.', '__', $table->getName());
237 1
        $args->addSql('CREATE TABLE ' . $name . ' (' . $queryFields . ')');
238
239 1
        if (isset($options['alter']) && true === $options['alter']) {
240
            return;
241
        }
242
243 1
        if (!empty($options['indexes'])) {
244
            foreach ($options['indexes'] as $indexDef) {
245
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
246
            }
247
        }
248
249 1
        if (!empty($options['unique'])) {
250
            foreach ($options['unique'] as $indexDef) {
251
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
252
            }
253
        }
254
    }
255
256
    /**
257
     * This function contains workarounds for reading existing Midgard databases
258
     *
259
     * ENUM fields are converted to string for now (Like in the XML reader)
260
     */
261 3
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args)
262
    {
263 3
        $column = array_change_key_case($args->getTableColumn(), CASE_LOWER);
264 3
        $type = strtok($column['type'], '()');
265
266 3
        if ($type == 'enum') {
267
            $options = [
268
                'length' => 255,
269
                'default' => $column['default'] ?? null,
270
                'notnull' => $column['null'] != 'YES',
271
                'comment' => $column['type']
272
            ];
273
274
            $args->preventDefault();
275
            $args->setColumn(new Column($column['field'], Type::getType(Types::STRING), $options));
276
        }
277
    }
278
279
    /**
280
     * This is mostly a workaround for the fact that SchemaTool wants to create FKs on
281
     * each run since it doesn't detect that MyISAM tables don't support them
282
     *
283
     * @see https://github.com/doctrine/orm/issues/4270
284
     */
285 5
    public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $args)
286
    {
287 5
        $table = $args->getClassTable();
288 5
        if (   !$table->hasOption('engine')
289 5
            || $table->getOption('engine') !== 'MyISAM') {
290
            return;
291
        }
292 5
        foreach ($table->getForeignKeys() as $key) {
293 4
            $table->removeForeignKey($key->getName());
294
        }
295
    }
296
297 7
    public function getSubscribedEvents()
298
    {
299 7
        return [
300 7
            Events::postLoad,
301 7
            Events::onFlush,
302 7
            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

302
            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

302
            /** @scrutinizer ignore-deprecated */ dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
Loading history...
303 7
            ToolEvents::postGenerateSchemaTable
304 7
        ];
305
    }
306
}
307