Passed
Push — master ( c30de3...48b08e )
by Andreas
03:36
created

subscriber::onSchemaCreateTable()   F

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 117
    public function onFlush(OnFlushEventArgs $args)
39
    {
40 117
        $em = $args->getObjectManager();
41 117
        $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 117
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
44 117
            $this->on_create($entity, $em);
45
        }
46
47 117
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
48 40
            $this->on_update($entity, $em);
49
        }
50
51 117
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
52 18
            $this->on_remove($entity, $em);
53
        }
54
    }
55
56 82
    public function postLoad(LifecycleEventArgs $args)
57
    {
58 82
        $entity = $args->getObject();
59 82
        if ($entity instanceof dbobject) {
60 82
            $om = $args->getObjectManager();
61 82
            $entity->injectObjectManager($om, $om->getClassMetadata(get_class($entity)));
62
        }
63
    }
64
65 117
    private function on_create(dbobject $entity, EntityManagerInterface $em)
66
    {
67 117
        $cm = $em->getClassMetadata(get_class($entity));
68 117
        if (!($entity instanceof repligard)) {
69 117
            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 104
                $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 104
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
72
            }
73
74 117
            $om = new objectmanager($em);
75 117
            $repligard_cm = $em->getClassMetadata(connection::get_fqcn('midgard_repligard'));
76 117
            $repligard_entry = $om->new_instance($repligard_cm->getName());
77 117
            $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 117
            $repligard_entry->guid = $entity->guid;
79 117
            $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 117
            $em->persist($repligard_entry);
81 117
            $em->getUnitOfWork()->computeChangeSet($repligard_cm, $repligard_entry);
82
        }
83
84 117
        if ($entity instanceof metadata) {
85 99
            $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 99
            $entity->metadata->revised = $entity->metadata->created;
89 99
            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 99
            $entity->metadata->size = $this->calculate_size($cm, $entity);
94 99
            $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 ($deleted) {
137 27
                $repligard_entry->object_action = self::ACTION_DELETE;
138
            } else {
139 21
                $repligard_entry->object_action = self::ACTION_UPDATE;
140
            }
141 40
            $em->persist($repligard_entry);
142 40
            $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata(connection::get_fqcn('midgard_repligard')), $repligard_entry);
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 99
    private function calculate_size(ClassMetadata $cm, metadata $entity) : int
161
    {
162 99
        $size = 0;
163 99
        foreach ($cm->getAssociationNames() as $name) {
164 94
            $size += strlen($entity->$name);
165
        }
166 99
        foreach ($cm->getFieldNames() as $name) {
167 99
            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...
168 99
                Types::DATETIME_MUTABLE => $size += 19, // Y-m-d H:i:s
169 99
                Types::DATE_MUTABLE => $size += 10, // Y-m-d
170 99
                default => $size += strlen($entity->$name),
171 99
            };
172
        }
173 99
        return $size;
174
    }
175
176 7
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
177
    {
178 7
        $platform = $args->getPlatform();
179 7
        $columns = $args->getColumns();
180 7
        $modified = false;
181
182 7
        foreach ($columns as $name => &$config) {
183 7
            if ($platform instanceof SqlitePlatform) {
184 7
                if (   !empty($config['comment'])
185 7
                    && $config['comment'] == 'BINARY') {
186 1
                    $modified = true;
187 1
                    $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' COLLATE BINARY' . $platform->getDefaultValueDeclarationSQL($config);
188
                }
189
            }
190 7
            if ($platform instanceof AbstractMySQLPlatform) {
191
                if (!empty($config['comment'])) {
192
                    if ($config['comment'] == 'BINARY') {
193
                        $modified = true;
194
                        $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' CHARACTER SET utf8 COLLATE utf8_bin' . $platform->getDefaultValueDeclarationSQL($config);
195
                    }
196
                    if (str_starts_with(strtolower(trim($config['comment'])), 'set')) {
197
                        $modified = true;
198
                        $config['columnDefinition'] = $config['comment'] . $platform->getDefaultValueDeclarationSQL($config);
199
                    }
200
                    if (!empty($config['columnDefinition']) && $platform->supportsInlineColumnComments()) {
201
                        $config['columnDefinition'] .=  " COMMENT " . $platform->quoteStringLiteral($config['comment']);
202
                    }
203
                }
204
            }
205
        }
206
207 7
        if (!$modified) {
208 6
            return;
209
        }
210
211 1
        $args->preventDefault();
212
213
        //The following is basically copied from the respective Doctrine function, since there seems to be no way
214
        //to just modify columns and pass them back to the SchemaManager
215 1
        $table = $args->getTable();
216 1
        $options = $args->getOptions();
217
218 1
        $queryFields = $platform->getColumnDeclarationListSQL($columns);
219
220 1
        if (!empty($options['uniqueConstraints'])) {
221
            foreach ($options['uniqueConstraints'] as $name => $definition) {
222
                $queryFields .= ', ' . $platform->getUniqueConstraintDeclarationSQL($name, $definition);
223
            }
224
        }
225
226 1
        if (!empty($options['foreignKeys'])) {
227
            foreach ($options['foreignKeys'] as $foreignKey) {
228
                $queryFields .= ', ' . $platform->getForeignKeyDeclarationSQL($foreignKey);
229
            }
230
        }
231
232 1
        $name = str_replace('.', '__', $table->getName());
233 1
        $args->addSql('CREATE TABLE ' . $name . ' (' . $queryFields . ')');
234
235 1
        if (isset($options['alter']) && true === $options['alter']) {
236
            return;
237
        }
238
239 1
        if (!empty($options['indexes'])) {
240
            foreach ($options['indexes'] as $indexDef) {
241
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
242
            }
243
        }
244
245 1
        if (!empty($options['unique'])) {
246
            foreach ($options['unique'] as $indexDef) {
247
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
248
            }
249
        }
250
    }
251
252
    /**
253
     * This function contains workarounds for reading existing Midgard databases
254
     *
255
     * ENUM fields are converted to string for now (Like in the XML reader)
256
     */
257 3
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args)
258
    {
259 3
        $column = array_change_key_case($args->getTableColumn(), CASE_LOWER);
260 3
        $type = strtok($column['type'], '()');
261
262 3
        if ($type == 'enum') {
263
            $options = [
264
                'length' => 255,
265
                'default' => $column['default'] ?? null,
266
                'notnull' => $column['null'] != 'YES',
267
                'comment' => $column['type']
268
            ];
269
270
            $args->preventDefault();
271
            $args->setColumn(new Column($column['field'], Type::getType(Types::STRING), $options));
272
        }
273
    }
274
275
    /**
276
     * This is mostly a workaround for the fact that SchemaTool wants to create FKs on
277
     * each run since it doesn't detect that MyISAM tables don't support them
278
     *
279
     * @see https://github.com/doctrine/orm/issues/4270
280
     */
281 5
    public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $args)
282
    {
283 5
        $table = $args->getClassTable();
284 5
        if (   !$table->hasOption('engine')
285 5
            || $table->getOption('engine') !== 'MyISAM') {
286
            return;
287
        }
288 5
        foreach ($table->getForeignKeys() as $key) {
289 4
            $table->removeForeignKey($key->getName());
290
        }
291
    }
292
293 7
    public function getSubscribedEvents()
294
    {
295 7
        return [
296 7
            Events::postLoad,
297 7
            Events::onFlush,
298 7
            dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
0 ignored issues
show
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

298
            /** @scrutinizer ignore-deprecated */ dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
Loading history...
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

298
            dbal_events::onSchemaCreateTable, /** @scrutinizer ignore-deprecated */ dbal_events::onSchemaColumnDefinition,
Loading history...
299 7
            ToolEvents::postGenerateSchemaTable
300 7
        ];
301
    }
302
}
303