Passed
Push — master ( 6a14f8...ed2d6e )
by Morris
14:56 queued 12s
created
lib/public/AppFramework/Db/Entity.php 1 patch
Indentation   +241 added lines, -241 removed lines patch added patch discarded remove patch
@@ -34,245 +34,245 @@
 block discarded – undo
34 34
  * @since 7.0.0
35 35
  */
36 36
 abstract class Entity {
37
-	public $id;
38
-
39
-	private $_updatedFields = [];
40
-	private $_fieldTypes = ['id' => 'integer'];
41
-
42
-
43
-	/**
44
-	 * Simple alternative constructor for building entities from a request
45
-	 * @param array $params the array which was obtained via $this->params('key')
46
-	 * in the controller
47
-	 * @return Entity
48
-	 * @since 7.0.0
49
-	 */
50
-	public static function fromParams(array $params) {
51
-		$instance = new static();
52
-
53
-		foreach ($params as $key => $value) {
54
-			$method = 'set' . ucfirst($key);
55
-			$instance->$method($value);
56
-		}
57
-
58
-		return $instance;
59
-	}
60
-
61
-
62
-	/**
63
-	 * Maps the keys of the row array to the attributes
64
-	 * @param array $row the row to map onto the entity
65
-	 * @since 7.0.0
66
-	 */
67
-	public static function fromRow(array $row) {
68
-		$instance = new static();
69
-
70
-		foreach ($row as $key => $value) {
71
-			$prop = ucfirst($instance->columnToProperty($key));
72
-			$setter = 'set' . $prop;
73
-			$instance->$setter($value);
74
-		}
75
-
76
-		$instance->resetUpdatedFields();
77
-
78
-		return $instance;
79
-	}
80
-
81
-
82
-	/**
83
-	 * @return array with attribute and type
84
-	 * @since 7.0.0
85
-	 */
86
-	public function getFieldTypes() {
87
-		return $this->_fieldTypes;
88
-	}
89
-
90
-
91
-	/**
92
-	 * Marks the entity as clean needed for setting the id after the insertion
93
-	 * @since 7.0.0
94
-	 */
95
-	public function resetUpdatedFields() {
96
-		$this->_updatedFields = [];
97
-	}
98
-
99
-	/**
100
-	 * Generic setter for properties
101
-	 * @since 7.0.0
102
-	 */
103
-	protected function setter($name, $args) {
104
-		// setters should only work for existing attributes
105
-		if (property_exists($this, $name)) {
106
-			if ($this->$name === $args[0]) {
107
-				return;
108
-			}
109
-			$this->markFieldUpdated($name);
110
-
111
-			// if type definition exists, cast to correct type
112
-			if ($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) {
113
-				$type = $this->_fieldTypes[$name];
114
-				if ($type === 'blob') {
115
-					// (B)LOB is treated as string when we read from the DB
116
-					$type = 'string';
117
-				}
118
-
119
-				if ($type === 'datetime') {
120
-					if (!$args[0] instanceof \DateTime) {
121
-						$args[0] = new \DateTime($args[0]);
122
-					}
123
-				} else {
124
-					settype($args[0], $type);
125
-				}
126
-			}
127
-			$this->$name = $args[0];
128
-		} else {
129
-			throw new \BadFunctionCallException($name .
130
-				' is not a valid attribute');
131
-		}
132
-	}
133
-
134
-	/**
135
-	 * Generic getter for properties
136
-	 * @since 7.0.0
137
-	 */
138
-	protected function getter($name) {
139
-		// getters should only work for existing attributes
140
-		if (property_exists($this, $name)) {
141
-			return $this->$name;
142
-		} else {
143
-			throw new \BadFunctionCallException($name .
144
-				' is not a valid attribute');
145
-		}
146
-	}
147
-
148
-
149
-	/**
150
-	 * Each time a setter is called, push the part after set
151
-	 * into an array: for instance setId will save Id in the
152
-	 * updated fields array so it can be easily used to create the
153
-	 * getter method
154
-	 * @since 7.0.0
155
-	 */
156
-	public function __call($methodName, $args) {
157
-		if (strpos($methodName, 'set') === 0) {
158
-			$this->setter(lcfirst(substr($methodName, 3)), $args);
159
-		} elseif (strpos($methodName, 'get') === 0) {
160
-			return $this->getter(lcfirst(substr($methodName, 3)));
161
-		} elseif ($this->isGetterForBoolProperty($methodName)) {
162
-			return $this->getter(lcfirst(substr($methodName, 2)));
163
-		} else {
164
-			throw new \BadFunctionCallException($methodName .
165
-				' does not exist');
166
-		}
167
-	}
168
-
169
-	/**
170
-	 * @param string $methodName
171
-	 * @return bool
172
-	 * @since 18.0.0
173
-	 */
174
-	protected function isGetterForBoolProperty(string $methodName): bool {
175
-		if (strpos($methodName, 'is') === 0) {
176
-			$fieldName = lcfirst(substr($methodName, 2));
177
-			return isset($this->_fieldTypes[$fieldName]) && strpos($this->_fieldTypes[$fieldName], 'bool') === 0;
178
-		}
179
-		return false;
180
-	}
181
-
182
-	/**
183
-	 * Mark am attribute as updated
184
-	 * @param string $attribute the name of the attribute
185
-	 * @since 7.0.0
186
-	 */
187
-	protected function markFieldUpdated($attribute) {
188
-		$this->_updatedFields[$attribute] = true;
189
-	}
190
-
191
-
192
-	/**
193
-	 * Transform a database columnname to a property
194
-	 * @param string $columnName the name of the column
195
-	 * @return string the property name
196
-	 * @since 7.0.0
197
-	 */
198
-	public function columnToProperty($columnName) {
199
-		$parts = explode('_', $columnName);
200
-		$property = null;
201
-
202
-		foreach ($parts as $part) {
203
-			if ($property === null) {
204
-				$property = $part;
205
-			} else {
206
-				$property .= ucfirst($part);
207
-			}
208
-		}
209
-
210
-		return $property;
211
-	}
212
-
213
-
214
-	/**
215
-	 * Transform a property to a database column name
216
-	 * @param string $property the name of the property
217
-	 * @return string the column name
218
-	 * @since 7.0.0
219
-	 */
220
-	public function propertyToColumn($property) {
221
-		$parts = preg_split('/(?=[A-Z])/', $property);
222
-		$column = null;
223
-
224
-		foreach ($parts as $part) {
225
-			if ($column === null) {
226
-				$column = $part;
227
-			} else {
228
-				$column .= '_' . lcfirst($part);
229
-			}
230
-		}
231
-
232
-		return $column;
233
-	}
234
-
235
-
236
-	/**
237
-	 * @return array array of updated fields for update query
238
-	 * @since 7.0.0
239
-	 */
240
-	public function getUpdatedFields() {
241
-		return $this->_updatedFields;
242
-	}
243
-
244
-
245
-	/**
246
-	 * Adds type information for a field so that its automatically casted to
247
-	 * that value once its being returned from the database
248
-	 * @param string $fieldName the name of the attribute
249
-	 * @param string $type the type which will be used to call settype()
250
-	 * @since 7.0.0
251
-	 */
252
-	protected function addType($fieldName, $type) {
253
-		$this->_fieldTypes[$fieldName] = $type;
254
-	}
255
-
256
-
257
-	/**
258
-	 * Slugify the value of a given attribute
259
-	 * Warning: This doesn't result in a unique value
260
-	 * @param string $attributeName the name of the attribute, which value should be slugified
261
-	 * @return string slugified value
262
-	 * @since 7.0.0
263
-	 */
264
-	public function slugify($attributeName) {
265
-		// toSlug should only work for existing attributes
266
-		if (property_exists($this, $attributeName)) {
267
-			$value = $this->$attributeName;
268
-			// replace everything except alphanumeric with a single '-'
269
-			$value = preg_replace('/[^A-Za-z0-9]+/', '-', $value);
270
-			$value = strtolower($value);
271
-			// trim '-'
272
-			return trim($value, '-');
273
-		} else {
274
-			throw new \BadFunctionCallException($attributeName .
275
-				' is not a valid attribute');
276
-		}
277
-	}
37
+    public $id;
38
+
39
+    private $_updatedFields = [];
40
+    private $_fieldTypes = ['id' => 'integer'];
41
+
42
+
43
+    /**
44
+     * Simple alternative constructor for building entities from a request
45
+     * @param array $params the array which was obtained via $this->params('key')
46
+     * in the controller
47
+     * @return Entity
48
+     * @since 7.0.0
49
+     */
50
+    public static function fromParams(array $params) {
51
+        $instance = new static();
52
+
53
+        foreach ($params as $key => $value) {
54
+            $method = 'set' . ucfirst($key);
55
+            $instance->$method($value);
56
+        }
57
+
58
+        return $instance;
59
+    }
60
+
61
+
62
+    /**
63
+     * Maps the keys of the row array to the attributes
64
+     * @param array $row the row to map onto the entity
65
+     * @since 7.0.0
66
+     */
67
+    public static function fromRow(array $row) {
68
+        $instance = new static();
69
+
70
+        foreach ($row as $key => $value) {
71
+            $prop = ucfirst($instance->columnToProperty($key));
72
+            $setter = 'set' . $prop;
73
+            $instance->$setter($value);
74
+        }
75
+
76
+        $instance->resetUpdatedFields();
77
+
78
+        return $instance;
79
+    }
80
+
81
+
82
+    /**
83
+     * @return array with attribute and type
84
+     * @since 7.0.0
85
+     */
86
+    public function getFieldTypes() {
87
+        return $this->_fieldTypes;
88
+    }
89
+
90
+
91
+    /**
92
+     * Marks the entity as clean needed for setting the id after the insertion
93
+     * @since 7.0.0
94
+     */
95
+    public function resetUpdatedFields() {
96
+        $this->_updatedFields = [];
97
+    }
98
+
99
+    /**
100
+     * Generic setter for properties
101
+     * @since 7.0.0
102
+     */
103
+    protected function setter($name, $args) {
104
+        // setters should only work for existing attributes
105
+        if (property_exists($this, $name)) {
106
+            if ($this->$name === $args[0]) {
107
+                return;
108
+            }
109
+            $this->markFieldUpdated($name);
110
+
111
+            // if type definition exists, cast to correct type
112
+            if ($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) {
113
+                $type = $this->_fieldTypes[$name];
114
+                if ($type === 'blob') {
115
+                    // (B)LOB is treated as string when we read from the DB
116
+                    $type = 'string';
117
+                }
118
+
119
+                if ($type === 'datetime') {
120
+                    if (!$args[0] instanceof \DateTime) {
121
+                        $args[0] = new \DateTime($args[0]);
122
+                    }
123
+                } else {
124
+                    settype($args[0], $type);
125
+                }
126
+            }
127
+            $this->$name = $args[0];
128
+        } else {
129
+            throw new \BadFunctionCallException($name .
130
+                ' is not a valid attribute');
131
+        }
132
+    }
133
+
134
+    /**
135
+     * Generic getter for properties
136
+     * @since 7.0.0
137
+     */
138
+    protected function getter($name) {
139
+        // getters should only work for existing attributes
140
+        if (property_exists($this, $name)) {
141
+            return $this->$name;
142
+        } else {
143
+            throw new \BadFunctionCallException($name .
144
+                ' is not a valid attribute');
145
+        }
146
+    }
147
+
148
+
149
+    /**
150
+     * Each time a setter is called, push the part after set
151
+     * into an array: for instance setId will save Id in the
152
+     * updated fields array so it can be easily used to create the
153
+     * getter method
154
+     * @since 7.0.0
155
+     */
156
+    public function __call($methodName, $args) {
157
+        if (strpos($methodName, 'set') === 0) {
158
+            $this->setter(lcfirst(substr($methodName, 3)), $args);
159
+        } elseif (strpos($methodName, 'get') === 0) {
160
+            return $this->getter(lcfirst(substr($methodName, 3)));
161
+        } elseif ($this->isGetterForBoolProperty($methodName)) {
162
+            return $this->getter(lcfirst(substr($methodName, 2)));
163
+        } else {
164
+            throw new \BadFunctionCallException($methodName .
165
+                ' does not exist');
166
+        }
167
+    }
168
+
169
+    /**
170
+     * @param string $methodName
171
+     * @return bool
172
+     * @since 18.0.0
173
+     */
174
+    protected function isGetterForBoolProperty(string $methodName): bool {
175
+        if (strpos($methodName, 'is') === 0) {
176
+            $fieldName = lcfirst(substr($methodName, 2));
177
+            return isset($this->_fieldTypes[$fieldName]) && strpos($this->_fieldTypes[$fieldName], 'bool') === 0;
178
+        }
179
+        return false;
180
+    }
181
+
182
+    /**
183
+     * Mark am attribute as updated
184
+     * @param string $attribute the name of the attribute
185
+     * @since 7.0.0
186
+     */
187
+    protected function markFieldUpdated($attribute) {
188
+        $this->_updatedFields[$attribute] = true;
189
+    }
190
+
191
+
192
+    /**
193
+     * Transform a database columnname to a property
194
+     * @param string $columnName the name of the column
195
+     * @return string the property name
196
+     * @since 7.0.0
197
+     */
198
+    public function columnToProperty($columnName) {
199
+        $parts = explode('_', $columnName);
200
+        $property = null;
201
+
202
+        foreach ($parts as $part) {
203
+            if ($property === null) {
204
+                $property = $part;
205
+            } else {
206
+                $property .= ucfirst($part);
207
+            }
208
+        }
209
+
210
+        return $property;
211
+    }
212
+
213
+
214
+    /**
215
+     * Transform a property to a database column name
216
+     * @param string $property the name of the property
217
+     * @return string the column name
218
+     * @since 7.0.0
219
+     */
220
+    public function propertyToColumn($property) {
221
+        $parts = preg_split('/(?=[A-Z])/', $property);
222
+        $column = null;
223
+
224
+        foreach ($parts as $part) {
225
+            if ($column === null) {
226
+                $column = $part;
227
+            } else {
228
+                $column .= '_' . lcfirst($part);
229
+            }
230
+        }
231
+
232
+        return $column;
233
+    }
234
+
235
+
236
+    /**
237
+     * @return array array of updated fields for update query
238
+     * @since 7.0.0
239
+     */
240
+    public function getUpdatedFields() {
241
+        return $this->_updatedFields;
242
+    }
243
+
244
+
245
+    /**
246
+     * Adds type information for a field so that its automatically casted to
247
+     * that value once its being returned from the database
248
+     * @param string $fieldName the name of the attribute
249
+     * @param string $type the type which will be used to call settype()
250
+     * @since 7.0.0
251
+     */
252
+    protected function addType($fieldName, $type) {
253
+        $this->_fieldTypes[$fieldName] = $type;
254
+    }
255
+
256
+
257
+    /**
258
+     * Slugify the value of a given attribute
259
+     * Warning: This doesn't result in a unique value
260
+     * @param string $attributeName the name of the attribute, which value should be slugified
261
+     * @return string slugified value
262
+     * @since 7.0.0
263
+     */
264
+    public function slugify($attributeName) {
265
+        // toSlug should only work for existing attributes
266
+        if (property_exists($this, $attributeName)) {
267
+            $value = $this->$attributeName;
268
+            // replace everything except alphanumeric with a single '-'
269
+            $value = preg_replace('/[^A-Za-z0-9]+/', '-', $value);
270
+            $value = strtolower($value);
271
+            // trim '-'
272
+            return trim($value, '-');
273
+        } else {
274
+            throw new \BadFunctionCallException($attributeName .
275
+                ' is not a valid attribute');
276
+        }
277
+    }
278 278
 }
Please login to merge, or discard this patch.
lib/public/AppFramework/Db/QBMapper.php 2 patches
Indentation   +306 added lines, -306 removed lines patch added patch discarded remove patch
@@ -44,310 +44,310 @@
 block discarded – undo
44 44
  */
45 45
 abstract class QBMapper {
46 46
 
47
-	/** @var string */
48
-	protected $tableName;
49
-
50
-	/** @var string|class-string<T> */
51
-	protected $entityClass;
52
-
53
-	/** @var IDBConnection */
54
-	protected $db;
55
-
56
-	/**
57
-	 * @param IDBConnection $db Instance of the Db abstraction layer
58
-	 * @param string $tableName the name of the table. set this to allow entity
59
-	 * @param string|null $entityClass the name of the entity that the sql should be
60
-	 * @psalm-param class-string<T>|null $entityClass the name of the entity that the sql should be
61
-	 * mapped to queries without using sql
62
-	 * @since 14.0.0
63
-	 */
64
-	public function __construct(IDBConnection $db, string $tableName, string $entityClass = null) {
65
-		$this->db = $db;
66
-		$this->tableName = $tableName;
67
-
68
-		// if not given set the entity name to the class without the mapper part
69
-		// cache it here for later use since reflection is slow
70
-		if ($entityClass === null) {
71
-			$this->entityClass = str_replace('Mapper', '', \get_class($this));
72
-		} else {
73
-			$this->entityClass = $entityClass;
74
-		}
75
-	}
76
-
77
-
78
-	/**
79
-	 * @return string the table name
80
-	 * @since 14.0.0
81
-	 */
82
-	public function getTableName(): string {
83
-		return $this->tableName;
84
-	}
85
-
86
-
87
-	/**
88
-	 * Deletes an entity from the table
89
-	 * @param Entity $entity the entity that should be deleted
90
-	 * @psalm-param T $entity the entity that should be deleted
91
-	 * @return Entity the deleted entity
92
-	 * @psalm-return T the deleted entity
93
-	 * @since 14.0.0
94
-	 */
95
-	public function delete(Entity $entity): Entity {
96
-		$qb = $this->db->getQueryBuilder();
97
-
98
-		$idType = $this->getParameterTypeForProperty($entity, 'id');
99
-
100
-		$qb->delete($this->tableName)
101
-			->where(
102
-				$qb->expr()->eq('id', $qb->createNamedParameter($entity->getId(), $idType))
103
-			);
104
-		$qb->executeStatement();
105
-		return $entity;
106
-	}
107
-
108
-
109
-	/**
110
-	 * Creates a new entry in the db from an entity
111
-	 * @param Entity $entity the entity that should be created
112
-	 * @psalm-param T $entity the entity that should be created
113
-	 * @return Entity the saved entity with the set id
114
-	 * @psalm-return T the saved entity with the set id
115
-	 * @since 14.0.0
116
-	 */
117
-	public function insert(Entity $entity): Entity {
118
-		// get updated fields to save, fields have to be set using a setter to
119
-		// be saved
120
-		$properties = $entity->getUpdatedFields();
121
-
122
-		$qb = $this->db->getQueryBuilder();
123
-		$qb->insert($this->tableName);
124
-
125
-		// build the fields
126
-		foreach ($properties as $property => $updated) {
127
-			$column = $entity->propertyToColumn($property);
128
-			$getter = 'get' . ucfirst($property);
129
-			$value = $entity->$getter();
130
-
131
-			$type = $this->getParameterTypeForProperty($entity, $property);
132
-			$qb->setValue($column, $qb->createNamedParameter($value, $type));
133
-		}
134
-
135
-		$qb->executeStatement();
136
-
137
-		if ($entity->id === null) {
138
-			// When autoincrement is used id is always an int
139
-			$entity->setId($qb->getLastInsertId());
140
-		}
141
-
142
-		return $entity;
143
-	}
144
-
145
-	/**
146
-	 * Tries to creates a new entry in the db from an entity and
147
-	 * updates an existing entry if duplicate keys are detected
148
-	 * by the database
149
-	 *
150
-	 * @param Entity $entity the entity that should be created/updated
151
-	 * @psalm-param T $entity the entity that should be created/updated
152
-	 * @return Entity the saved entity with the (new) id
153
-	 * @psalm-return T the saved entity with the (new) id
154
-	 * @throws \InvalidArgumentException if entity has no id
155
-	 * @since 15.0.0
156
-	 */
157
-	public function insertOrUpdate(Entity $entity): Entity {
158
-		try {
159
-			return $this->insert($entity);
160
-		} catch (Exception $ex) {
161
-			if ($ex->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
162
-				return $this->update($entity);
163
-			}
164
-			throw $ex;
165
-		}
166
-	}
167
-
168
-	/**
169
-	 * Updates an entry in the db from an entity
170
-	 * @throws \InvalidArgumentException if entity has no id
171
-	 * @param Entity $entity the entity that should be created
172
-	 * @psalm-param T $entity the entity that should be created
173
-	 * @return Entity the saved entity with the set id
174
-	 * @psalm-return T the saved entity with the set id
175
-	 * @since 14.0.0
176
-	 */
177
-	public function update(Entity $entity): Entity {
178
-		// if entity wasn't changed it makes no sense to run a db query
179
-		$properties = $entity->getUpdatedFields();
180
-		if (\count($properties) === 0) {
181
-			return $entity;
182
-		}
183
-
184
-		// entity needs an id
185
-		$id = $entity->getId();
186
-		if ($id === null) {
187
-			throw new \InvalidArgumentException(
188
-				'Entity which should be updated has no id');
189
-		}
190
-
191
-		// get updated fields to save, fields have to be set using a setter to
192
-		// be saved
193
-		// do not update the id field
194
-		unset($properties['id']);
195
-
196
-		$qb = $this->db->getQueryBuilder();
197
-		$qb->update($this->tableName);
198
-
199
-		// build the fields
200
-		foreach ($properties as $property => $updated) {
201
-			$column = $entity->propertyToColumn($property);
202
-			$getter = 'get' . ucfirst($property);
203
-			$value = $entity->$getter();
204
-
205
-			$type = $this->getParameterTypeForProperty($entity, $property);
206
-			$qb->set($column, $qb->createNamedParameter($value, $type));
207
-		}
208
-
209
-		$idType = $this->getParameterTypeForProperty($entity, 'id');
210
-
211
-		$qb->where(
212
-			$qb->expr()->eq('id', $qb->createNamedParameter($id, $idType))
213
-		);
214
-		$qb->executeStatement();
215
-
216
-		return $entity;
217
-	}
218
-
219
-	/**
220
-	 * Returns the type parameter for the QueryBuilder for a specific property
221
-	 * of the $entity
222
-	 *
223
-	 * @param Entity $entity   The entity to get the types from
224
-	 * @psalm-param T $entity
225
-	 * @param string $property The property of $entity to get the type for
226
-	 * @return int|string
227
-	 * @since 16.0.0
228
-	 */
229
-	protected function getParameterTypeForProperty(Entity $entity, string $property) {
230
-		$types = $entity->getFieldTypes();
231
-
232
-		if (!isset($types[ $property ])) {
233
-			return IQueryBuilder::PARAM_STR;
234
-		}
235
-
236
-		switch ($types[ $property ]) {
237
-			case 'int':
238
-			case 'integer':
239
-				return IQueryBuilder::PARAM_INT;
240
-			case 'string':
241
-				return IQueryBuilder::PARAM_STR;
242
-			case 'bool':
243
-			case 'boolean':
244
-				return IQueryBuilder::PARAM_BOOL;
245
-			case 'blob':
246
-				return IQueryBuilder::PARAM_LOB;
247
-			case 'datetime':
248
-				return IQueryBuilder::PARAM_DATE;
249
-		}
250
-
251
-		return IQueryBuilder::PARAM_STR;
252
-	}
253
-
254
-	/**
255
-	 * Returns an db result and throws exceptions when there are more or less
256
-	 * results
257
-	 *
258
-	 * @see findEntity
259
-	 *
260
-	 * @param IQueryBuilder $query
261
-	 * @throws DoesNotExistException if the item does not exist
262
-	 * @throws MultipleObjectsReturnedException if more than one item exist
263
-	 * @return array the result as row
264
-	 * @since 14.0.0
265
-	 */
266
-	protected function findOneQuery(IQueryBuilder $query): array {
267
-		$result = $query->executeQuery();
268
-
269
-		$row = $result->fetch();
270
-		if ($row === false) {
271
-			$result->closeCursor();
272
-			$msg = $this->buildDebugMessage(
273
-				'Did expect one result but found none when executing', $query
274
-			);
275
-			throw new DoesNotExistException($msg);
276
-		}
277
-
278
-		$row2 = $result->fetch();
279
-		$result->closeCursor();
280
-		if ($row2 !== false) {
281
-			$msg = $this->buildDebugMessage(
282
-				'Did not expect more than one result when executing', $query
283
-			);
284
-			throw new MultipleObjectsReturnedException($msg);
285
-		}
286
-
287
-		return $row;
288
-	}
289
-
290
-	/**
291
-	 * @param string $msg
292
-	 * @param IQueryBuilder $sql
293
-	 * @return string
294
-	 * @since 14.0.0
295
-	 */
296
-	private function buildDebugMessage(string $msg, IQueryBuilder $sql): string {
297
-		return $msg .
298
-			': query "' . $sql->getSQL() . '"; ';
299
-	}
300
-
301
-
302
-	/**
303
-	 * Creates an entity from a row. Automatically determines the entity class
304
-	 * from the current mapper name (MyEntityMapper -> MyEntity)
305
-	 *
306
-	 * @param array $row the row which should be converted to an entity
307
-	 * @return Entity the entity
308
-	 * @psalm-return T the entity
309
-	 * @since 14.0.0
310
-	 */
311
-	protected function mapRowToEntity(array $row): Entity {
312
-		return \call_user_func($this->entityClass .'::fromRow', $row);
313
-	}
314
-
315
-
316
-	/**
317
-	 * Runs a sql query and returns an array of entities
318
-	 *
319
-	 * @param IQueryBuilder $query
320
-	 * @return Entity[] all fetched entities
321
-	 * @psalm-return T[] all fetched entities
322
-	 * @since 14.0.0
323
-	 */
324
-	protected function findEntities(IQueryBuilder $query): array {
325
-		$result = $query->executeQuery();
326
-
327
-		$entities = [];
328
-
329
-		while ($row = $result->fetch()) {
330
-			$entities[] = $this->mapRowToEntity($row);
331
-		}
332
-
333
-		$result->closeCursor();
334
-
335
-		return $entities;
336
-	}
337
-
338
-
339
-	/**
340
-	 * Returns an db result and throws exceptions when there are more or less
341
-	 * results
342
-	 *
343
-	 * @param IQueryBuilder $query
344
-	 * @throws DoesNotExistException if the item does not exist
345
-	 * @throws MultipleObjectsReturnedException if more than one item exist
346
-	 * @return Entity the entity
347
-	 * @psalm-return T the entity
348
-	 * @since 14.0.0
349
-	 */
350
-	protected function findEntity(IQueryBuilder $query): Entity {
351
-		return $this->mapRowToEntity($this->findOneQuery($query));
352
-	}
47
+    /** @var string */
48
+    protected $tableName;
49
+
50
+    /** @var string|class-string<T> */
51
+    protected $entityClass;
52
+
53
+    /** @var IDBConnection */
54
+    protected $db;
55
+
56
+    /**
57
+     * @param IDBConnection $db Instance of the Db abstraction layer
58
+     * @param string $tableName the name of the table. set this to allow entity
59
+     * @param string|null $entityClass the name of the entity that the sql should be
60
+     * @psalm-param class-string<T>|null $entityClass the name of the entity that the sql should be
61
+     * mapped to queries without using sql
62
+     * @since 14.0.0
63
+     */
64
+    public function __construct(IDBConnection $db, string $tableName, string $entityClass = null) {
65
+        $this->db = $db;
66
+        $this->tableName = $tableName;
67
+
68
+        // if not given set the entity name to the class without the mapper part
69
+        // cache it here for later use since reflection is slow
70
+        if ($entityClass === null) {
71
+            $this->entityClass = str_replace('Mapper', '', \get_class($this));
72
+        } else {
73
+            $this->entityClass = $entityClass;
74
+        }
75
+    }
76
+
77
+
78
+    /**
79
+     * @return string the table name
80
+     * @since 14.0.0
81
+     */
82
+    public function getTableName(): string {
83
+        return $this->tableName;
84
+    }
85
+
86
+
87
+    /**
88
+     * Deletes an entity from the table
89
+     * @param Entity $entity the entity that should be deleted
90
+     * @psalm-param T $entity the entity that should be deleted
91
+     * @return Entity the deleted entity
92
+     * @psalm-return T the deleted entity
93
+     * @since 14.0.0
94
+     */
95
+    public function delete(Entity $entity): Entity {
96
+        $qb = $this->db->getQueryBuilder();
97
+
98
+        $idType = $this->getParameterTypeForProperty($entity, 'id');
99
+
100
+        $qb->delete($this->tableName)
101
+            ->where(
102
+                $qb->expr()->eq('id', $qb->createNamedParameter($entity->getId(), $idType))
103
+            );
104
+        $qb->executeStatement();
105
+        return $entity;
106
+    }
107
+
108
+
109
+    /**
110
+     * Creates a new entry in the db from an entity
111
+     * @param Entity $entity the entity that should be created
112
+     * @psalm-param T $entity the entity that should be created
113
+     * @return Entity the saved entity with the set id
114
+     * @psalm-return T the saved entity with the set id
115
+     * @since 14.0.0
116
+     */
117
+    public function insert(Entity $entity): Entity {
118
+        // get updated fields to save, fields have to be set using a setter to
119
+        // be saved
120
+        $properties = $entity->getUpdatedFields();
121
+
122
+        $qb = $this->db->getQueryBuilder();
123
+        $qb->insert($this->tableName);
124
+
125
+        // build the fields
126
+        foreach ($properties as $property => $updated) {
127
+            $column = $entity->propertyToColumn($property);
128
+            $getter = 'get' . ucfirst($property);
129
+            $value = $entity->$getter();
130
+
131
+            $type = $this->getParameterTypeForProperty($entity, $property);
132
+            $qb->setValue($column, $qb->createNamedParameter($value, $type));
133
+        }
134
+
135
+        $qb->executeStatement();
136
+
137
+        if ($entity->id === null) {
138
+            // When autoincrement is used id is always an int
139
+            $entity->setId($qb->getLastInsertId());
140
+        }
141
+
142
+        return $entity;
143
+    }
144
+
145
+    /**
146
+     * Tries to creates a new entry in the db from an entity and
147
+     * updates an existing entry if duplicate keys are detected
148
+     * by the database
149
+     *
150
+     * @param Entity $entity the entity that should be created/updated
151
+     * @psalm-param T $entity the entity that should be created/updated
152
+     * @return Entity the saved entity with the (new) id
153
+     * @psalm-return T the saved entity with the (new) id
154
+     * @throws \InvalidArgumentException if entity has no id
155
+     * @since 15.0.0
156
+     */
157
+    public function insertOrUpdate(Entity $entity): Entity {
158
+        try {
159
+            return $this->insert($entity);
160
+        } catch (Exception $ex) {
161
+            if ($ex->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
162
+                return $this->update($entity);
163
+            }
164
+            throw $ex;
165
+        }
166
+    }
167
+
168
+    /**
169
+     * Updates an entry in the db from an entity
170
+     * @throws \InvalidArgumentException if entity has no id
171
+     * @param Entity $entity the entity that should be created
172
+     * @psalm-param T $entity the entity that should be created
173
+     * @return Entity the saved entity with the set id
174
+     * @psalm-return T the saved entity with the set id
175
+     * @since 14.0.0
176
+     */
177
+    public function update(Entity $entity): Entity {
178
+        // if entity wasn't changed it makes no sense to run a db query
179
+        $properties = $entity->getUpdatedFields();
180
+        if (\count($properties) === 0) {
181
+            return $entity;
182
+        }
183
+
184
+        // entity needs an id
185
+        $id = $entity->getId();
186
+        if ($id === null) {
187
+            throw new \InvalidArgumentException(
188
+                'Entity which should be updated has no id');
189
+        }
190
+
191
+        // get updated fields to save, fields have to be set using a setter to
192
+        // be saved
193
+        // do not update the id field
194
+        unset($properties['id']);
195
+
196
+        $qb = $this->db->getQueryBuilder();
197
+        $qb->update($this->tableName);
198
+
199
+        // build the fields
200
+        foreach ($properties as $property => $updated) {
201
+            $column = $entity->propertyToColumn($property);
202
+            $getter = 'get' . ucfirst($property);
203
+            $value = $entity->$getter();
204
+
205
+            $type = $this->getParameterTypeForProperty($entity, $property);
206
+            $qb->set($column, $qb->createNamedParameter($value, $type));
207
+        }
208
+
209
+        $idType = $this->getParameterTypeForProperty($entity, 'id');
210
+
211
+        $qb->where(
212
+            $qb->expr()->eq('id', $qb->createNamedParameter($id, $idType))
213
+        );
214
+        $qb->executeStatement();
215
+
216
+        return $entity;
217
+    }
218
+
219
+    /**
220
+     * Returns the type parameter for the QueryBuilder for a specific property
221
+     * of the $entity
222
+     *
223
+     * @param Entity $entity   The entity to get the types from
224
+     * @psalm-param T $entity
225
+     * @param string $property The property of $entity to get the type for
226
+     * @return int|string
227
+     * @since 16.0.0
228
+     */
229
+    protected function getParameterTypeForProperty(Entity $entity, string $property) {
230
+        $types = $entity->getFieldTypes();
231
+
232
+        if (!isset($types[ $property ])) {
233
+            return IQueryBuilder::PARAM_STR;
234
+        }
235
+
236
+        switch ($types[ $property ]) {
237
+            case 'int':
238
+            case 'integer':
239
+                return IQueryBuilder::PARAM_INT;
240
+            case 'string':
241
+                return IQueryBuilder::PARAM_STR;
242
+            case 'bool':
243
+            case 'boolean':
244
+                return IQueryBuilder::PARAM_BOOL;
245
+            case 'blob':
246
+                return IQueryBuilder::PARAM_LOB;
247
+            case 'datetime':
248
+                return IQueryBuilder::PARAM_DATE;
249
+        }
250
+
251
+        return IQueryBuilder::PARAM_STR;
252
+    }
253
+
254
+    /**
255
+     * Returns an db result and throws exceptions when there are more or less
256
+     * results
257
+     *
258
+     * @see findEntity
259
+     *
260
+     * @param IQueryBuilder $query
261
+     * @throws DoesNotExistException if the item does not exist
262
+     * @throws MultipleObjectsReturnedException if more than one item exist
263
+     * @return array the result as row
264
+     * @since 14.0.0
265
+     */
266
+    protected function findOneQuery(IQueryBuilder $query): array {
267
+        $result = $query->executeQuery();
268
+
269
+        $row = $result->fetch();
270
+        if ($row === false) {
271
+            $result->closeCursor();
272
+            $msg = $this->buildDebugMessage(
273
+                'Did expect one result but found none when executing', $query
274
+            );
275
+            throw new DoesNotExistException($msg);
276
+        }
277
+
278
+        $row2 = $result->fetch();
279
+        $result->closeCursor();
280
+        if ($row2 !== false) {
281
+            $msg = $this->buildDebugMessage(
282
+                'Did not expect more than one result when executing', $query
283
+            );
284
+            throw new MultipleObjectsReturnedException($msg);
285
+        }
286
+
287
+        return $row;
288
+    }
289
+
290
+    /**
291
+     * @param string $msg
292
+     * @param IQueryBuilder $sql
293
+     * @return string
294
+     * @since 14.0.0
295
+     */
296
+    private function buildDebugMessage(string $msg, IQueryBuilder $sql): string {
297
+        return $msg .
298
+            ': query "' . $sql->getSQL() . '"; ';
299
+    }
300
+
301
+
302
+    /**
303
+     * Creates an entity from a row. Automatically determines the entity class
304
+     * from the current mapper name (MyEntityMapper -> MyEntity)
305
+     *
306
+     * @param array $row the row which should be converted to an entity
307
+     * @return Entity the entity
308
+     * @psalm-return T the entity
309
+     * @since 14.0.0
310
+     */
311
+    protected function mapRowToEntity(array $row): Entity {
312
+        return \call_user_func($this->entityClass .'::fromRow', $row);
313
+    }
314
+
315
+
316
+    /**
317
+     * Runs a sql query and returns an array of entities
318
+     *
319
+     * @param IQueryBuilder $query
320
+     * @return Entity[] all fetched entities
321
+     * @psalm-return T[] all fetched entities
322
+     * @since 14.0.0
323
+     */
324
+    protected function findEntities(IQueryBuilder $query): array {
325
+        $result = $query->executeQuery();
326
+
327
+        $entities = [];
328
+
329
+        while ($row = $result->fetch()) {
330
+            $entities[] = $this->mapRowToEntity($row);
331
+        }
332
+
333
+        $result->closeCursor();
334
+
335
+        return $entities;
336
+    }
337
+
338
+
339
+    /**
340
+     * Returns an db result and throws exceptions when there are more or less
341
+     * results
342
+     *
343
+     * @param IQueryBuilder $query
344
+     * @throws DoesNotExistException if the item does not exist
345
+     * @throws MultipleObjectsReturnedException if more than one item exist
346
+     * @return Entity the entity
347
+     * @psalm-return T the entity
348
+     * @since 14.0.0
349
+     */
350
+    protected function findEntity(IQueryBuilder $query): Entity {
351
+        return $this->mapRowToEntity($this->findOneQuery($query));
352
+    }
353 353
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -125,7 +125,7 @@  discard block
 block discarded – undo
125 125
 		// build the fields
126 126
 		foreach ($properties as $property => $updated) {
127 127
 			$column = $entity->propertyToColumn($property);
128
-			$getter = 'get' . ucfirst($property);
128
+			$getter = 'get'.ucfirst($property);
129 129
 			$value = $entity->$getter();
130 130
 
131 131
 			$type = $this->getParameterTypeForProperty($entity, $property);
@@ -199,7 +199,7 @@  discard block
 block discarded – undo
199 199
 		// build the fields
200 200
 		foreach ($properties as $property => $updated) {
201 201
 			$column = $entity->propertyToColumn($property);
202
-			$getter = 'get' . ucfirst($property);
202
+			$getter = 'get'.ucfirst($property);
203 203
 			$value = $entity->$getter();
204 204
 
205 205
 			$type = $this->getParameterTypeForProperty($entity, $property);
@@ -229,11 +229,11 @@  discard block
 block discarded – undo
229 229
 	protected function getParameterTypeForProperty(Entity $entity, string $property) {
230 230
 		$types = $entity->getFieldTypes();
231 231
 
232
-		if (!isset($types[ $property ])) {
232
+		if (!isset($types[$property])) {
233 233
 			return IQueryBuilder::PARAM_STR;
234 234
 		}
235 235
 
236
-		switch ($types[ $property ]) {
236
+		switch ($types[$property]) {
237 237
 			case 'int':
238 238
 			case 'integer':
239 239
 				return IQueryBuilder::PARAM_INT;
@@ -294,8 +294,8 @@  discard block
 block discarded – undo
294 294
 	 * @since 14.0.0
295 295
 	 */
296 296
 	private function buildDebugMessage(string $msg, IQueryBuilder $sql): string {
297
-		return $msg .
298
-			': query "' . $sql->getSQL() . '"; ';
297
+		return $msg.
298
+			': query "'.$sql->getSQL().'"; ';
299 299
 	}
300 300
 
301 301
 
@@ -309,7 +309,7 @@  discard block
 block discarded – undo
309 309
 	 * @since 14.0.0
310 310
 	 */
311 311
 	protected function mapRowToEntity(array $row): Entity {
312
-		return \call_user_func($this->entityClass .'::fromRow', $row);
312
+		return \call_user_func($this->entityClass.'::fromRow', $row);
313 313
 	}
314 314
 
315 315
 
Please login to merge, or discard this patch.