Completed
Push — master ( 79e5e4...ea1a1c )
by Andreas
06:07
created

subscriber::calculate_size()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 10
ccs 9
cts 9
cp 1
crap 3
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 metadata) {
89 39
            $cm = $em->getClassMetadata(get_class($entity));
90 39
            $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...
91 39
            $entity->metadata_revision++;
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...
92 39
            if ($user = connection::get_user()) {
93 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...
94 7
            }
95 39
            $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...
96 39
            $em->getUnitOfWork()->recomputeSingleEntityChangeSet($cm, $entity);
97 39
        }
98
99 40
        if (!($entity instanceof repligard)) {
100 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...
101
102
            if (   $entity instanceof metadata
103 40
                && $entity->{metadata::DELETED_FIELD}) {
104 27
                $repligard_entry->object_action = self::ACTION_DELETE;
105 27
            } else {
106 21
                $repligard_entry->object_action = self::ACTION_UPDATE;
107
            }
108 40
            $em->persist($repligard_entry);
109 40
            $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata('midgard:midgard_repligard'), $repligard_entry);
110 40
        }
111 40
    }
112
113 18
    private function on_remove(dbobject $entity, EntityManagerInterface $em)
114
    {
115 18
        if (!($entity instanceof repligard)) {
116 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...
117 18
            if (empty($repligard_entry)) {
118
                connection::log()->error('No repligard entry found for GUID ' . $entity->guid);
119
            } else {
120 18
                $repligard_entry->object_action = self::ACTION_PURGE;
121 18
                $em->persist($repligard_entry);
122 18
                $em->getUnitOfWork()->computeChangeSet($em->getClassMetadata('midgard:midgard_repligard'), $repligard_entry);
123
            }
124 18
        }
125 18
    }
126
127 98
    private function calculate_size(ClassMetadata $cm, metadata $entity)
128
    {
129 98
        $size = 0;
130 98
        foreach ($cm->getAssociationNames() as $name) {
131 94
            $size += strlen($entity->$name);
132 98
        }
133 98
        foreach ($cm->getFieldNames() as $name) {
134 98
            $size += strlen($entity->$name);
135 98
        }
136 98
        return $size;
137
    }
138
139 7
    public function onSchemaCreateTable(SchemaCreateTableEventArgs $args)
140
    {
141 7
        $platform = $args->getPlatform();
142 7
        $columns = $args->getColumns();
143 7
        $modified = false;
144
145 7
        foreach ($columns as $name => &$config) {
146 7
            if ($platform->getName() === 'sqlite') {
147 7
                if (   !empty($config['primary'])
148 7
                    && !empty($config['autoincrement'])) {
149
                    /*
150
                     * This is essentially a workaround for http://www.doctrine-project.org/jira/browse/DBAL-642
151
                     * It makes sure we get auto increment behavior similar to msyql (i.e. IDs unique during table's lifetime)
152
                     */
153 5
                    $modified = true;
154 5
                    $config['columnDefinition'] = 'INTEGER PRIMARY KEY AUTOINCREMENT';
155 5
                }
156 7
                if (   !empty($config['comment'])
157 7
                    && $config['comment'] == 'BINARY') {
158 1
                    $modified = true;
159 1
                    $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' COLLATE BINARY' . $platform->getDefaultValueDeclarationSQL($config);
160 1
                }
161 7
            }
162 7
            if ($platform->getName() === 'mysql') {
163
                if (!empty($config['comment'])) {
164
                    if ($config['comment'] == 'BINARY') {
165
                        $modified = true;
166
                        $config['columnDefinition'] = $config['type']->getSQLDeclaration($config, $platform) . ' CHARACTER SET utf8 COLLATE utf8_bin' . $platform->getDefaultValueDeclarationSQL($config);
167
                    }
168
                    if (substr(strtolower(trim($config['comment'])), 0, 3) == 'set') {
169
                        $modified = true;
170
                        $config['columnDefinition'] = $config['comment'] . $platform->getDefaultValueDeclarationSQL($config);
171
                    }
172
                    if (!empty($config['columnDefinition']) && $platform->supportsInlineColumnComments()) {
173
                        $config['columnDefinition'] .=  " COMMENT " . $platform->quoteStringLiteral($config['comment']);
174
                    }
175
                }
176
            }
177 7
        }
178
179 7
        if (!$modified) {
180 4
            return;
181
        }
182
183 6
        $args->preventDefault();
184
185
        //The following is basically copied from the respective Doctrine function, since there seems to be no way
186
        //to just modify columns and pass them back to the SchemaManager
187 6
        $table = $args->getTable();
188 6
        $options = $args->getOptions();
189
190 6
        $queryFields = $platform->getColumnDeclarationListSQL($columns);
191
192 6
        if (!empty($options['uniqueConstraints'])) {
193
            foreach ($options['uniqueConstraints'] as $name => $definition) {
194
                $queryFields .= ', ' . $platform->getUniqueConstraintDeclarationSQL($name, $definition);
195
            }
196
        }
197
198 6
        if (!empty($options['foreignKeys'])) {
199
            foreach ($options['foreignKeys'] as $foreignKey) {
200
                $queryFields .= ', ' . $platform->getForeignKeyDeclarationSQL($foreignKey);
201
            }
202
        }
203
204 6
        $name = str_replace('.', '__', $table->getName());
205 6
        $args->addSql('CREATE TABLE ' . $name . ' (' . $queryFields . ')');
206
207 6
        if (isset($options['alter']) && true === $options['alter']) {
208 1
            return;
209
        }
210
211 6
        if (!empty($options['indexes'])) {
212 4
            foreach ($options['indexes'] as $indexDef) {
213 4
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
214 4
            }
215 4
        }
216
217 6
        if (!empty($options['unique'])) {
218
            foreach ($options['unique'] as $indexDef) {
219
                $args->addSql($platform->getCreateIndexSQL($indexDef, $name));
220
            }
221
        }
222 6
    }
223
224
    /**
225
     * This function contains workarounds for reading existing Midgard databases
226
     *
227
     * ENUM fields are converted to string for now (Like in the XML reader)
228
     * DATETIME files use our custom datetime type
229
     */
230 2
    public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $args)
231
    {
232 2
        $column = array_change_key_case($args->getTableColumn(), CASE_LOWER);
233 2
        $type = strtok($column['type'], '()');
234
235 2
        if ($type == 'enum') {
236
            $args->preventDefault();
237
238
            $options = [
239
                'length' => 255,
240
                'default' => isset($column['default']) ? $column['default'] : null,
241
                'notnull' => (bool) ($column['null'] != 'YES'),
242
                'comment' => $column['type']
243
            ];
244
245
            $args->setColumn(new Column($column['field'], Type::getType(Type::STRING), $options));
246 2
        } elseif ($type == 'datetime') {
247
            $args->preventDefault();
248
            $options = [
249
                'default' => isset($column['default']) ? $column['default'] : null,
250
                'notnull' => (bool) ($column['null'] != 'YES'),
251
            ];
252
253
            $args->setColumn(new Column($column['field'], Type::getType(datetime::TYPE), $options));
254
        }
255 2
    }
256
257
    /**
258
     * This is mostly a workaround for the fact that SchemaTool wants to create FKs on
259
     * each run since it doesn't detect that MyISAM tables don't support them
260
     *
261
     * @see http://www.doctrine-project.org/jira/browse/DDC-3460
262
     * @param GenerateSchemaTableEventArgs $args
263
     */
264 4
    public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $args)
265
    {
266 4
        $table = $args->getClassTable();
267 4
        if (   !$table->hasOption('engine')
268 4
            || $table->getOption('engine') !== 'MyISAM') {
269
            return;
270
        }
271 4
        foreach ($table->getForeignKeys() as $key) {
272 3
            $table->removeForeignKey($key->getName());
273 4
        }
274 4
    }
275
276 10
    public function getSubscribedEvents()
277
    {
278
        return [
279 10
            Events::onFlush,
280 10
            dbal_events::onSchemaCreateTable, dbal_events::onSchemaColumnDefinition,
281
            ToolEvents::postGenerateSchemaTable
282 10
        ];
283
    }
284
}
285