Passed
Branch master (b2f909)
by Andreas
04:01
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 0
Metric Value
cc 5
eloc 21
c 0
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
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
    }
53
54 82
    public function postLoad(LifecycleEventArgs $args)
55
    {
56 82
        $entity = $args->getObject();
57 82
        if ($entity instanceof dbobject) {
58 82
            $om = $args->getObjectManager();
59 82
            $entity->injectObjectManager($om, $om->getClassMetadata(get_class($entity)));
60
        }
61
    }
62
63 116
    private function on_create(dbobject $entity, EntityManagerInterface $em)
64
    {
65 116
        $cm = $em->getClassMetadata(get_class($entity));
66 116
        if (!($entity instanceof repligard)) {
67 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...
68 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

68
                $entity->/** @scrutinizer ignore-call */ 
69
                         set_guid(connection::generate_guid());
Loading history...
69 103
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
70
            }
71
72 116
            $om = new objectmanager($em);
73 116
            $repligard_cm = $em->getClassMetadata(connection::get_fqcn('midgard_repligard'));
74 116
            $repligard_entry = $om->new_instance($repligard_cm->getName());
75 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...
76 116
            $repligard_entry->guid = $entity->guid;
77 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...
78 116
            $em->persist($repligard_entry);
79 116
            $em->getUnitOfWork()->computeChangeSet($repligard_cm, $repligard_entry);
80
        }
81
82 116
        if ($entity instanceof metadata) {
83 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\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...
84
            // we copy here instead of creating a new, because otherwise we might have
85
            // a one second difference if the code runs at the right millisecond
86 98
            $entity->metadata->revised = $entity->metadata->created;
87 98
            if ($user = connection::get_user()) {
88 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...
89 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...
90
            }
91 98
            $entity->metadata->size = $this->calculate_size($cm, $entity);
92 98
            $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
93
        }
94
    }
95
96 40
    private function on_update(dbobject $entity, EntityManagerInterface $em)
97
    {
98 40
        if ($entity instanceof repligard) {
99
            return;
100
        }
101 40
        $check_repligard = true;
102 40
        $deleted = false;
103 40
        if ($entity instanceof metadata) {
104 39
            $deleted = $entity->{metadata::DELETED_FIELD};
105 39
            $cs = $em->getUnitOfWork()->getEntityChangeSet($entity);
106
            // We only need to update repligard if we're coming from create (revision 0)
107
            // or if we delete/undelete
108 39
            if (   !array_key_exists('metadata_deleted', $cs)
109 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...
110 4
                $check_repligard = false;
111
            }
112
113 39
            $create_revision = true;
114 39
            if (array_key_exists('metadata_islocked', $cs)) {
115 2
                $lock_fields = array_flip(['metadata_locked', 'metadata_islocked', 'metadata_locker']);
116 2
                $create_revision = !empty(array_diff_key($cs, $lock_fields));
117
            }
118
119 39
            if ($create_revision) {
120 37
                $cm = $em->getClassMetadata(get_class($entity));
121 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...
122 37
                $entity->metadata_revision++;
123 37
                if ($user = connection::get_user()) {
124 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...
125
                }
126 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...
127 37
                $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
128
            }
129
        }
130
131 40
        if ($check_repligard) {
132 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...
133
134 40
            if ($deleted) {
135 27
                $repligard_entry->object_action = self::ACTION_DELETE;
136
            } else {
137 21
                $repligard_entry->object_action = self::ACTION_UPDATE;
138
            }
139 40
            $em->persist($repligard_entry);
140 40
            $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata(connection::get_fqcn('midgard_repligard')), $repligard_entry);
141
        }
142
    }
143
144 18
    private function on_remove(dbobject $entity, EntityManagerInterface $em)
145
    {
146 18
        if (!($entity instanceof repligard)) {
147 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...
148 18
            if (empty($repligard_entry)) {
149
                connection::log()->error('No repligard entry found for GUID ' . $entity->guid);
150
            } else {
151 18
                $repligard_entry->object_action = self::ACTION_PURGE;
152 18
                $em->persist($repligard_entry);
153 18
                $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata(connection::get_fqcn('midgard_repligard')), $repligard_entry);
154
            }
155
        }
156
    }
157
158 98
    private function calculate_size(ClassMetadata $cm, metadata $entity) : int
159
    {
160 98
        $size = 0;
161 98
        foreach ($cm->getAssociationNames() as $name) {
162 94
            $size += strlen($entity->$name);
163
        }
164 98
        foreach ($cm->getFieldNames() as $name) {
165 98
            $size += strlen($entity->$name);
166
        }
167 98
        return $size;
168
    }
169
170 6
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
171
    {
172 6
        $platform = $args->getPlatform();
173 6
        $columns = $args->getColumns();
174 6
        $modified = false;
175
176 6
        foreach ($columns as $name => &$config) {
177 6
            if ($platform->getName() === 'sqlite') {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Platforms\...ractPlatform::getName() has been deprecated: Identify platforms by their class. ( Ignorable by Annotation )

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

177
            if (/** @scrutinizer ignore-deprecated */ $platform->getName() === 'sqlite') {

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

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

Loading history...
178 6
                if (   !empty($config['comment'])
179 6
                    && $config['comment'] == 'BINARY') {
180 1
                    $modified = true;
181 1
                    $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' COLLATE BINARY' . $platform->getDefaultValueDeclarationSQL($config);
182
                }
183
            }
184 6
            if ($platform->getName() === 'mysql') {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Platforms\...ractPlatform::getName() has been deprecated: Identify platforms by their class. ( Ignorable by Annotation )

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

184
            if (/** @scrutinizer ignore-deprecated */ $platform->getName() === 'mysql') {

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

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

Loading history...
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 6
        if (!$modified) {
202 5
            return;
203
        }
204
205 1
        $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 1
        $table = $args->getTable();
210 1
        $options = $args->getOptions();
211
212 1
        $queryFields = $platform->getColumnDeclarationListSQL($columns);
213
214 1
        if (!empty($options['uniqueConstraints'])) {
215
            foreach ($options['uniqueConstraints'] as $name => $definition) {
216
                $queryFields .= ', ' . $platform->getUniqueConstraintDeclarationSQL($name, $definition);
217
            }
218
        }
219
220 1
        if (!empty($options['foreignKeys'])) {
221
            foreach ($options['foreignKeys'] as $foreignKey) {
222
                $queryFields .= ', ' . $platform->getForeignKeyDeclarationSQL($foreignKey);
223
            }
224
        }
225
226 1
        $name = str_replace('.', '__', $table->getName());
227 1
        $args->addSql('CREATE TABLE ' . $name . ' (' . $queryFields . ')');
228
229 1
        if (isset($options['alter']) && true === $options['alter']) {
230
            return;
231
        }
232
233 1
        if (!empty($options['indexes'])) {
234
            foreach ($options['indexes'] as $indexDef) {
235
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
236
            }
237
        }
238
239 1
        if (!empty($options['unique'])) {
240
            foreach ($options['unique'] as $indexDef) {
241
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
242
            }
243
        }
244
    }
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
     */
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(Types::STRING), $options));
266
        }
267
    }
268
269
    /**
270
     * This is mostly a workaround for the fact that SchemaTool wants to create FKs on
271
     * each run since it doesn't detect that MyISAM tables don't support them
272
     *
273
     * @see https://github.com/doctrine/orm/issues/4270
274
     * @param GenerateSchemaTableEventArgs $args
275
     */
276 4
    public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $args)
277
    {
278 4
        $table = $args->getClassTable();
279 4
        if (   !$table->hasOption('engine')
280 4
            || $table->getOption('engine') !== 'MyISAM') {
281
            return;
282
        }
283 4
        foreach ($table->getForeignKeys() as $key) {
284 3
            $table->removeForeignKey($key->getName());
285
        }
286
    }
287
288 9
    public function getSubscribedEvents()
289
    {
290
        return [
291 9
            Events::postLoad,
292
            Events::onFlush,
293
            dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
294
            ToolEvents::postGenerateSchemaTable
295
        ];
296
    }
297
}
298