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
|
69 |
|
public function __construct(Adapter $adapter, Select $select = null) |
78
|
|
|
{ |
79
|
69 |
|
$this->adapter = $adapter; |
80
|
69 |
|
$this->sql = new Sql($this->adapter); |
81
|
69 |
|
if ($select !== null) { |
82
|
60 |
|
$this->setSelect($select); |
83
|
60 |
|
} |
84
|
69 |
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* @param Select $select |
88
|
|
|
* |
89
|
|
|
* @return SqlSource |
90
|
|
|
*/ |
91
|
64 |
|
public function setSelect(Select $select) |
92
|
|
|
{ |
93
|
64 |
|
$this->select = $select; |
94
|
|
|
|
95
|
64 |
|
return $this; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @return Select |
100
|
|
|
*/ |
101
|
38 |
|
public function getSelect() |
102
|
|
|
{ |
103
|
38 |
|
return $this->select(); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @return Select |
108
|
|
|
*/ |
109
|
43 |
|
public function select() |
110
|
|
|
{ |
111
|
43 |
|
if ($this->select === null) { |
112
|
14 |
|
$this->select = $this->sql->select(); |
113
|
14 |
|
} |
114
|
|
|
|
115
|
43 |
|
return $this->select; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* @param Select $select |
120
|
|
|
* @param Options $options |
121
|
|
|
* |
122
|
|
|
* @return Select |
123
|
|
|
*/ |
124
|
38 |
|
protected function assignOptions(Select $select, Options $options) |
125
|
|
|
{ |
126
|
38 |
|
if ($options->hasLimit()) { |
127
|
8 |
|
$select->limit($options->getLimit()); |
128
|
8 |
|
if ($options->hasOffset()) { |
129
|
3 |
|
$select->offset($options->getOffset()); |
130
|
3 |
|
} |
131
|
|
|
|
132
|
8 |
|
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
|
6 |
|
$calc_found_rows = 'SQL_CALC_FOUND_ROWS'; |
138
|
6 |
|
$quant_state = $select->getRawState($select::QUANTIFIER); |
139
|
6 |
|
if ($quant_state !== null) { |
140
|
1 |
|
if ($quant_state instanceof Expression) { |
|
|
|
|
141
|
1 |
|
$quant_state->setExpression($calc_found_rows . ' ' . $quant_state->getExpression()); |
142
|
1 |
|
} elseif (is_string($quant_state)) { |
143
|
1 |
|
$quant_state = $calc_found_rows . ' ' . $quant_state; |
144
|
1 |
|
} |
145
|
1 |
|
$select->quantifier($quant_state); |
146
|
1 |
|
} else { |
147
|
6 |
|
$select->quantifier(new Expression($calc_found_rows)); |
148
|
|
|
} |
149
|
6 |
|
} |
150
|
8 |
|
} |
151
|
|
|
|
152
|
38 |
|
return $select; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* @param Options $options |
157
|
|
|
* |
158
|
|
|
* @throws Exception\EmptyQueryException |
159
|
|
|
* @throws Exception\ErrorException |
160
|
|
|
* |
161
|
|
|
* @return ResultSet |
162
|
|
|
*/ |
163
|
38 |
|
public function getData(Options $options = null) |
164
|
|
|
{ |
165
|
38 |
|
if ($options === null) { |
166
|
21 |
|
$options = $this->getOptions(); |
167
|
21 |
|
} |
168
|
|
|
|
169
|
38 |
|
$select = $this->assignOptions(clone $this->getSelect(), $options); |
170
|
|
|
|
171
|
38 |
|
$sql = new Sql($this->adapter); |
172
|
|
|
|
173
|
38 |
|
$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
|
38 |
|
if (in_array($sql_string, ['', 'SELECT .*', 'SELECT *'])) { |
179
|
2 |
|
throw new Exception\EmptyQueryException(__METHOD__ . ': Cannot return data of an empty query'); |
180
|
|
|
} |
181
|
36 |
|
$this->query_string = $sql_string; |
182
|
|
|
|
183
|
|
|
try { |
184
|
36 |
|
$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
|
36 |
|
$r = new ResultSet($results); |
190
|
36 |
|
$r->setSource($this); |
191
|
36 |
|
$r->setHydrationOptions($options->getHydrationOptions()); |
192
|
|
|
|
193
|
36 |
|
if ($options->hasLimit() && $options->getLimit() > 0) { |
194
|
|
|
//$row = $this->adapter->query('select FOUND_ROWS() as total_count')->execute()->current(); |
195
|
6 |
|
$row = $this->adapter->createStatement('select FOUND_ROWS() as total_count')->execute()->current(); |
196
|
6 |
|
$r->setTotalRows($row['total_count']); |
197
|
6 |
|
} else { |
198
|
32 |
|
$r->setTotalRows($r->count()); |
199
|
|
|
} |
200
|
36 |
|
} catch (\Exception $e) { |
201
|
|
|
throw new Exception\ErrorException(__METHOD__ . ': Cannot retrieve data (' . $e->getMessage() . ')'); |
202
|
|
|
} |
203
|
|
|
|
204
|
36 |
|
return $r; |
205
|
|
|
} |
206
|
|
|
|
207
|
32 |
|
public function loadDefaultColumnModel() |
208
|
|
|
{ |
209
|
31 |
|
$sql = new Sql($this->adapter); |
210
|
31 |
|
$select = clone $this->select; |
211
|
31 |
|
$select->limit(0); |
212
|
31 |
|
$sql_string = $sql->getSqlStringForSqlObject($select); |
213
|
32 |
|
$metadata_columns = $this->getMetadataReader()->getColumnsMetadata($sql_string); |
214
|
31 |
|
$this->setColumnModel(MetadataMapper::getColumnModelFromMetadata($metadata_columns)); |
215
|
31 |
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* {@inheritdoc} |
219
|
|
|
* |
220
|
|
|
* @throws Exception\UnsupportedFeatureException |
221
|
|
|
*/ |
222
|
33 |
|
public function getMetadataReader() |
223
|
|
|
{ |
224
|
33 |
|
if ($this->metadataReader === null) { |
225
|
33 |
|
$this->setMetadataReader($this->getDefaultMetadataReader()); |
226
|
33 |
|
} |
227
|
|
|
|
228
|
33 |
|
return $this->metadataReader; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* @throws Exception\UnsupportedFeatureException |
233
|
|
|
*/ |
234
|
33 |
|
protected function getDefaultMetadataReader() |
235
|
|
|
{ |
236
|
33 |
|
$conn = $this->adapter->getDriver()->getConnection()->getResource(); |
237
|
33 |
|
$class = strtolower(get_class($conn)); |
238
|
|
|
switch ($class) { |
239
|
33 |
|
case 'pdo': |
240
|
|
|
return new MetadataReader\PdoMysqlMetadataReader($conn); |
241
|
33 |
|
case 'mysqli': |
242
|
33 |
|
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
|
5 |
|
public function getQueryString() |
256
|
|
|
{ |
257
|
5 |
|
if ($this->query_string == '') { |
258
|
1 |
|
throw new Exception\InvalidUsageException(__METHOD__ . ': Invalid usage, getQueryString must be called after data has been loaded (performance reason).'); |
259
|
|
|
} |
260
|
|
|
|
261
|
4 |
|
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
|
2 |
|
public function __toString() |
272
|
|
|
{ |
273
|
2 |
|
if (trim($this->query_string) != '') { |
274
|
2 |
|
$sql = str_replace("\n", ' ', $this->query_string); |
275
|
2 |
|
} 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
|
2 |
|
return $sql; |
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
|
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 thecomposer.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
orrequire-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 you have not tested against this specific condition, such errors might go unnoticed.