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

subscriber::on_remove()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 3
b 0
f 0
nc 3
nop 2
dl 0
loc 10
ccs 7
cts 9
cp 0.7778
crap 3.0987
rs 10
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