TextOrJSONToRelationshipMigration   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 31
eloc 129
c 3
b 0
f 0
dl 0
loc 228
rs 9.92

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getTableName() 0 10 2
A testRelationships() 0 8 2
B performMigration() 0 23 9
A updateEmptyRows() 0 18 2
A addToRelationship() 0 16 4
A updateRow() 0 35 4
A getLookupClassName() 0 12 2
A updateRows() 0 19 3
A sanitiseChars() 0 14 3
1
<?php
2
3
namespace Sunnysideup\MigrateData\Tasks;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\ORM\DB;
7
use SilverStripe\Versioned\Versioned;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Versioned\Versioned was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
9
/**
10
 * Used to debug a QueueJob.
11
 */
12
class TextOrJSONToRelationshipMigration extends MigrateDataTaskBase
13
{
14
    /**
15
     * @var string
16
     */
17
    protected $title = 'Migrate Text Or JSON to Proper Relationship in DB';
18
19
    /**
20
     * @var string
21
     */
22
    protected $description = '
23
        For example, it can convert all the values of all DB columnns used by listbox fields
24
        from comma separated strings to JSON and then to Many Many';
25
26
    protected $enabled = true;
27
28
    protected $sanitiseCharList = [
29
        '"',
30
        '[',
31
        ']',
32
        "'",
33
    ];
34
35
    protected $dbTablesTypes = [
36
        '',
37
        '_Live',
38
        '_Versions',
39
    ];
40
41
    protected $lookupClassNames = [];
42
43
    protected $tables = [];
44
45
    /**
46
     * add your data here, like this:
47
     *     ClassNameA
48
     *         OldField => NewRelation
49
     *     ClassNameB
50
     *         OldField => NewRelation.
51
     *
52
     * @var array
53
     */
54
    private static $data_to_fix = [];
55
56
    /**
57
     * @throws \Exception
58
     */
59
    protected function performMigration()
60
    {
61
        $dataToFix = $this->Config()->data_to_fix;
62
        if (0 === count($dataToFix)) {
63
            user_error('You need to specify at least some data to fix!');
64
        }
65
        for ($i = 1; $i < 3; ++$i) {
66
            $this->flushNow('LOOP LOOP: ' . $i);
67
            foreach ($dataToFix as $className => $columns) {
68
                $this->flushNow('... LOOP ClassName: ' . $className);
69
                foreach ($this->dbTablesTypes as $tableExtension) {
70
                    $this->flushNow('... ... LOOP Table Extension: ' . $tableExtension);
71
                    foreach ($columns as $column => $lookupMethod) {
72
                        $this->flushNow('... ... ... LOOP Field: ' . $column);
73
                        $this->updateRows($className, $tableExtension, $column, $lookupMethod);
74
                        $stage = null;
75
                        if ('' === $tableExtension) {
76
                            $stage = Versioned::DRAFT;
77
                        } elseif ('_Live' === $tableExtension) {
78
                            $stage = Versioned::LIVE;
79
                        }
80
                        if (null !== $stage) {
81
                            $this->testRelationships($className, $lookupMethod, $stage);
82
                        }
83
                    }
84
                }
85
            }
86
        }
87
    }
88
89
    protected function updateRows(string $className, string $tableExtension, string $column, string $lookupMethod): void
90
    {
91
        $tableName = $this->getTableName($className, $tableExtension);
92
        $sql = '
93
            SELECT "ID", ' . $column . '
94
            FROM
95
            ' . $tableName . ';';
96
        $rows = DB::query($sql);
97
        foreach ($rows as $row) {
98
            $id = $row['ID'];
99
            $this->flushNow(
100
                '... ... ... ... ' .
101
                'LOOP Table: ' . $tableName . ' Row ID: ' . $row['ID']
102
            );
103
            $fieldValue = $row[$column];
104
            $fieldValue = $this->updateRow($tableName, $id, $column, $fieldValue);
105
            $fieldValue = $this->updateEmptyRows($tableName, $id, $column, $fieldValue);
106
            if ('' === $tableExtension) {
107
                $this->addToRelationship($className, $id, $lookupMethod, $fieldValue);
108
            }
109
        }
110
    }
111
112
    protected function updateRow($tableName, $id, $column, $fieldValue): string
113
    {
114
        if (0 === strpos($fieldValue, '["') && strpos($fieldValue, '"]')) {
115
            $this->flushNow(
116
                '... ... ... ... ... ' .
117
                'column ' . $column . ' in table: ' . $tableName . ' with row ID: ' . $id .
118
                ' already has the correct format, the value is: ' . $fieldValue,
119
                'created'
120
            );
121
        } else {
122
            //adding empty string ...
123
            $fieldValue = $this->sanitiseChars($fieldValue . '');
124
            if ('' !== $fieldValue) {
125
                $fieldValue = json_encode(explode(',', (string) $fieldValue));
126
                $sql = '
127
                    UPDATE ' . $tableName . ' SET ' . $column . " = '" . $fieldValue . '\'
128
                    WHERE ' . $tableName . '."ID" = ' . $id . ';';
129
                $this->flushNow(
130
                    '... ... ... ... ... ' .
131
                    'updating value of column ' . $column . ' in table: ' . $tableName .
132
                    ' with row ID: ' . $id . ' to new value of ' . $fieldValue,
133
                    'repaired'
134
                );
135
                $this->runUpdateQuery($sql);
136
            } else {
137
                $this->flushNow(
138
                    '... ... ... ... ... ' .
139
                    'column ' . $column . ' in table: ' . $tableName .
140
                    ' with row ID: ' . $id . " is empty so doesn't need to be updated",
141
                    'repaired'
142
                );
143
            }
144
        }
145
146
        return $fieldValue;
147
    }
148
149
    protected function updateEmptyRows(string $tableName, int $id, string $column, string $fieldValue): string
150
    {
151
        $array = @json_decode($fieldValue, false);
152
        if (empty($array)) {
153
            $fieldValue = '';
154
            $sql = '
155
                UPDATE ' . $tableName . ' SET ' . $column . " = '" . $fieldValue . '\'
156
                WHERE ' . $tableName . '."ID" = ' . $id . ';';
157
            $this->flushNow(
158
                '... ... ... ... ... ' .
159
                'column ' . $column . ' in table: ' . $tableName . ' with row ID: ' . $id .
160
                ' had an incorrect empty value so has been updated to an empty string',
161
                'repaired'
162
            );
163
            DB::query($sql);
164
        }
165
166
        return $fieldValue;
167
    }
168
169
    protected function addToRelationship(string $className, int $id, string $lookupMethod, string $fieldValue): void
170
    {
171
        if ('' !== $fieldValue) {
172
            $array = @json_decode($fieldValue, false);
173
            if (! empty($array)) {
174
                $obj = $className::get_by_id($id);
175
                $lookupClassName = $this->getlookupClassName($className, $lookupMethod);
176
                $obj->{$lookupMethod}()->removeAll();
177
                foreach ($array as $value) {
178
                    $lookupItem = $lookupClassName::find_or_create(['Code' => $value]);
179
                    $this->flushNow(
180
                        '... ... ... ... ... ... ' .
181
                        'adding ' . $value . ' as many-many relation',
182
                        'created'
183
                    );
184
                    $obj->{$lookupMethod}()->add($lookupItem);
185
                }
186
            }
187
        }
188
    }
189
190
    protected function sanitiseChars(string $value): string
191
    {
192
        foreach ($this->sanitiseCharList as $char) {
193
            if (false !== strpos($value, $char)) {
194
                $this->flushNow(
195
                    '... ... ... ... ... ... ' .
196
                    $char . ' was found in ' . $value . ' we are removing it',
197
                    'error'
198
                );
199
            }
200
            $value = str_replace($char, '', (string) $value);
201
        }
202
203
        return $value;
204
    }
205
206
    protected function getTableName(string $className, string $tableExtension): string
207
    {
208
        $key = $className . '_' . $tableExtension;
209
        if (! isset($this->tables[$key])) {
210
            $dbtable = Config::inst()->get($className, 'table_name');
211
            $tableName = $dbtable . $tableExtension;
212
            $this->tables[$key] = $tableName;
213
        }
214
215
        return $this->tables[$key];
216
    }
217
218
    protected function getLookupClassName(string $className, string $lookupMethod): string
219
    {
220
        $key = $className . '_' . $lookupMethod;
221
        if (! isset($this->lookupClassNames[$key])) {
222
            /** @var array $fields */
223
            $fields = Config::inst()->get($className, 'has_many');
224
            $fields += Config::inst()->get($className, 'many_many');
225
            $fields += Config::inst()->get($className, 'belongs_many_many');
226
            $this->lookupClassNames[$key] = $fields[$lookupMethod] ?? $this->lookupClassNames[$key];
227
        }
228
229
        return $this->lookupClassNames[$key];
230
    }
231
232
    protected function testRelationships(string $className, string $lookupMethod, $stage)
233
    {
234
        $objects = Versioned::get_by_stage($className, $stage);
235
        foreach ($objects as $obj) {
236
            $count = $obj->{$lookupMethod}()->count();
237
            $this->flushNow(
238
                '... ... ... ' .
239
                'Testing (' . $stage . ') - ' . $className . '.' . $lookupMethod . ' for ID ' . $obj->ID . ' => ' . $count
240
            );
241
        }
242
    }
243
}
244