MySQLSelect::__toString()   A
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
dl 0
loc 25
rs 9.6
c 1
b 0
f 0
cc 4
nc 8
nop 0
1
<?php
2
3
namespace Kir\MySQL\Databases\MySQL;
4
5
use Kir\MySQL\Builder\Internal\Types;
6
use Kir\MySQL\Builder\RunnableSelect;
7
use Kir\MySQL\Builder\RunnableTemporaryTable;
8
use Kir\MySQL\Builder\Select;
9
use Kir\MySQL\Builder\Statement;
10
use Kir\MySQL\Builder\Traits\GroupByBuilder;
11
use Kir\MySQL\Builder\Traits\HavingBuilder;
12
use Kir\MySQL\Builder\Traits\JoinBuilder;
13
use Kir\MySQL\Builder\Traits\LimitBuilder;
14
use Kir\MySQL\Builder\Traits\OffsetBuilder;
15
use Kir\MySQL\Builder\Traits\OrderByBuilder;
16
use Kir\MySQL\Builder\Traits\TableBuilder;
17
use Kir\MySQL\Builder\Traits\TableNameBuilder;
18
use Kir\MySQL\Builder\Traits\UnionBuilder;
19
use Kir\MySQL\Builder\Traits\WhereBuilder;
20
use RuntimeException;
21
22
/**
23
 * @phpstan-import-type DBTableNameType from Types
24
 */
25
abstract class MySQLSelect extends Statement implements RunnableSelect {
26
	use TableNameBuilder;
27
	use TableBuilder;
28
	use JoinBuilder;
29
	use WhereBuilder;
30
	use HavingBuilder;
31
	use GroupByBuilder;
32
	use OrderByBuilder;
33
	use LimitBuilder;
34
	use OffsetBuilder;
35
	use UnionBuilder;
36
37
	/** @var array<int|string, string> */
38
	private array $fields = [];
39
	private bool $calcFoundRows = false;
40
	private bool $forUpdate = false;
41
	private bool $distinct = false;
42
43
	/**
44
	 * @param bool $distinct
45
	 * @return $this
46
	 */
47
	public function distinct(bool $distinct = true) {
48
		$this->distinct = $distinct;
49
50
		return $this;
51
	}
52
53
	/**
54
	 * @param array<int|string, string> $schema
55
	 * @param array<string, mixed> $options
56
	 * @return RunnableTemporaryTable
57
	 */
58
	public function temporary(array $schema, array $options = []): RunnableTemporaryTable {
59
		$name = 'tmp_' . bin2hex(random_bytes(16));
60
61
		$schemaSql = '';
62
		if($schema) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $schema of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
63
			$lines = [];
64
			foreach($schema as $key => $val) {
65
				if(is_int($key)) {
66
					$lines[] = $val;
67
				} else {
68
					$lines[] = "`{$key}` {$val}";
69
				}
70
			}
71
			$schemaSql = '(' . implode(', ', $lines) . ')';
72
		}
73
74
		$optionsSql = '';
75
		if(isset($options['engine'])) {
76
			$optionsSql .= " ENGINE={$options['engine']}";
77
		}
78
79
		$query = (string) $this;
80
		$sql = "CREATE TEMPORARY TABLE {$name} {$schemaSql} {$optionsSql} AS {$query}";
81
82
		$this->db()->exec($sql);
83
84
		return new MySQLTemporaryTable($this->db(), $name);
85
	}
86
87
	/**
88
	 * @param string|Select $expression
89
	 * @param string|null $alias
90
	 * @return $this
91
	 */
92
	public function field($expression, $alias = null) {
93
		if(is_object($expression)) {
94
			$expression = (string) $expression;
95
			$expression = trim($expression);
96
			$expression = rtrim($expression, ';');
97
			$expression = trim($expression);
98
			$lines = explode("\n", $expression);
99
			$lines = array_map(static fn($line) => "\t\t{$line}", $lines);
100
			$expression = implode("\n", $lines);
101
			$expression = sprintf("(\n%s\n\t)", $expression);
102
		}
103
		if($alias === null) {
104
			$this->fields[] = $expression;
105
		} else {
106
			$this->fields[$alias] = $expression;
107
		}
108
109
		return $this;
110
	}
111
112
	/**
113
	 * @param array<string, string>|array<int, string> $fields
114
	 * @return $this
115
	 */
116
	public function fields(array $fields) {
117
		foreach($fields as $alias => $expression) {
118
			$this->field($expression, is_int($alias) ? null : $alias);
119
		}
120
121
		return $this;
122
	}
123
124
	/**
125
	 * @return array<int|string, string>
126
	 */
127
	public function getFields(): array {
128
		return $this->fields;
129
	}
130
131
	/**
132
	 * @param bool $enabled
133
	 * @return $this
134
	 */
135
	public function forUpdate(bool $enabled = true) {
136
		$this->forUpdate = $enabled;
137
138
		return $this;
139
	}
140
141
	/**
142
	 * @return bool
143
	 */
144
	public function getCalcFoundRows(): bool {
145
		return $this->calcFoundRows;
146
	}
147
148
	/**
149
	 * @param bool $calcFoundRows
150
	 * @return $this
151
	 */
152
	public function setCalcFoundRows($calcFoundRows = true) {
153
		if(ini_get("mysql.trace_mode")) {
154
			throw new RuntimeException('This function cant operate with mysql.trace_mode is set.');
155
		}
156
		$this->calcFoundRows = $calcFoundRows;
157
158
		return $this;
159
	}
160
161
	/**
162
	 * @param ($table is null ? DBTableNameType : string) $alias
0 ignored issues
show
Bug introduced by
The type Kir\MySQL\Databases\MySQL\is was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
163
	 * @param null|DBTableNameType $table
0 ignored issues
show
Bug introduced by
The type Kir\MySQL\Databases\MySQL\DBTableNameType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
164
	 * @return $this
165
	 */
166
	public function from($alias, $table = null) {
167
		if($table === null) {
168
			[$alias, $table] = [$table, $alias];
169
			$this->addTable($alias, (string) $table);
0 ignored issues
show
Bug introduced by
(string)$table of type string is incompatible with the type Kir\MySQL\Builder\Traits\DBTableNameType expected by parameter $table of Kir\MySQL\Databases\MySQL\MySQLSelect::addTable(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
			$this->addTable($alias, /** @scrutinizer ignore-type */ (string) $table);
Loading history...
170
		} else {
171
			$this->addTable($alias, $table);
172
		}
173
174
		return $this;
175
	}
176
177
	/**
178
	 * @return string
179
	 */
180
	public function __toString(): string {
181
		$query = "SELECT";
182
		if($this->calcFoundRows) {
183
			$query .= " SQL_CALC_FOUND_ROWS";
184
		}
185
		if($this->distinct) {
186
			$query .= " DISTINCT";
187
		}
188
		$query .= "\n";
189
		$query = $this->buildFields($query);
190
		if(count($this->getTables())) {
191
			$query .= "FROM\n";
192
		}
193
		$query = $this->buildTables($query);
194
		$query = $this->buildJoins($query);
195
		$query = $this->buildWhereConditions($query);
196
		$query = $this->buildGroups($query);
197
		$query = $this->buildHavingConditions($query);
198
		$query = $this->buildOrder($query);
199
		$query = $this->buildLimit($query, $this->getOffset());
200
		$query = $this->buildOffset($query);
201
		$query = $this->buildUnions($query);
202
		$query = $this->buildForUpdate($query);
203
204
		return $query;
205
	}
206
207
	/**
208
	 * @param string $query
209
	 * @return string
210
	 */
211
	private function buildFields(string $query): string {
212
		$fields = [];
213
		if(count($this->fields)) {
214
			foreach($this->fields as $alias => $expression) {
215
				if(is_numeric($alias)) {
216
					$fields[] = "\t{$expression}";
217
				} else {
218
					$fields[] = "\t{$expression} AS `{$alias}`";
219
				}
220
			}
221
		} else {
222
			$fields[] = "\t*";
223
		}
224
225
		return $query . implode(",\n", $fields) . "\n";
226
	}
227
228
	/**
229
	 * @param string $query
230
	 * @return string
231
	 */
232
	private function buildForUpdate(string $query): string {
233
		if($this->forUpdate) {
234
			$query .= "FOR UPDATE\n";
235
		}
236
237
		return $query;
238
	}
239
}
240