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

subscriber::on_create()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 5

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 5
eloc 21
c 4
b 0
f 0
nc 9
nop 2
dl 0
loc 30
ccs 22
cts 22
cp 1
crap 5
rs 9.2728
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