Passed
Push — master ( bd537c...93b551 )
by Andreas
03:32
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 6
Bugs 1 Features 0
Metric Value
cc 22
eloc 41
c 6
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 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