Completed
Push — master ( 90ce09...e781d5 )
by Andreas
05:35
created

subscriber::on_update()   B

Complexity

Conditions 10
Paths 40

Size

Total Lines 45
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 10.0036

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 10
eloc 30
c 3
b 0
f 0
nc 40
nop 2
dl 0
loc 45
ccs 29
cts 30
cp 0.9667
crap 10.0036
rs 7.6666

How to fix   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 midgard\portable\storage\type\datetime;
14
use Doctrine\Common\EventSubscriber;
15
use Doctrine\Persistence\Mapping\ClassMetadata;
16
use Doctrine\ORM\Events;
17
use Doctrine\ORM\EntityManagerInterface;
18
use Doctrine\ORM\Event\OnFlushEventArgs;
19
use Doctrine\DBAL\Schema\Column;
20
use Doctrine\DBAL\Types\Type;
21
use Doctrine\DBAL\Events as dbal_events;
22
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
23
use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
24
use Doctrine\ORM\Tools\ToolEvents;
25
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
26
use Doctrine\DBAL\Types\Types;
27
28
class subscriber implements EventSubscriber
29
{
30
    const ACTION_NONE = 0;
31
    const ACTION_DELETE = 1;
32
    const ACTION_PURGE = 2;
33
    const ACTION_CREATE = 3;
34
    const ACTION_UPDATE = 4;
35
36 116
    public function onFlush(OnFlushEventArgs $args)
37
    {
38 116
        $em = $args->getEntityManager();
39 116
        $uow = $em->getUnitOfWork();
40
41 116
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
42 116
            $this->on_create($entity, $em);
43
        }
44
45 116
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
46 40
            $this->on_update($entity, $em);
47
        }
48
49 116
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
50 18
            $this->on_remove($entity, $em);
51
        }
52 116
    }
53
54 116
    private function on_create(dbobject $entity, EntityManagerInterface $em)
55
    {
56 116
        $cm = $em->getClassMetadata(get_class($entity));
57 116
        if (!($entity instanceof repligard)) {
58 116
            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...
59 103
                $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

59
                $entity->/** @scrutinizer ignore-call */ 
60
                         set_guid(connection::generate_guid());
Loading history...
60 103
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
61
            }
62
63 116
            $om = new objectmanager($em);
64 116
            $repligard_cm = $em->getClassMetadata('midgard:midgard_repligard');
65 116
            $repligard_entry = $om->new_instance($repligard_cm->getName());
66 116
            $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...
67 116
            $repligard_entry->guid = $entity->guid;
68 116
            $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...
69 116
            $em->persist($repligard_entry);
70 116
            $em->getUnitOfWork()->computeChangeSet($repligard_cm, $repligard_entry);
71
        }
72
73 116
        if ($entity instanceof metadata) {
74 98
            $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\dbobject. 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\repligard. Since you implemented __get, consider adding a @property annotation.
Loading history...
75
            // we copy here instead of creating a new, because otherwise we might have
76
            // a one second difference if the code runs at the right millisecond
77 98
            $entity->metadata->revised = $entity->metadata->created;
78 98
            if ($user = connection::get_user()) {
79 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\dbobject. 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\repligard. Since you implemented __set, consider adding a @property annotation.
Loading history...
80 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\dbobject. 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\repligard. Since you implemented __set, consider adding a @property annotation.
Loading history...
81
            }
82 98
            $entity->metadata->size = $this->calculate_size($cm, $entity);
83 98
            $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
84
        }
85 116
    }
86
87 40
    private function on_update(dbobject $entity, EntityManagerInterface $em)
88
    {
89 40
        if ($entity instanceof repligard) {
90
            return;
91
        }
92 40
        $check_repligard = true;
93 40
        $deleted = false;
94 40
        if ($entity instanceof metadata) {
95 39
            $deleted = $entity->{metadata::DELETED_FIELD};
96 39
            $cs = $em->getUnitOfWork()->getEntityChangeSet($entity);
97
            // We only need to update repligard if we're coming from create (revision 0)
98
            // or if we delete/undelete
99 39
            if (   !array_key_exists('metadata_deleted', $cs)
100 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...
101 4
                $check_repligard = false;
102
            }
103
104 39
            $create_revision = true;
105 39
            if (array_key_exists('metadata_islocked', $cs)) {
106 2
                $lock_fields = array_flip(['metadata_locked', 'metadata_islocked', 'metadata_locker']);
107 2
                $create_revision = !empty(array_diff_key($cs, $lock_fields));
108
            }
109
110 39
            if ($create_revision) {
111 37
                $cm = $em->getClassMetadata(get_class($entity));
112 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...
113 37
                $entity->metadata_revision++;
114 37
                if ($user = connection::get_user()) {
115 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...
116
                }
117 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...
118 37
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
119
            }
120
        }
121
122 40
        if ($check_repligard) {
123 40
            $repligard_entry = $em->getRepository('midgard: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...
124
125 40
            if ($deleted) {
126 27
                $repligard_entry->object_action = self::ACTION_DELETE;
127
            } else {
128 21
                $repligard_entry->object_action = self::ACTION_UPDATE;
129
            }
130 40
            $em->persist($repligard_entry);
131 40
            $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata('midgard:midgard_repligard'), $repligard_entry);
132
        }
133 40
    }
134
135 18
    private function on_remove(dbobject $entity, EntityManagerInterface $em)
136
    {
137 18
        if (!($entity instanceof repligard)) {
138 18
            $repligard_entry = $em->getRepository('midgard: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...
139 18
            if (empty($repligard_entry)) {
140
                connection::log()->error('No repligard entry found for GUID ' . $entity->guid);
141
            } else {
142 18
                $repligard_entry->object_action = self::ACTION_PURGE;
143 18
                $em->persist($repligard_entry);
144 18
                $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata('midgard:midgard_repligard'), $repligard_entry);
145
            }
146
        }
147 18
    }
148
149 98
    private function calculate_size(ClassMetadata $cm, metadata $entity) : int
150
    {
151 98
        $size = 0;
152 98
        foreach ($cm->getAssociationNames() as $name) {
153 94
            $size += strlen($entity->$name);
154
        }
155 98
        foreach ($cm->getFieldNames() as $name) {
156 98
            $size += strlen($entity->$name);
157
        }
158 98
        return $size;
159
    }
160
161 7
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
162
    {
163 7
        $platform = $args->getPlatform();
164 7
        $columns = $args->getColumns();
165 7
        $modified = false;
166
167 7
        foreach ($columns as $name => &$config) {
168 7
            if ($platform->getName() === 'sqlite') {
169 7
                if (   !empty($config['primary'])
170 7
                    && !empty($config['autoincrement'])) {
171
                    /*
172
                     * This is essentially a workaround for http://www.doctrine-project.org/jira/browse/DBAL-642
173
                     * It makes sure we get auto increment behavior similar to msyql (i.e. IDs unique during table's lifetime)
174
                     */
175 5
                    $modified = true;
176 5
                    $config['columnDefinition'] = 'INTEGER PRIMARY KEY AUTOINCREMENT';
177
                }
178 7
                if (   !empty($config['comment'])
179 7
                    && $config['comment'] == 'BINARY') {
180 1
                    $modified = true;
181 1
                    $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' COLLATE BINARY' . $platform->getDefaultValueDeclarationSQL($config);
182
                }
183
            }
184 7
            if ($platform->getName() === 'mysql') {
185
                if (!empty($config['comment'])) {
186
                    if ($config['comment'] == 'BINARY') {
187
                        $modified = true;
188
                        $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' CHARACTER SET utf8 COLLATE utf8_bin' . $platform->getDefaultValueDeclarationSQL($config);
189
                    }
190
                    if (substr(strtolower(trim($config['comment'])), 0, 3) == 'set') {
191
                        $modified = true;
192
                        $config['columnDefinition'] = $config['comment'] . $platform->getDefaultValueDeclarationSQL($config);
193
                    }
194
                    if (!empty($config['columnDefinition']) && $platform->supportsInlineColumnComments()) {
195
                        $config['columnDefinition'] .=  " COMMENT " . $platform->quoteStringLiteral($config['comment']);
196
                    }
197
                }
198
            }
199
        }
200
201 7
        if (!$modified) {
202 4
            return;
203
        }
204
205 6
        $args->preventDefault();
206
207
        //The following is basically copied from the respective Doctrine function, since there seems to be no way
208
        //to just modify columns and pass them back to the SchemaManager
209 6
        $table = $args->getTable();
210 6
        $options = $args->getOptions();
211
212 6
        $queryFields = $platform->getColumnDeclarationListSQL($columns);
213
214 6
        if (!empty($options['uniqueConstraints'])) {
215
            foreach ($options['uniqueConstraints'] as $name => $definition) {
216
                $queryFields .= ', ' . $platform->getUniqueConstraintDeclarationSQL($name, $definition);
217
            }
218
        }
219
220 6
        if (!empty($options['foreignKeys'])) {
221
            foreach ($options['foreignKeys'] as $foreignKey) {
222
                $queryFields .= ', ' . $platform->getForeignKeyDeclarationSQL($foreignKey);
223
            }
224
        }
225
226 6
        $name = str_replace('.', '__', $table->getName());
227 6
        $args->addSql('CREATE TABLE ' . $name . ' (' . $queryFields . ')');
228
229 6
        if (isset($options['alter']) && true === $options['alter']) {
230 1
            return;
231
        }
232
233 6
        if (!empty($options['indexes'])) {
234 4
            foreach ($options['indexes'] as $indexDef) {
235 4
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
236
            }
237
        }
238
239 6
        if (!empty($options['unique'])) {
240
            foreach ($options['unique'] as $indexDef) {
241
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
242
            }
243
        }
244 6
    }
245
246
    /**
247
     * This function contains workarounds for reading existing Midgard databases
248
     *
249
     * ENUM fields are converted to string for now (Like in the XML reader)
250
     * DATETIME files use our custom datetime type
251
     */
252 2
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args)
253
    {
254 2
        $column = array_change_key_case($args->getTableColumn(), CASE_LOWER);
255 2
        $type = strtok($column['type'], '()');
256
257 2
        if ($type == 'enum') {
258
            $options = [
259
                'length' => 255,
260
                'default' => $column['default'] ?? null,
261
                'notnull' => $column['null'] != 'YES',
262
                'comment' => $column['type']
263
            ];
264
265
            $args->preventDefault();
266
            $args->setColumn(new Column($column['field'], Type::getType(Types::STRING), $options));
267 2
        } elseif ($type == 'datetime') {
268
            $options = [
269
                'default' => $column['default'] ?? null,
270
                'notnull' => $column['null'] != 'YES',
271
            ];
272
            if ($options['default'] == "'0001-01-01 00:00:00'") {
273
                $options['default'] = '0001-01-01 00:00:00';
274
            }
275
276
            $args->preventDefault();
277
            $args->setColumn(new Column($column['field'], Type::getType(datetime::TYPE), $options));
278
        }
279 2
    }
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 4
    }
299
300 10
    public function getSubscribedEvents()
301
    {
302
        return [
303 10
            Events::onFlush,
304 10
            dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
305 10
            ToolEvents::postGenerateSchemaTable
306
        ];
307
    }
308
}
309