Completed
Push — master ( 18c4e8...24e938 )
by André
39:56 queued 23:48
created

DoctrineDatabase::bulkRemoveTranslation()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 16
nc 1
nop 2
dl 0
loc 24
rs 8.9713
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
namespace eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway;
10
11
use Doctrine\DBAL\Connection;
12
use eZ\Publish\Core\Persistence\Legacy\Content\UrlAlias\Gateway;
13
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
14
use eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator;
15
use eZ\Publish\Core\Persistence\Database\Query;
16
use RuntimeException;
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
     * Main URL database table name.
68
     *
69
     * @var string
70
     */
71
    protected $table;
72
73
    /**
74
     * Creates a new DoctrineDatabase UrlAlias Gateway.
75
     *
76
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $dbHandler
77
     * @param \eZ\Publish\Core\Persistence\Legacy\Content\Language\MaskGenerator $languageMaskGenerator
78
     */
79
    public function __construct(
80
        DatabaseHandler $dbHandler,
81
        LanguageMaskGenerator $languageMaskGenerator
82
    ) {
83
        $this->dbHandler = $dbHandler;
84
        $this->languageMaskGenerator = $languageMaskGenerator;
85
        $this->table = static::TABLE;
86
    }
87
88
    public function setTable($name)
89
    {
90
        $this->table = $name;
91
    }
92
93
    /**
94
     * Loads list of aliases by given $locationId.
95
     *
96
     * @param mixed $locationId
97
     * @param bool $custom
98
     * @param mixed $languageId
99
     *
100
     * @return array
101
     */
102
    public function loadLocationEntries($locationId, $custom = false, $languageId = false)
103
    {
104
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
105
        $query = $this->dbHandler->createSelectQuery();
106
        $query->select(
107
            $this->dbHandler->quoteColumn('id'),
108
            $this->dbHandler->quoteColumn('link'),
109
            $this->dbHandler->quoteColumn('is_alias'),
110
            $this->dbHandler->quoteColumn('alias_redirects'),
111
            $this->dbHandler->quoteColumn('lang_mask'),
112
            $this->dbHandler->quoteColumn('is_original'),
113
            $this->dbHandler->quoteColumn('parent'),
114
            $this->dbHandler->quoteColumn('text'),
115
            $this->dbHandler->quoteColumn('text_md5'),
116
            $this->dbHandler->quoteColumn('action')
117
        )->from(
118
            $this->dbHandler->quoteTable($this->table)
119
        )->where(
120
            $query->expr->lAnd(
121
                $query->expr->eq(
122
                    $this->dbHandler->quoteColumn('action'),
123
                    $query->bindValue("eznode:{$locationId}", null, \PDO::PARAM_STR)
124
                ),
125
                $query->expr->eq(
126
                    $this->dbHandler->quoteColumn('is_original'),
127
                    $query->bindValue(1, null, \PDO::PARAM_INT)
128
                ),
129
                $query->expr->eq(
130
                    $this->dbHandler->quoteColumn('is_alias'),
131
                    $query->bindValue(
132
                        $custom ? 1 : 0,
133
                        null,
134
                        \PDO::PARAM_INT
135
                    )
136
                )
137
            )
138
        );
139
140 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...
141
            $query->where(
142
                $query->expr->gt(
143
                    $query->expr->bitAnd(
144
                        $this->dbHandler->quoteColumn('lang_mask'),
145
                        $query->bindValue($languageId, null, \PDO::PARAM_INT)
146
                    ),
147
                    0
148
                )
149
            );
150
        }
151
152
        $statement = $query->prepare();
153
        $statement->execute();
154
155
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
156
    }
157
158
    /**
159
     * Loads paged list of global aliases.
160
     *
161
     * @param string|null $languageCode
162
     * @param int $offset
163
     * @param int $limit
164
     *
165
     * @return array
166
     */
167
    public function listGlobalEntries($languageCode = null, $offset = 0, $limit = -1)
168
    {
169
        $limit = $limit === -1 ? self::MAX_LIMIT : $limit;
170
171
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
172
        $query = $this->dbHandler->createSelectQuery();
173
        $query->select(
174
            $this->dbHandler->quoteColumn('action'),
175
            $this->dbHandler->quoteColumn('id'),
176
            $this->dbHandler->quoteColumn('link'),
177
            $this->dbHandler->quoteColumn('is_alias'),
178
            $this->dbHandler->quoteColumn('alias_redirects'),
179
            $this->dbHandler->quoteColumn('lang_mask'),
180
            $this->dbHandler->quoteColumn('is_original'),
181
            $this->dbHandler->quoteColumn('parent'),
182
            $this->dbHandler->quoteColumn('text_md5')
183
        )->from(
184
            $this->dbHandler->quoteTable($this->table)
185
        )->where(
186
            $query->expr->lAnd(
187
                $query->expr->eq(
188
                    $this->dbHandler->quoteColumn('action_type'),
189
                    $query->bindValue('module', null, \PDO::PARAM_STR)
190
                ),
191
                $query->expr->eq(
192
                    $this->dbHandler->quoteColumn('is_original'),
193
                    $query->bindValue(1, null, \PDO::PARAM_INT)
194
                ),
195
                $query->expr->eq(
196
                    $this->dbHandler->quoteColumn('is_alias'),
197
                    $query->bindValue(1, null, \PDO::PARAM_INT)
198
                )
199
            )
200
        )->limit(
201
            $limit,
202
            $offset
203
        );
204 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...
205
            $query->where(
206
                $query->expr->gt(
207
                    $query->expr->bitAnd(
208
                        $this->dbHandler->quoteColumn('lang_mask'),
209
                        $query->bindValue(
210
                            $this->languageMaskGenerator->generateLanguageIndicator($languageCode, false),
211
                            null,
212
                            \PDO::PARAM_INT
213
                        )
214
                    ),
215
                    0
216
                )
217
            );
218
        }
219
        $statement = $query->prepare();
220
        $statement->execute();
221
222
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
223
    }
224
225
    /**
226
     * Returns boolean indicating if the row with given $id is special root entry.
227
     *
228
     * Special root entry entry will have parentId=0 and text=''.
229
     * In standard installation this entry will point to location with id=2.
230
     *
231
     * @param mixed $id
232
     *
233
     * @return bool
234
     */
235
    public function isRootEntry($id)
236
    {
237
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
238
        $query = $this->dbHandler->createSelectQuery();
239
        $query->select(
240
            $this->dbHandler->quoteColumn('text'),
241
            $this->dbHandler->quoteColumn('parent')
242
        )->from(
243
            $this->dbHandler->quoteTable($this->table)
244
        )->where(
245
            $query->expr->eq(
246
                $this->dbHandler->quoteColumn('id'),
247
                $query->bindValue($id, null, \PDO::PARAM_INT)
248
            )
249
        );
250
        $statement = $query->prepare();
251
        $statement->execute();
252
        $row = $statement->fetch(\PDO::FETCH_ASSOC);
253
254
        return strlen($row['text']) == 0 && $row['parent'] == 0;
255
    }
256
257
    /**
258
     * Downgrades autogenerated entry matched by given $action and $languageId and negatively matched by
259
     * composite primary key.
260
     *
261
     * If language mask of the found entry is composite (meaning it consists of multiple language ids) given
262
     * $languageId will be removed from mask. Otherwise entry will be marked as history.
263
     *
264
     * @param string $action
265
     * @param mixed $languageId
266
     * @param mixed $newId
267
     * @param mixed $parentId
268
     * @param string $textMD5
269
     */
270
    public function cleanupAfterPublish($action, $languageId, $newId, $parentId, $textMD5)
271
    {
272
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
273
        $query = $this->dbHandler->createSelectQuery();
274
        $query->select(
275
            $this->dbHandler->quoteColumn('parent'),
276
            $this->dbHandler->quoteColumn('text_md5'),
277
            $this->dbHandler->quoteColumn('lang_mask')
278
        )->from(
279
            $this->dbHandler->quoteTable($this->table)
280
        )->where(
281
            $query->expr->lAnd(
282
                // 1) Autogenerated aliases that match action and language...
283
                $query->expr->eq(
284
                    $this->dbHandler->quoteColumn('action'),
285
                    $query->bindValue($action, null, \PDO::PARAM_STR)
286
                ),
287
                $query->expr->eq(
288
                    $this->dbHandler->quoteColumn('is_original'),
289
                    $query->bindValue(1, null, \PDO::PARAM_INT)
290
                ),
291
                $query->expr->eq(
292
                    $this->dbHandler->quoteColumn('is_alias'),
293
                    $query->bindValue(0, null, \PDO::PARAM_INT)
294
                ),
295
                $query->expr->gt(
296
                    $query->expr->bitAnd(
297
                        $this->dbHandler->quoteColumn('lang_mask'),
298
                        $query->bindValue($languageId, null, \PDO::PARAM_INT)
299
                    ),
300
                    0
301
                ),
302
                // 2) ...but not newly published entry
303
                $query->expr->not(
304
                    $query->expr->lAnd(
305
                        $query->expr->eq(
306
                            $this->dbHandler->quoteColumn('parent'),
307
                            $query->bindValue($parentId, null, \PDO::PARAM_INT)
308
                        ),
309
                        $query->expr->eq(
310
                            $this->dbHandler->quoteColumn('text_md5'),
311
                            $query->bindValue($textMD5, null, \PDO::PARAM_STR)
312
                        )
313
                    )
314
                )
315
            )
316
        );
317
318
        $statement = $query->prepare();
319
        $statement->execute();
320
        $row = $statement->fetch(\PDO::FETCH_ASSOC);
321
322
        if (!empty($row)) {
323
            // If language mask is composite (consists of multiple languages) then remove given language from entry
324
            if ($row['lang_mask'] & ~($languageId | 1)) {
325
                $this->removeTranslation($row['parent'], $row['text_md5'], $languageId);
326
            } else {
327
                // Otherwise mark entry as history
328
                $this->historize($row['parent'], $row['text_md5'], $newId);
329
            }
330
        }
331
    }
332
333
    public function historizeBeforeSwap($action, $languageMask)
334
    {
335
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
336
        $query = $this->dbHandler->createUpdateQuery();
337
        $query->update(
338
            $this->dbHandler->quoteTable($this->table)
339
        )->set(
340
            $this->dbHandler->quoteColumn('is_original'),
341
            $query->bindValue(0, null, \PDO::PARAM_INT)
342
        )->set(
343
            $this->dbHandler->quoteColumn('id'),
344
            $query->bindValue(
345
                $this->getNextId(),
346
                null,
347
                \PDO::PARAM_INT
348
            )
349
        )->where(
350
            $query->expr->lAnd(
351
                $query->expr->eq(
352
                    $this->dbHandler->quoteColumn('action'),
353
                    $query->bindValue($action, null, \PDO::PARAM_STR)
354
                ),
355
                $query->expr->eq(
356
                    $this->dbHandler->quoteColumn('is_original'),
357
                    $query->bindValue(1, null, \PDO::PARAM_INT)
358
                ),
359
                $query->expr->gt(
360
                    $query->expr->bitAnd(
361
                        $this->dbHandler->quoteColumn('lang_mask'),
362
                        $query->bindValue($languageMask & ~1, null, \PDO::PARAM_INT)
363
                    ),
364
                    0
365
                )
366
            )
367
        );
368
        $query->prepare()->execute();
369
    }
370
371
    /**
372
     * Updates single row matched by composite primary key.
373
     *
374
     * Sets "is_original" to 0 thus marking entry as history.
375
     *
376
     * Re-links history entries.
377
     *
378
     * When location alias is published we need to check for new history entries created with self::downgrade()
379
     * with the same action and language, update their "link" column with id of the published entry.
380
     * History entry "id" column is moved to next id value so that all active (non-history) entries are kept
381
     * under the same id.
382
     *
383
     * @param int $parentId
384
     * @param string $textMD5
385
     * @param int $newId
386
     */
387
    protected function historize($parentId, $textMD5, $newId)
388
    {
389
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
390
        $query = $this->dbHandler->createUpdateQuery();
391
        $query->update(
392
            $this->dbHandler->quoteTable($this->table)
393
        )->set(
394
            $this->dbHandler->quoteColumn('is_original'),
395
            $query->bindValue(0, null, \PDO::PARAM_INT)
396
        )->set(
397
            $this->dbHandler->quoteColumn('link'),
398
            $query->bindValue($newId, null, \PDO::PARAM_INT)
399
        )->set(
400
            $this->dbHandler->quoteColumn('id'),
401
            $query->bindValue(
402
                $this->getNextId(),
403
                null,
404
                \PDO::PARAM_INT
405
            )
406
        )->where(
407
            $query->expr->lAnd(
408
                $query->expr->eq(
409
                    $this->dbHandler->quoteColumn('parent'),
410
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
411
                ),
412
                $query->expr->eq(
413
                    $this->dbHandler->quoteColumn('text_md5'),
414
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
415
                )
416
            )
417
        );
418
        $query->prepare()->execute();
419
    }
420
421
    /**
422
     * Updates single row data matched by composite primary key.
423
     *
424
     * Removes given $languageId from entry's language mask
425
     *
426
     * @param mixed $parentId
427
     * @param string $textMD5
428
     * @param mixed $languageId
429
     */
430 View Code Duplication
    protected function removeTranslation($parentId, $textMD5, $languageId)
431
    {
432
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
433
        $query = $this->dbHandler->createUpdateQuery();
434
        $query->update(
435
            $this->dbHandler->quoteTable($this->table)
436
        )->set(
437
            $this->dbHandler->quoteColumn('lang_mask'),
438
            $query->expr->bitAnd(
439
                $this->dbHandler->quoteColumn('lang_mask'),
440
                $query->bindValue(~$languageId, null, \PDO::PARAM_INT)
441
            )
442
        )->where(
443
            $query->expr->lAnd(
444
                $query->expr->eq(
445
                    $this->dbHandler->quoteColumn('parent'),
446
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
447
                ),
448
                $query->expr->eq(
449
                    $this->dbHandler->quoteColumn('text_md5'),
450
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
451
                )
452
            )
453
        );
454
        $query->prepare()->execute();
455
    }
456
457
    /**
458
     * Marks all entries with given $id as history entries.
459
     *
460
     * This method is used by Handler::locationMoved(). Each row is separately historized
461
     * because future publishing needs to be able to take over history entries safely.
462
     *
463
     * @param mixed $id
464
     * @param mixed $link
465
     */
466
    public function historizeId($id, $link)
467
    {
468
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
469
        $query = $this->dbHandler->createSelectQuery();
470
        $query->select(
471
            $this->dbHandler->quoteColumn('parent'),
472
            $this->dbHandler->quoteColumn('text_md5')
473
        )->from(
474
            $this->dbHandler->quoteTable($this->table)
475
        )->where(
476
            $query->expr->lAnd(
477
                $query->expr->eq(
478
                    $this->dbHandler->quoteColumn('is_alias'),
479
                    $query->bindValue(0, null, \PDO::PARAM_INT)
480
                ),
481
                $query->expr->eq(
482
                    $this->dbHandler->quoteColumn('is_original'),
483
                    $query->bindValue(1, null, \PDO::PARAM_INT)
484
                ),
485
                $query->expr->eq(
486
                    $this->dbHandler->quoteColumn('action_type'),
487
                    $query->bindValue('eznode', null, \PDO::PARAM_STR)
488
                ),
489
                $query->expr->eq(
490
                    $this->dbHandler->quoteColumn('link'),
491
                    $query->bindValue($id, null, \PDO::PARAM_INT)
492
                )
493
            )
494
        );
495
496
        $statement = $query->prepare();
497
        $statement->execute();
498
499
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
500
501
        foreach ($rows as $row) {
502
            $this->historize($row['parent'], $row['text_md5'], $link);
503
        }
504
    }
505
506
    /**
507
     * Updates parent id of autogenerated entries.
508
     *
509
     * Update includes history entries.
510
     *
511
     * @param mixed $oldParentId
512
     * @param mixed $newParentId
513
     */
514 View Code Duplication
    public function reparent($oldParentId, $newParentId)
515
    {
516
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
517
        $query = $this->dbHandler->createUpdateQuery();
518
        $query->update(
519
            $this->dbHandler->quoteTable($this->table)
520
        )->set(
521
            $this->dbHandler->quoteColumn('parent'),
522
            $query->bindValue($newParentId, null, \PDO::PARAM_INT)
523
        )->where(
524
            $query->expr->lAnd(
525
                $query->expr->eq(
526
                    $this->dbHandler->quoteColumn('is_alias'),
527
                    $query->bindValue(0, null, \PDO::PARAM_INT)
528
                ),
529
                $query->expr->eq(
530
                    $this->dbHandler->quoteColumn('parent'),
531
                    $query->bindValue($oldParentId, null, \PDO::PARAM_INT)
532
                )
533
            )
534
        );
535
536
        $query->prepare()->execute();
537
    }
538
539
    /**
540
     * Updates single row data matched by composite primary key.
541
     *
542
     * Use optional parameter $languageMaskMatch to additionally limit the query match with languages.
543
     *
544
     * @param mixed $parentId
545
     * @param string $textMD5
546
     * @param array $values associative array with column names as keys and column values as values
547
     */
548 View Code Duplication
    public function updateRow($parentId, $textMD5, array $values)
549
    {
550
        /** @var $query \eZ\Publish\Core\Persistence\Database\UpdateQuery */
551
        $query = $this->dbHandler->createUpdateQuery();
552
        $query->update($this->dbHandler->quoteTable($this->table));
553
        $this->setQueryValues($query, $values);
554
        $query->where(
555
            $query->expr->lAnd(
556
                $query->expr->eq(
557
                    $this->dbHandler->quoteColumn('parent'),
558
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
559
                ),
560
                $query->expr->eq(
561
                    $this->dbHandler->quoteColumn('text_md5'),
562
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
563
                )
564
            )
565
        );
566
        $query->prepare()->execute();
567
    }
568
569
    /**
570
     * Inserts new row in urlalias_ml table.
571
     *
572
     * @param array $values
573
     *
574
     * @return mixed
575
     */
576
    public function insertRow(array $values)
577
    {
578
        // @todo remove after testing
579
        if (
580
            !isset($values['text']) ||
581
            !isset($values['text_md5']) ||
582
            !isset($values['action']) ||
583
            !isset($values['parent']) ||
584
            !isset($values['lang_mask'])) {
585
            throw new \Exception('value set is incomplete: ' . var_export($values, true) . ", can't execute insert");
586
        }
587
        if (!isset($values['id'])) {
588
            $values['id'] = $this->getNextId();
589
        }
590
        if (!isset($values['link'])) {
591
            $values['link'] = $values['id'];
592
        }
593
        if (!isset($values['is_original'])) {
594
            $values['is_original'] = ($values['id'] == $values['link'] ? 1 : 0);
595
        }
596
        if (!isset($values['is_alias'])) {
597
            $values['is_alias'] = 0;
598
        }
599
        if (!isset($values['alias_redirects'])) {
600
            $values['alias_redirects'] = 0;
601
        }
602
        if (!isset($values['action_type'])) {
603
            if (preg_match('#^(.+):.*#', $values['action'], $matches)) {
604
                $values['action_type'] = $matches[1];
605
            }
606
        }
607
        if ($values['is_alias']) {
608
            $values['is_original'] = 1;
609
        }
610
        if ($values['action'] === 'nop:') {
611
            $values['is_original'] = 0;
612
        }
613
614
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
615
        $query = $this->dbHandler->createInsertQuery();
616
        $query->insertInto($this->dbHandler->quoteTable($this->table));
617
        $this->setQueryValues($query, $values);
618
        $query->prepare()->execute();
619
620
        return $values['id'];
621
    }
622
623
    /**
624
     * Sets value for insert or update query.
625
     *
626
     * @param \eZ\Publish\Core\Persistence\Database\Query|\eZ\Publish\Core\Persistence\Database\InsertQuery|\eZ\Publish\Core\Persistence\Database\UpdateQuery $query
627
     * @param array $values
628
     *
629
     * @throws \Exception
630
     */
631
    protected function setQueryValues(Query $query, $values)
632
    {
633
        foreach ($values as $column => $value) {
634
            // @todo remove after testing
635
            if (!in_array($column, $this->columns['ezurlalias_ml'])) {
636
                throw new \Exception("unknown column '$column' for table 'ezurlalias_ml'");
637
            }
638
            switch ($column) {
639
                case 'text':
640
                case 'action':
641
                case 'text_md5':
642
                case 'action_type':
643
                    $pdoDataType = \PDO::PARAM_STR;
644
                    break;
645
                default:
646
                    $pdoDataType = \PDO::PARAM_INT;
647
            }
648
            $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...
649
                $this->dbHandler->quoteColumn($column),
650
                $query->bindValue($value, null, $pdoDataType)
651
            );
652
        }
653
    }
654
655
    /**
656
     * Returns next value for "id" column.
657
     *
658
     * @return mixed
659
     */
660 View Code Duplication
    public function getNextId()
661
    {
662
        $sequence = $this->dbHandler->getSequenceName('ezurlalias_ml_incr', 'id');
663
        /** @var $query \eZ\Publish\Core\Persistence\Database\InsertQuery */
664
        $query = $this->dbHandler->createInsertQuery();
665
        $query->insertInto(
666
            $this->dbHandler->quoteTable('ezurlalias_ml_incr')
667
        );
668
        // ezcDatabase does not abstract the "auto increment id"
669
        // INSERT INTO ezurlalias_ml_incr VALUES(DEFAULT) is not an option due
670
        // to this mysql bug: http://bugs.mysql.com/bug.php?id=42270
671
        // as a result we are forced to check which database is currently used
672
        // to generate the correct SQL query
673
        // see https://jira.ez.no/browse/EZP-20652
674
        if ($this->dbHandler->useSequences()) {
675
            $query->set(
676
                $this->dbHandler->quoteColumn('id'),
677
                "nextval('{$sequence}')"
678
            );
679
        } else {
680
            $query->set(
681
                $this->dbHandler->quoteColumn('id'),
682
                $query->bindValue(null, null, \PDO::PARAM_NULL)
683
            );
684
        }
685
        $query->prepare()->execute();
686
687
        return $this->dbHandler->lastInsertId($sequence);
688
    }
689
690
    /**
691
     * Loads single row data matched by composite primary key.
692
     *
693
     * @param mixed $parentId
694
     * @param string $textMD5
695
     *
696
     * @return array
697
     */
698 View Code Duplication
    public function loadRow($parentId, $textMD5)
699
    {
700
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
701
        $query = $this->dbHandler->createSelectQuery();
702
        $query->select('*')->from(
703
            $this->dbHandler->quoteTable($this->table)
704
        )->where(
705
            $query->expr->lAnd(
706
                $query->expr->eq(
707
                    $this->dbHandler->quoteColumn('parent'),
708
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
709
                ),
710
                $query->expr->eq(
711
                    $this->dbHandler->quoteColumn('text_md5'),
712
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
713
                )
714
            )
715
        );
716
717
        $statement = $query->prepare();
718
        $statement->execute();
719
720
        return $statement->fetch(\PDO::FETCH_ASSOC);
721
    }
722
723
    /**
724
     * Loads complete URL alias data by given array of path hashes.
725
     *
726
     * @param string[] $urlHashes URL string hashes
727
     *
728
     * @return array
729
     */
730
    public function loadUrlAliasData(array $urlHashes)
731
    {
732
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
733
        $query = $this->dbHandler->createSelectQuery();
734
735
        $count = count($urlHashes);
736
        foreach ($urlHashes as $level => $urlPartHash) {
737
            $tableName = $this->table . ($level === $count - 1 ? '' : $level);
738
739
            if ($level === $count - 1) {
740
                $query->select(
741
                    $this->dbHandler->quoteColumn('id', $tableName),
742
                    $this->dbHandler->quoteColumn('link', $tableName),
743
                    $this->dbHandler->quoteColumn('is_alias', $tableName),
744
                    $this->dbHandler->quoteColumn('alias_redirects', $tableName),
745
                    $this->dbHandler->quoteColumn('is_original', $tableName),
746
                    $this->dbHandler->quoteColumn('action', $tableName),
747
                    $this->dbHandler->quoteColumn('action_type', $tableName),
748
                    $this->dbHandler->quoteColumn('lang_mask', $tableName),
749
                    $this->dbHandler->quoteColumn('text', $tableName),
750
                    $this->dbHandler->quoteColumn('parent', $tableName),
751
                    $this->dbHandler->quoteColumn('text_md5', $tableName)
752
                )->from(
753
                    $this->dbHandler->quoteTable($this->table)
754
                );
755
            } else {
756
                $query->select(
757
                    $this->dbHandler->aliasedColumn($query, 'id', $tableName),
758
                    $this->dbHandler->aliasedColumn($query, 'link', $tableName),
759
                    $this->dbHandler->aliasedColumn($query, 'is_alias', $tableName),
760
                    $this->dbHandler->aliasedColumn($query, 'alias_redirects', $tableName),
761
                    $this->dbHandler->aliasedColumn($query, 'is_original', $tableName),
762
                    $this->dbHandler->aliasedColumn($query, 'action', $tableName),
763
                    $this->dbHandler->aliasedColumn($query, 'action_type', $tableName),
764
                    $this->dbHandler->aliasedColumn($query, 'lang_mask', $tableName),
765
                    $this->dbHandler->aliasedColumn($query, 'text', $tableName),
766
                    $this->dbHandler->aliasedColumn($query, 'parent', $tableName),
767
                    $this->dbHandler->aliasedColumn($query, 'text_md5', $tableName)
768
                )->from(
769
                    $query->alias($this->table, $tableName)
770
                );
771
            }
772
773
            $query->where(
774
                $query->expr->lAnd(
775
                    $query->expr->eq(
776
                        $this->dbHandler->quoteColumn('text_md5', $tableName),
777
                        $query->bindValue($urlPartHash, null, \PDO::PARAM_STR)
778
                    ),
779
                    $query->expr->eq(
780
                        $this->dbHandler->quoteColumn('parent', $tableName),
781
                        // root entry has parent column set to 0
782
                        isset($previousTableName) ? $this->dbHandler->quoteColumn('link', $previousTableName) : $query->bindValue(0, null, \PDO::PARAM_INT)
783
                    )
784
                )
785
            );
786
787
            $previousTableName = $tableName;
788
        }
789
        $query->limit(1);
790
791
        $statement = $query->prepare();
792
        $statement->execute();
793
794
        return $statement->fetch(\PDO::FETCH_ASSOC);
795
    }
796
797
    /**
798
     * Loads autogenerated entry id by given $action and optionally $parentId.
799
     *
800
     * @param string $action
801
     * @param mixed|null $parentId
802
     *
803
     * @return array
804
     */
805 View Code Duplication
    public function loadAutogeneratedEntry($action, $parentId = null)
806
    {
807
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
808
        $query = $this->dbHandler->createSelectQuery();
809
        $query->select(
810
            '*'
811
        )->from(
812
            $this->dbHandler->quoteTable($this->table)
813
        )->where(
814
            $query->expr->lAnd(
815
                $query->expr->eq(
816
                    $this->dbHandler->quoteColumn('action'),
817
                    $query->bindValue($action, null, \PDO::PARAM_STR)
818
                ),
819
                $query->expr->eq(
820
                    $this->dbHandler->quoteColumn('is_original'),
821
                    $query->bindValue(1, null, \PDO::PARAM_INT)
822
                ),
823
                $query->expr->eq(
824
                    $this->dbHandler->quoteColumn('is_alias'),
825
                    $query->bindValue(0, null, \PDO::PARAM_INT)
826
                )
827
            )
828
        );
829
830
        if (isset($parentId)) {
831
            $query->where(
832
                $query->expr->eq(
833
                    $this->dbHandler->quoteColumn('parent'),
834
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
835
                )
836
            );
837
        }
838
839
        $statement = $query->prepare();
840
        $statement->execute();
841
842
        return $statement->fetch(\PDO::FETCH_ASSOC);
843
    }
844
845
    /**
846
     * Loads all data for the path identified by given $id.
847
     *
848
     * @throws \RuntimeException
849
     *
850
     * @param mixed $id
851
     *
852
     * @return array
853
     */
854
    public function loadPathData($id)
855
    {
856
        $pathData = array();
857
858
        while ($id != 0) {
859
            /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
860
            $query = $this->dbHandler->createSelectQuery();
861
            $query->select(
862
                $this->dbHandler->quoteColumn('parent'),
863
                $this->dbHandler->quoteColumn('lang_mask'),
864
                $this->dbHandler->quoteColumn('text')
865
            )->from(
866
                $this->dbHandler->quoteTable($this->table)
867
            )->where(
868
                $query->expr->eq(
869
                    $this->dbHandler->quoteColumn('id'),
870
                    $query->bindValue($id, null, \PDO::PARAM_INT)
871
                )
872
            );
873
874
            $statement = $query->prepare();
875
            $statement->execute();
876
877
            $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
878
            if (empty($rows)) {
879
                // Normally this should never happen
880
                // @todo remove throw when tested
881
                $path = implode('/', $pathData);
882
                throw new \RuntimeException("Path ({$path}...) is broken, last id is '{$id}': " . __METHOD__);
883
            }
884
885
            $id = $rows[0]['parent'];
886
            array_unshift($pathData, $rows);
887
        }
888
889
        return $pathData;
890
    }
891
892
    /**
893
     * Loads path data identified by given ordered array of hierarchy data.
894
     *
895
     * The first entry in $hierarchyData corresponds to the top-most path element in the path, the second entry the
896
     * child of the first path element and so on.
897
     * This method is faster than self::getPath() since it can fetch all elements using only one query, but can be used
898
     * only for autogenerated paths.
899
     *
900
     * @param array $hierarchyData
901
     *
902
     * @return array
903
     */
904
    public function loadPathDataByHierarchy(array $hierarchyData)
905
    {
906
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
907
        $query = $this->dbHandler->createSelectQuery();
908
909
        $hierarchyConditions = array();
910
        foreach ($hierarchyData as $levelData) {
911
            $hierarchyConditions[] = $query->expr->lAnd(
912
                $query->expr->eq(
913
                    $this->dbHandler->quoteColumn('parent'),
914
                    $query->bindValue(
915
                        $levelData['parent'],
916
                        null,
917
                        \PDO::PARAM_INT
918
                    )
919
                ),
920
                $query->expr->eq(
921
                    $this->dbHandler->quoteColumn('action'),
922
                    $query->bindValue(
923
                        $levelData['action'],
924
                        null,
925
                        \PDO::PARAM_STR
926
                    )
927
                ),
928
                $query->expr->eq(
929
                    $this->dbHandler->quoteColumn('id'),
930
                    $query->bindValue(
931
                        $levelData['id'],
932
                        null,
933
                        \PDO::PARAM_INT
934
                    )
935
                )
936
            );
937
        }
938
939
        $query->select(
940
            $this->dbHandler->quoteColumn('action'),
941
            $this->dbHandler->quoteColumn('lang_mask'),
942
            $this->dbHandler->quoteColumn('text')
943
        )->from(
944
            $this->dbHandler->quoteTable($this->table)
945
        )->where(
946
            $query->expr->lOr($hierarchyConditions)
947
        );
948
949
        $statement = $query->prepare();
950
        $statement->execute();
951
952
        $rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
953
        $rowsMap = array();
954
        foreach ($rows as $row) {
955
            $rowsMap[$row['action']][] = $row;
956
        }
957
958
        if (count($rowsMap) !== count($hierarchyData)) {
959
            throw new \RuntimeException('The path is corrupted.');
960
        }
961
962
        $data = array();
963
        foreach ($hierarchyData as $levelData) {
964
            $data[] = $rowsMap[$levelData['action']];
965
        }
966
967
        return $data;
968
    }
969
970
    /**
971
     * Deletes single custom alias row matched by composite primary key.
972
     *
973
     * @param mixed $parentId
974
     * @param string $textMD5
975
     *
976
     * @return bool
977
     */
978 View Code Duplication
    public function removeCustomAlias($parentId, $textMD5)
979
    {
980
        /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
981
        $query = $this->dbHandler->createDeleteQuery();
982
        $query->deleteFrom(
983
            $this->dbHandler->quoteTable($this->table)
984
        )->where(
985
            $query->expr->lAnd(
986
                $query->expr->eq(
987
                    $this->dbHandler->quoteColumn('parent'),
988
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
989
                ),
990
                $query->expr->eq(
991
                    $this->dbHandler->quoteColumn('text_md5'),
992
                    $query->bindValue($textMD5, null, \PDO::PARAM_STR)
993
                ),
994
                $query->expr->eq(
995
                    $this->dbHandler->quoteColumn('is_alias'),
996
                    $query->bindValue(1, null, \PDO::PARAM_INT)
997
                )
998
            )
999
        );
1000
        $statement = $query->prepare();
1001
        $statement->execute();
1002
1003
        return $statement->rowCount() === 1 ?: false;
1004
    }
1005
1006
    /**
1007
     * Deletes all rows with given $action and optionally $id.
1008
     *
1009
     * If $id is set only autogenerated entries will be removed.
1010
     *
1011
     * @param mixed $action
1012
     * @param mixed|null $id
1013
     *
1014
     * @return bool
1015
     */
1016 View Code Duplication
    public function remove($action, $id = null)
1017
    {
1018
        /** @var $query \eZ\Publish\Core\Persistence\Database\DeleteQuery */
1019
        $query = $this->dbHandler->createDeleteQuery();
1020
        $query->deleteFrom(
1021
            $this->dbHandler->quoteTable($this->table)
1022
        )->where(
1023
            $query->expr->eq(
1024
                $this->dbHandler->quoteColumn('action'),
1025
                $query->bindValue($action, null, \PDO::PARAM_STR)
1026
            )
1027
        );
1028
1029
        if ($id !== null) {
1030
            $query->where(
1031
                $query->expr->lAnd(
1032
                    $query->expr->eq(
1033
                        $this->dbHandler->quoteColumn('is_alias'),
1034
                        $query->bindValue(0, null, \PDO::PARAM_INT)
1035
                    ),
1036
                    $query->expr->eq(
1037
                        $this->dbHandler->quoteColumn('id'),
1038
                        $query->bindValue($id, null, \PDO::PARAM_INT)
1039
                    )
1040
                )
1041
            );
1042
        }
1043
1044
        $query->prepare()->execute();
1045
    }
1046
1047
    /**
1048
     * Loads all autogenerated entries with given $parentId with optionally included history entries.
1049
     *
1050
     * @param mixed $parentId
1051
     * @param bool $includeHistory
1052
     *
1053
     * @return array
1054
     */
1055 View Code Duplication
    public function loadAutogeneratedEntries($parentId, $includeHistory = false)
1056
    {
1057
        /** @var $query \eZ\Publish\Core\Persistence\Database\SelectQuery */
1058
        $query = $this->dbHandler->createSelectQuery();
1059
        $query->select(
1060
            '*'
1061
        )->from(
1062
            $this->dbHandler->quoteTable($this->table)
1063
        )->where(
1064
            $query->expr->lAnd(
1065
                $query->expr->eq(
1066
                    $this->dbHandler->quoteColumn('parent'),
1067
                    $query->bindValue($parentId, null, \PDO::PARAM_INT)
1068
                ),
1069
                $query->expr->eq(
1070
                    $this->dbHandler->quoteColumn('action_type'),
1071
                    $query->bindValue('eznode', null, \PDO::PARAM_STR)
1072
                ),
1073
                $query->expr->eq(
1074
                    $this->dbHandler->quoteColumn('is_alias'),
1075
                    $query->bindValue(0, null, \PDO::PARAM_INT)
1076
                )
1077
            )
1078
        );
1079
1080
        if (!$includeHistory) {
1081
            $query->where(
1082
                $query->expr->eq(
1083
                    $this->dbHandler->quoteColumn('is_original'),
1084
                    $query->bindValue(1, null, \PDO::PARAM_INT)
1085
                )
1086
            );
1087
        }
1088
1089
        $statement = $query->prepare();
1090
        $statement->execute();
1091
1092
        return $statement->fetchAll(\PDO::FETCH_ASSOC);
1093
    }
1094
1095
    public function getLocationContentMainLanguageId($locationId)
1096
    {
1097
        $dbHandler = $this->dbHandler;
1098
        $query = $dbHandler->createSelectQuery();
1099
        $query
1100
            ->select($dbHandler->quoteColumn('initial_language_id', 'ezcontentobject'))
1101
            ->from($dbHandler->quoteTable('ezcontentobject'))
1102
            ->innerJoin(
1103
                $dbHandler->quoteTable('ezcontentobject_tree'),
1104
                $query->expr->lAnd(
1105
                    $query->expr->eq(
1106
                        $dbHandler->quoteColumn('contentobject_id', 'ezcontentobject_tree'),
1107
                        $dbHandler->quoteColumn('id', 'ezcontentobject')
1108
                    ),
1109
                    $query->expr->eq(
1110
                        $dbHandler->quoteColumn('node_id', 'ezcontentobject_tree'),
1111
                        $dbHandler->quoteColumn('main_node_id', 'ezcontentobject_tree')
1112
                    ),
1113
                    $query->expr->eq(
1114
                        $dbHandler->quoteColumn('node_id', 'ezcontentobject_tree'),
1115
                        $query->bindValue($locationId, null, \PDO::PARAM_INT)
1116
                    )
1117
                )
1118
            );
1119
1120
        $statement = $query->prepare();
1121
        $statement->execute();
1122
        $languageId = $statement->fetchColumn();
1123
1124
        if ($languageId === false) {
1125
            throw new RuntimeException("Could not find Content for Location #{$locationId}");
1126
        }
1127
1128
        return $languageId;
1129
    }
1130
1131
    /**
1132
     * Removes languageId of removed translation from lang_mask and deletes single language rows for multiple Locations.
1133
     *
1134
     * Note: URL aliases are not historized as translation removal from all Versions is permanent w/o preserving history.
1135
     *
1136
     * @param int $languageId Language Id to be removed
1137
     * @param string[] $actions actions for which to perform the update
1138
     */
1139
    public function bulkRemoveTranslation($languageId, $actions)
1140
    {
1141
        $connection = $this->dbHandler->getConnection();
1142
        /** @var \Doctrine\DBAL\Connection $connection */
1143
        $query = $connection->createQueryBuilder();
1144
        $query
1145
            ->update($connection->quoteIdentifier($this->table))
1146
            // parameter for bitwise operation has to be placed verbatim (w/o binding) for this to work cross-DBMS
1147
            ->set('lang_mask', 'lang_mask & ~ ' . $languageId)
1148
            ->where('action IN (:actions)')
1149
            ->setParameter(':actions', $actions, Connection::PARAM_STR_ARRAY)
1150
        ;
1151
        $query->execute();
1152
1153
        // cleanup: delete single language rows (including alwaysAvailable)
1154
        $query = $connection->createQueryBuilder();
1155
        $query
1156
            ->delete($this->table)
1157
            ->where('action IN (:actions)')
1158
            ->andWhere('lang_mask IN (0, 1)')
1159
            ->setParameter(':actions', $actions, Connection::PARAM_STR_ARRAY)
1160
        ;
1161
        $query->execute();
1162
    }
1163
}
1164