Test Failed
Push — master ( 1e7eb6...0a8a93 )
by Andreas
02:24
created

subscriber   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 275
Duplicated Lines 0 %

Test Coverage

Coverage 77.72%

Importance

Changes 17
Bugs 2 Features 1
Metric Value
eloc 150
c 17
b 2
f 1
dl 0
loc 275
ccs 150
cts 193
cp 0.7772
rs 5.04
wmc 57

9 Methods

Rating   Name   Duplication   Size   Complexity  
F onSchemaCreateTable() 0 81 24
A getSubscribedEvents() 0 6 1
A postGenerateSchemaTable() 0 9 4
A onFlush() 0 15 4
A on_create() 0 30 5
B on_update() 0 45 10
A on_remove() 0 10 3
A calculate_size() 0 10 3
A onSchemaColumnDefinition() 0 23 3

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 midgard\portable\storage\type\datetime;
14
use Doctrine\Common\EventSubscriber;
15
use Doctrine\Common\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
27
class subscriber implements EventSubscriber
28
{
29
    const ACTION_NONE = 0;
30
    const ACTION_DELETE = 1;
31
    const ACTION_PURGE = 2;
32
    const ACTION_CREATE = 3;
33
    const ACTION_UPDATE = 4;
34
35 116
    public function onFlush(OnFlushEventArgs $args)
36
    {
37 116
        $em = $args->getEntityManager();
38 116
        $uow = $em->getUnitOfWork();
39
40 116
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
41 116
            $this->on_create($entity, $em);
42 116
        }
43
44 116
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
45 40
            $this->on_update($entity, $em);
46 116
        }
47
48 116
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
49 18
            $this->on_remove($entity, $em);
50 116
        }
51 116
    }
52
53 116
    private function on_create(dbobject $entity, EntityManagerInterface $em)
54
    {
55 116
        $cm = $em->getClassMetadata(get_class($entity));
56 116
        if (!($entity instanceof repligard)) {
57 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...
58 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

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

265
            $args->setColumn(new Column($column['field'], Type::getType(/** @scrutinizer ignore-deprecated */ Type::STRING), $options));

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
266 2
        } elseif ($type == 'datetime') {
267
            $options = [
268
                'default' => $column['default'] ?? null,
269
                'notnull' => $column['null'] != 'YES',
270
            ];
271
272
            $args->preventDefault();
273
            $args->setColumn(new Column($column['field'], Type::getType(datetime::TYPE), $options));
274
        }
275 2
    }
276
277
    /**
278
     * This is mostly a workaround for the fact that SchemaTool wants to create FKs on
279
     * each run since it doesn't detect that MyISAM tables don't support them
280
     *
281
     * @see http://www.doctrine-project.org/jira/browse/DDC-3460
282
     * @param GenerateSchemaTableEventArgs $args
283
     */
284 4
    public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $args)
285
    {
286 4
        $table = $args->getClassTable();
287 4
        if (   !$table->hasOption('engine')
288 4
            || $table->getOption('engine') !== 'MyISAM') {
289
            return;
290
        }
291 4
        foreach ($table->getForeignKeys() as $key) {
292 3
            $table->removeForeignKey($key->getName());
293 4
        }
294 4
    }
295
296 10
    public function getSubscribedEvents()
297
    {
298
        return [
299 10
            Events::onFlush,
300 10
            dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
301
            ToolEvents::postGenerateSchemaTable
302 10
        ];
303
    }
304
}
305