Completed
Push — master ( e37d11...f223f9 )
by Ron
02:36
created

RunnableSelect   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 20
Bugs 1 Features 7
Metric Value
wmc 29
c 20
b 1
f 7
lcom 1
cbo 6
dl 0
loc 203
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A bindValues() 0 4 1
A bindValue() 0 4 1
A clearValues() 0 4 1
A setPreserveTypes() 0 4 1
A fetchRows() 0 15 4
A fetchRowsLazy() 0 8 2
A fetchRow() 0 13 3
A fetchKeyValue() 0 14 3
A fetchGroups() 0 16 4
A fetchArray() 0 5 1
A fetchValue() 0 9 2
A getFoundRows() 0 3 1
A createTempStatement() 0 12 2
A createStatement() 0 10 2
A getIterator() 0 3 1
1
<?php
2
namespace Kir\MySQL\Builder;
3
4
use Closure;
5
use Generator;
6
use IteratorAggregate;
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;
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
				$data = array_map($callback, $data);
73
			}
74
			return $data;
75
		});
76
	}
77
78
	/**
79
	 * @param Closure $callback
80
	 * @return array[]|Generator
81
	 */
82
	public function fetchRowsLazy(Closure $callback = null) {
83
		if(version_compare(PHP_VERSION, '5.5', '<')) {
84
			return $this->fetchRows($callback);
85
		}
86
		$statement = $this->createStatement();
87
		$generator = new LazyRowGenerator($this->preserveTypes);
88
		return $generator->generate($statement, $callback);
89
	}
90
91
	/**
92
	 * @return string[]
93
	 */
94
	public function fetchRow() {
95
		return $this->createTempStatement(function (QueryStatement $statement) {
96
			$row = $statement->fetch(\PDO::FETCH_ASSOC);
97
			if(!is_array($row)) {
98
				return array();
99
			}
100
			if($this->preserveTypes) {
101
				$columnDefinitions = FieldTypeProvider::getFieldTypes($statement);
102
				$row = FieldValueConverter::convertValues($row, $columnDefinitions);
103
			}
104
			return $row;
105
		});
106
	}
107
108
	/**
109
	 * @param bool $treatValueAsArray
110
	 * @return mixed[]
111
	 */
112
	public function fetchKeyValue($treatValueAsArray = false) {
113
		return $this->createTempStatement(function (QueryStatement $statement) use ($treatValueAsArray) {
114
			if($treatValueAsArray) {
115
				$rows = $statement->fetchAll(\PDO::FETCH_ASSOC);
116
				$result = array();
117
				foreach($rows as $row) {
118
					list($key) = array_values($row);
119
					$result[$key] = $row;
120
				}
121
				return $result;
122
			}
123
			return $statement->fetchAll(\PDO::FETCH_KEY_PAIR);
124
		});
125
	}
126
127
	/**
128
	 * @param array $fields
129
	 * @return array
130
	 */
131
	public function fetchGroups(array $fields) {
132
		$rows = $this->fetchRows();
133
		$result = array();
134
		foreach($rows as $row) {
135
			$tmp = &$result;
136
			foreach($fields as $field) {
137
				$value = $row[$field];
138
				if(!array_key_exists($value, $tmp)) {
139
					$tmp[$value] = [];
140
				}
141
				$tmp = &$tmp[$value];
142
			}
143
			$tmp[] = $row;
144
		}
145
		return $result;
146
	}
147
148
	/**
149
	 * @return string[]
150
	 */
151
	public function fetchArray() {
152
		return $this->createTempStatement(function (QueryStatement $stmt) {
153
			return $stmt->fetchAll(\PDO::FETCH_COLUMN);
154
		});
155
	}
156
157
	/**
158
	 * @param mixed $default
159
	 * @return null|bool|string|int|float
160
	 */
161
	public function fetchValue($default = null) {
162
		return $this->createTempStatement(function (QueryStatement $stmt) use ($default) {
163
			$result = $stmt->fetch(\PDO::FETCH_NUM);
164
			if($result !== false) {
165
				return $result[0];
166
			}
167
			return $default;
168
		});
169
	}
170
171
	/**
172
	 * @return bool
173
	 */
174
	public function getFoundRows() {
175
		return $this->foundRows;
176
	}
177
178
	/**
179
	 * @param callback $fn
180
	 * @return mixed
181
	 * @throws \Exception
182
	 */
183
	private function createTempStatement($fn) {
184
		$stmt = $this->createStatement();
185
		$res = null;
186
		try {
187
			$res = call_user_func($fn, $stmt);
188
		} catch (\Exception $e) { // PHP 5.4 compatibility
189
			$stmt->closeCursor();
190
			throw $e;
191
		}
192
		$stmt->closeCursor();
193
		return $res;
194
	}
195
196
	/**
197
	 * @return QueryStatement
198
	 */
199
	private function createStatement() {
200
		$db = $this->db();
201
		$query = $this->__toString();
202
		$statement = $db->prepare($query);
203
		$statement->execute($this->values);
204
		if($this->getCalcFoundRows()) {
205
			$this->foundRows = (int) $db->query('SELECT FOUND_ROWS()')->fetchColumn();
206
		}
207
		return $statement;
208
	}
209
210
	/**
211
	 * @return Traversable|array[]|Generator
212
	 */
213
	public function getIterator() {
214
		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...
215
	}
216
}
217