Completed
Push — ezp-26146_location_swap_incons... ( 4a02af...baaf24 )
by
unknown
21:53
created

DoctrineDatabase::loadPathData()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 23
nc 3
nop 1
dl 0
loc 37
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the DoctrineDatabase UrlAlias Gateway class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 *
9
 * @version //autogentag//
10
 */
11
namespace eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway;
12
13
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway;
14
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
15
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
16
use eZ\Publish\Core\Persistence\Database\Query;
17
18
/**
19
 * UrlAlias Gateway.
20
 */
21
class DoctrineDatabase extends Gateway
22
{
23
    /**
24
     * 2^30, since PHP_INT_MAX can cause overflows in DB systems, if PHP is run
25
     * on 64 bit systems.
26
     */
27
    const MAX_LIMIT = 1073741824;
28
29
    /**
30
     * Columns of database tables.
31
     *
32
     * @var array
33
     *
34
     * @todo remove after testing
35
     */
36
    protected $columns = array(
37
        'ezurlalias_ml' => array(
38
            'action',
39
            'action_type',
40
            'alias_redirects',
41
            'id',
42
            'is_alias',
43
            'is_original',
44
            'lang_mask',
45
            'link',
46
            'parent',
47
            'text',
48
            'text_md5',
49
        ),
50
    );
51
52
    /**
53
     * Doctrine database handler.
54
     *
55
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
56
     */
57
    protected $dbHandler;
58
59
    /**
60
     * Language mask generator.
61
     *
62
     * @var \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator
63
     */
64
    protected $languageMaskGenerator;
65
66
    /**
67
     * Creates a new DoctrineDatabase UrlAlias Gateway.
68
     *
69
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $dbHandler
70
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
71
     */
72
    public function __construct(
73
        DatabaseHandler $dbHandler,
74
        LanguageMaskGenerator $languageMaskGenerator
75
    ) {
76
        $this->dbHandler = $dbHandler;
77
        $this->languageMaskGenerator = $languageMaskGenerator;
78
    }
79
80
    /**
81
     * Loads list of aliases by given $locationId.
82
     *
83
     * @param mixed $locationId
84
     * @param bool $custom
85
     * @param mixed $languageId
86
     *
87
     * @return array
88
     */
89
    public function loadLocationEntries($locationId, $custom = false, $languageId = false)
90
    {
91
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
92
        $query = $this->dbHandler->createSelectQuery();
93
        $query->select(
94
            $this->dbHandler->quoteColumn('id'),
95
            $this->dbHandler->quoteColumn('link'),
96
            $this->dbHandler->quoteColumn('is_alias'),
97
            $this->dbHandler->quoteColumn('alias_redirects'),
98
            $this->dbHandler->quoteColumn('lang_mask'),
99
            $this->dbHandler->quoteColumn('is_original'),
100
            $this->dbHandler->quoteColumn('parent'),
101
            $this->dbHandler->quoteColumn('text'),
102
            $this->dbHandler->quoteColumn('text_md5'),
103
            $this->dbHandler->quoteColumn('action')
104
        )->from(
105
            $this->dbHandler->quoteTable('ezurlalias_ml')
106
        )->where(
107
            $query->expr->lAnd(
108
                $query->expr->eq(
109
                    $this->dbHandler->quoteColumn('action'),
110
                    $query->bindValue("eznode:{$locationId}", null, \PDO::PARAM_STR)
111
                ),
112
                $query->expr->eq(
113
                    $this->dbHandler->quoteColumn('is_original'),
114
                    $query->bindValue(1, null, \PDO::PARAM_INT)
115
                ),
116
                $query->expr->eq(
117
                    $this->dbHandler->quoteColumn('is_alias'),
118
                    $query->bindValue(
119
                        $custom ? 1 : 0,
120
                        null,
121
                        \PDO::PARAM_INT
122
                    )
123
                )
124
            )
125
        );
126
127 View Code Duplication
        if ($languageId !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
            $query->where(
129
                $query->expr->gt(
130
                    $query->expr->bitAnd(
131
                        $this->dbHandler->quoteColumn('lang_mask'),
132
                        $query->bindValue($languageId, null, \PDO::PARAM_INT)
133
                    ),
134
                    0
135
                )
136
            );
137
        }
138
139
        $statement = $query->prepare();
140
        $statement->execute();
141
142
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
143
    }
144
145
    /**
146
     * Loads paged list of global aliases.
147
     *
148
     * @param string|null $languageCode
149
     * @param int $offset
150
     * @param int $limit
151
     *
152
     * @return array
153
     */
154
    public function listGlobalEntries($languageCode = null, $offset = 0, $limit = -1)
155
    {
156
        $limit = $limit === -1 ? self::MAX_LIMIT : $limit;
157
158
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
159
        $query = $this->dbHandler->createSelectQuery();
160
        $query->select(
161
            $this->dbHandler->quoteColumn('action'),
162
            $this->dbHandler->quoteColumn('id'),
163
            $this->dbHandler->quoteColumn('link'),
164
            $this->dbHandler->quoteColumn('is_alias'),
165
            $this->dbHandler->quoteColumn('alias_redirects'),
166
            $this->dbHandler->quoteColumn('lang_mask'),
167
            $this->dbHandler->quoteColumn('is_original'),
168
            $this->dbHandler->quoteColumn('parent'),
169
            $this->dbHandler->quoteColumn('text_md5')
170
        )->from(
171
            $this->dbHandler->quoteTable('ezurlalias_ml')
172
        )->where(
173
            $query->expr->lAnd(
174
                $query->expr->eq(
175
                    $this->dbHandler->quoteColumn('action_type'),
176
                    $query->bindValue('module', null, \PDO::PARAM_STR)
177
                ),
178
                $query->expr->eq(
179
                    $this->dbHandler->quoteColumn('is_original'),
180
                    $query->bindValue(1, null, \PDO::PARAM_INT)
181
                ),
182
                $query->expr->eq(
183
                    $this->dbHandler->quoteColumn('is_alias'),
184
                    $query->bindValue(1, null, \PDO::PARAM_INT)
185
                )
186
            )
187
        )->limit(
188
            $limit,
189
            $offset
190
        );
191 View Code Duplication
        if (isset($languageCode)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
192
            $query->where(
193
                $query->expr->gt(
194
                    $query->expr->bitAnd(
195
                        $this->dbHandler->quoteColumn('lang_mask'),
196
                        $query->bindValue(
197
                            $this->languageMaskGenerator->generateLanguageIndicator($languageCode, false),
198
                            null,
199
                            \PDO::PARAM_INT
200
                        )
201
                    ),
202
                    0
203
                )
204
            );
205
        }
206
        $statement = $query->prepare();
207
        $statement->execute();
208
209
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
210
    }
211
212
    /**
213
     * Returns boolean indicating if the row with given $id is special root entry.
214
     *
215
     * Special root entry entry will have parentId=0 and text=''.
216
     * In standard installation this entry will point to location with id=2.
217
     *
218
     * @param mixed $id
219
     *
220
     * @return bool
221
     */
222
    public function isRootEntry($id)
223
    {
224
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
225
        $query = $this->dbHandler->createSelectQuery();
226
        $query->select(
227
            $this->dbHandler->quoteColumn('text'),
228
            $this->dbHandler->quoteColumn('parent')
229
        )->from(
230
            $this->dbHandler->quoteTable('ezurlalias_ml')
231
        )->where(
232
            $query->expr->eq(
233
                $this->dbHandler->quoteColumn('id'),
234
                $query->bindValue($id, null, \PDO::PARAM_INT)
235
            )
236
        );
237
        $statement = $query->prepare();
238
        $statement->execute();
239
        $row = $statement->fetch(\PDO::FETCH_ASSOC);
240
241
        return strlen($row['text']) == 0 && $row['parent'] == 0;
242
    }
243
244
    /**
245
     * Downgrades autogenerated entry matched by given $action and $languageId and negatively matched by
246
     * composite primary key.
247
     *
248
     * If language mask of the found entry is composite (meaning it consists of multiple language ids) given
249
     * $languageId will be removed from mask. Otherwise entry will be marked as history.
250
     *
251
     * @param string $action
252
     * @param mixed $languageId
253
     * @param mixed $newId
254
     * @param mixed $parentId
255
     * @param string $textMD5
256
     */
257
    public function cleanupAfterPublish($action, $languageId, $newId, $parentId, $textMD5)
258
    {
259
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
260
        $query = $this->dbHandler->createSelectQuery();
261
        $query->select(
262
            $this->dbHandler->quoteColumn('parent'),
263
            $this->dbHandler->quoteColumn('text_md5'),
264
            $this->dbHandler->quoteColumn('lang_mask')
265
        )->from(
266
            $this->dbHandler->quoteTable('ezurlalias_ml')
267
        )->where(
268
            $query->expr->lAnd(
269
                // 1) Autogenerated aliases that match action and language...
270
                $query->expr->eq(
271
                    $this->dbHandler->quoteColumn('action'),
272
                    $query->bindValue($action, null, \PDO::PARAM_STR)
273
                ),
274
                $query->expr->eq(
275
                    $this->dbHandler->quoteColumn('is_original'),
276
                    $query->bindValue(1, null, \PDO::PARAM_INT)
277
                ),
278
                $query->expr->eq(
279
                    $this->dbHandler->quoteColumn('is_alias'),
280
                    $query->bindValue(0, null, \PDO::PARAM_INT)
281
                ),
282
                $query->expr->gt(
283
                    $query->expr->bitAnd(
284
                        $this->dbHandler->quoteColumn('lang_mask'),
285
                        $query->bindValue($languageId, null, \PDO::PARAM_INT)
286
                    ),
287
                    0
288
                ),
289
                // 2) ...but not newly published entry
290
                $query->expr->not(
291
                    $query->expr->lAnd(
292
                        $query->expr->eq(
293
                            $this->dbHandler->quoteColumn('parent'),
294
                            $query->bindValue($parentId, null, \PDO::PARAM_INT)
295
                        ),
296
                        $query->expr->eq(
297
                            $this->dbHandler->quoteColumn('text_md5'),
298
                            $query->bindValue($textMD5, null, \PDO::PARAM_STR)
299
                        )
300
                    )
301
                )
302
            )
303
        );
304
305
        $statement = $query->prepare();
306
        $statement->execute();
307
        $row = $statement->fetch(\PDO::FETCH_ASSOC);
308
309
        if (!empty($row)) {
310
            // If language mask is composite (consists of multiple languages) then remove given language from entry
311
            if ($row['lang_mask'] & ~($languageId | 1)) {
312
                $this->removeTranslation($row['parent'], $row['text_md5'], $languageId);
313
            } else { // Otherwise mark entry as history
314
                $this->historize($row['parent'], $row['text_md5'], $newId);
315
            }
316
        }
317
    }
318
319
    /**
320
     * Swaps the content being referred to by two aliases.
321
     *
322
     * @param mixed $locationId1
323
     * @param mixed $locationId2
324
     *
325
     * @return bool
326
     */
327
    public function swap($locationId1, $locationId2)
328
    {
329
        $nodeAction1 = "eznode:$locationId1";
330
        $nodeAction2 = "eznode:$locationId2";
331
332
        $query = $this->dbHandler->createSelectQuery();
333
        $query
334
            ->select(
335
                $this->dbHandler->quoteColumn('action'),
336
                $this->dbHandler->quoteColumn('text'),
337
                $this->dbHandler->quoteColumn('text_md5'),
338
                $this->dbHandler->quoteColumn('lang_mask')
339
            )
340
            ->from($this->dbHandler->quoteTable('ezurlalias_ml'))
341
            ->where(
342
                $query->expr->in(
343
                    $this->dbHandler->quoteColumn('action'),
344
                    array($nodeAction1, $nodeAction2)
345
                )
346
            );
347
        $statement = $query->prepare();
348
        $statement->execute();
349
        $aliases = [];
350
        foreach ($statement->fetchAll() as $row) {
351
            $aliases[$row['action']] = $row;
352
        }
353
354
        if (!isset($aliases[$nodeAction1]) || !isset($aliases[$nodeAction2])) {
355
            return false;
356
        }
357
358
        $query = $this->dbHandler->createUpdateQuery();
359
        $query
360
            ->update($this->dbHandler->quoteTable('ezurlalias_ml'))
361
            ->set(
362
                $this->dbHandler->quoteColumn('text'),
363
                $query->bindValue($aliases[$nodeAction2]['text'])
364
            )
365
            ->set(
366
                $this->dbHandler->quoteColumn('text_md5'),
367
                $query->bindValue($aliases[$nodeAction2]['text_md5'])
368
            )
369
            ->set(
370
                $this->dbHandler->quoteColumn('lang_mask'),
371
                $query->bindValue($aliases[$nodeAction2]['lang_mask'])
372
            )
373
            ->where(
374
                $query->expr->eq(
375
                    $this->dbHandler->quoteColumn('action'),
376
                    $query->bindValue($nodeAction1)
377
                )
378
            );
379
        $query->prepare()->execute();
380
381
        $query = $this->dbHandler->createUpdateQuery();
382
        $query
383
            ->update($this->dbHandler->quoteTable('ezurlalias_ml'))
384
            ->set(
385
                $this->dbHandler->quoteColumn('text'),
386
                $query->bindValue($aliases[$nodeAction1]['text'])
387
            )
388
            ->set(
389
                $this->dbHandler->quoteColumn('text_md5'),
390
                $query->bindValue($aliases[$nodeAction1]['text_md5'])
391
            )
392
            ->set(
393
                $this->dbHandler->quoteColumn('lang_mask'),
394
                $query->bindValue($aliases[$nodeAction1]['lang_mask'])
395
            )
396
            ->where(
397
                $query->expr->eq(
398
                    $this->dbHandler->quoteColumn('action'),
399
                    $query->bindValue($nodeAction2)
400
                )
401
            );
402
        $query->prepare()->execute();
403
    }
404
405
    /**
406
     * Updates single row matched by composite primary key.
407
     *
408
     * Sets "is_original" to 0 thus marking entry as history.
409
     *
410
     * Re-links history entries.
411
     *
412
     * When location alias is published we need to check for new history entries created with self::downgrade()
413
     * with the same action and language, update their "link" column with id of the published entry.
414
     * History entry "id" column is moved to next id value so that all active (non-history) entries are kept
415
     * under the same id.
416
     *
417
     * @param mixed $parentId
418
     * @param string $textMD5
419
     */
420 View Code Duplication
    protected function historize($parentId, $textMD5, $newId)
421
    {
422
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
423
        $query = $this->dbHandler->createUpdateQuery();
424
        $query->update(
425
            $this->dbHandler->quoteTable('ezurlalias_ml')
426
        )->set(
427
            $this->dbHandler->quoteColumn('is_original'),
428
            $query->bindValue(0, null, \PDO::PARAM_INT)
429
        )->set(
430
            $this->dbHandler->quoteColumn('link'),
431
            $query->bindValue($newId, null, \PDO::PARAM_INT)
432
        )->set(
433
            $this->dbHandler->quoteColumn('id'),
434
            $query->bindValue(
435
                $this->getNextId(),
436
                null,
437
                \PDO::PARAM_INT
438
            )
439
        )->where(
440
            $query->expr->lAnd(
441
                $query->expr->eq(
442
                    $this->dbHandler->quoteColumn('parent'),
443
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
444
                ),
445
                $query->expr->eq(
446
                    $this->dbHandler->quoteColumn('text_md5'),
447
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
448
                )
449
            )
450
        );
451
        $query->prepare()->execute();
452
    }
453
454
    /**
455
     * Updates single row data matched by composite primary key.
456
     *
457
     * Removes given $languageId from entry's language mask
458
     *
459
     * @param mixed $parentId
460
     * @param string $textMD5
461
     * @param mixed $languageId
462
     */
463 View Code Duplication
    protected function removeTranslation($parentId, $textMD5, $languageId)
464
    {
465
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
466
        $query = $this->dbHandler->createUpdateQuery();
467
        $query->update(
468
            $this->dbHandler->quoteTable('ezurlalias_ml')
469
        )->set(
470
            $this->dbHandler->quoteColumn('lang_mask'),
471
            $query->expr->bitAnd(
472
                $this->dbHandler->quoteColumn('lang_mask'),
473
                $query->bindValue(~$languageId, null, \PDO::PARAM_INT)
474
            )
475
        )->where(
476
            $query->expr->lAnd(
477
                $query->expr->eq(
478
                    $this->dbHandler->quoteColumn('parent'),
479
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
480
                ),
481
                $query->expr->eq(
482
                    $this->dbHandler->quoteColumn('text_md5'),
483
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
484
                )
485
            )
486
        );
487
        $query->prepare()->execute();
488
    }
489
490
    /**
491
     * Marks all entries with given $id as history entries.
492
     *
493
     * This method is used by Handler::locationMoved(). For this reason rows are not updated with next id value as
494
     * all entries with given id are being marked as history and there is no need for id separation.
495
     * Thus only "link" and "is_original" columns are updated.
496
     *
497
     * @param mixed $id
498
     * @param mixed $link
499
     */
500 View Code Duplication
    public function historizeId($id, $link)
501
    {
502
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
503
        $query = $this->dbHandler->createUpdateQuery();
504
        $query->update(
505
            $this->dbHandler->quoteTable('ezurlalias_ml')
506
        )->set(
507
            $this->dbHandler->quoteColumn('is_original'),
508
            $query->bindValue(0, null, \PDO::PARAM_INT)
509
        )->set(
510
            $this->dbHandler->quoteColumn('link'),
511
            $query->bindValue($link, null, \PDO::PARAM_INT)
512
        )->where(
513
            $query->expr->lAnd(
514
                $query->expr->eq(
515
                    $this->dbHandler->quoteColumn('is_alias'),
516
                    $query->bindValue(0, null, \PDO::PARAM_INT)
517
                ),
518
                $query->expr->eq(
519
                    $this->dbHandler->quoteColumn('action_type'),
520
                    $query->bindValue('eznode', null, \PDO::PARAM_STR)
521
                ),
522
                $query->expr->eq(
523
                    $this->dbHandler->quoteColumn('link'),
524
                    $query->bindValue($id, null, \PDO::PARAM_INT)
525
                )
526
            )
527
        );
528
        $query->prepare()->execute();
529
    }
530
531
    /**
532
     * Updates parent id of autogenerated entries.
533
     *
534
     * Update includes history entries.
535
     *
536
     * @param mixed $oldParentId
537
     * @param mixed $newParentId
538
     */
539 View Code Duplication
    public function reparent($oldParentId, $newParentId)
540
    {
541
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
542
        $query = $this->dbHandler->createUpdateQuery();
543
        $query->update(
544
            $this->dbHandler->quoteTable('ezurlalias_ml')
545
        )->set(
546
            $this->dbHandler->quoteColumn('parent'),
547
            $query->bindValue($newParentId, null, \PDO::PARAM_INT)
548
        )->where(
549
            $query->expr->lAnd(
550
                $query->expr->eq(
551
                    $this->dbHandler->quoteColumn('is_alias'),
552
                    $query->bindValue(0, null, \PDO::PARAM_INT)
553
                ),
554
                $query->expr->eq(
555
                    $this->dbHandler->quoteColumn('parent'),
556
                    $query->bindValue($oldParentId, null, \PDO::PARAM_INT)
557
                )
558
            )
559
        );
560
561
        $query->prepare()->execute();
562
    }
563
564
    /**
565
     * Updates single row data matched by composite primary key.
566
     *
567
     * Use optional parameter $languageMaskMatch to additionally limit the query match with languages.
568
     *
569
     * @param mixed $parentId
570
     * @param string $textMD5
571
     * @param array $values associative array with column names as keys and column values as values
572
     */
573 View Code Duplication
    public function updateRow($parentId, $textMD5, array $values)
574
    {
575
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
576
        $query = $this->dbHandler->createUpdateQuery();
577
        $query->update($this->dbHandler->quoteTable('ezurlalias_ml'));
578
        $this->setQueryValues($query, $values);
579
        $query->where(
580
            $query->expr->lAnd(
581
                $query->expr->eq(
582
                    $this->dbHandler->quoteColumn('parent'),
583
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
584
                ),
585
                $query->expr->eq(
586
                    $this->dbHandler->quoteColumn('text_md5'),
587
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
588
                )
589
            )
590
        );
591
        $query->prepare()->execute();
592
    }
593
594
    /**
595
     * Inserts new row in urlalias_ml table.
596
     *
597
     * @param array $values
598
     *
599
     * @return mixed
600
     */
601
    public function insertRow(array $values)
602
    {
603
        // @todo remove after testing
604
        if (
605
            !isset($values['text']) ||
606
            !isset($values['text_md5']) ||
607
            !isset($values['action']) ||
608
            !isset($values['parent']) ||
609
            !isset($values['lang_mask'])) {
610
            throw new \Exception('value set is incomplete: ' . var_export($values, true) . ", can't execute insert");
611
        }
612
        if (!isset($values['id'])) {
613
            $values['id'] = $this->getNextId();
614
        }
615
        if (!isset($values['link'])) {
616
            $values['link'] = $values['id'];
617
        }
618
        if (!isset($values['is_original'])) {
619
            $values['is_original'] = ($values['id'] == $values['link'] ? 1 : 0);
620
        }
621
        if (!isset($values['is_alias'])) {
622
            $values['is_alias'] = 0;
623
        }
624
        if (!isset($values['alias_redirects'])) {
625
            $values['alias_redirects'] = 0;
626
        }
627
        if (!isset($values['action_type'])) {
628
            if (preg_match('#^(.+):.*#', $values['action'], $matches)) {
629
                $values['action_type'] = $matches[1];
630
            }
631
        }
632
        if ($values['is_alias']) {
633
            $values['is_original'] = 1;
634
        }
635
        if ($values['action'] === 'nop:') {
636
            $values['is_original'] = 0;
637
        }
638
639
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
640
        $query = $this->dbHandler->createInsertQuery();
641
        $query->insertInto($this->dbHandler->quoteTable('ezurlalias_ml'));
642
        $this->setQueryValues($query, $values);
643
        $query->prepare()->execute();
644
645
        return $values['id'];
646
    }
647
648
    /**
649
     * Sets value for insert or update query.
650
     *
651
     * @param \eZ\Publish\Core\Persistence\Database\Query|\eZ\Publish\Core\Persistence\Database\InsertQuery|\eZ\Publish\Core\Persistence\Database\UpdateQuery $query
652
     * @param array $values
653
     *
654
     * @throws \Exception
655
     */
656
    protected function setQueryValues(Query $query, $values)
657
    {
658
        foreach ($values as $column => $value) {
659
            // @todo remove after testing
660
            if (!in_array($column, $this->columns['ezurlalias_ml'])) {
661
                throw new \Exception("unknown column '$column' for table 'ezurlalias_ml'");
662
            }
663
            switch ($column) {
664
                case 'text':
665
                case 'action':
666
                case 'text_md5':
667
                case 'action_type':
668
                    $pdoDataType = \PDO::PARAM_STR;
669
                    break;
670
                default:
671
                    $pdoDataType = \PDO::PARAM_INT;
672
            }
673
            $query->set(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eZ\Publish\Core\Persistence\Database\Query as the method set() does only exist in the following implementations of said interface: eZ\Publish\Core\Persiste...ine\InsertDoctrineQuery, eZ\Publish\Core\Persiste...ine\UpdateDoctrineQuery.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
674
                $this->dbHandler->quoteColumn($column),
675
                $query->bindValue($value, null, $pdoDataType)
676
            );
677
        }
678
    }
679
680
    /**
681
     * Returns next value for "id" column.
682
     *
683
     * @return mixed
684
     */
685 View Code Duplication
    public function getNextId()
686
    {
687
        $sequence = $this->dbHandler->getSequenceName('ezurlalias_ml_incr', 'id');
688
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
689
        $query = $this->dbHandler->createInsertQuery();
690
        $query->insertInto(
691
            $this->dbHandler->quoteTable('ezurlalias_ml_incr')
692
        );
693
        // ezcDatabase does not abstract the "auto increment id"
694
        // INSERT INTO ezurlalias_ml_incr VALUES(DEFAULT) is not an option due
695
        // to this mysql bug: http://bugs.mysql.com/bug.php?id=42270
696
        // as a result we are forced to check which database is currently used
697
        // to generate the correct SQL query
698
        // see https://jira.ez.no/browse/EZP-20652
699
        if ($this->dbHandler->useSequences()) {
700
            $query->set(
701
                $this->dbHandler->quoteColumn('id'),
702
                "nextval('{$sequence}')"
703
            );
704
        } else {
705
            $query->set(
706
                $this->dbHandler->quoteColumn('id'),
707
                $query->bindValue(null, null, \PDO::PARAM_NULL)
708
            );
709
        }
710
        $query->prepare()->execute();
711
712
        return $this->dbHandler->lastInsertId($sequence);
713
    }
714
715
    /**
716
     * Loads single row data matched by composite primary key.
717
     *
718
     * @param mixed $parentId
719
     * @param string $textMD5
720
     *
721
     * @return array
722
     */
723 View Code Duplication
    public function loadRow($parentId, $textMD5)
724
    {
725
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
726
        $query = $this->dbHandler->createSelectQuery();
727
        $query->select('*')->from(
728
            $this->dbHandler->quoteTable('ezurlalias_ml')
729
        )->where(
730
            $query->expr->lAnd(
731
                $query->expr->eq(
732
                    $this->dbHandler->quoteColumn('parent'),
733
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
734
                ),
735
                $query->expr->eq(
736
                    $this->dbHandler->quoteColumn('text_md5'),
737
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
738
                )
739
            )
740
        );
741
742
        $statement = $query->prepare();
743
        $statement->execute();
744
745
        return $statement->fetch(\PDO::FETCH_ASSOC);
746
    }
747
748
    /**
749
     * Loads complete URL alias data by given array of path hashes.
750
     *
751
     * @param string[] $urlHashes URL string hashes
752
     *
753
     * @return array
754
     */
755
    public function loadUrlAliasData(array $urlHashes)
756
    {
757
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
758
        $query = $this->dbHandler->createSelectQuery();
759
760
        $count = count($urlHashes);
761
        foreach ($urlHashes as $level => $urlPartHash) {
762
            $tableName = 'ezurlalias_ml' . ($level === $count - 1 ? '' : $level);
763
764
            if ($level === $count - 1) {
765
                $query->select(
766
                    $this->dbHandler->quoteColumn('id', $tableName),
767
                    $this->dbHandler->quoteColumn('link', $tableName),
768
                    $this->dbHandler->quoteColumn('is_alias', $tableName),
769
                    $this->dbHandler->quoteColumn('alias_redirects', $tableName),
770
                    $this->dbHandler->quoteColumn('is_original', $tableName),
771
                    $this->dbHandler->quoteColumn('action', $tableName),
772
                    $this->dbHandler->quoteColumn('action_type', $tableName),
773
                    $this->dbHandler->quoteColumn('lang_mask', $tableName),
774
                    $this->dbHandler->quoteColumn('text', $tableName),
775
                    $this->dbHandler->quoteColumn('parent', $tableName),
776
                    $this->dbHandler->quoteColumn('text_md5', $tableName)
777
                )->from(
778
                    $this->dbHandler->quoteTable('ezurlalias_ml')
779
                );
780
            } else {
781
                $query->select(
782
                    $this->dbHandler->aliasedColumn($query, 'id', $tableName),
783
                    $this->dbHandler->aliasedColumn($query, 'link', $tableName),
784
                    $this->dbHandler->aliasedColumn($query, 'is_alias', $tableName),
785
                    $this->dbHandler->aliasedColumn($query, 'alias_redirects', $tableName),
786
                    $this->dbHandler->aliasedColumn($query, 'is_original', $tableName),
787
                    $this->dbHandler->aliasedColumn($query, 'action', $tableName),
788
                    $this->dbHandler->aliasedColumn($query, 'action_type', $tableName),
789
                    $this->dbHandler->aliasedColumn($query, 'lang_mask', $tableName),
790
                    $this->dbHandler->aliasedColumn($query, 'text', $tableName),
791
                    $this->dbHandler->aliasedColumn($query, 'parent', $tableName),
792
                    $this->dbHandler->aliasedColumn($query, 'text_md5', $tableName)
793
                )->from(
794
                    $query->alias('ezurlalias_ml', $tableName)
795
                );
796
            }
797
798
            $query->where(
799
                $query->expr->lAnd(
800
                    $query->expr->eq(
801
                        $this->dbHandler->quoteColumn('text_md5', $tableName),
802
                        $query->bindValue($urlPartHash, null, \PDO::PARAM_STR)
803
                    ),
804
                    $query->expr->eq(
805
                        $this->dbHandler->quoteColumn('parent', $tableName),
806
                        // root entry has parent column set to 0
807
                        isset($previousTableName) ? $this->dbHandler->quoteColumn('link', $previousTableName) : $query->bindValue(0, null, \PDO::PARAM_INT)
808
                    )
809
                )
810
            );
811
812
            $previousTableName = $tableName;
813
        }
814
        $query->limit(1);
815
816
        $statement = $query->prepare();
817
        $statement->execute();
818
819
        return $statement->fetch(\PDO::FETCH_ASSOC);
820
    }
821
822
    /**
823
     * Loads autogenerated entry id by given $action and optionally $parentId.
824
     *
825
     * @param string $action
826
     * @param mixed|null $parentId
827
     *
828
     * @return array
829
     */
830 View Code Duplication
    public function loadAutogeneratedEntry($action, $parentId = null)
831
    {
832
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
833
        $query = $this->dbHandler->createSelectQuery();
834
        $query->select(
835
            '*'
836
        )->from(
837
            $this->dbHandler->quoteTable('ezurlalias_ml')
838
        )->where(
839
            $query->expr->lAnd(
840
                $query->expr->eq(
841
                    $this->dbHandler->quoteColumn('action'),
842
                    $query->bindValue($action, null, \PDO::PARAM_STR)
843
                ),
844
                $query->expr->eq(
845
                    $this->dbHandler->quoteColumn('is_original'),
846
                    $query->bindValue(1, null, \PDO::PARAM_INT)
847
                ),
848
                $query->expr->eq(
849
                    $this->dbHandler->quoteColumn('is_alias'),
850
                    $query->bindValue(0, null, \PDO::PARAM_INT)
851
                )
852
            )
853
        );
854
855
        if (isset($parentId)) {
856
            $query->where(
857
                $query->expr->eq(
858
                    $this->dbHandler->quoteColumn('parent'),
859
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
860
                )
861
            );
862
        }
863
864
        $statement = $query->prepare();
865
        $statement->execute();
866
867
        return $statement->fetch(\PDO::FETCH_ASSOC);
868
    }
869
870
    /**
871
     * Loads all data for the path identified by given $id.
872
     *
873
     * @throws \RuntimeException
874
     *
875
     * @param mixed $id
876
     *
877
     * @return array
878
     */
879
    public function loadPathData($id)
880
    {
881
        $pathData = array();
882
883
        while ($id != 0) {
884
            /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
885
            $query = $this->dbHandler->createSelectQuery();
886
            $query->select(
887
                $this->dbHandler->quoteColumn('parent'),
888
                $this->dbHandler->quoteColumn('lang_mask'),
889
                $this->dbHandler->quoteColumn('text')
890
            )->from(
891
                $this->dbHandler->quoteTable('ezurlalias_ml')
892
            )->where(
893
                $query->expr->eq(
894
                    $this->dbHandler->quoteColumn('id'),
895
                    $query->bindValue($id, null, \PDO::PARAM_INT)
896
                )
897
            );
898
899
            $statement = $query->prepare();
900
            $statement->execute();
901
902
            $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
903
            if (empty($rows)) {
904
                // Normally this should never happen
905
                // @todo remove throw when tested
906
                $path = implode('/', $pathData);
907
                throw new \RuntimeException("Path ({$path}...) is broken, last id is '{$id}': " . __METHOD__);
908
            }
909
910
            $id = $rows[0]['parent'];
911
            array_unshift($pathData, $rows);
912
        }
913
914
        return $pathData;
915
    }
916
917
    /**
918
     * Loads path data identified by given ordered array of hierarchy data.
919
     *
920
     * The first entry in $hierarchyData corresponds to the top-most path element in the path, the second entry the
921
     * child of the first path element and so on.
922
     * This method is faster than self::getPath() since it can fetch all elements using only one query, but can be used
923
     * only for autogenerated paths.
924
     *
925
     * @param array $hierarchyData
926
     *
927
     * @return array
928
     */
929
    public function loadPathDataByHierarchy(array $hierarchyData)
930
    {
931
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
932
        $query = $this->dbHandler->createSelectQuery();
933
934
        $hierarchyConditions = array();
935
        foreach ($hierarchyData as $levelData) {
936
            $hierarchyConditions[] = $query->expr->lAnd(
937
                $query->expr->eq(
938
                    $this->dbHandler->quoteColumn('parent'),
939
                    $query->bindValue(
940
                        $levelData['parent'],
941
                        null,
942
                        \PDO::PARAM_INT
943
                    )
944
                ),
945
                $query->expr->eq(
946
                    $this->dbHandler->quoteColumn('action'),
947
                    $query->bindValue(
948
                        $levelData['action'],
949
                        null,
950
                        \PDO::PARAM_STR
951
                    )
952
                ),
953
                $query->expr->eq(
954
                    $this->dbHandler->quoteColumn('id'),
955
                    $query->bindValue(
956
                        $levelData['id'],
957
                        null,
958
                        \PDO::PARAM_INT
959
                    )
960
                )
961
            );
962
        }
963
964
        $query->select(
965
            $this->dbHandler->quoteColumn('action'),
966
            $this->dbHandler->quoteColumn('lang_mask'),
967
            $this->dbHandler->quoteColumn('text')
968
        )->from(
969
            $this->dbHandler->quoteTable('ezurlalias_ml')
970
        )->where(
971
            $query->expr->lOr($hierarchyConditions)
972
        );
973
974
        $statement = $query->prepare();
975
        $statement->execute();
976
977
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
978
        $rowsMap = array();
979
        foreach ($rows as $row) {
980
            $rowsMap[$row['action']][] = $row;
981
        }
982
983
        if (count($rowsMap) !== count($hierarchyData)) {
984
            throw new \RuntimeException('The path is corrupted.');
985
        }
986
987
        $data = array();
988
        foreach ($hierarchyData as $levelData) {
989
            $data[] = $rowsMap[$levelData['action']];
990
        }
991
992
        return $data;
993
    }
994
995
    /**
996
     * Deletes single custom alias row matched by composite primary key.
997
     *
998
     * @param mixed $parentId
999
     * @param string $textMD5
1000
     *
1001
     * @return bool
1002
     */
1003
    public function removeCustomAlias($parentId, $textMD5)
1004
    {
1005
        /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1006
        $query = $this->dbHandler->createDeleteQuery();
1007
        $query->deleteFrom(
1008
            $this->dbHandler->quoteTable('ezurlalias_ml')
1009
        )->where(
1010
            $query->expr->lAnd(
1011
                $query->expr->eq(
1012
                    $this->dbHandler->quoteColumn('parent'),
1013
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
1014
                ),
1015
                $query->expr->eq(
1016
                    $this->dbHandler->quoteColumn('text_md5'),
1017
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
1018
                ),
1019
                $query->expr->eq(
1020
                    $this->dbHandler->quoteColumn('is_alias'),
1021
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1022
                )
1023
            )
1024
        );
1025
        $statement = $query->prepare();
1026
        $statement->execute();
1027
1028
        return $statement->rowCount() === 1 ?: false;
1029
    }
1030
1031
    /**
1032
     * Deletes all rows with given $action and optionally $id.
1033
     *
1034
     * If $id is set only autogenerated entries will be removed.
1035
     *
1036
     * @param mixed $action
1037
     * @param mixed|null $id
1038
     *
1039
     * @return bool
1040
     */
1041 View Code Duplication
    public function remove($action, $id = null)
1042
    {
1043
        /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1044
        $query = $this->dbHandler->createDeleteQuery();
1045
        $query->deleteFrom(
1046
            $this->dbHandler->quoteTable('ezurlalias_ml')
1047
        )->where(
1048
            $query->expr->eq(
1049
                $this->dbHandler->quoteColumn('action'),
1050
                $query->bindValue($action, null, \PDO::PARAM_STR)
1051
            )
1052
        );
1053
1054
        if ($id !== null) {
1055
            $query->where(
1056
                $query->expr->lAnd(
1057
                    $query->expr->eq(
1058
                        $this->dbHandler->quoteColumn('is_alias'),
1059
                        $query->bindValue(0, null, \PDO::PARAM_INT)
1060
                    ),
1061
                    $query->expr->eq(
1062
                        $this->dbHandler->quoteColumn('id'),
1063
                        $query->bindValue($id, null, \PDO::PARAM_INT)
1064
                    )
1065
                )
1066
            );
1067
        }
1068
1069
        $query->prepare()->execute();
1070
    }
1071
1072
    /**
1073
     * Loads all autogenerated entries with given $parentId with optionally included history entries.
1074
     *
1075
     * @param mixed $parentId
1076
     * @param bool $includeHistory
1077
     *
1078
     * @return array
1079
     */
1080 View Code Duplication
    public function loadAutogeneratedEntries($parentId, $includeHistory = false)
1081
    {
1082
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1083
        $query = $this->dbHandler->createSelectQuery();
1084
        $query->select(
1085
            '*'
1086
        )->from(
1087
            $this->dbHandler->quoteTable('ezurlalias_ml')
1088
        )->where(
1089
            $query->expr->lAnd(
1090
                $query->expr->eq(
1091
                    $this->dbHandler->quoteColumn('parent'),
1092
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
1093
                ),
1094
                $query->expr->eq(
1095
                    $this->dbHandler->quoteColumn('action_type'),
1096
                    $query->bindValue('eznode', null, \PDO::PARAM_STR)
1097
                ),
1098
                $query->expr->eq(
1099
                    $this->dbHandler->quoteColumn('is_alias'),
1100
                    $query->bindValue(0, null, \PDO::PARAM_INT)
1101
                )
1102
            )
1103
        );
1104
1105
        if (!$includeHistory) {
1106
            $query->where(
1107
                $query->expr->eq(
1108
                    $this->dbHandler->quoteColumn('is_original'),
1109
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1110
                )
1111
            );
1112
        }
1113
1114
        $statement = $query->prepare();
1115
        $statement->execute();
1116
1117
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1118
    }
1119
}
1120