Skip to content

Commit 472989f

Browse files
committed
Add AppFramework/Db base classes
This will allow apps to use Entities that do not have an id attribute. They can have other unique columns forcing an id is not ideal in all cases. For this two base classes are introduced and the current Entity and QBMapper are actually based on the base classes. The Entity is really simple and just adds the id property. The BaseMapper is a bit less clean. As we can't know the where clause for insert/delete or update parts. Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
1 parent d2d7e37 commit 472989f

File tree

6 files changed

+563
-423
lines changed

6 files changed

+563
-423
lines changed

lib/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
'OCP\\AppFramework\\App' => $baseDir . '/lib/public/AppFramework/App.php',
2525
'OCP\\AppFramework\\AuthPublicShareController' => $baseDir . '/lib/public/AppFramework/AuthPublicShareController.php',
2626
'OCP\\AppFramework\\Controller' => $baseDir . '/lib/public/AppFramework/Controller.php',
27+
'OCP\\AppFramework\\Db\\BaseEntity' => $baseDir . '/lib/public/AppFramework/Db/BaseEntity.php',
2728
'OCP\\AppFramework\\Db\\DoesNotExistException' => $baseDir . '/lib/public/AppFramework/Db/DoesNotExistException.php',
2829
'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.php',
2930
'OCP\\AppFramework\\Db\\IMapperException' => $baseDir . '/lib/public/AppFramework/Db/IMapperException.php',
3031
'OCP\\AppFramework\\Db\\Mapper' => $baseDir . '/lib/public/AppFramework/Db/Mapper.php',
3132
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => $baseDir . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
33+
'OCP\\AppFramework\\Db\\QBBaseMapper' => $baseDir . '/lib/public/AppFramework/Db/QBBaseMapper.php',
3234
'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php',
3335
'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php',
3436
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',

lib/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
5353
'OCP\\AppFramework\\App' => __DIR__ . '/../../..' . '/lib/public/AppFramework/App.php',
5454
'OCP\\AppFramework\\AuthPublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/AuthPublicShareController.php',
5555
'OCP\\AppFramework\\Controller' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Controller.php',
56+
'OCP\\AppFramework\\Db\\BaseEntity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/BaseEntity.php',
5657
'OCP\\AppFramework\\Db\\DoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/DoesNotExistException.php',
5758
'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.php',
5859
'OCP\\AppFramework\\Db\\IMapperException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/IMapperException.php',
5960
'OCP\\AppFramework\\Db\\Mapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Mapper.php',
6061
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
62+
'OCP\\AppFramework\\Db\\QBBaseMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBBaseMapper.php',
6163
'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php',
6264
'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php',
6365
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
<?php
2+
declare(strict_types=1);
3+
/**
4+
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
5+
*
6+
* @author Roeland Jago Douma <roeland@famdouma.nl>
7+
*
8+
* @license GNU AGPL version 3 or any later version
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU Affero General Public License as
12+
* published by the Free Software Foundation, either version 3 of the
13+
* License, or (at your option) any later version.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Affero General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Affero General Public License
21+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
*
23+
*/
24+
25+
namespace OCP\AppFramework\Db;
26+
27+
/**
28+
* Base entity class that does not require a id column
29+
*
30+
* @since 18.0.0
31+
*/
32+
class BaseEntity {
33+
34+
private $_updatedFields = [];
35+
private $_fieldTypes = [];
36+
37+
38+
/**
39+
* Simple alternative constructor for building entities from a request
40+
* @param array $params the array which was obtained via $this->params('key')
41+
* in the controller
42+
* @return BaseEntity
43+
* @since 7.0.0
44+
*/
45+
public static function fromParams(array $params) {
46+
$instance = new static();
47+
48+
foreach($params as $key => $value) {
49+
$setter = 'set' . ucfirst($key);
50+
$value = $instance->convertToType($key, $value);
51+
$instance->$setter($value);
52+
}
53+
54+
return $instance;
55+
}
56+
57+
58+
/**
59+
* Maps the keys of the row array to the attributes
60+
* @param array $row the row to map onto the entity
61+
* @since 7.0.0
62+
* @return BaseEntity
63+
*/
64+
public static function fromRow(array $row){
65+
$instance = new static();
66+
67+
foreach($row as $key => $value){
68+
$key = $instance->columnToProperty($key);
69+
$setter = 'set' . ucfirst($key);
70+
$value = $instance->convertToType($key, $value);
71+
$instance->$setter($value);
72+
}
73+
74+
$instance->resetUpdatedFields();
75+
76+
return $instance;
77+
}
78+
79+
/**
80+
* @return array with attribute and type
81+
* @since 7.0.0
82+
*/
83+
public function getFieldTypes(): array {
84+
return $this->_fieldTypes;
85+
}
86+
87+
88+
/**
89+
* Marks the entity as clean needed for setting the id after the insertion
90+
* @since 7.0.0
91+
*/
92+
public function resetUpdatedFields(): void {
93+
$this->_updatedFields = [];
94+
}
95+
96+
/**
97+
* Generic setter for properties
98+
* @since 7.0.0
99+
*/
100+
protected function setter($name, $args) {
101+
// setters should only work for existing attributes
102+
if(property_exists($this, $name)){
103+
if($this->$name === $args[0]) {
104+
return;
105+
}
106+
$this->markFieldUpdated($name);
107+
108+
// if type definition exists, cast to correct type
109+
$value = $this->convertToType($name, $args[0]);
110+
$this->$name = $value;
111+
112+
} else {
113+
throw new \BadFunctionCallException($name .
114+
' is not a valid attribute');
115+
}
116+
}
117+
118+
/**
119+
* Generic getter for properties
120+
* @since 7.0.0
121+
*/
122+
protected function getter($name) {
123+
// getters should only work for existing attributes
124+
if(property_exists($this, $name)){
125+
return $this->$name;
126+
} else {
127+
throw new \BadFunctionCallException($name .
128+
' is not a valid attribute');
129+
}
130+
}
131+
132+
133+
/**
134+
* Each time a setter is called, push the part after set
135+
* into an array: for instance setId will save Id in the
136+
* updated fields array so it can be easily used to create the
137+
* getter method
138+
* @since 7.0.0
139+
*/
140+
public function __call($methodName, $args){
141+
if(strpos($methodName, 'set') === 0){
142+
$this->setter(lcfirst(substr($methodName, 3)), $args);
143+
} elseif(strpos($methodName, 'get') === 0) {
144+
return $this->getter(lcfirst(substr($methodName, 3)));
145+
} elseif ($this->isGetterForBoolProperty($methodName)) {
146+
return $this->getter(lcfirst(substr($methodName, 2)));
147+
} else {
148+
throw new \BadFunctionCallException($methodName .
149+
' does not exist');
150+
}
151+
}
152+
153+
/**
154+
* @param string $methodName
155+
* @return bool
156+
* @since 18.0.0
157+
*/
158+
protected function isGetterForBoolProperty(string $methodName): bool {
159+
if (strpos($methodName, 'is') === 0) {
160+
$fieldName = lcfirst(substr($methodName, 2));
161+
return isset($this->_fieldTypes[$fieldName]) && strpos($this->_fieldTypes[$fieldName], 'bool') === 0;
162+
}
163+
return false;
164+
}
165+
166+
167+
/**
168+
* Mark am attribute as updated
169+
* @param string $attribute the name of the attribute
170+
* @since 7.0.0
171+
*/
172+
protected function markFieldUpdated($attribute){
173+
$this->_updatedFields[$attribute] = true;
174+
}
175+
176+
177+
/**
178+
* Transform a database columnname to a property
179+
* @param string $columnName the name of the column
180+
* @return string the property name
181+
* @since 7.0.0
182+
*/
183+
public function columnToProperty($columnName){
184+
$parts = explode('_', $columnName);
185+
$property = null;
186+
187+
foreach($parts as $part){
188+
if($property === null){
189+
$property = $part;
190+
} else {
191+
$property .= ucfirst($part);
192+
}
193+
}
194+
195+
return $property;
196+
}
197+
198+
199+
/**
200+
* Transform a property to a database column name
201+
* @param string $property the name of the property
202+
* @return string the column name
203+
* @since 7.0.0
204+
*/
205+
public function propertyToColumn($property){
206+
$parts = preg_split('/(?=[A-Z])/', $property);
207+
$column = null;
208+
209+
foreach($parts as $part){
210+
if($column === null){
211+
$column = $part;
212+
} else {
213+
$column .= '_' . lcfirst($part);
214+
}
215+
}
216+
217+
return $column;
218+
}
219+
220+
221+
/**
222+
* @return array array of updated fields for update query
223+
* @since 7.0.0
224+
*/
225+
public function getUpdatedFields(){
226+
return $this->_updatedFields;
227+
}
228+
229+
230+
/**
231+
* Adds type information for a field so that its automatically casted to
232+
* that value once its being returned from the database
233+
* @param string $fieldName the name of the attribute
234+
* @param string $type the type which will be used to call settype()
235+
* @since 7.0.0
236+
*/
237+
protected function addType(string $fieldName, string $type){
238+
$this->_fieldTypes[$fieldName] = $type;
239+
}
240+
241+
242+
/**
243+
* Slugify the value of a given attribute
244+
* Warning: This doesn't result in a unique value
245+
* @param string $attributeName the name of the attribute, which value should be slugified
246+
* @return string slugified value
247+
* @since 7.0.0
248+
*/
249+
public function slugify(string $attributeName): string {
250+
// toSlug should only work for existing attributes
251+
if(property_exists($this, $attributeName)){
252+
$value = $this->$attributeName;
253+
// replace everything except alphanumeric with a single '-'
254+
$value = preg_replace('/[^A-Za-z0-9]+/', '-', $value);
255+
$value = strtolower($value);
256+
// trim '-'
257+
return trim($value, '-');
258+
} else {
259+
throw new \BadFunctionCallException($attributeName .
260+
' is not a valid attribute');
261+
}
262+
}
263+
264+
protected function convertToType(string $name, $value) {
265+
if ($value !== null && array_key_exists($name, $this->getFieldTypes())) {
266+
settype($value, $this->getFieldTypes()[$name]);
267+
}
268+
269+
return $value;
270+
}
271+
272+
}

0 commit comments

Comments
 (0)