Compare commits
1 Commits
master
...
enh/appfra
Author | SHA1 | Date |
---|---|---|
Roeland Jago Douma | c44511b099 |
|
@ -24,6 +24,8 @@ return array(
|
|||
'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\\AQBBaseMapper' => $baseDir . '/lib/public/AppFramework/Db/AQBBaseMapper.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',
|
||||
|
|
|
@ -53,6 +53,8 @@ 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\\AQBBaseMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/AQBBaseMapper.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',
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
<?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;
|
||||
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/**
|
||||
* Simple parent class for inheriting your data access layer from. This class
|
||||
* may be subject to change in the future
|
||||
*
|
||||
* @since 19.0.0
|
||||
*/
|
||||
abstract class AQBBaseMapper {
|
||||
|
||||
/** @var string */
|
||||
protected $tableName;
|
||||
|
||||
/** @var string */
|
||||
protected $entityClass;
|
||||
|
||||
/** @var IDBConnection */
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @param IDBConnection $db Instance of the Db abstraction layer
|
||||
* @param string $tableName the name of the table. set this to allow entity
|
||||
* @param string $entityClass the name of the entity that the sql should be
|
||||
* mapped to queries without using sql
|
||||
* @since 14.0.0
|
||||
*/
|
||||
public function __construct(IDBConnection $db, string $tableName, string $entityClass = null) {
|
||||
$this->db = $db;
|
||||
$this->tableName = $tableName;
|
||||
|
||||
// if not given set the entity name to the class without the mapper part
|
||||
// cache it here for later use since reflection is slow
|
||||
if ($entityClass === null) {
|
||||
$this->entityClass = str_replace('Mapper', '', \get_class($this));
|
||||
} else {
|
||||
$this->entityClass = $entityClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string the table name
|
||||
* @since 14.0.0
|
||||
*/
|
||||
public function getTableName(): string {
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an entity from the table
|
||||
* @param BaseEntity $entity the entity that should be deleted
|
||||
* @return BaseEntity the deleted entity
|
||||
* @since 14.0.0
|
||||
*/
|
||||
abstract public function delete(BaseEntity $entity): BaseEntity;
|
||||
|
||||
/**
|
||||
* Creates a new entry in the db from an entity
|
||||
* @param BaseEntity $entity the entity that should be created
|
||||
* @return BaseEntity the saved entity with the set id
|
||||
* @since 14.0.0
|
||||
* @suppress SqlInjectionChecker
|
||||
*/
|
||||
public function insert(BaseEntity $entity): BaseEntity {
|
||||
// get updated fields to save, fields have to be set using a setter to
|
||||
// be saved
|
||||
$properties = $entity->getUpdatedFields();
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->insert($this->tableName);
|
||||
|
||||
// build the fields
|
||||
foreach($properties as $property => $updated) {
|
||||
$column = $entity->propertyToColumn($property);
|
||||
$getter = 'get' . ucfirst($property);
|
||||
$value = $entity->$getter();
|
||||
|
||||
$type = $this->getParameterTypeForProperty($entity, $property);
|
||||
$qb->setValue($column, $qb->createNamedParameter($value, $type));
|
||||
}
|
||||
$qb->execute();
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to creates a new entry in the db from an entity and
|
||||
* updates an existing entry if duplicate keys are detected
|
||||
* by the database
|
||||
*
|
||||
* @param Entity $entity the entity that should be created/updated
|
||||
* @return Entity the saved entity with the (new) id
|
||||
* @throws \InvalidArgumentException if entity has no id
|
||||
* @since 15.0.0
|
||||
* @suppress SqlInjectionChecker
|
||||
*/
|
||||
public function insertOrUpdate(Entity $entity): Entity {
|
||||
try {
|
||||
return $this->insert($entity);
|
||||
} catch (UniqueConstraintViolationException $ex) {
|
||||
return $this->update($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entry in the db from an entity
|
||||
* @throws \InvalidArgumentException if entity has no id
|
||||
* @param Entity $entity the entity that should be created
|
||||
* @return Entity the saved entity with the set id
|
||||
* @since 14.0.0
|
||||
* @suppress SqlInjectionChecker
|
||||
*/
|
||||
abstract public function update(BaseEntity $entity): BaseEntity;
|
||||
|
||||
/**
|
||||
* Returns the type parameter for the QueryBuilder for a specific property
|
||||
* of the $entity
|
||||
*
|
||||
* @param BaseEntity $entity The entity to get the types from
|
||||
* @param string $property The property of $entity to get the type for
|
||||
* @return int
|
||||
* @since 16.0.0
|
||||
*/
|
||||
protected function getParameterTypeForProperty(BaseEntity $entity, string $property): int {
|
||||
$types = $entity->getFieldTypes();
|
||||
|
||||
if (!isset($types[$property])) {
|
||||
return IQueryBuilder::PARAM_STR;
|
||||
}
|
||||
|
||||
switch ($types[$property]) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return IQueryBuilder::PARAM_INT;
|
||||
case 'string':
|
||||
return IQueryBuilder::PARAM_STR;
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
return IQueryBuilder::PARAM_BOOL;
|
||||
}
|
||||
|
||||
return IQueryBuilder::PARAM_STR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an db result and throws exceptions when there are more or less
|
||||
* results
|
||||
*
|
||||
* @param IQueryBuilder $query
|
||||
* @return array the result as row
|
||||
* @throws MultipleObjectsReturnedException if more than one item exist
|
||||
* @throws DoesNotExistException if the item does not exist
|
||||
* @see findEntity
|
||||
*
|
||||
* @since 14.0.0
|
||||
*/
|
||||
protected function findOneQuery(IQueryBuilder $query): array {
|
||||
$cursor = $query->execute();
|
||||
|
||||
$row = $cursor->fetch();
|
||||
if ($row === false) {
|
||||
$cursor->closeCursor();
|
||||
$msg = $this->buildDebugMessage(
|
||||
'Did expect one result but found none when executing', $query
|
||||
);
|
||||
throw new DoesNotExistException($msg);
|
||||
}
|
||||
|
||||
$row2 = $cursor->fetch();
|
||||
$cursor->closeCursor();
|
||||
if ($row2 !== false) {
|
||||
$msg = $this->buildDebugMessage(
|
||||
'Did not expect more than one result when executing', $query
|
||||
);
|
||||
throw new MultipleObjectsReturnedException($msg);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $msg
|
||||
* @param IQueryBuilder $sql
|
||||
* @return string
|
||||
* @since 14.0.0
|
||||
*/
|
||||
private function buildDebugMessage(string $msg, IQueryBuilder $sql): string {
|
||||
return $msg .
|
||||
': query "' . $sql->getSQL() . '"; ';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an entity from a row. Automatically determines the entity class
|
||||
* from the current mapper name (MyEntityMapper -> MyEntity)
|
||||
*
|
||||
* @param array $row the row which should be converted to an entity
|
||||
* @return BaseEntity the entity
|
||||
* @since 14.0.0
|
||||
*/
|
||||
protected function mapRowToEntity(array $row): BaseEntity {
|
||||
return \call_user_func($this->entityClass . '::fromRow', $row);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs a sql query and returns an array of entities
|
||||
*
|
||||
* @param IQueryBuilder $query
|
||||
* @return BaseEntity[] all fetched entities
|
||||
* @since 14.0.0
|
||||
*/
|
||||
protected function findEntities(IQueryBuilder $query): array {
|
||||
$cursor = $query->execute();
|
||||
|
||||
$entities = [];
|
||||
|
||||
while ($row = $cursor->fetch()) {
|
||||
$entities[] = $this->mapRowToEntity($row);
|
||||
}
|
||||
|
||||
$cursor->closeCursor();
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an db result and throws exceptions when there are more or less
|
||||
* results
|
||||
*
|
||||
* @param IQueryBuilder $query
|
||||
* @return BaseEntity the entity
|
||||
* @throws MultipleObjectsReturnedException if more than one item exist
|
||||
* @throws DoesNotExistException if the item does not exist
|
||||
* @since 14.0.0
|
||||
*/
|
||||
protected function findEntity(IQueryBuilder $query): BaseEntity {
|
||||
return $this->mapRowToEntity($this->findOneQuery($query));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
<?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 19.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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 19.0.0
|
||||
*/
|
||||
protected function convertToType(string $name, $value) {
|
||||
if ($value !== null && array_key_exists($name, $this->getFieldTypes())) {
|
||||
settype($value, $this->getFieldTypes()[$name]);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
|
@ -34,238 +34,18 @@ use function substr;
|
|||
* @method void setId(integer $id)
|
||||
* @since 7.0.0
|
||||
*/
|
||||
abstract class Entity {
|
||||
abstract class Entity extends BaseEntity {
|
||||
|
||||
/** @var int */
|
||||
public $id;
|
||||
|
||||
private $_updatedFields = array();
|
||||
private $_fieldTypes = array('id' => 'integer');
|
||||
|
||||
|
||||
/**
|
||||
* 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 Entity
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public static function fromParams(array $params) {
|
||||
$instance = new static();
|
||||
|
||||
foreach($params as $key => $value) {
|
||||
$method = 'set' . ucfirst($key);
|
||||
$instance->$method($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
|
||||
*/
|
||||
public static function fromRow(array $row){
|
||||
$instance = new static();
|
||||
|
||||
foreach($row as $key => $value){
|
||||
$prop = ucfirst($instance->columnToProperty($key));
|
||||
$setter = 'set' . $prop;
|
||||
$instance->$setter($value);
|
||||
}
|
||||
|
||||
$instance->resetUpdatedFields();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array with attribute and type
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public function getFieldTypes() {
|
||||
return $this->_fieldTypes;
|
||||
public function getFieldTypes(): array {
|
||||
$types = parent::getFieldTypes();
|
||||
$types['id'] = 'integer';
|
||||
return $types;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Marks the entity as clean needed for setting the id after the insertion
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public function resetUpdatedFields(){
|
||||
$this->_updatedFields = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
if($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) {
|
||||
settype($args[0], $this->_fieldTypes[$name]);
|
||||
}
|
||||
$this->$name = $args[0];
|
||||
|
||||
} 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($fieldName, $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($attributeName){
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ namespace OCP\AppFramework\Db;
|
|||
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/**
|
||||
* Simple parent class for inheriting your data access layer from. This class
|
||||
|
@ -39,54 +38,15 @@ use OCP\IDBConnection;
|
|||
*
|
||||
* @since 14.0.0
|
||||
*/
|
||||
abstract class QBMapper {
|
||||
|
||||
/** @var string */
|
||||
protected $tableName;
|
||||
|
||||
/** @var string */
|
||||
protected $entityClass;
|
||||
|
||||
/** @var IDBConnection */
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @param IDBConnection $db Instance of the Db abstraction layer
|
||||
* @param string $tableName the name of the table. set this to allow entity
|
||||
* @param string $entityClass the name of the entity that the sql should be
|
||||
* mapped to queries without using sql
|
||||
* @since 14.0.0
|
||||
*/
|
||||
public function __construct(IDBConnection $db, string $tableName, string $entityClass=null){
|
||||
$this->db = $db;
|
||||
$this->tableName = $tableName;
|
||||
|
||||
// if not given set the entity name to the class without the mapper part
|
||||
// cache it here for later use since reflection is slow
|
||||
if($entityClass === null) {
|
||||
$this->entityClass = str_replace('Mapper', '', \get_class($this));
|
||||
} else {
|
||||
$this->entityClass = $entityClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string the table name
|
||||
* @since 14.0.0
|
||||
*/
|
||||
public function getTableName(): string {
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
abstract class QBMapper extends AQBBaseMapper {
|
||||
|
||||
/**
|
||||
* Deletes an entity from the table
|
||||
* @param Entity $entity the entity that should be deleted
|
||||
* @return Entity the deleted entity
|
||||
* @param BaseEntity $entity the entity that should be deleted
|
||||
* @return BaseEntity the deleted entity
|
||||
* @since 14.0.0
|
||||
*/
|
||||
public function delete(Entity $entity): Entity {
|
||||
public function delete(BaseEntity $entity): BaseEntity {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->delete($this->tableName)
|
||||
|
@ -100,12 +60,12 @@ abstract class QBMapper {
|
|||
|
||||
/**
|
||||
* Creates a new entry in the db from an entity
|
||||
* @param Entity $entity the entity that should be created
|
||||
* @return Entity the saved entity with the set id
|
||||
* @param BaseEntity $entity the entity that should be created
|
||||
* @return BaseEntity the saved entity with the set id
|
||||
* @since 14.0.0
|
||||
* @suppress SqlInjectionChecker
|
||||
*/
|
||||
public function insert(Entity $entity): Entity {
|
||||
public function insert(BaseEntity $entity): BaseEntity {
|
||||
// get updated fields to save, fields have to be set using a setter to
|
||||
// be saved
|
||||
$properties = $entity->getUpdatedFields();
|
||||
|
@ -132,34 +92,15 @@ abstract class QBMapper {
|
|||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to creates a new entry in the db from an entity and
|
||||
* updates an existing entry if duplicate keys are detected
|
||||
* by the database
|
||||
*
|
||||
* @param Entity $entity the entity that should be created/updated
|
||||
* @return Entity the saved entity with the (new) id
|
||||
* @throws \InvalidArgumentException if entity has no id
|
||||
* @since 15.0.0
|
||||
* @suppress SqlInjectionChecker
|
||||
*/
|
||||
public function insertOrUpdate(Entity $entity): Entity {
|
||||
try {
|
||||
return $this->insert($entity);
|
||||
} catch (UniqueConstraintViolationException $ex) {
|
||||
return $this->update($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entry in the db from an entity
|
||||
* @throws \InvalidArgumentException if entity has no id
|
||||
* @param Entity $entity the entity that should be created
|
||||
* @return Entity the saved entity with the set id
|
||||
* @param BaseEntity $entity the entity that should be created
|
||||
* @return BaseEntity the saved entity with the set id
|
||||
* @since 14.0.0
|
||||
* @suppress SqlInjectionChecker
|
||||
*/
|
||||
public function update(Entity $entity): Entity {
|
||||
public function update(BaseEntity $entity): BaseEntity {
|
||||
// if entity wasn't changed it makes no sense to run a db query
|
||||
$properties = $entity->getUpdatedFields();
|
||||
if(\count($properties) === 0) {
|
||||
|
@ -198,132 +139,4 @@ abstract class QBMapper {
|
|||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type parameter for the QueryBuilder for a specific property
|
||||
* of the $entity
|
||||
*
|
||||
* @param Entity $entity The entity to get the types from
|
||||
* @param string $property The property of $entity to get the type for
|
||||
* @return int
|
||||
* @since 16.0.0
|
||||
*/
|
||||
protected function getParameterTypeForProperty(Entity $entity, string $property): int {
|
||||
$types = $entity->getFieldTypes();
|
||||
|
||||
if(!isset($types[ $property ])) {
|
||||
return IQueryBuilder::PARAM_STR;
|
||||
}
|
||||
|
||||
switch($types[ $property ]) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return IQueryBuilder::PARAM_INT;
|
||||
case 'string':
|
||||
return IQueryBuilder::PARAM_STR;
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
return IQueryBuilder::PARAM_BOOL;
|
||||
}
|
||||
|
||||
return IQueryBuilder::PARAM_STR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an db result and throws exceptions when there are more or less
|
||||
* results
|
||||
*
|
||||
* @see findEntity
|
||||
*
|
||||
* @param IQueryBuilder $query
|
||||
* @throws DoesNotExistException if the item does not exist
|
||||
* @throws MultipleObjectsReturnedException if more than one item exist
|
||||
* @return array the result as row
|
||||
* @since 14.0.0
|
||||
*/
|
||||
protected function findOneQuery(IQueryBuilder $query): array {
|
||||
$cursor = $query->execute();
|
||||
|
||||
$row = $cursor->fetch();
|
||||
if($row === false) {
|
||||
$cursor->closeCursor();
|
||||
$msg = $this->buildDebugMessage(
|
||||
'Did expect one result but found none when executing', $query
|
||||
);
|
||||
throw new DoesNotExistException($msg);
|
||||
}
|
||||
|
||||
$row2 = $cursor->fetch();
|
||||
$cursor->closeCursor();
|
||||
if($row2 !== false ) {
|
||||
$msg = $this->buildDebugMessage(
|
||||
'Did not expect more than one result when executing', $query
|
||||
);
|
||||
throw new MultipleObjectsReturnedException($msg);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $msg
|
||||
* @param IQueryBuilder $sql
|
||||
* @return string
|
||||
* @since 14.0.0
|
||||
*/
|
||||
private function buildDebugMessage(string $msg, IQueryBuilder $sql): string {
|
||||
return $msg .
|
||||
': query "' . $sql->getSQL() . '"; ';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an entity from a row. Automatically determines the entity class
|
||||
* from the current mapper name (MyEntityMapper -> MyEntity)
|
||||
*
|
||||
* @param array $row the row which should be converted to an entity
|
||||
* @return Entity the entity
|
||||
* @since 14.0.0
|
||||
*/
|
||||
protected function mapRowToEntity(array $row): Entity {
|
||||
return \call_user_func($this->entityClass .'::fromRow', $row);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs a sql query and returns an array of entities
|
||||
*
|
||||
* @param IQueryBuilder $query
|
||||
* @return Entity[] all fetched entities
|
||||
* @since 14.0.0
|
||||
*/
|
||||
protected function findEntities(IQueryBuilder $query): array {
|
||||
$cursor = $query->execute();
|
||||
|
||||
$entities = [];
|
||||
|
||||
while($row = $cursor->fetch()){
|
||||
$entities[] = $this->mapRowToEntity($row);
|
||||
}
|
||||
|
||||
$cursor->closeCursor();
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an db result and throws exceptions when there are more or less
|
||||
* results
|
||||
*
|
||||
* @param IQueryBuilder $query
|
||||
* @throws DoesNotExistException if the item does not exist
|
||||
* @throws MultipleObjectsReturnedException if more than one item exist
|
||||
* @return Entity the entity
|
||||
* @since 14.0.0
|
||||
*/
|
||||
protected function findEntity(IQueryBuilder $query): Entity {
|
||||
return $this->mapRowToEntity($this->findOneQuery($query));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue