Completed
Push — master ( 7a714b...727ee1 )
by Sébastien
17:57 queued 15:36
created

SqlSource::select()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
crap 6
1
<?php
2
3
/*
4
 * soluble-flexstore library
5
 *
6
 * @author    Vanvelthem Sébastien
7
 * @link      https://github.com/belgattitude/soluble-flexstore
8
 * @copyright Copyright (c) 2016-2017 Vanvelthem Sébastien
9
 * @license   MIT License https://github.com/belgattitude/soluble-flexstore/blob/master/LICENSE.md
10
 *
11
 */
12
13
namespace Soluble\FlexStore\Source\Zend;
14
15
use Soluble\FlexStore\Source\AbstractSource;
16
use Soluble\FlexStore\Source\QueryableSourceInterface;
17
use Soluble\FlexStore\ResultSet\ResultSet;
18
use Soluble\FlexStore\Exception;
19
use Soluble\FlexStore\Options;
20
use Zend\Db\Adapter\Adapter;
21
use Zend\Db\Sql\Sql;
22
use Zend\Db\Sql\Select;
23
use Zend\Db\Sql\Expression;
24
use ArrayObject;
25
use Soluble\FlexStore\Column\ColumnModel;
26
use Soluble\FlexStore\Column\Type\MetadataMapper;
27
use Soluble\Metadata\Reader as MetadataReader;
28
29
class SqlSource extends AbstractSource implements QueryableSourceInterface
30
{
31
    /**
32
     * @var Sql
33
     */
34
    protected $sql;
35
36
    /**
37
     * @var Select
38
     */
39
    protected $select;
40
41
    /**
42
     * @var Adapter
43
     */
44
    protected $adapter;
45
46
    /**
47
     * Initial params received in the constructor.
48
     *
49
     * @var ArrayObject
50
     */
51
    protected $params;
52
53
    /**
54
     * @var string
55
     */
56
    protected $query_string;
57
58
    /**
59
     * @var \Zend\Db\Adapter\Driver\Mysqli\Statement
60
     */
61
    protected static $cache_stmt_prototype;
62
63
    /**
64
     * @var \Zend\Db\Adapter\Driver\ResultInterface
65
     */
66
    protected static $cache_result_prototype;
67
68
    /**
69
     * @var ColumnModel
70
     */
71
    protected $columnModel;
72
73
    /**
74
     * @param Adapter $adapter
75
     * @param Select  $select
76
     */
77
    public function __construct(Adapter $adapter, Select $select = null)
78
    {
79
        $this->adapter = $adapter;
80
        $this->sql = new Sql($this->adapter);
81
        if ($select !== null) {
82
            $this->setSelect($select);
83
        }
84
    }
85
86
    /**
87
     * @param Select $select
88
     *
89
     * @return SqlSource
90
     */
91
    public function setSelect(Select $select)
92
    {
93
        $this->select = $select;
94
95
        return $this;
96
    }
97
98
    /**
99
     * @return Select
100
     */
101
    public function getSelect()
102
    {
103
        return $this->select();
104
    }
105
106
    /**
107
     * @return Select
108
     */
109
    public function select()
110
    {
111
        if ($this->select === null) {
112
            $this->select = $this->sql->select();
113
        }
114
115
        return $this->select;
116
    }
117
118
    /**
119
     * @param Select  $select
120
     * @param Options $options
121
     *
122
     * @return Select
123
     */
124
    protected function assignOptions(Select $select, Options $options)
125
    {
126
        if ($options->hasLimit()) {
127
            $select->limit($options->getLimit());
128
            if ($options->hasOffset()) {
129
                $select->offset($options->getOffset());
130
            }
131
132
            if ($options->getLimit() > 0) {
133
                /**
134
                 * For mysql queries, to allow counting rows we must prepend
135
                 * SQL_CALC_FOUND_ROWS to the select quantifiers.
136
                 */
137
                $calc_found_rows = 'SQL_CALC_FOUND_ROWS';
138
                $quant_state = $select->getRawState($select::QUANTIFIER);
139
                if ($quant_state !== null) {
140
                    if ($quant_state instanceof Expression) {
1 ignored issue
show
Bug introduced by
The class Zend\Db\Sql\Expression does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
141
                        $quant_state->setExpression($calc_found_rows . ' ' . $quant_state->getExpression());
142
                    } elseif (is_string($quant_state)) {
143
                        $quant_state = $calc_found_rows . ' ' . $quant_state;
144
                    }
145
                    $select->quantifier($quant_state);
146
                } else {
147
                    $select->quantifier(new Expression($calc_found_rows));
148
                }
149
            }
150
        }
151
152
        return $select;
153
    }
154
155
    /**
156
     * @param Options $options
157
     *
158
     * @throws Exception\EmptyQueryException
159
     * @throws Exception\ErrorException
160
     *
161
     * @return ResultSet
162
     */
163
    public function getData(Options $options = null)
164
    {
165
        if ($options === null) {
166
            $options = $this->getOptions();
167
        }
168
169
        $select = $this->assignOptions(clone $this->getSelect(), $options);
170
171
        $sql = new Sql($this->adapter);
172
173
        $sql_string = (string) $sql->buildSqlString($select);
174
        //echo $this->select->getSqlString($this->adapter->getPlatform());
175
        //echo "----" . var_dump($sql_string) . "----\n";
176
        // In ZF 2.3.0 an empty query will return SELECT .*
177
        // In ZF 2.4.0 and empty query will return SELECT *
178
        if (in_array($sql_string, ['', 'SELECT .*', 'SELECT *'], true)) {
179
            throw new Exception\EmptyQueryException(__METHOD__ . ': Cannot return data of an empty query');
180
        }
181
        $this->query_string = $sql_string;
182
183
        try {
184
            $results = $this->adapter->query($sql_string, Adapter::QUERY_MODE_EXECUTE);
185
            //$stmt = $sql->prepareStatementForSqlObject( $select );
186
            //$results = $stmt->execute();
187
            //var_dump(get_class($results));
188
189
            $r = new ResultSet($results);
190
            $r->setSource($this);
191
            $r->setHydrationOptions($options->getHydrationOptions());
192
193
            if ($options->hasLimit() && $options->getLimit() > 0) {
194
                //$row = $this->adapter->query('select FOUND_ROWS() as total_count')->execute()->current();
195
                $row = $this->adapter->createStatement('select FOUND_ROWS() as total_count')->execute()->current();
196
                $r->setTotalRows($row['total_count']);
197
            } else {
198
                $r->setTotalRows($r->count());
199
            }
200
        } catch (\Exception $e) {
201
            throw new Exception\ErrorException(__METHOD__ . ': Cannot retrieve data (' . $e->getMessage() . ')');
202
        }
203
204
        return $r;
205
    }
206
207
    public function loadDefaultColumnModel()
208
    {
209
        $sql = new Sql($this->adapter);
210
        $select = clone $this->select;
211
        $select->limit(0);
212
        $sql_string = $sql->getSqlStringForSqlObject($select);
213
        $metadata_columns = $this->getMetadataReader()->getColumnsMetadata($sql_string);
214
        $this->setColumnModel(MetadataMapper::getColumnModelFromMetadata($metadata_columns));
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     *
220
     * @throws Exception\UnsupportedFeatureException
221
     */
222
    public function getMetadataReader()
223
    {
224
        if ($this->metadataReader === null) {
225
            $this->setMetadataReader($this->getDefaultMetadataReader());
226
        }
227
228
        return $this->metadataReader;
229
    }
230
231
    /**
232
     * @throws Exception\UnsupportedFeatureException
233
     */
234
    protected function getDefaultMetadataReader()
235
    {
236
        $conn = $this->adapter->getDriver()->getConnection()->getResource();
237
        $class = strtolower(get_class($conn));
238
        switch ($class) {
239
            case 'pdo':
240
                return new MetadataReader\PdoMysqlMetadataReader($conn);
241
            case 'mysqli':
242
                return new MetadataReader\MysqliMetadataReader($conn);
243
            default:
244
                throw new Exception\UnsupportedFeatureException(__METHOD__ . " Does not support default metadata reader for driver '$class'");
245
        }
246
    }
247
248
    /**
249
     * Return the query string that was executed.
250
     *
251
     * @throws Exception\InvalidUsageException
252
     *
253
     * @return string
254
     */
255
    public function getQueryString()
256
    {
257
        if ($this->query_string == '') {
258
            throw new Exception\InvalidUsageException(__METHOD__ . ': Invalid usage, getQueryString must be called after data has been loaded (performance reason).');
259
        }
260
261
        return str_replace("\n", ' ', $this->query_string);
262
    }
263
264
    /**
265
     * Return the query string.
266
     *
267
     * @throws Exception\InvalidUsageException
268
     *
269
     * @return string
270
     */
271
    public function __toString()
272
    {
273
        if (trim($this->query_string) != '') {
274
            $sql = str_replace("\n", ' ', $this->query_string);
275
        } elseif ($this->select !== null) {
276
            $sql = $this->sql->getSqlStringForSqlObject($this->select);
277
        } else {
278
            throw new Exception\InvalidUsageException(__METHOD__ . ': No select given.');
279
        }
280
281
        return $sql;
282
    }
283
}
284