From fda547834af4d76ee2919b647254fcac96dcf085 Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Thu, 2 Jan 2020 02:28:47 +0100 Subject: [PATCH] daos: break daos depending on Database The structure of DAOs is pretty convoluted: sqlite and pgsql items/sources/tags inherit from the corresponding mysql classes to facilitate code reuse. Additionally, the mysql classes inherited from mysql\database, which instantiated statements helper based on the database type. We move the statements initalization to each individual class to allow us break the dependency on database. Late static binding allows us to do this pretty conveniently while retaining the inheritance as code deduplication hack (to be fixed later). --- src/daos/mysql/Database.php | 6 ---- src/daos/mysql/Items.php | 53 ++++++++++++++++++----------------- src/daos/mysql/Sources.php | 23 ++++++++------- src/daos/mysql/Statements.php | 4 +-- src/daos/mysql/Tags.php | 11 +++++--- src/daos/pgsql/Items.php | 2 ++ src/daos/pgsql/Sources.php | 2 ++ src/daos/pgsql/Statements.php | 2 +- src/daos/pgsql/Tags.php | 2 ++ src/daos/sqlite/Items.php | 3 +- src/daos/sqlite/Sources.php | 2 ++ src/daos/sqlite/Tags.php | 2 ++ 12 files changed, 63 insertions(+), 49 deletions(-) diff --git a/src/daos/mysql/Database.php b/src/daos/mysql/Database.php index 8691b4f14b..3a794c9a8a 100644 --- a/src/daos/mysql/Database.php +++ b/src/daos/mysql/Database.php @@ -13,9 +13,6 @@ class Database { /** @var bool indicates whether database connection was initialized */ private static $initialized = false; - /** @var mixed helpers for creating SQL queries */ - protected $stmt; - /** * establish connection and * create undefined tables @@ -248,9 +245,6 @@ public function __construct() { // just initialize once self::$initialized = true; } - - $class = 'daos\\' . \F3::get('db_type') . '\\Statements'; - $this->stmt = new $class(); } /** diff --git a/src/daos/mysql/Items.php b/src/daos/mysql/Items.php index 7ef9c2156d..4f1b014812 100644 --- a/src/daos/mysql/Items.php +++ b/src/daos/mysql/Items.php @@ -12,10 +12,13 @@ * @author Tobias Zeising * @author Harald Lapp */ -class Items extends Database { +class Items { /** @var bool indicates whether last run has more results or not */ protected $hasMore = false; + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; + /** * mark item as read * @@ -182,7 +185,7 @@ public function findAll($itemsInFeed, $sourceId) { */ public function updateLastSeen(array $itemIds) { \F3::get('db')->exec('UPDATE ' . \F3::get('db_prefix') . 'items SET lastseen = CURRENT_TIMESTAMP - WHERE ' . $this->stmt->intRowMatches('id', $itemIds)); + WHERE ' . static::$stmt::intRowMatches('id', $itemIds)); } /** @@ -198,7 +201,7 @@ public function cleanup(DateTime $date = null) { SELECT id FROM ' . \F3::get('db_prefix') . 'sources)'); if ($date !== null) { \F3::get('db')->exec('DELETE FROM ' . \F3::get('db_prefix') . 'items - WHERE ' . $this->stmt->isFalse('starred') . ' AND lastseen<:date', + WHERE ' . static::$stmt::isFalse('starred') . ' AND lastseen<:date', [':date' => $date->format('Y-m-d') . ' 00:00:00'] ); } @@ -213,17 +216,17 @@ public function cleanup(DateTime $date = null) { */ public function get($options = []) { $params = []; - $where = [$this->stmt->bool(true)]; + $where = [static::$stmt::bool(true)]; $order = 'DESC'; // only starred if (isset($options['type']) && $options['type'] === 'starred') { - $where[] = $this->stmt->isTrue('starred'); + $where[] = static::$stmt::isTrue('starred'); } // only unread elseif (isset($options['type']) && $options['type'] === 'unread') { - $where[] = $this->stmt->isTrue('unread'); + $where[] = static::$stmt::isTrue('unread'); if (\F3::get('unread_order') === 'asc') { $order = 'ASC'; } @@ -240,7 +243,7 @@ public function get($options = []) { if (isset($options['tag']) && strlen($options['tag']) > 0) { $params[':tag'] = $options['tag']; $where[] = 'items.source=sources.id'; - $where[] = $this->stmt->csvRowMatches('sources.tags', ':tag'); + $where[] = static::$stmt::csvRowMatches('sources.tags', ':tag'); } // source filter elseif (isset($options['source']) && strlen($options['source']) > 0) { @@ -263,7 +266,7 @@ public function get($options = []) { // with seek pagination. $options['offset'] = 0; - $offset_from_datetime_sql = $this->stmt->datetime($options['fromDatetime']); + $offset_from_datetime_sql = static::$stmt::datetime($options['fromDatetime']); $params[':offset_from_datetime'] = [ $offset_from_datetime_sql, \PDO::PARAM_STR ]; @@ -294,7 +297,7 @@ public function get($options = []) { && count($options['extraIds']) > 0 // limit the query to a sensible max && count($options['extraIds']) <= \F3::get('items_perpage')) { - $extra_ids_stmt = $this->stmt->intRowMatches('items.id', $options['extraIds']); + $extra_ids_stmt = static::$stmt::intRowMatches('items.id', $options['extraIds']); if ($extra_ids_stmt !== null) { $where_ids = $extra_ids_stmt; } @@ -346,7 +349,7 @@ public function get($options = []) { $query = "$select $where_sql $order_sql LIMIT " . $options['items'] . ' OFFSET ' . $options['offset']; } - return $this->stmt->ensureRowTypes(\F3::get('db')->exec($query, $params), [ + return static::$stmt::ensureRowTypes(\F3::get('db')->exec($query, $params), [ 'id' => \daos\PARAM_INT, 'unread' => \daos\PARAM_BOOL, 'starred' => \daos\PARAM_BOOL, @@ -380,8 +383,8 @@ public function sync($sinceId, DateTime $notBefore, DateTime $since, $howMany) { items.id, datetime, items.title AS title, content, unread, starred, source, thumbnail, icon, uid, link, updatetime, author, sources.title as sourcetitle, sources.tags as tags FROM ' . \F3::get('db_prefix') . 'items AS items, ' . \F3::get('db_prefix') . 'sources AS sources WHERE items.source=sources.id - AND (' . $this->stmt->isTrue('unread') . - ' OR ' . $this->stmt->isTrue('starred') . + AND (' . static::$stmt::isTrue('unread') . + ' OR ' . static::$stmt::isTrue('starred') . ' OR datetime >= :notBefore ) AND (items.id > :sinceId OR @@ -395,7 +398,7 @@ public function sync($sinceId, DateTime $notBefore, DateTime $since, $howMany) { 'since' => [$since->format(\DateTime::ATOM), \PDO::PARAM_STR] ]; - return $this->stmt->ensureRowTypes(\F3::get('db')->exec($query, $params), [ + return static::$stmt::ensureRowTypes(\F3::get('db')->exec($query, $params), [ 'id' => \daos\PARAM_INT, 'unread' => \daos\PARAM_BOOL, 'starred' => \daos\PARAM_BOOL, @@ -409,11 +412,11 @@ public function sync($sinceId, DateTime $notBefore, DateTime $since, $howMany) { * @return int lowest id of interest */ public function lowestIdOfInterest() { - $lowest = $this->stmt->ensureRowTypes( + $lowest = static::$stmt::ensureRowTypes( \F3::get('db')->exec( 'SELECT id FROM ' . \F3::get('db_prefix') . 'items AS items - WHERE ' . $this->stmt->isTrue('unread') . - ' OR ' . $this->stmt->isTrue('starred') . + WHERE ' . static::$stmt::isTrue('unread') . + ' OR ' . static::$stmt::isTrue('starred') . ' ORDER BY id LIMIT 1'), ['id' => \daos\PARAM_INT] ); @@ -430,7 +433,7 @@ public function lowestIdOfInterest() { * @return int last id in db */ public function lastId() { - $lastId = $this->stmt->ensureRowTypes( + $lastId = static::$stmt::ensureRowTypes( \F3::get('db')->exec( 'SELECT id FROM ' . \F3::get('db_prefix') . 'items AS items ORDER BY id DESC LIMIT 1'), @@ -572,7 +575,7 @@ public function getLastIcon($sourceid) { public function numberOfUnread() { $res = \F3::get('db')->exec('SELECT count(*) AS amount FROM ' . \F3::get('db_prefix') . 'items - WHERE ' . $this->stmt->isTrue('unread')); + WHERE ' . static::$stmt::isTrue('unread')); return $res[0]['amount']; } @@ -585,10 +588,10 @@ public function numberOfUnread() { public function stats() { $res = \F3::get('db')->exec('SELECT COUNT(*) AS total, - ' . $this->stmt->sumBool('unread') . ' AS unread, - ' . $this->stmt->sumBool('starred') . ' AS starred + ' . static::$stmt::sumBool('unread') . ' AS unread, + ' . static::$stmt::sumBool('starred') . ' AS starred FROM ' . \F3::get('db_prefix') . 'items;'); - $res = $this->stmt->ensureRowTypes($res, [ + $res = static::$stmt::ensureRowTypes($res, [ 'total' => \daos\PARAM_INT, 'unread' => \daos\PARAM_INT, 'starred' => \daos\PARAM_INT @@ -622,7 +625,7 @@ public function statuses(DateTime $since) { FROM ' . \F3::get('db_prefix') . 'items WHERE ' . \F3::get('db_prefix') . 'items.updatetime > :since;', [':since' => [$since->format(DateTime::ATOM), \PDO::PARAM_STR]]); - $res = $this->stmt->ensureRowTypes($res, [ + $res = static::$stmt::ensureRowTypes($res, [ 'id' => \daos\PARAM_INT, 'unread' => \daos\PARAM_BOOL, 'starred' => \daos\PARAM_BOOL @@ -652,12 +655,12 @@ public function bulkStatusUpdate(array $statuses) { if ($status[$sk] == 'true') { $statusUpdate = [ 'sk' => $sk, - 'sql' => $this->stmt->isTrue($sk) + 'sql' => static::$stmt::isTrue($sk) ]; } elseif ($status[$sk] == 'false') { $statusUpdate = [ 'sk' => $sk, - 'sql' => $this->stmt->isFalse($sk) + 'sql' => static::$stmt::isFalse($sk) ]; } } @@ -712,7 +715,7 @@ public function bulkStatusUpdate(array $statuses) { // statuses. \F3::get('db')->exec( 'UPDATE ' . \F3::get('db_prefix') . 'items - SET ' . $this->stmt->rowTouch('updatetime') . ' + SET ' . static::$stmt::rowTouch('updatetime') . ' WHERE id = :id', [':id' => [$id, \PDO::PARAM_INT]]); } } diff --git a/src/daos/mysql/Sources.php b/src/daos/mysql/Sources.php index 26d160b535..ada542ccbf 100644 --- a/src/daos/mysql/Sources.php +++ b/src/daos/mysql/Sources.php @@ -9,7 +9,10 @@ * @license GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html) * @author Tobias Zeising */ -class Sources extends Database { +class Sources { + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; + /** * add new source * @@ -22,9 +25,9 @@ class Sources extends Database { * @return int new id */ public function add($title, array $tags, $filter, $spout, array $params) { - return $this->stmt->insert('INSERT INTO ' . \F3::get('db_prefix') . 'sources (title, tags, filter, spout, params) VALUES (:title, :tags, :filter, :spout, :params)', [ + return static::$stmt::insert('INSERT INTO ' . \F3::get('db_prefix') . 'sources (title, tags, filter, spout, params) VALUES (:title, :tags, :filter, :spout, :params)', [ ':title' => trim($title), - ':tags' => $this->stmt->csvRow($tags), + ':tags' => static::$stmt::csvRow($tags), ':filter' => $filter, ':spout' => $spout, ':params' => htmlentities(json_encode($params)) @@ -46,7 +49,7 @@ public function add($title, array $tags, $filter, $spout, array $params) { public function edit($id, $title, array $tags, $filter, $spout, array $params) { \F3::get('db')->exec('UPDATE ' . \F3::get('db_prefix') . 'sources SET title=:title, tags=:tags, filter=:filter, spout=:spout, params=:params WHERE id=:id', [ ':title' => trim($title), - ':tags' => $this->stmt->csvRow($tags), + ':tags' => static::$stmt::csvRow($tags), ':filter' => $filter, ':spout' => $spout, ':params' => htmlentities(json_encode($params)), @@ -140,7 +143,7 @@ public function get($id = null) { // select source by id if specified or return all sources if (isset($id)) { $ret = \F3::get('db')->exec('SELECT id, title, tags, spout, params, filter, error FROM ' . \F3::get('db_prefix') . 'sources WHERE id=:id', [':id' => $id]); - $ret = $this->stmt->ensureRowTypes($ret, ['id' => \daos\PARAM_INT]); + $ret = static::$stmt::ensureRowTypes($ret, ['id' => \daos\PARAM_INT]); if (isset($ret[0])) { $ret = $ret[0]; } else { @@ -148,7 +151,7 @@ public function get($id = null) { } } else { $ret = \F3::get('db')->exec('SELECT id, title, tags, spout, params, filter, error FROM ' . \F3::get('db_prefix') . 'sources ORDER BY error DESC, lower(title) ASC'); - $ret = $this->stmt->ensureRowTypes($ret, [ + $ret = static::$stmt::ensureRowTypes($ret, [ 'id' => \daos\PARAM_INT, 'tags' => \daos\PARAM_CSV ]); @@ -167,11 +170,11 @@ public function getWithUnread() { sources.id, sources.title, COUNT(items.id) AS unread FROM ' . \F3::get('db_prefix') . 'sources AS sources LEFT OUTER JOIN ' . \F3::get('db_prefix') . 'items AS items - ON (items.source=sources.id AND ' . $this->stmt->isTrue('items.unread') . ') + ON (items.source=sources.id AND ' . static::$stmt::isTrue('items.unread') . ') GROUP BY sources.id, sources.title ORDER BY lower(sources.title) ASC'); - return $this->stmt->ensureRowTypes($ret, [ + return static::$stmt::ensureRowTypes($ret, [ 'id' => \daos\PARAM_INT, 'unread' => \daos\PARAM_INT ]); @@ -198,9 +201,9 @@ public function getWithIcon() { WHERE items.id=icons.maxid AND items.source=icons.source ) AS sourceicons ON sources.id=sourceicons.source - ORDER BY ' . $this->stmt->nullFirst('sources.error', 'DESC') . ', lower(sources.title)'); + ORDER BY ' . static::$stmt::nullFirst('sources.error', 'DESC') . ', lower(sources.title)'); - return $this->stmt->ensureRowTypes($ret, [ + return static::$stmt::ensureRowTypes($ret, [ 'id' => \daos\PARAM_INT, 'tags' => \daos\PARAM_CSV ]); diff --git a/src/daos/mysql/Statements.php b/src/daos/mysql/Statements.php index 20a322fb74..91a388b1a1 100644 --- a/src/daos/mysql/Statements.php +++ b/src/daos/mysql/Statements.php @@ -161,7 +161,7 @@ public static function datetime($datestr) { * @return array of associative array representing row results having * expected types */ - public function ensureRowTypes(array $rows, array $expectedRowTypes) { + public static function ensureRowTypes(array $rows, array $expectedRowTypes) { foreach ($rows as $rowIndex => $row) { foreach ($expectedRowTypes as $columnIndex => $type) { if (array_key_exists($columnIndex, $row)) { @@ -203,7 +203,7 @@ public function ensureRowTypes(array $rows, array $expectedRowTypes) { * * @return string */ - public function csvRow(array $a) { + public static function csvRow(array $a) { $filtered = []; foreach ($a as $s) { $t = trim($s); diff --git a/src/daos/mysql/Tags.php b/src/daos/mysql/Tags.php index 80d079133f..83f31f0bc2 100644 --- a/src/daos/mysql/Tags.php +++ b/src/daos/mysql/Tags.php @@ -9,7 +9,10 @@ * @license GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html) * @author Tobias Zeising */ -class Tags extends Database { +class Tags { + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; + /** * save given tag color * @@ -82,12 +85,12 @@ public function getWithUnread() { FROM ' . \F3::get('db_prefix') . 'tags AS tags, ' . \F3::get('db_prefix') . 'sources AS sources LEFT OUTER JOIN ' . \F3::get('db_prefix') . 'items AS items - ON (items.source=sources.id AND ' . $this->stmt->isTrue('items.unread') . ') - WHERE ' . $this->stmt->csvRowMatches('sources.tags', 'tags.tag') . ' + ON (items.source=sources.id AND ' . static::$stmt::isTrue('items.unread') . ') + WHERE ' . static::$stmt::csvRowMatches('sources.tags', 'tags.tag') . ' GROUP BY tags.tag, tags.color ORDER BY LOWER(tags.tag);'; - return $this->stmt->ensureRowTypes(\F3::get('db')->exec($select), ['unread' => \daos\PARAM_INT]); + return static::$stmt::ensureRowTypes(\F3::get('db')->exec($select), ['unread' => \daos\PARAM_INT]); } /** diff --git a/src/daos/pgsql/Items.php b/src/daos/pgsql/Items.php index 0b9a66ccfb..d2a3ee5c65 100644 --- a/src/daos/pgsql/Items.php +++ b/src/daos/pgsql/Items.php @@ -11,4 +11,6 @@ * @author Tobias Zeising */ class Items extends \daos\mysql\Items { + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; } diff --git a/src/daos/pgsql/Sources.php b/src/daos/pgsql/Sources.php index d9e2a4da39..ea4ba39ead 100644 --- a/src/daos/pgsql/Sources.php +++ b/src/daos/pgsql/Sources.php @@ -11,4 +11,6 @@ * @author Tobias Zeising */ class Sources extends \daos\mysql\Sources { + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; } diff --git a/src/daos/pgsql/Statements.php b/src/daos/pgsql/Statements.php index f5ea677e13..5495474fec 100644 --- a/src/daos/pgsql/Statements.php +++ b/src/daos/pgsql/Statements.php @@ -97,7 +97,7 @@ public static function csvRowMatches($column, $value) { * @return array of associative array representing row results having * expected types */ - public function ensureRowTypes(array $rows, array $expectedRowTypes) { + public static function ensureRowTypes(array $rows, array $expectedRowTypes) { foreach ($rows as $rowIndex => $row) { foreach ($expectedRowTypes as $columnIndex => $type) { if (array_key_exists($columnIndex, $row)) { diff --git a/src/daos/pgsql/Tags.php b/src/daos/pgsql/Tags.php index ebf129db83..57f56a3b8f 100644 --- a/src/daos/pgsql/Tags.php +++ b/src/daos/pgsql/Tags.php @@ -11,4 +11,6 @@ * @author Tobias Zeising */ class Tags extends \daos\mysql\Tags { + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; } diff --git a/src/daos/sqlite/Items.php b/src/daos/sqlite/Items.php index bea120d11a..a48b6ebfd3 100644 --- a/src/daos/sqlite/Items.php +++ b/src/daos/sqlite/Items.php @@ -10,6 +10,7 @@ * @author Harald Lapp * @author Tobias Zeising */ -// class Items extends Database { class Items extends \daos\mysql\Items { + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; } diff --git a/src/daos/sqlite/Sources.php b/src/daos/sqlite/Sources.php index 37e39395fc..8f84af8854 100644 --- a/src/daos/sqlite/Sources.php +++ b/src/daos/sqlite/Sources.php @@ -11,4 +11,6 @@ * @author Tobias Zeising */ class Sources extends \daos\mysql\Sources { + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; } diff --git a/src/daos/sqlite/Tags.php b/src/daos/sqlite/Tags.php index e1b9368f97..17dd24074b 100644 --- a/src/daos/sqlite/Tags.php +++ b/src/daos/sqlite/Tags.php @@ -10,4 +10,6 @@ * @author Tobias Zeising */ class Tags extends \daos\mysql\Tags { + /** @var class-string SQL helper */ + protected static $stmt = Statements::class; }