Completed
Push — master ( 345759...43fb10 )
by
unknown
14:22
created

FixReportsQuery::processRow()   C

Complexity

Conditions 8
Paths 54

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 16
nc 54
nop 1
1
<?php
2
3
namespace OroCRM\Bundle\SalesBundle\Migrations\Schema\v1_25_4;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Query\QueryBuilder;
7
8
use Psr\Log\LoggerInterface;
9
10
use Oro\Bundle\MigrationBundle\Migration\ArrayLogger;
11
use Oro\Bundle\MigrationBundle\Migration\ParametrizedMigrationQuery;
12
use Oro\Component\PhpUtils\ArrayUtil;
13
14
/**
15
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
16
 */
17
class FixReportsQuery extends ParametrizedMigrationQuery
18
{
19
    const LIMIT = 100;
20
21
    /** @var array */
22
    protected $fixes = [
23
        'filters' => [
24
            'OroCRM\Bundle\SalesBundle\Entity\Opportunity' => 'status',
25
            'OroCRM\Bundle\SalesBundle\Entity\Lead' => 'status',
26
        ],
27
        'removedFields' => [
28
            'status+OroCRM\Bundle\SalesBundle\Entity\OpportunityStatus::label' => 'status',
29
            'status+OroCRM\Bundle\SalesBundle\Entity\OpportunityStatus::name' => 'status',
30
            'status+OroCRM\Bundle\SalesBundle\Entity\LeadStatus::label' => 'status',
31
            'status+OroCRM\Bundle\SalesBundle\Entity\LeadStatus::name' => 'status',
32
        ],
33
        'chainFilters' => [
34
            'OroCRM\Bundle\SalesBundle\Entity\Opportunity::status',
35
            'OroCRM\Bundle\SalesBundle\Entity\Lead::status',
36
        ],
37
    ];
38
39
    /**
40
     * {@inheritdoc}
41
     */
42
    public function getDescription()
43
    {
44
        $logger = new ArrayLogger();
45
        $this->doExecute($logger, true);
46
47
        return $logger->getMessages();
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function setConnection(Connection $connection)
54
    {
55
        $this->connection = $connection;
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function execute(LoggerInterface $logger)
62
    {
63
        $this->doExecute($logger);
64
    }
65
66
    /**
67
     * @param LoggerInterface $logger
68
     * @param bool            $dryRun
69
     */
70
    protected function doExecute(LoggerInterface $logger, $dryRun = false)
71
    {
72
        $tables = ['oro_segment', 'oro_report'];
73
        foreach ($tables as $table) {
74
            $this->updateRows($logger, $table, $dryRun);
75
        }
76
    }
77
78
    /**
79
     * @param LoggerInterface $logger
80
     * @param string          $table
81
     * @param bool            $dryRun
82
     */
83
    protected function updateRows(LoggerInterface $logger, $table, $dryRun = false)
84
    {
85
        $steps = ceil($this->getCount($table) / static::LIMIT);
86
87
        $reportQb = $this->createQb($table)
88
            ->setMaxResults(static::LIMIT);
89
90
        for ($i = 0; $i < $steps; $i++) {
91
            $rows = $reportQb
92
                ->setFirstResult($i * static::LIMIT)
93
                ->execute()
94
                ->fetchAll(\PDO::FETCH_ASSOC);
95
96
            foreach ($rows as $row) {
97
                if ($this->processRow($row)) {
98
                    $this->saveChanges($logger, $table, $row, $dryRun);
99
                }
100
            }
101
        }
102
    }
103
104
    /**
105
     * @param LoggerInterface $logger
106
     * @param string          $table
107
     * @param array           $row
108
     * @param bool            $dryRun
109
     */
110
    protected function saveChanges(LoggerInterface $logger, $table, array $row, $dryRun)
111
    {
112
        $query = <<<SQL
113
UPDATE $table
114
SET definition = :definition
115
WHERE id = :id
116
SQL;
117
        $params = [
118
            'id'         => $row['id'],
119
            'definition' => $row['definition'],
120
        ];
121
122
        $this->logQuery($logger, $query, $params);
123
        if (!$dryRun) {
124
            $this->connection->executeUpdate($query, $params);
125
        }
126
    }
127
128
    /**
129
     * @param array $row
130
     *
131
     * @return bool True if there are changes, false otherwise
132
     */
133
    protected function processRow(array &$row)
134
    {
135
        $isChanged = false;
136
        $def = json_decode($row['definition'], true);
137
        if (isset($def['columns'])) {
138
            if ($this->fixColumns($def)) {
139
                $isChanged = true;
140
            }
141
        }
142
143
        if (isset($def['filters'])) {
144
            if ($this->fixFilters($row, $def)) {
145
                $isChanged = true;
146
            }
147
        }
148
149
        if (isset($def['grouping_columns'])) {
150
            if ($this->fixGroupingColumns($def)) {
151
                $isChanged = true;
152
            }
153
        }
154
155
        if ($isChanged) {
156
            $this->removeDuplicates($def);
157
            $row['definition'] = json_encode($def);
158
        }
159
160
        return $isChanged;
161
    }
162
163
    /**
164
     * If you add same field twice in "Columns" section in query designer,
165
     * you'll get exception that alias is already defined
166
     *
167
     * Also there is no point in having duplicated stuff
168
     *
169
     * @param array $def
170
     */
171
    protected function removeDuplicates(array &$def)
172
    {
173
        if (!isset($def['columns'])) {
174
            return;
175
        }
176
177
        $existingColumns = [];
178
        foreach ($def['columns'] as $key => $column) {
179
            $id = implode('.', [$column['name'], $column['func']]);
180
            if (isset($existingColumns[$id])) {
181
                unset($def['columns'][$key]);
182
            } else {
183
                $existingColumns[$id] = $key;
184
            }
185
        }
186
187
        $def['columns'] = array_values($def['columns']);
188
    }
189
190
    /**
191
     * @param array $def
192
     *
193
     * @return bool
194
     */
195
    protected function fixColumns(array &$def)
196
    {
197
        $isChanged = false;
198
        if (!isset($def['columns'])) {
199
            return $isChanged;
200
        }
201
202
        foreach ($def['columns'] as &$column) {
203
            if ($this->fixRemovedField($column, 'name')) {
204
                $isChanged = true;
205
            }
206
        }
207
208
        return $isChanged;
209
    }
210
211
    /**
212
     * @param array $row
213
     * @param array $def
214
     *
215
     * @return bool
216
     */
217
    protected function fixFilters(array $row, array &$def)
218
    {
219
        $isChanged = false;
220
        foreach ($def['filters'] as $key => $filter) {
221
            $this->fixRemovedField($filter, 'columnName');
222
            if (!$this->isNeedToUpdateEnumFilter($row, $filter)) {
223
                continue;
224
            }
225
226
            $isChanged = true;
227
            $value = $this->filterValue($filter);
228
            $stringType = isset($filter['criterion']['data']['type'])
229
                ? (int) $filter['criterion']['data']['type']
230
                : null;
231
            // If string operator expects operand to be array
232
            // parse value off the db representation
233
            if (in_array($stringType, [6, 7], true)) {
234
                $value = explode(',', $value);
235
            }
236
            // As enum operators expects operand to be always array
237
            // as opposed to string operators, make sure it is array
238
            if (!is_array($value)) {
239
                $value = [$value];
240
            }
241
242
            $entityNameParts = explode('\\', $row['entity']);
243
            $def['filters'][$key] = [
244
                'columnName' => $filter['columnName'],
245
                'criterion' => [
246
                    'filter' => 'enum',
247
                    'data' => [
248
                        'type' => $this->getEnumType($stringType),
249
                        'value' => $value,
250
                        'params' => [
251
                            'class' => sprintf('Extend\Entity\EV_%s_Status', end($entityNameParts)),
252
                        ],
253
                    ],
254
                ],
255
            ];
256
        }
257
258
        return $isChanged;
259
    }
260
261
    /**
262
     * @param mixed $config
263
     *
264
     * @return bool
265
     */
266
    public function fixRemovedField(&$config, $key)
267
    {
268
        if (!isset($config[$key])) {
269
            return false;
270
        }
271
272
        $search = ArrayUtil::find(
273
            function ($column) use ($config, $key) {
274
                return preg_match(sprintf('/%s$/', preg_quote($column)), $config[$key]);
275
            },
276
            array_keys($this->fixes['removedFields'])
277
        );
278
279
        if (!$search) {
280
            return false;
281
        }
282
283
        $config[$key] = str_replace($search, $this->fixes['removedFields'][$search], $config[$key]);
284
285
        return true;
286
    }
287
288
    /**
289
     * @param mixed $filter
290
     *
291
     * @return mixed
292
     */
293
    protected function filterValue($filter)
294
    {
295
        return isset($filter['criterion']['data']['value']) ? $filter['criterion']['data']['value'] : '';
296
    }
297
298
    /**
299
     * @param array $def
300
     *
301
     * @return bool
302
     */
303
    protected function fixGroupingColumns(array &$def)
304
    {
305
        $isChanged = false;
306
        foreach ($def['grouping_columns'] as &$group) {
307
            if ($this->fixRemovedField($group, 'name')) {
308
                $isChanged = true;
309
            }
310
        }
311
312
        return $isChanged;
313
    }
314
315
    /**
316
     * @param array $row
317
     * @param mixed $filter
318
     *
319
     * @return bool
320
     */
321
    protected function isNeedToUpdateEnumFilter(array $row, $filter)
322
    {
323
        if (!isset($filter['columnName'], $filter['criterion']['filter'])
324
            || $filter['criterion']['filter'] !== 'string'
325
        ) {
326
            return false;
327
        }
328
329
        if (isset($this->fixes['filters'][$row['entity']])
330
            && $filter['columnName'] === $this->fixes['filters'][$row['entity']]
331
        ) {
332
            return true;
333
        }
334
335
        return ArrayUtil::some(
336
            function ($column) use ($filter) {
337
                return preg_match(sprintf('/%s$/', preg_quote($column)), $filter['columnName']);
338
            },
339
            $this->fixes['chainFilters']
340
        );
341
    }
342
343
    /**
344
     * Converts string operators to enum operators
345
     *
346
     * @param int $stringType
347
     *
348
     * @return string
349
     */
350
    protected function getEnumType($stringType)
351
    {
352
        return in_array($stringType, [1, 3, 4, 5, 6], true) ? '1' : '2';
353
    }
354
355
    /**
356
     * @return int
357
     */
358
    protected function getCount($table)
359
    {
360
        return $this->createQb($table)
361
            ->select('COUNT(1)')
362
            ->execute()
363
            ->fetchColumn();
364
    }
365
366
    /**
367
     * @return QueryBuilder
368
     */
369
    protected function createQb($table)
370
    {
371
        return $this->connection->createQueryBuilder()
372
            ->select('r.id AS id, r.entity AS entity, r.definition AS definition')
373
            ->from($table, 'r');
374
    }
375
}
376