1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* |
5
|
|
|
* @author Vanvelthem Sébastien |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Soluble\FlexStore\Source\DbWrapper; |
9
|
|
|
|
10
|
|
|
use Soluble\FlexStore\Source\AbstractSource; |
11
|
|
|
use Soluble\FlexStore\Source\QueryableSourceInterface; |
12
|
|
|
use Soluble\FlexStore\ResultSet\ResultSet; |
13
|
|
|
use Soluble\FlexStore\Exception; |
14
|
|
|
use Soluble\FlexStore\Options; |
15
|
|
|
use ArrayObject; |
16
|
|
|
use Soluble\DbWrapper\Adapter\AdapterInterface; |
17
|
|
|
use Soluble\FlexStore\Column\ColumnModel; |
18
|
|
|
use Soluble\FlexStore\Column\Type\MetadataMapper; |
19
|
|
|
use Soluble\Metadata\Reader as MetadataReader; |
20
|
|
|
|
21
|
|
|
class QuerySource extends AbstractSource implements QueryableSourceInterface |
22
|
|
|
{ |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* |
26
|
|
|
* @var string |
27
|
|
|
*/ |
28
|
|
|
protected $query; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* |
32
|
|
|
* @var AdapterInterface |
33
|
|
|
*/ |
34
|
|
|
protected $adapter; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Initial params received in the constructor |
38
|
|
|
* @var ArrayObject |
39
|
|
|
*/ |
40
|
|
|
protected $params; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* |
44
|
|
|
* @var string |
45
|
|
|
*/ |
46
|
|
|
protected $query_string; |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* |
52
|
|
|
* @var ColumnModel |
53
|
|
|
*/ |
54
|
|
|
protected $columnModel; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* |
58
|
|
|
* @param AdapterInterface $adapter |
59
|
|
|
* @param string $query |
60
|
|
|
*/ |
61
|
|
|
public function __construct(AdapterInterface $adapter, $query = null) |
62
|
|
|
{ |
63
|
|
|
$this->adapter = $adapter; |
64
|
|
|
|
65
|
|
|
if ($query !== null) { |
66
|
|
|
$this->setQuery($query); |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @param string $query |
72
|
|
|
*/ |
73
|
|
|
public function setQuery($query) |
74
|
|
|
{ |
75
|
|
|
$this->query = $query; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* |
80
|
|
|
* @return string |
81
|
|
|
*/ |
82
|
|
|
public function getQuery() |
83
|
|
|
{ |
84
|
|
|
return $this->query; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* |
90
|
|
|
* @param Select $select |
91
|
|
|
* @param Options $options |
92
|
|
|
* @return Select |
93
|
|
|
*/ |
94
|
|
|
protected function assignOptions(Select $select, Options $options) |
95
|
|
|
{ |
96
|
|
|
if ($options->hasLimit()) { |
97
|
|
|
$select->limit($options->getLimit()); |
98
|
|
|
if ($options->hasOffset()) { |
99
|
|
|
$select->offset($options->getOffset()); |
100
|
|
|
} |
101
|
|
|
/** |
102
|
|
|
* For mysql queries, to allow counting rows we must prepend |
103
|
|
|
* SQL_CALC_FOUND_ROWS to the select quantifiers |
104
|
|
|
*/ |
105
|
|
|
$calc_found_rows = 'SQL_CALC_FOUND_ROWS'; |
106
|
|
|
$quant_state = $select->getRawState($select::QUANTIFIER); |
107
|
|
|
if ($quant_state !== null) { |
108
|
|
|
if ($quant_state instanceof Expression) { |
|
|
|
|
109
|
|
|
$quant_state->setExpression($calc_found_rows . ' ' . $quant_state->getExpression()); |
110
|
|
|
} elseif (is_string($quant_state)) { |
111
|
|
|
$quant_state = $calc_found_rows . ' ' . $quant_state; |
112
|
|
|
} |
113
|
|
|
$select->quantifier($quant_state); |
114
|
|
|
} else { |
115
|
|
|
$select->quantifier(new Expression($calc_found_rows)); |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
return $select; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* |
123
|
|
|
* @param Options $options |
124
|
|
|
* @throws Exception\EmptyQueryException |
125
|
|
|
* @throws Exception\ErrorException |
126
|
|
|
* @return ResultSet |
127
|
|
|
*/ |
128
|
|
|
public function getData(Options $options = null) |
129
|
|
|
{ |
130
|
|
|
if ($options === null) { |
131
|
|
|
$options = $this->getOptions(); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
// todo |
135
|
|
|
//$query = $this->assignOptions(clone $this->query, $options); |
136
|
|
|
|
137
|
|
|
|
138
|
|
|
$sql_string = $this->query; |
139
|
|
|
|
140
|
|
|
try { |
141
|
|
|
$results = $this->adapter->query($sql_string); |
142
|
|
|
|
143
|
|
|
|
144
|
|
|
|
145
|
|
|
$r = new ResultSet($results); |
|
|
|
|
146
|
|
|
$r->setSource($this); |
147
|
|
|
$r->setHydrationOptions($options->getHydrationOptions()); |
148
|
|
|
|
149
|
|
|
if ($options->hasLimit()) { |
150
|
|
|
//$row = $this->adapter->query('select FOUND_ROWS() as total_count')->execute()->current(); |
151
|
|
|
$row = $this->adapter->query('select FOUND_ROWS() as total_count')->current(); |
152
|
|
|
$r->setTotalRows($row['total_count']); |
153
|
|
|
} else { |
154
|
|
|
$r->setTotalRows($r->count()); |
155
|
|
|
} |
156
|
|
|
} catch (\Exception $e) { |
157
|
|
|
throw new Exception\ErrorException(__METHOD__ . ': Cannot retrieve data (' . $e->getMessage() . ')'); |
158
|
|
|
} |
159
|
|
|
return $r; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* |
164
|
|
|
*/ |
165
|
|
|
public function loadDefaultColumnModel() |
166
|
|
|
{ |
167
|
|
|
$metadata_columns = $this->getMetadataReader()->getColumnsMetadata($this->query); |
168
|
|
|
$this->setColumnModel(MetadataMapper::getColumnModelFromMetadata($metadata_columns)); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* {@inheritdoc} |
173
|
|
|
* @throws Exception\UnsupportedFeatureException |
174
|
|
|
*/ |
175
|
|
|
public function getMetadataReader() |
176
|
|
|
{ |
177
|
|
|
if ($this->metadataReader === null) { |
178
|
|
|
$this->setMetadataReader($this->getDefaultMetadataReader()); |
179
|
|
|
} |
180
|
|
|
return $this->metadataReader; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* @throws Exception\UnsupportedFeatureException |
185
|
|
|
*/ |
186
|
|
|
protected function getDefaultMetadataReader() |
187
|
|
|
{ |
188
|
|
|
$conn = $this->adapter->getConnection()->getResource(); |
189
|
|
|
$class = strtolower(get_class($conn)); |
190
|
|
|
switch ($class) { |
191
|
|
|
case 'pdo': |
192
|
|
|
return new MetadataReader\PdoMysqlMetadataReader($conn); |
193
|
|
|
case 'mysqli': |
194
|
|
|
return new MetadataReader\MysqliMetadataReader($conn); |
195
|
|
|
default: |
196
|
|
|
throw new Exception\UnsupportedFeatureException(__METHOD__ . " Does not support default metadata reader for driver '$class'"); |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Return the query string that was executed |
202
|
|
|
* @throws Exception\InvalidUsageException |
203
|
|
|
* @return string |
204
|
|
|
*/ |
205
|
|
|
public function getQueryString() |
206
|
|
|
{ |
207
|
|
|
if ($this->query_string == '') { |
208
|
|
|
throw new Exception\InvalidUsageException(__METHOD__ . ": Invalid usage, getQueryString must be called after data has been loaded (performance reason)."); |
209
|
|
|
} |
210
|
|
|
return str_replace("\n", ' ', $this->query_string); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Return the query string |
215
|
|
|
* |
216
|
|
|
* @throws Exception\InvalidUsageException |
217
|
|
|
* @return string |
218
|
|
|
*/ |
219
|
|
|
public function __toString() |
220
|
|
|
{ |
221
|
|
|
if ($this->query_string != '') { |
222
|
|
|
$sql = str_replace("\n", ' ', $this->query_string); |
223
|
|
|
} elseif ($this->select !== null) { |
|
|
|
|
224
|
|
|
$sql = $this->sql->getSqlStringForSqlObject($this->select); |
|
|
|
|
225
|
|
|
} else { |
226
|
|
|
throw new Exception\InvalidUsageException(__METHOD__ . ": No select given."); |
227
|
|
|
} |
228
|
|
|
return $sql; |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
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.