Skip to content

Commit

Permalink
Add AppFramework/Db base classes
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
rullzer committed Feb 6, 2020
1 parent d2d7e37 commit 472989f
Show file tree
Hide file tree
Showing 6 changed files with 563 additions and 423 deletions.
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
'OCP\\AppFramework\\App' => $baseDir . '/lib/public/AppFramework/App.php',
'OCP\\AppFramework\\AuthPublicShareController' => $baseDir . '/lib/public/AppFramework/AuthPublicShareController.php',
'OCP\\AppFramework\\Controller' => $baseDir . '/lib/public/AppFramework/Controller.php',
'OCP\\AppFramework\\Db\\BaseEntity' => $baseDir . '/lib/public/AppFramework/Db/BaseEntity.php',
'OCP\\AppFramework\\Db\\DoesNotExistException' => $baseDir . '/lib/public/AppFramework/Db/DoesNotExistException.php',
'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.php',
'OCP\\AppFramework\\Db\\IMapperException' => $baseDir . '/lib/public/AppFramework/Db/IMapperException.php',
'OCP\\AppFramework\\Db\\Mapper' => $baseDir . '/lib/public/AppFramework/Db/Mapper.php',
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => $baseDir . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
'OCP\\AppFramework\\Db\\QBBaseMapper' => $baseDir . '/lib/public/AppFramework/Db/QBBaseMapper.php',
'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php',
'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php',
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\AppFramework\\App' => __DIR__ . '/../../..' . '/lib/public/AppFramework/App.php',
'OCP\\AppFramework\\AuthPublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/AuthPublicShareController.php',
'OCP\\AppFramework\\Controller' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Controller.php',
'OCP\\AppFramework\\Db\\BaseEntity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/BaseEntity.php',
'OCP\\AppFramework\\Db\\DoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/DoesNotExistException.php',
'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.php',
'OCP\\AppFramework\\Db\\IMapperException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/IMapperException.php',
'OCP\\AppFramework\\Db\\Mapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Mapper.php',
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
'OCP\\AppFramework\\Db\\QBBaseMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBBaseMapper.php',
'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php',
'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php',
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
Expand Down
272 changes: 272 additions & 0 deletions lib/public/AppFramework/Db/BaseEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCP\AppFramework\Db;

/**
* Base entity class that does not require a id column
*
* @since 18.0.0
*/
class BaseEntity {

private $_updatedFields = [];
private $_fieldTypes = [];


/**
* Simple alternative constructor for building entities from a request
* @param array $params the array which was obtained via $this->params('key')
* in the controller
* @return BaseEntity
* @since 7.0.0
*/
public static function fromParams(array $params) {
$instance = new static();

foreach($params as $key => $value) {
$setter = 'set' . ucfirst($key);
$value = $instance->convertToType($key, $value);
$instance->$setter($value);
}

return $instance;
}


/**
* Maps the keys of the row array to the attributes
* @param array $row the row to map onto the entity
* @since 7.0.0
* @return BaseEntity
*/
public static function fromRow(array $row){
$instance = new static();

foreach($row as $key => $value){
$key = $instance->columnToProperty($key);
$setter = 'set' . ucfirst($key);
$value = $instance->convertToType($key, $value);
$instance->$setter($value);
}

$instance->resetUpdatedFields();

return $instance;
}

/**
* @return array with attribute and type
* @since 7.0.0
*/
public function getFieldTypes(): array {
return $this->_fieldTypes;
}


/**
* Marks the entity as clean needed for setting the id after the insertion
* @since 7.0.0
*/
public function resetUpdatedFields(): void {
$this->_updatedFields = [];
}

/**
* Generic setter for properties
* @since 7.0.0
*/
protected function setter($name, $args) {
// setters should only work for existing attributes
if(property_exists($this, $name)){
if($this->$name === $args[0]) {
return;
}
$this->markFieldUpdated($name);

// if type definition exists, cast to correct type
$value = $this->convertToType($name, $args[0]);
$this->$name = $value;

} else {
throw new \BadFunctionCallException($name .
' is not a valid attribute');
}
}

/**
* Generic getter for properties
* @since 7.0.0
*/
protected function getter($name) {
// getters should only work for existing attributes
if(property_exists($this, $name)){
return $this->$name;
} else {
throw new \BadFunctionCallException($name .
' is not a valid attribute');
}
}


/**
* Each time a setter is called, push the part after set
* into an array: for instance setId will save Id in the
* updated fields array so it can be easily used to create the
* getter method
* @since 7.0.0
*/
public function __call($methodName, $args){
if(strpos($methodName, 'set') === 0){
$this->setter(lcfirst(substr($methodName, 3)), $args);
} elseif(strpos($methodName, 'get') === 0) {
return $this->getter(lcfirst(substr($methodName, 3)));
} elseif ($this->isGetterForBoolProperty($methodName)) {
return $this->getter(lcfirst(substr($methodName, 2)));
} else {
throw new \BadFunctionCallException($methodName .
' does not exist');
}
}

/**
* @param string $methodName
* @return bool
* @since 18.0.0
*/
protected function isGetterForBoolProperty(string $methodName): bool {
if (strpos($methodName, 'is') === 0) {
$fieldName = lcfirst(substr($methodName, 2));
return isset($this->_fieldTypes[$fieldName]) && strpos($this->_fieldTypes[$fieldName], 'bool') === 0;
}
return false;
}


/**
* Mark am attribute as updated
* @param string $attribute the name of the attribute
* @since 7.0.0
*/
protected function markFieldUpdated($attribute){
$this->_updatedFields[$attribute] = true;
}


/**
* Transform a database columnname to a property
* @param string $columnName the name of the column
* @return string the property name
* @since 7.0.0
*/
public function columnToProperty($columnName){
$parts = explode('_', $columnName);
$property = null;

foreach($parts as $part){
if($property === null){
$property = $part;
} else {
$property .= ucfirst($part);
}
}

return $property;
}


/**
* Transform a property to a database column name
* @param string $property the name of the property
* @return string the column name
* @since 7.0.0
*/
public function propertyToColumn($property){
$parts = preg_split('/(?=[A-Z])/', $property);
$column = null;

foreach($parts as $part){
if($column === null){
$column = $part;
} else {
$column .= '_' . lcfirst($part);
}
}

return $column;
}


/**
* @return array array of updated fields for update query
* @since 7.0.0
*/
public function getUpdatedFields(){
return $this->_updatedFields;
}


/**
* Adds type information for a field so that its automatically casted to
* that value once its being returned from the database
* @param string $fieldName the name of the attribute
* @param string $type the type which will be used to call settype()
* @since 7.0.0
*/
protected function addType(string $fieldName, string $type){
$this->_fieldTypes[$fieldName] = $type;
}


/**
* Slugify the value of a given attribute
* Warning: This doesn't result in a unique value
* @param string $attributeName the name of the attribute, which value should be slugified
* @return string slugified value
* @since 7.0.0
*/
public function slugify(string $attributeName): string {
// toSlug should only work for existing attributes
if(property_exists($this, $attributeName)){
$value = $this->$attributeName;
// replace everything except alphanumeric with a single '-'
$value = preg_replace('/[^A-Za-z0-9]+/', '-', $value);
$value = strtolower($value);
// trim '-'
return trim($value, '-');
} else {
throw new \BadFunctionCallException($attributeName .
' is not a valid attribute');
}
}

protected function convertToType(string $name, $value) {
if ($value !== null && array_key_exists($name, $this->getFieldTypes())) {
settype($value, $this->getFieldTypes()[$name]);
}

return $value;
}

}
Loading

0 comments on commit 472989f

Please sign in to comment.