Skip to content

Commit

Permalink
daos: break daos depending on Database
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
jtojnar committed Feb 5, 2020
1 parent 25b4b60 commit fda5478
Show file tree
Hide file tree
Showing 12 changed files with 63 additions and 49 deletions.
6 changes: 0 additions & 6 deletions src/daos/mysql/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -248,9 +245,6 @@ public function __construct() {
// just initialize once
self::$initialized = true;
}

$class = 'daos\\' . \F3::get('db_type') . '\\Statements';
$this->stmt = new $class();
}

/**
Expand Down
53 changes: 28 additions & 25 deletions src/daos/mysql/Items.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
* @author Tobias Zeising <[email protected]>
* @author Harald Lapp <[email protected]>
*/
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
*
Expand Down Expand Up @@ -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));
}

/**
Expand All @@ -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']
);
}
Expand All @@ -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';
}
Expand All @@ -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) {
Expand All @@ -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
];
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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]
);
Expand All @@ -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'),
Expand Down Expand Up @@ -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'];
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
];
}
}
Expand Down Expand Up @@ -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]]);
}
}
Expand Down
23 changes: 13 additions & 10 deletions src/daos/mysql/Sources.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
* @license GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html)
* @author Tobias Zeising <[email protected]>
*/
class Sources extends Database {
class Sources {
/** @var class-string SQL helper */
protected static $stmt = Statements::class;

/**
* add new source
*
Expand All @@ -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))
Expand All @@ -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)),
Expand Down Expand Up @@ -140,15 +143,15 @@ 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 {
$ret = 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
]);
Expand All @@ -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
]);
Expand All @@ -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
]);
Expand Down
4 changes: 2 additions & 2 deletions src/daos/mysql/Statements.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 7 additions & 4 deletions src/daos/mysql/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
* @license GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html)
* @author Tobias Zeising <[email protected]>
*/
class Tags extends Database {
class Tags {
/** @var class-string SQL helper */
protected static $stmt = Statements::class;

/**
* save given tag color
*
Expand Down Expand Up @@ -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]);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/daos/pgsql/Items.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
* @author Tobias Zeising <[email protected]>
*/
class Items extends \daos\mysql\Items {
/** @var class-string SQL helper */
protected static $stmt = Statements::class;
}
2 changes: 2 additions & 0 deletions src/daos/pgsql/Sources.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
* @author Tobias Zeising <[email protected]>
*/
class Sources extends \daos\mysql\Sources {
/** @var class-string SQL helper */
protected static $stmt = Statements::class;
}
2 changes: 1 addition & 1 deletion src/daos/pgsql/Statements.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
2 changes: 2 additions & 0 deletions src/daos/pgsql/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
* @author Tobias Zeising <[email protected]>
*/
class Tags extends \daos\mysql\Tags {
/** @var class-string SQL helper */
protected static $stmt = Statements::class;
}
3 changes: 2 additions & 1 deletion src/daos/sqlite/Items.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @author Harald Lapp <[email protected]>
* @author Tobias Zeising <[email protected]>
*/
// class Items extends Database {
class Items extends \daos\mysql\Items {
/** @var class-string SQL helper */
protected static $stmt = Statements::class;
}
2 changes: 2 additions & 0 deletions src/daos/sqlite/Sources.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
* @author Tobias Zeising <[email protected]>
*/
class Sources extends \daos\mysql\Sources {
/** @var class-string SQL helper */
protected static $stmt = Statements::class;
}
2 changes: 2 additions & 0 deletions src/daos/sqlite/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
* @author Tobias Zeising <[email protected]>
*/
class Tags extends \daos\mysql\Tags {
/** @var class-string SQL helper */
protected static $stmt = Statements::class;
}

0 comments on commit fda5478

Please sign in to comment.