Completed
Push — master ( 3e0fd7...23f593 )
by Ron
03:11
created

RunnableSelect::fetchRows()   C

Complexity

Conditions 7
Paths 1

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 0 Features 2
Metric Value
c 10
b 0
f 2
dl 0
loc 24
rs 6.7272
cc 7
eloc 17
nc 1
nop 1
1
<?php
2
namespace Kir\MySQL\Builder;
3
4
use Closure;
5
use IteratorAggregate;
6
use Kir\MySQL\Builder\Helpers\DBIgnoreRow;
7
use Kir\MySQL\Builder\Helpers\FieldTypeProvider;
8
use Kir\MySQL\Builder\Helpers\FieldValueConverter;
9
use Kir\MySQL\Builder\Helpers\LazyRowGenerator;
10
use Traversable;
11
12
/**
13
 */
14
class RunnableSelect extends Select implements IteratorAggregate {
15
	/** @var array */
16
	private $values = array();
17
	/** @var bool */
18
	private $preserveTypes = false;
19
	/** @var int */
20
	private $foundRows = 0;
21
22
	/**
23
	 * @param array $values
24
	 * @return $this
25
	 */
26
	public function bindValues(array $values) {
27
		$this->values = array_merge($this->values, $values);
28
		return $this;
29
	}
30
31
	/**
32
	 * @param string $key
33
	 * @param string|int|bool|float|null $value
34
	 * @return $this
35
	 */
36
	public function bindValue($key, $value) {
37
		$this->values[$key] = $value;
38
		return $this;
39
	}
40
41
	/**
42
	 * @return $this
43
	 */
44
	public function clearValues() {
45
		$this->values = array();
46
		return $this;
47
	}
48
49
	/**
50
	 * @param bool $preserveTypes
51
	 * @return $this
52
	 */
53
	public function setPreserveTypes($preserveTypes = true) {
54
		$this->preserveTypes = $preserveTypes;
55
		return $this;
56
	}
57
58
	/**
59
	 * @param Closure $callback
60
	 * @return array[]
61
	 */
62
	public function fetchRows(Closure $callback = null) {
63
		return $this->createTempStatement(function (QueryStatement $statement) use ($callback) {
64
			$data = $statement->fetchAll(\PDO::FETCH_ASSOC);
65
			if($this->preserveTypes) {
66
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
67
				foreach($data as &$row) {
68
					$row = FieldValueConverter::convertValues($row, $columnDefinitions);
69
				}
70
			}
71
			if($callback !== null) {
72
				$resultData = [];
73
				foreach($data as $row) {
74
					$result = $callback($row);
75
					if($result !== null && !($result instanceof DBIgnoreRow)) {
76
						$resultData[] = $result;
77
					} else {
78
						$resultData[] = $row;
79
					}
80
				}
81
				return $resultData;
82
			}
83
			return $data;
84
		});
85
	}
86
87
	/**
88
	 * @param Closure $callback
89
	 * @return array[]|\Generator
90
	 */
91
	public function fetchRowsLazy(Closure $callback = null) {
92
		if(version_compare(PHP_VERSION, '5.5', '<')) {
93
			return $this->fetchRows($callback);
94
		}
95
		$statement = $this->createStatement();
96
		$generator = new LazyRowGenerator($this->preserveTypes);
97
		return $generator->generate($statement, $callback);
98
	}
99
100
	/**
101
	 * @param Closure|null $callback
102
	 * @return string[]
103
	 * @throws \Exception
104
	 */
105
	public function fetchRow(Closure $callback = null) {
106
		return $this->createTempStatement(function (QueryStatement $statement) use ($callback) {
107
			$row = $statement->fetch(\PDO::FETCH_ASSOC);
108
			if(!is_array($row)) {
109
				return [];
110
			}
111
			if($this->preserveTypes) {
112
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
113
				$row = FieldValueConverter::convertValues($row, $columnDefinitions);
114
			}
115
			if($callback !== null) {
116
				$result = $callback($row);
117
				if($result !== null) {
118
					$row = $result;
119
				}
120
			}
121
			return $row;
122
		});
123
	}
124
125
	/**
126
	 * @param bool $treatValueAsArray
127
	 * @return mixed[]
128
	 */
129
	public function fetchKeyValue($treatValueAsArray = false) {
130
		return $this->createTempStatement(function (QueryStatement $statement) use ($treatValueAsArray) {
131
			if($treatValueAsArray) {
132
				$rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
133
				$result = array();
134
				foreach($rows as $row) {
135
					list($key) = array_values($row);
136
					$result[$key] = $row;
137
				}
138
				return $result;
139
			}
140
			return $statement->fetchAll(\PDO::FETCH_KEY_PAIR);
141
		});
142
	}
143
144
	/**
145
	 * @param array $fields
146
	 * @return array
147
	 */
148
	public function fetchGroups(array $fields) {
149
		$rows = $this->fetchRows();
150
		$result = array();
151
		foreach($rows as $row) {
152
			$tmp = &$result;
153
			foreach($fields as $field) {
154
				$value = $row[$field];
155
				if(!array_key_exists($value, $tmp)) {
156
					$tmp[$value] = [];
157
				}
158
				$tmp = &$tmp[$value];
159
			}
160
			$tmp[] = $row;
161
		}
162
		return $result;
163
	}
164
165
	/**
166
	 * @return string[]
167
	 */
168
	public function fetchArray() {
169
		return $this->createTempStatement(function (QueryStatement $stmt) {
170
			return $stmt->fetchAll(\PDO::FETCH_COLUMN);
171
		});
172
	}
173
174
	/**
175
	 * @param mixed $default
176
	 * @return null|bool|string|int|float
177
	 */
178
	public function fetchValue($default = null) {
179
		return $this->createTempStatement(function (QueryStatement $stmt) use ($default) {
180
			$result = $stmt->fetch(\PDO::FETCH_NUM);
181
			if($result !== false) {
182
				return $result[0];
183
			}
184
			return $default;
185
		});
186
	}
187
188
	/**
189
	 * @return bool
190
	 */
191
	public function getFoundRows() {
192
		return $this->foundRows;
193
	}
194
195
	/**
196
	 * @param callback $fn
197
	 * @return mixed
198
	 * @throws \Exception
199
	 */
200
	private function createTempStatement($fn) {
201
		$stmt = $this->createStatement();
202
		$res = null;
203
		try {
204
			$res = call_user_func($fn, $stmt);
205
		} catch (\Exception $e) { // PHP 5.4 compatibility
206
			$stmt->closeCursor();
207
			throw $e;
208
		}
209
		$stmt->closeCursor();
210
		return $res;
211
	}
212
213
	/**
214
	 * @return QueryStatement
215
	 */
216
	private function createStatement() {
217
		$db = $this->db();
218
		$query = $this->__toString();
219
		$statement = $db->prepare($query);
220
		$statement->execute($this->values);
221
		if($this->getCalcFoundRows()) {
222
			$this->foundRows = (int) $db->query('SELECT FOUND_ROWS()')->fetchColumn();
223
		}
224
		return $statement;
225
	}
226
227
	/**
228
	 * @return Traversable|array[]|\Generator
229
	 */
230
	public function getIterator() {
231
		return $this->fetchRowsLazy();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->fetchRowsLazy(); (array[]|Generator) is incompatible with the return type declared by the interface IteratorAggregate::getIterator of type Traversable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
232
	}
233
}
234