Completed
Pull Request — 2.0 (#62)
by
unknown
04:42
created

ReadQueries::findOneWhere()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 10
rs 9.4285
cc 3
eloc 5
nc 4
nop 3
1
<?php
2
/*
3
 * This file is part of the PommProject/ModelManager package.
4
 *
5
 * (c) 2014 - 2015 Grégoire HUBERT <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace PommProject\ModelManager\Model\ModelTrait;
11
12
use PommProject\Foundation\Pager;
13
use PommProject\Foundation\Where;
14
use PommProject\ModelManager\Exception\ModelException;
15
use PommProject\ModelManager\Model\CollectionIterator;
16
use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface;
17
use PommProject\ModelManager\Model\Projection;
18
19
/**
20
 * ReadQueries
21
 *
22
 * Basic read queries for model instances.
23
 *
24
 * @package     ModelManager
25
 * @copyright   2014 - 2015 Grégoire HUBERT
26
 * @author      Grégoire HUBERT
27
 * @license     X11 {@link http://opensource.org/licenses/mit-license.php}
28
 */
29
trait ReadQueries
30
{
31
    use BaseTrait;
32
33
    /**
34
     * escapeIdentifier
35
     *
36
     * @see Model
37
     */
38
    abstract protected function escapeIdentifier($string);
39
40
    /**
41
     * getStructure
42
     *
43
     * @see Model
44
     */
45
    abstract public function getStructure();
46
47
    /**
48
     * findAll
49
     *
50
     * Return all elements from a relation. If a suffix is given, it is append
51
     * to the query. This is mainly useful for "order by" statements.
52
     * NOTE: suffix is inserted as is with NO ESCAPING. DO NOT use it to place
53
     * "where" condition nor any untrusted params.
54
     *
55
     * @access public
56
     * @param  string             $suffix
57
     * @return CollectionIterator
58
     */
59
    public function findAll($suffix = null)
60
    {
61
        $sql = strtr(
62
            "select :fields from :table :suffix",
63
            [
64
                ':fields' => $this->createProjection()->formatFieldsWithFieldAlias(),
65
                ':table'  => $this->getStructure()->getRelation(),
66
                ':suffix' => $suffix,
67
            ]
68
        );
69
70
        return $this->query($sql);
71
    }
72
73
    /**
74
     * findWhere
75
     *
76
     * Perform a simple select on a given condition
77
     * NOTE: suffix is inserted as is with NO ESCAPING. DO NOT use it to place
78
     * "where" condition nor any untrusted params.
79
     *
80
     * @access public
81
     * @param  mixed              $where
82
     * @param  array              $values
83
     * @param  string             $suffix order by, limit, etc.
84
     * @return CollectionIterator
85
     */
86
    public function findWhere($where, array $values = [], $suffix = '')
87
    {
88
        if (!$where instanceof Where) {
89
            $where = new Where($where, $values);
90
        }
91
92
        return $this->query($this->getFindWhereSql($where, $this->createProjection(), $suffix), $where->getValues());
93
    }
94
95
    /**
96
     * findOneWhere
97
     *
98
     * Perform a simple select on a given condition that is expected to return one result
99
     * NOTE: suffix is inserted as is with NO ESCAPING. DO NOT use it to place
100
     * "where" condition nor any untrusted params.
101
     *
102
     * @access public
103
     * @param  mixed              $where
104
     * @param  array              $values
105
     * @param  string             $suffix order by, limit, etc.
106
     * @return CollectionIterator
107
     */
108
    public function findOneWhere($where, array $values = [], $suffix = '')
109
    {
110
        if (!$where instanceof Where) {
111
            $where = new Where($where, $values);
112
        }
113
114
        $iterator = $this->query($this->getFindWhereSql($where, $this->createProjection(), $suffix), $where->getValues());
115
116
        return $iterator->isEmpty() ? null : $iterator->current();
117
    }
118
119
    /**
120
     * findByPK
121
     *
122
     * Return an entity upon its primary key. If no entities are found, null is
123
     * returned.
124
     *
125
     * @access public
126
     * @param  array          $primary_key
127
     * @return FlexibleEntityInterface
128
     */
129
    public function findByPK(array $primary_key)
130
    {
131
        $where = $this
132
            ->checkPrimaryKey($primary_key)
133
            ->getWhereFrom($primary_key)
134
            ;
135
136
        //$iterator = $this->findWhere($where);
137
138
        //return $iterator->isEmpty() ? null : $iterator->current();
139
        return $this->findOneWhere($where);
140
    }
141
142
    /**
143
     * countWhere
144
     *
145
     * Return the number of records matching a condition.
146
     *
147
     * @access public
148
     * @param  string|Where $where
149
     * @param  array        $values
150
     * @return int
151
     */
152
    public function countWhere($where, array $values = [])
153
    {
154
        $sql = sprintf(
155
            "select count(*) as result from %s where :condition",
156
            $this->getStructure()->getRelation()
157
        );
158
159
        return $this->fetchSingleValue($sql, $where, $values);
160
    }
161
162
    /**
163
     * existWhere
164
     *
165
     * Check if rows matching the given condition do exist or not.
166
     *
167
     * @access public
168
     * @param  mixed $where
169
     * @param  array $values
170
     * @return bool
171
     */
172
    public function existWhere($where, array $values = [])
173
    {
174
        $sql = sprintf(
175
            "select exists (select true from %s where :condition) as result",
176
            $this->getStructure()->getRelation()
177
        );
178
179
        return $this->fetchSingleValue($sql, $where, $values);
180
    }
181
182
    /**
183
     * fetchSingleValue
184
     *
185
     * Fetch a single value named « result » from a query.
186
     * The query must be formatted with ":condition" as WHERE condition
187
     * placeholder. If the $where argument is a string, it is turned into a
188
     * Where instance.
189
     *
190
     * @access protected
191
     * @param  string       $sql
192
     * @param  mixed        $where
193
     * @param  array        $values
194
     * @return mixed
195
     */
196
    protected function fetchSingleValue($sql, $where, array $values)
197
    {
198
        if (!$where instanceof Where) {
199
            $where = new Where($where, $values);
200
        }
201
202
        $sql = str_replace(":condition", (string) $where, $sql);
203
204
        return $this
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface PommProject\Foundation\Client\ClientInterface as the method query() does only exist in the following implementations of said interface: PommProject\Foundation\P...ry\PreparedQueryManager, PommProject\Foundation\Q...ager\QueryManagerClient, PommProject\Foundation\Q...ager\SimpleQueryManager, PommProject\ModelManager\Model\Model.

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...
205
            ->getSession()
206
            ->getClientUsingPooler('query_manager', '\PommProject\Foundation\PreparedQuery\PreparedQueryManager')
207
            ->query($sql, $where->getValues())
208
            ->current()['result']
209
            ;
210
    }
211
212
    /**
213
     * paginateFindWhere
214
     *
215
     * Paginate a query.
216
     *
217
     * @access public
218
     * @param  Where    $where
219
     * @param  int      $item_per_page
220
     * @param  int      $page
221
     * @param  string   $suffix
222
     * @return Pager
223
     */
224
    public function paginateFindWhere(Where $where, $item_per_page, $page = 1, $suffix = '')
225
    {
226
        $projection = $this->createProjection();
227
228
        return $this->paginateQuery(
229
            $this->getFindWhereSql($where, $projection, $suffix),
230
            $where->getValues(),
231
            $this->countWhere($where),
232
            $item_per_page,
233
            $page,
234
            $projection
235
        );
236
    }
237
238
    /**
239
     * paginateQuery
240
     *
241
     * Paginate a SQL query.
242
     * It is important to note it adds limit and offset at the end of the given
243
     * query.
244
     *
245
     * @access  protected
246
     * @param   string       $sql
247
     * @param   array        $values parameters
248
     * @param   int          $count
249
     * @param   int          $item_per_page
250
     * @param   int          $page
251
     * @param   Projection   $projection
252
     * @throws  \InvalidArgumentException if pager args are invalid.
253
     * @return  Pager
254
     */
255
    protected function paginateQuery($sql, array $values, $count, $item_per_page, $page = 1, Projection $projection = null)
256
    {
257
        if ($page < 1) {
258
            throw new \InvalidArgumentException(
259
                sprintf("Page cannot be < 1. (%d given)", $page)
260
            );
261
        }
262
263
        if ($item_per_page <= 0) {
264
            throw new \InvalidArgumentException(
265
                sprintf("'item_per_page' must be strictly positive (%d given).", $item_per_page)
266
            );
267
        }
268
269
        $offset = $item_per_page * ($page - 1);
270
        $limit  = $item_per_page;
271
272
        return new Pager(
273
            $this->query(
274
                sprintf("%s offset %d limit %d", $sql, $offset, $limit),
275
                $values,
276
                $projection
277
            ),
278
            $count,
279
            $item_per_page,
280
            $page
281
        );
282
    }
283
284
    /**
285
     * getFindWhereSql
286
     *
287
     * This is the standard SQL query to fetch instances from the current
288
     * relation.
289
     *
290
     * @access protected
291
     * @param  Where        $where
292
     * @param  Projection   $projection
293
     * @param  string       $suffix
294
     * @return string
295
     */
296
    protected function getFindWhereSql(Where $where, Projection $projection, $suffix = '')
297
    {
298
        return strtr(
299
            'select :projection from :relation where :condition :suffix',
300
            [
301
                ':projection' => $projection->formatFieldsWithFieldAlias(),
302
                ':relation'   => $this->getStructure()->getRelation(),
303
                ':condition'  => (string) $where,
304
                ':suffix'     => $suffix,
305
            ]
306
        );
307
    }
308
309
    /**
310
     * hasPrimaryKey
311
     *
312
     * Check if model has a primary key
313
     *
314
     * @access protected
315
     * @return bool
316
     */
317
    protected function hasPrimaryKey()
318
    {
319
        $primaryKeys = $this->getStructure()->getPrimaryKey();
320
321
        return !empty($primaryKeys);
322
    }
323
324
    /**
325
     * checkPrimaryKey
326
     *
327
     * Check if the given values fully describe a primary key. Throw a
328
     * ModelException if not.
329
     *
330
     * @access private
331
     * @param  array $values
332
     * @throws ModelException
333
     * @return $this
334
     */
335
    protected function checkPrimaryKey(array $values)
336
    {
337
        if (!$this->hasPrimaryKey()) {
338
            throw new ModelException(
339
                sprintf(
340
                    "Attached structure '%s' has no primary key.",
341
                    get_class($this->getStructure())
342
                )
343
            );
344
        }
345
346
        foreach ($this->getStructure()->getPrimaryKey() as $key) {
347
            if (!isset($values[$key])) {
348
                throw new ModelException(
349
                    sprintf(
350
                        "Key '%s' is missing to fully describes the primary key {%s}.",
351
                        $key,
352
                        join(', ', $this->getStructure()->getPrimaryKey())
353
                    )
354
                );
355
            }
356
        }
357
358
        return $this;
359
    }
360
361
    /**
362
     * getWhereFrom
363
     *
364
     * Build a condition on given values.
365
     *
366
     * @access protected
367
     * @param  array $values
368
     * @return Where
369
     */
370
    protected function getWhereFrom(array $values)
371
    {
372
        $where = new Where();
373
374
        foreach ($values as $field => $value) {
375
            $where->andWhere(
376
                sprintf(
377
                    "%s = $*::%s",
378
                    $this->escapeIdentifier($field),
379
                    $this->getStructure()->getTypeFor($field)
380
                ),
381
                [$value]
382
            );
383
        }
384
385
        return $where;
386
    }
387
}
388