diff options
author | Roeland Jago Douma <rullzer@users.noreply.github.com> | 2018-05-14 21:12:12 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-14 21:12:12 +0200 |
commit | 497a4facdf0bf1e2ac78967f5e77f1353cf3e8aa (patch) | |
tree | 2c947518652ca4583799ac07e726812611b4afde /lib | |
parent | 8657f56418e4982eecce3a7a64a1b0db42fc42a7 (diff) | |
parent | ed7b4839d9ac543799e291ad2d338db559c86354 (diff) | |
download | nextcloud-server-497a4facdf0bf1e2ac78967f5e77f1353cf3e8aa.tar.gz nextcloud-server-497a4facdf0bf1e2ac78967f5e77f1353cf3e8aa.zip |
Merge pull request #9444 from nextcloud/techdep/noid/appframework_mapper_to_qb
Add a QueryBuilder based Mapper
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 1 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 1 | ||||
-rw-r--r-- | lib/private/Authentication/Token/DefaultTokenMapper.php | 3 | ||||
-rw-r--r-- | lib/public/AppFramework/Db/Mapper.php | 15 | ||||
-rw-r--r-- | lib/public/AppFramework/Db/QBMapper.php | 272 |
5 files changed, 290 insertions, 2 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 48aa9533e81..cd5090cc93c 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -23,6 +23,7 @@ return array( 'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.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\\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', 'OCP\\AppFramework\\Http\\DataDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/DataDisplayResponse.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 0ed2d0a7632..4e47536eacc 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -53,6 +53,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.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\\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', 'OCP\\AppFramework\\Http\\DataDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataDisplayResponse.php', diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 41d1b9f203d..55494d72370 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -30,11 +30,12 @@ namespace OC\Authentication\Token; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Mapper; +use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; -class DefaultTokenMapper extends Mapper { +class DefaultTokenMapper extends QBMapper { public function __construct(IDBConnection $db) { parent::__construct($db, 'authtoken'); diff --git a/lib/public/AppFramework/Db/Mapper.php b/lib/public/AppFramework/Db/Mapper.php index b008702ba54..6910757add0 100644 --- a/lib/public/AppFramework/Db/Mapper.php +++ b/lib/public/AppFramework/Db/Mapper.php @@ -34,6 +34,7 @@ use OCP\IDBConnection; * Simple parent class for inheriting your data access layer from. This class * may be subject to change in the future * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ abstract class Mapper { @@ -47,6 +48,7 @@ abstract class Mapper { * @param string $entityClass the name of the entity that the sql should be * mapped to queries without using sql * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ public function __construct(IDBConnection $db, $tableName, $entityClass=null){ $this->db = $db; @@ -65,6 +67,7 @@ abstract class Mapper { /** * @return string the table name * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ public function getTableName(){ return $this->tableName; @@ -76,6 +79,7 @@ abstract class Mapper { * @param Entity $entity the entity that should be deleted * @return Entity the deleted entity * @since 7.0.0 - return value added in 8.1.0 + * @deprecated 14.0.0 Move over to QBMapper */ public function delete(Entity $entity){ $sql = 'DELETE FROM `' . $this->tableName . '` WHERE `id` = ?'; @@ -90,6 +94,7 @@ abstract class Mapper { * @param Entity $entity the entity that should be created * @return Entity the saved entity with the set id * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ public function insert(Entity $entity){ // get updated fields to save, fields have to be set using a setter to @@ -139,6 +144,7 @@ abstract class Mapper { * @param Entity $entity the entity that should be created * @return Entity the saved entity with the set id * @since 7.0.0 - return value was added in 8.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ public function update(Entity $entity){ // if entity wasn't changed it makes no sense to run a db query @@ -195,6 +201,7 @@ abstract class Mapper { * @param array $array * @return bool true if associative * @since 8.1.0 + * @deprecated 14.0.0 Move over to QBMapper */ private function isAssocArray(array $array) { return array_values($array) !== $array; @@ -205,6 +212,7 @@ abstract class Mapper { * @param $value * @return int PDO constant * @since 8.1.0 + * @deprecated 14.0.0 Move over to QBMapper */ private function getPDOType($value) { switch (gettype($value)) { @@ -226,6 +234,7 @@ abstract class Mapper { * @param int $offset from which row we want to start * @return \PDOStatement the database query result * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ protected function execute($sql, array $params=[], $limit=null, $offset=null){ $query = $this->db->prepare($sql, $limit, $offset); @@ -249,7 +258,6 @@ abstract class Mapper { return $query; } - /** * Returns an db result and throws exceptions when there are more or less * results @@ -262,6 +270,7 @@ abstract class Mapper { * @throws MultipleObjectsReturnedException if more than one item exist * @return array the result as row * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ protected function findOneQuery($sql, array $params=[], $limit=null, $offset=null){ $stmt = $this->execute($sql, $params, $limit, $offset); @@ -297,6 +306,7 @@ abstract class Mapper { * @param int $offset from which row we want to start * @return string formatted error message string * @since 9.1.0 + * @deprecated 14.0.0 Move over to QBMapper */ private function buildDebugMessage($msg, $sql, array $params=[], $limit=null, $offset=null) { return $msg . @@ -313,6 +323,7 @@ abstract class Mapper { * @param array $row the row which should be converted to an entity * @return Entity the entity * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ protected function mapRowToEntity($row) { return call_user_func($this->entityClass .'::fromRow', $row); @@ -327,6 +338,7 @@ abstract class Mapper { * @param int $offset from which row we want to start * @return array all fetched entities * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ protected function findEntities($sql, array $params=[], $limit=null, $offset=null) { $stmt = $this->execute($sql, $params, $limit, $offset); @@ -354,6 +366,7 @@ abstract class Mapper { * @throws MultipleObjectsReturnedException if more than one item exist * @return Entity the entity * @since 7.0.0 + * @deprecated 14.0.0 Move over to QBMapper */ protected function findEntity($sql, array $params=[], $limit=null, $offset=null){ return $this->mapRowToEntity($this->findOneQuery($sql, $params, $limit, $offset)); diff --git a/lib/public/AppFramework/Db/QBMapper.php b/lib/public/AppFramework/Db/QBMapper.php new file mode 100644 index 00000000000..a9b38732a30 --- /dev/null +++ b/lib/public/AppFramework/Db/QBMapper.php @@ -0,0 +1,272 @@ +<?php +declare(strict_types=1); +/** + * @copyright 2018, 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 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 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; + } + + + /** + * Deletes an entity from the table + * @param Entity $entity the entity that should be deleted + * @return Entity the deleted entity + * @since 14.0.0 + */ + public function delete(Entity $entity): Entity { + $qb = $this->db->getQueryBuilder(); + + $qb->delete($this->tableName) + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($entity->getId())) + ); + $qb->execute(); + return $entity; + } + + + /** + * 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 + * @since 14.0.0 + * @suppress SqlInjectionChecker + */ + public function insert(Entity $entity): Entity { + // 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(); + + $qb->setValue($column, $qb->createNamedParameter($value)); + } + + $qb->execute(); + + $entity->setId((int) $qb->getLastInsertId()); + + 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 + * @return Entity the saved entity with the set id + * @since 14.0.0 + * @suppress SqlInjectionChecker + */ + public function update(Entity $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 + // do not update the id field + unset($properties['id']); + + $qb = $this->db->getQueryBuilder(); + $qb->update($this->tableName); + + // build the fields + foreach($properties as $property => $updated) { + $column = $entity->propertyToColumn($property); + $getter = 'get' . ucfirst($property); + $value = $entity->$getter(); + + $qb->set($column, $qb->createNamedParameter($value)); + } + + $qb->where( + $qb->expr()->eq('id', $qb->createNamedParameter($id)) + ); + $qb->execute(); + + return $entity; + } + + /** + * 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)); + } + +} |