diff options
author | Morris Jobke <hey@morrisjobke.de> | 2014-05-12 00:12:19 +0200 |
---|---|---|
committer | Morris Jobke <hey@morrisjobke.de> | 2014-05-12 00:12:19 +0200 |
commit | 7a6ff56b138f1b7d5f1db49855e5069cc241b052 (patch) | |
tree | be330362388816aa565647825b308c0c034474f3 /lib | |
parent | 58dc10349aed7cb7ad635a50ef0a16f1db1ae5a0 (diff) | |
parent | 3af30625ab05f0a793ddc6e9053c79dbdcc23c2a (diff) | |
download | nextcloud-server-7a6ff56b138f1b7d5f1db49855e5069cc241b052.tar.gz nextcloud-server-7a6ff56b138f1b7d5f1db49855e5069cc241b052.zip |
Merge pull request #8274 from owncloud/appframework-db
Port database layer from appframework to core
Diffstat (limited to 'lib')
-rw-r--r-- | lib/private/appframework/db/db.php | 57 | ||||
-rw-r--r-- | lib/private/server.php | 13 | ||||
-rw-r--r-- | lib/public/appframework/db/doesnotexistexception.php | 42 | ||||
-rw-r--r-- | lib/public/appframework/db/entity.php | 236 | ||||
-rw-r--r-- | lib/public/appframework/db/mapper.php | 291 | ||||
-rw-r--r-- | lib/public/appframework/db/multipleobjectsreturnedexception.php | 42 | ||||
-rw-r--r-- | lib/public/idb.php | 51 | ||||
-rw-r--r-- | lib/public/iservercontainer.php | 8 |
8 files changed, 740 insertions, 0 deletions
diff --git a/lib/private/appframework/db/db.php b/lib/private/appframework/db/db.php new file mode 100644 index 00000000000..fc77a38f814 --- /dev/null +++ b/lib/private/appframework/db/db.php @@ -0,0 +1,57 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\AppFramework\Db; + +use \OCP\IDb; + + +/** + * Small Facade for being able to inject the database connection for tests + */ +class Db implements IDb { + + + /** + * Used to abstract the owncloud database access away + * @param string $sql the sql query with ? placeholder for params + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \OC_DB_StatementWrapper prepared SQL query + */ + public function prepareQuery($sql, $limit=null, $offset=null){ + return \OCP\DB::prepare($sql, $limit, $offset); + } + + + /** + * Used to get the id of the just inserted element + * @param string $tableName the name of the table where we inserted the item + * @return int the id of the inserted element + */ + public function getInsertId($tableName){ + return \OCP\DB::insertid($tableName); + } + + +} diff --git a/lib/private/server.php b/lib/private/server.php index 4c29092cf44..27763ea6477 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -3,6 +3,7 @@ namespace OC; use OC\AppFramework\Http\Request; +use OC\AppFramework\Db\Db; use OC\AppFramework\Utility\SimpleContainer; use OC\Cache\UserCache; use OC\DB\ConnectionWrapper; @@ -177,6 +178,9 @@ class Server extends SimpleContainer implements IServerContainer { } return $router; }); + $this['Db'] = $this->share(function($c){ + return new Db(); + }); } /** @@ -400,4 +404,13 @@ class Server extends SimpleContainer implements IServerContainer { function getRouter(){ return $this->query('Router'); } + + + /** + * Returns an instance of the db facade + * @return \OCP\IDb + */ + function getDb() { + return $this->query('Db'); + } } diff --git a/lib/public/appframework/db/doesnotexistexception.php b/lib/public/appframework/db/doesnotexistexception.php new file mode 100644 index 00000000000..5861e74f6c8 --- /dev/null +++ b/lib/public/appframework/db/doesnotexistexception.php @@ -0,0 +1,42 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCP\AppFramework\Db; + + +/** + * This is returned or should be returned when a find request does not find an + * entry in the database + */ +class DoesNotExistException extends \Exception { + + /** + * Constructor + * @param string $msg the error message + */ + public function __construct($msg){ + parent::__construct($msg); + } + +}
\ No newline at end of file diff --git a/lib/public/appframework/db/entity.php b/lib/public/appframework/db/entity.php new file mode 100644 index 00000000000..8ab42bd9153 --- /dev/null +++ b/lib/public/appframework/db/entity.php @@ -0,0 +1,236 @@ +<?php + +/** +* ownCloud - App Framework +* +* @author Bernhard Posselt +* @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library 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 library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +namespace OCP\AppFramework\Db; + + +/** + * @method integer getId() + * @method void setId(integer $id) + */ +abstract class Entity { + + 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 + */ + 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 + */ + 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 an array with attribute and type + */ + public function getFieldTypes() { + return $this->_fieldTypes; + } + + + /** + * Marks the entity as clean needed for setting the id after the insertion + */ + public function resetUpdatedFields(){ + $this->_updatedFields = array(); + } + + + 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'); + } + } + + + 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 + */ + public function __call($methodName, $args){ + $attr = lcfirst( substr($methodName, 3) ); + + if(strpos($methodName, 'set') === 0){ + $this->setter($attr, $args); + } elseif(strpos($methodName, 'get') === 0) { + return $this->getter($attr); + } else { + throw new \BadFunctionCallException($methodName . + ' does not exist'); + } + + } + + + /** + * Mark am attribute as updated + * @param string $attribute the name of the attribute + */ + 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 + */ + 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 + */ + 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 + */ + 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() + */ + 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 + */ + 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'); + } + } + +} diff --git a/lib/public/appframework/db/mapper.php b/lib/public/appframework/db/mapper.php new file mode 100644 index 00000000000..21ccb686221 --- /dev/null +++ b/lib/public/appframework/db/mapper.php @@ -0,0 +1,291 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @author Morris Jobke + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * @copyright 2013 Morris Jobke morris.jobke@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCP\AppFramework\Db; + +use \OCP\IDb; + + +/** + * Simple parent class for inheriting your data access layer from. This class + * may be subject to change in the future + */ +abstract class Mapper { + + protected $tableName; + protected $entityClass; + protected $db; + + /** + * @param IDb $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 + */ + public function __construct(IDb $db, $tableName, $entityClass=null){ + $this->db = $db; + $this->tableName = '*PREFIX*' . $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 + */ + public function getTableName(){ + return $this->tableName; + } + + + /** + * Deletes an entity from the table + * @param Entity $entity the entity that should be deleted + */ + public function delete(Entity $entity){ + $sql = 'DELETE FROM `' . $this->tableName . '` WHERE `id` = ?'; + $this->execute($sql, array($entity->getId())); + } + + + /** + * 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 + */ + public function insert(Entity $entity){ + // get updated fields to save, fields have to be set using a setter to + // be saved + $properties = $entity->getUpdatedFields(); + $values = ''; + $columns = ''; + $params = array(); + + // build the fields + $i = 0; + foreach($properties as $property => $updated) { + $column = $entity->propertyToColumn($property); + $getter = 'get' . ucfirst($property); + + $columns .= '`' . $column . '`'; + $values .= '?'; + + // only append colon if there are more entries + if($i < count($properties)-1){ + $columns .= ','; + $values .= ','; + } + + array_push($params, $entity->$getter()); + $i++; + + } + + $sql = 'INSERT INTO `' . $this->tableName . '`(' . + $columns . ') VALUES(' . $values . ')'; + + $this->execute($sql, $params); + + $entity->setId((int) $this->db->getInsertId($this->tableName)); + return $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 + */ + public function update(Entity $entity){ + // if entity wasn't changed it makes no sense to run a db query + $properties = $entity->getUpdatedFields(); + if(count($properties) === 0) { + return $entity; + } + + // entity needs an id + $id = $entity->getId(); + if($id === null){ + throw new \InvalidArgumentException( + 'Entity which should be updated has no id'); + } + + // get updated fields to save, fields have to be set using a setter to + // be saved + // dont update the id field + unset($properties['id']); + + $columns = ''; + $params = array(); + + // build the fields + $i = 0; + foreach($properties as $property => $updated) { + + $column = $entity->propertyToColumn($property); + $getter = 'get' . ucfirst($property); + + $columns .= '`' . $column . '` = ?'; + + // only append colon if there are more entries + if($i < count($properties)-1){ + $columns .= ','; + } + + array_push($params, $entity->$getter()); + $i++; + } + + $sql = 'UPDATE `' . $this->tableName . '` SET ' . + $columns . ' WHERE `id` = ?'; + array_push($params, $id); + + $this->execute($sql, $params); + } + + + /** + * Runs an sql query + * @param string $sql the prepare string + * @param array $params the params which should replace the ? in the sql query + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \PDOStatement the database query result + */ + protected function execute($sql, array $params=array(), $limit=null, $offset=null){ + $query = $this->db->prepareQuery($sql, $limit, $offset); + + $index = 1; // bindParam is 1 indexed + foreach($params as $param) { + + switch (gettype($param)) { + case 'integer': + $pdoConstant = \PDO::PARAM_INT; + break; + + case 'boolean': + $pdoConstant = \PDO::PARAM_BOOL; + break; + + default: + $pdoConstant = \PDO::PARAM_STR; + break; + } + + $query->bindValue($index, $param, $pdoConstant); + + $index++; + } + + return $query->execute(); + } + + + /** + * Returns an db result and throws exceptions when there are more or less + * results + * @see findEntity + * @param string $sql the sql query + * @param array $params the parameters of the sql query + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @throws DoesNotExistException if the item does not exist + * @throws MultipleObjectsReturnedException if more than one item exist + * @return array the result as row + */ + protected function findOneQuery($sql, array $params=array(), $limit=null, $offset=null){ + $result = $this->execute($sql, $params, $limit, $offset); + $row = $result->fetchRow(); + + if($row === false || $row === null){ + throw new DoesNotExistException('No matching entry found'); + } + $row2 = $result->fetchRow(); + //MDB2 returns null, PDO and doctrine false when no row is available + if( ! ($row2 === false || $row2 === null )) { + throw new MultipleObjectsReturnedException('More than one result'); + } else { + return $row; + } + } + + + /** + * 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 + */ + protected function mapRowToEntity($row) { + return call_user_func($this->entityClass .'::fromRow', $row); + } + + + /** + * Runs a sql query and returns an array of entities + * @param string $sql the prepare string + * @param array $params the params which should replace the ? in the sql query + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return array all fetched entities + */ + protected function findEntities($sql, array $params=array(), $limit=null, $offset=null) { + $result = $this->execute($sql, $params, $limit, $offset); + + $entities = array(); + + while($row = $result->fetchRow()){ + $entities[] = $this->mapRowToEntity($row); + } + + return $entities; + } + + + /** + * Returns an db result and throws exceptions when there are more or less + * results + * @param string $sql the sql query + * @param array $params the parameters of the sql query + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @throws DoesNotExistException if the item does not exist + * @throws MultipleObjectsReturnedException if more than one item exist + * @return Entity the entity + */ + protected function findEntity($sql, array $params=array(), $limit=null, $offset=null){ + return $this->mapRowToEntity($this->findOneQuery($sql, $params, $limit, $offset)); + } + + +} diff --git a/lib/public/appframework/db/multipleobjectsreturnedexception.php b/lib/public/appframework/db/multipleobjectsreturnedexception.php new file mode 100644 index 00000000000..51d8d6bc7e1 --- /dev/null +++ b/lib/public/appframework/db/multipleobjectsreturnedexception.php @@ -0,0 +1,42 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCP\AppFramework\Db; + + +/** + * This is returned or should be returned when a find request finds more than one + * row + */ +class MultipleObjectsReturnedException extends \Exception { + + /** + * Constructor + * @param string $msg the error message + */ + public function __construct($msg){ + parent::__construct($msg); + } + +}
\ No newline at end of file diff --git a/lib/public/idb.php b/lib/public/idb.php new file mode 100644 index 00000000000..82a8a681500 --- /dev/null +++ b/lib/public/idb.php @@ -0,0 +1,51 @@ +<?php + +/** + * ownCloud - App Framework + * + * @author Bernhard Posselt + * @copyright 2012 Bernhard Posselt dev@bernhard-posselt.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP; + + +/** + * Small Facade for being able to inject the database connection for tests + */ +interface IDb { + + + /** + * Used to abstract the owncloud database access away + * @param string $sql the sql query with ? placeholder for params + * @param int $limit the maximum number of rows + * @param int $offset from which row we want to start + * @return \OC_DB_StatementWrapper prepared SQL query + */ + public function prepareQuery($sql, $limit=null, $offset=null); + + + /** + * Used to get the id of the just inserted element + * @param string $tableName the name of the table where we inserted the item + * @return int the id of the inserted element + */ + public function getInsertId($tableName); + + +} diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php index 600d81d83af..22176c36b8a 100644 --- a/lib/public/iservercontainer.php +++ b/lib/public/iservercontainer.php @@ -113,6 +113,14 @@ interface IServerContainer { */ function getConfig(); + + /** + * Returns an instance of the db facade + * @return \OCP\IDb + */ + function getDb(); + + /** * Returns the app config manager * |