Completed
Push — test-EZP-26707-issearchable-fu... ( 0d4c60 )
by
unknown
30:03 queued 14:08
created

getLocationContentMainLanguageId()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 35
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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