Completed
Push — ezp-31420-merge-up ( ec14fb...141a64 )
by
unknown
40:13 queued 27:42
created

DoctrineDatabase::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 11
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 4
dl 11
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
namespace eZ\Publish\Core\Search\Legacy\Content\Gateway;
8
9
use eZ\Publish\Core\Search\Legacy\Content\Common\Gateway\CriteriaConverter;
10
use eZ\Publish\Core\Search\Legacy\Content\Common\Gateway\SortClauseConverter;
11
use eZ\Publish\Core\Search\Legacy\Content\Gateway;
12
use eZ\Publish\Core\Persistence\Database\DatabaseHandler;
13
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
14
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
15
use eZ\Publish\API\Repository\Values\Content\VersionInfo;
16
use eZ\Publish\Core\Persistence\Database\SelectQuery;
17
use eZ\Publish\SPI\Persistence\Content\Language\Handler as LanguageHandler;
18
use PDO;
19
20
/**
21
 * Content locator gateway implementation using the Doctrine database.
22
 */
23
class DoctrineDatabase extends Gateway
24
{
25
    /**
26
     * Database handler.
27
     *
28
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
29
     */
30
    protected $handler;
31
32
    /**
33
     * Criteria converter.
34
     *
35
     * @var \eZ\Publish\Core\Search\Legacy\Content\Common\Gateway\CriteriaConverter
36
     */
37
    protected $criteriaConverter;
38
39
    /**
40
     * Sort clause converter.
41
     *
42
     * @var \eZ\Publish\Core\Search\Legacy\Content\Common\Gateway\SortClauseConverter
43
     */
44
    protected $sortClauseConverter;
45
46
    /**
47
     * Language handler.
48
     *
49
     * @var \eZ\Publish\SPI\Persistence\Content\Language\Handler
50
     */
51
    protected $languageHandler;
52
53
    /**
54
     * Construct from handler handler.
55
     *
56
     * @param \eZ\Publish\Core\Persistence\Database\DatabaseHandler $handler
57
     * @param \eZ\Publish\Core\Search\Legacy\Content\Common\Gateway\CriteriaConverter $criteriaConverter
58
     * @param \eZ\Publish\Core\Search\Legacy\Content\Common\Gateway\SortClauseConverter $sortClauseConverter
59
     * @param \eZ\Publish\SPI\Persistence\Content\Language\Handler $languageHandler
60
     */
61 View Code Duplication
    public function __construct(
62
        DatabaseHandler $handler,
63
        CriteriaConverter $criteriaConverter,
64
        SortClauseConverter $sortClauseConverter,
65
        LanguageHandler $languageHandler
66
    ) {
67
        $this->handler = $handler;
68
        $this->criteriaConverter = $criteriaConverter;
69
        $this->sortClauseConverter = $sortClauseConverter;
70
        $this->languageHandler = $languageHandler;
71
    }
72
73
    /**
74
     * Returns a list of object satisfying the $filter.
75
     *
76
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if Criterion is not applicable to its target
77
     *
78
     * @param Criterion $criterion
79
     * @param int $offset
80
     * @param int $limit
81
     * @param \eZ\Publish\API\Repository\Values\Content\Query\SortClause[] $sort
82
     * @param array $languageFilter
83
     * @param bool $doCount
84
     *
85
     * @return mixed[][]
86
     */
87
    public function find(
88
        Criterion $criterion,
89
        $offset,
90
        $limit,
91
        array $sort = null,
92
        array $languageFilter = [],
93
        $doCount = true
94
    ) {
95
        $count = $doCount ? $this->getResultCount($criterion, $languageFilter) : null;
96
97
        if (!$doCount && $limit === 0) {
98
            throw new \RuntimeException('Invalid query. Cannot disable count and request 0 items at the same time.');
99
        }
100
101 View Code Duplication
        if ($limit === 0 || ($count !== null && $count <= $offset)) {
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...
102
            return ['count' => $count, 'rows' => []];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('count' => ...nt, 'rows' => array()); (array<string,integer|null|array>) is incompatible with the return type declared by the abstract method eZ\Publish\Core\Search\L...y\Content\Gateway::find of type array[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
103
        }
104
105
        $contentInfoList = $this->getContentInfoList($criterion, $sort, $offset, $limit, $languageFilter);
0 ignored issues
show
Bug introduced by
It seems like $sort defined by parameter $sort on line 91 can also be of type null; however, eZ\Publish\Core\Search\L...e::getContentInfoList() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
106
107
        return [
0 ignored issues
show
Best Practice introduced by
The expression return array('count' => ...' => $contentInfoList); seems to be an array, but some of its elements' types (null|integer) are incompatible with the return type declared by the abstract method eZ\Publish\Core\Search\L...y\Content\Gateway::find of type array[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
108
            'count' => $count,
109
            'rows' => $contentInfoList,
110
        ];
111
    }
112
113
    /**
114
     * Generates a language mask from the given $languageSettings.
115
     *
116
     * @param array $languageSettings
117
     *
118
     * @return int
119
     */
120
    protected function getLanguageMask(array $languageSettings)
121
    {
122
        $mask = 0;
123
        if ($languageSettings['useAlwaysAvailable']) {
124
            $mask |= 1;
125
        }
126
127
        foreach ($languageSettings['languages'] as $languageCode) {
128
            $mask |= $this->languageHandler->loadByLanguageCode($languageCode)->id;
129
        }
130
131
        return $mask;
132
    }
133
134
    /**
135
     * Get query condition.
136
     *
137
     * @param Criterion $filter
138
     * @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query
139
     * @param array $languageFilter
140
     *
141
     * @return string
142
     */
143
    protected function getQueryCondition(
144
        Criterion $filter,
145
        SelectQuery $query,
146
        array $languageFilter
147
    ) {
148
        $condition = $query->expr->lAnd(
149
            $this->criteriaConverter->convertCriteria($query, $filter, $languageFilter),
150
            $query->expr->eq(
151
                'ezcontentobject.status',
152
                ContentInfo::STATUS_PUBLISHED
153
            ),
154
            $query->expr->eq(
155
                'ezcontentobject_version.status',
156
                VersionInfo::STATUS_PUBLISHED
157
            )
158
        );
159
160
        // If not main-languages query
161 View Code Duplication
        if (!empty($languageFilter['languages'])) {
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...
162
            $condition = $query->expr->lAnd(
163
                $condition,
164
                $query->expr->gt(
165
                    $query->expr->bitAnd(
166
                        $this->handler->quoteColumn('language_mask', 'ezcontentobject'),
167
                        $query->bindValue(
168
                            $this->getLanguageMask($languageFilter),
169
                            null,
170
                            PDO::PARAM_INT
171
                        )
172
                    ),
173
                    $query->bindValue(0, null, PDO::PARAM_INT)
174
                )
175
            );
176
        }
177
178
        return $condition;
179
    }
180
181
    /**
182
     * Get result count.
183
     *
184
     * @param \eZ\Publish\API\Repository\Values\Content\Query\Criterion $filter
185
     * @param array $languageFilter
186
     *
187
     * @return int
188
     */
189
    protected function getResultCount(Criterion $filter, array $languageFilter)
190
    {
191
        $query = $this->handler->createSelectQuery();
192
193
        $columnName = $this->handler->quoteColumn('id', 'ezcontentobject');
194
        $query
195
            ->select("COUNT( DISTINCT $columnName )")
196
            ->from($this->handler->quoteTable('ezcontentobject'))
197
            ->innerJoin(
198
                'ezcontentobject_version',
199
                'ezcontentobject.id',
200
                'ezcontentobject_version.contentobject_id'
201
            );
202
203
        $query->where(
204
            $this->getQueryCondition($filter, $query, $languageFilter)
205
        );
206
207
        $statement = $query->prepare();
208
        $statement->execute();
209
210
        return (int)$statement->fetchColumn();
211
    }
212
213
    /**
214
     * Get sorted arrays of content IDs, which should be returned.
215
     *
216
     * @param Criterion $filter
217
     * @param array $sort
218
     * @param mixed $offset
219
     * @param mixed $limit
220
     * @param array $languageFilter
221
     *
222
     * @return int[]
223
     */
224
    protected function getContentInfoList(
225
        Criterion $filter,
226
        $sort,
227
        $offset,
228
        $limit,
229
        array $languageFilter
230
    ) {
231
        $query = $this->handler->createSelectQuery();
232
        $query->selectDistinct(
233
            'ezcontentobject.*',
234
            $this->handler->aliasedColumn($query, 'main_node_id', 'main_tree')
235
        );
236
237
        if ($sort !== null) {
238
            $this->sortClauseConverter->applySelect($query, $sort);
239
        }
240
241
        $query->from(
242
            $this->handler->quoteTable('ezcontentobject')
243
        )->innerJoin(
244
            'ezcontentobject_version',
245
            'ezcontentobject.id',
246
            'ezcontentobject_version.contentobject_id'
247
        )->leftJoin(
248
            $this->handler->alias(
249
                $this->handler->quoteTable('ezcontentobject_tree'),
250
                $this->handler->quoteIdentifier('main_tree')
251
            ),
252
            $query->expr->lAnd(
253
                $query->expr->eq(
254
                    $this->handler->quoteColumn('contentobject_id', 'main_tree'),
255
                    $this->handler->quoteColumn('id', 'ezcontentobject')
256
                ),
257
                $query->expr->eq(
258
                    $this->handler->quoteColumn('main_node_id', 'main_tree'),
259
                    $this->handler->quoteColumn('node_id', 'main_tree')
260
                )
261
            )
262
        );
263
264
        if ($sort !== null) {
265
            $this->sortClauseConverter->applyJoin($query, $sort, $languageFilter);
266
        }
267
268
        $query->where(
269
            $this->getQueryCondition($filter, $query, $languageFilter)
270
        );
271
272
        if ($sort !== null) {
273
            $this->sortClauseConverter->applyOrderBy($query);
274
        }
275
276
        $query->limit($limit, $offset);
277
278
        $statement = $query->prepare();
279
        $statement->execute();
280
281
        return $statement->fetchAll(PDO::FETCH_ASSOC);
282
    }
283
}
284