subscriber::onSchemaCreateTable()   F
last analyzed

Complexity

Conditions 22
Paths 899

Size

Total Lines 72
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 60.1067

Importance

Changes 7
Bugs 1 Features 0
Metric Value
cc 22
eloc 41
c 7
b 1
f 0
nc 899
nop 1
dl 0
loc 72
ccs 24
cts 42
cp 0.5714
crap 60.1067
rs 0.1402

How to fix   Long Method    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
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