diff --git a/src/composer.lock b/src/composer.lock
index d929ee6..342b234 100644
--- a/src/composer.lock
+++ b/src/composer.lock
@@ -12,7 +12,7 @@
"source": {
"type": "git",
"url": "https://git.bitbadger.solutions/bit-badger/pdo-document",
- "reference": "bcca9f5ace2ba32c24412e284ff4789480d9619d"
+ "reference": "2d8f8b6e8728f8fbe0d801e2bc529a678f65ad9b"
},
"require": {
"ext-pdo": "*",
@@ -32,10 +32,11 @@
"autoload-dev": {
"psr-4": {
"Test\\Unit\\": "./tests/unit",
- "Test\\Integration\\": "./tests/integration"
+ "Test\\Integration\\": "./tests/integration",
+ "Test\\Integration\\SQLite\\": "./tests/integration/sqlite"
}
},
- "time": "2024-06-05T02:36:58+00:00"
+ "time": "2024-06-08T14:49:52+00:00"
},
{
"name": "netresearch/jsonmapper",
diff --git a/src/lib/Data.php b/src/lib/Data.php
index 435c1e8..4174b5d 100644
--- a/src/lib/Data.php
+++ b/src/lib/Data.php
@@ -2,16 +2,11 @@
namespace FeedReaderCentral;
-use BitBadger\PDODocument\Configuration;
-use BitBadger\PDODocument\Custom;
-use BitBadger\PDODocument\Definition;
-use BitBadger\PDODocument\DocumentException;
-use BitBadger\PDODocument\Field;
+use BitBadger\PDODocument\{Configuration, Custom, Definition, DocumentException, Field};
use BitBadger\PDODocument\Mapper\StringMapper;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
-use PDO;
/**
* A centralized place for data access for the application
@@ -21,18 +16,17 @@ class Data
/**
* Create the search index and synchronization triggers for the item table
*
- * @param PDO $pdo The database connection on which these will be created
* @throws DocumentException If any is encountered
*/
- public static function createSearchIndex(PDO $pdo): void
+ public static function createSearchIndex(): void
{
Custom::nonQuery("CREATE VIRTUAL TABLE item_search USING fts5(content, content='item', content_rowid='id')",
- [], $pdo);
+ []);
Custom::nonQuery(<<<'SQL'
CREATE TRIGGER item_ai AFTER INSERT ON item BEGIN
INSERT INTO item_search (rowid, content) VALUES (new.data->>'id', new.data->>'content');
END;
- SQL, [], $pdo);
+ SQL, []);
Custom::nonQuery(<<<'SQL'
CREATE TRIGGER item_au AFTER UPDATE ON item BEGIN
INSERT INTO item_search (
@@ -42,7 +36,7 @@ class Data
);
INSERT INTO item_search (rowid, content) VALUES (new.data->>'id', new.data->>'content');
END;
- SQL, [], $pdo);
+ SQL, []);
Custom::nonQuery(<<<'SQL'
CREATE TRIGGER item_ad AFTER DELETE ON item BEGIN
INSERT INTO item_search (
@@ -51,7 +45,7 @@ class Data
'delete', old.data->>'id', old.data->>'content'
);
END;
- SQL, [], $pdo);
+ SQL, []);
}
/**
@@ -62,19 +56,18 @@ class Data
public static function ensureDb(): void
{
$tables = Custom::array("SELECT name FROM sqlite_master WHERE type = 'table'", [], new StringMapper('name'));
- $pdo = Configuration::dbConn();
if (!in_array(Table::USER, $tables)) {
- Definition::ensureTable(Table::USER, $pdo);
- Definition::ensureFieldIndex(Table::USER, 'email', ['email'], $pdo);
+ Definition::ensureTable(Table::USER);
+ Definition::ensureFieldIndex(Table::USER, 'email', ['email']);
}
if (!in_array(Table::FEED, $tables)) {
- Definition::ensureTable(Table::FEED, $pdo);
- Definition::ensureFieldIndex(Table::FEED, 'user', ['user_id'], $pdo);
+ Definition::ensureTable(Table::FEED);
+ Definition::ensureFieldIndex(Table::FEED, 'user', ['user_id']);
}
if (!in_array(Table::ITEM, $tables)) {
- Definition::ensureTable(Table::ITEM, $pdo);
- Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link'], $pdo);
- self::createSearchIndex($pdo);
+ Definition::ensureTable(Table::ITEM);
+ Definition::ensureFieldIndex(Table::ITEM, 'feed', ['feed_id', 'item_link']);
+ self::createSearchIndex();
}
$journalMode = Custom::scalar("PRAGMA journal_mode", [], new StringMapper('journal_mode'));
if ($journalMode <> 'wal') Custom::nonQuery("PRAGMA journal_mode = 'wal'", []);
diff --git a/src/lib/Feed.php b/src/lib/Feed.php
index ea2aa83..32a0503 100644
--- a/src/lib/Feed.php
+++ b/src/lib/Feed.php
@@ -2,19 +2,10 @@
namespace FeedReaderCentral;
-use BitBadger\PDODocument\Configuration;
-use BitBadger\PDODocument\Custom;
-use BitBadger\PDODocument\Document;
-use BitBadger\PDODocument\DocumentException;
-use BitBadger\PDODocument\DocumentList;
-use BitBadger\PDODocument\Exists;
-use BitBadger\PDODocument\Field;
-use BitBadger\PDODocument\Find;
-use BitBadger\PDODocument\Parameters;
-use BitBadger\PDODocument\Patch;
-use BitBadger\PDODocument\Query;
+use BitBadger\PDODocument\{
+ Configuration, Custom, Document, DocumentException, DocumentList, Exists, Field, Find, Parameters, Patch, Query
+};
use DateTimeInterface;
-use PDO;
/**
* An RSS or Atom feed
@@ -75,23 +66,22 @@ class Feed
* @param int $feedId The ID of the feed to which these items belong
* @param ParsedFeed $parsed The extracted Atom or RSS feed items
* @param DateTimeInterface $lastChecked When this feed was last checked (only new items will be added)
- * @param PDO $pdo The database connection over which items should be updated
* @return array ['ok' => true] if successful, ['error' => message] if not
*/
- public static function updateItems(int $feedId, ParsedFeed $parsed, DateTimeInterface $lastChecked, PDO $pdo): array
+ public static function updateItems(int $feedId, ParsedFeed $parsed, DateTimeInterface $lastChecked): array
{
$results =
- array_map(function ($item) use ($pdo, $feedId) {
+ array_map(function ($item) use ($feedId) {
try {
$existing = Find::firstByFields(Table::ITEM,
[Field::EQ('feed_id', $feedId), Field::EQ('item_guid', $item->guid)], Item::class);
if ($existing) {
if ($existing->published_on != $item->publishedOn
|| ($existing->updated_on != ($item->updatedOn ?? ''))) {
- Patch::byId(Table::ITEM, $existing->id, $item->patchFields(), $pdo);
+ Patch::byId(Table::ITEM, $existing->id, $item->patchFields());
}
} else {
- Document::insert(Table::ITEM, Item::fromFeedItem($feedId, $item), $pdo);
+ Document::insert(Table::ITEM, Item::fromFeedItem($feedId, $item));
}
return ['ok' => true];
} catch (DocumentException $ex) {
@@ -107,10 +97,9 @@ class Feed
* Purge items for a feed
*
* @param int $feedId The ID of the feed to be purged
- * @param PDO $pdo The database connection on which items should be purged
* @return array|string[]|true[] ['ok' => true] if purging was successful, ['error' => message] if not
*/
- private static function purgeItems(int $feedId, PDO $pdo): array
+ private static function purgeItems(int $feedId): array
{
if (!array_search(PURGE_TYPE, [self::PURGE_READ, self::PURGE_BY_DAYS, self::PURGE_BY_COUNT])) {
return ['error' => 'Unrecognized purge type ' . PURGE_TYPE];
@@ -139,7 +128,7 @@ class Feed
SQL;
}
try {
- Custom::nonQuery($sql, Parameters::addFields($fields, []), $pdo);
+ Custom::nonQuery($sql, Parameters::addFields($fields, []));
return ['ok' => true];
} catch (DocumentException $ex) {
return ['error' => "$ex"];
@@ -151,10 +140,9 @@ class Feed
*
* @param int $feedId The ID of the feed to be refreshed
* @param string $url The URL of the feed to be refreshed
- * @param PDO $pdo A database connection to use to refresh the feed
* @return array|string[]|true[] ['ok' => true] if successful, ['error' => message] if not
*/
- public static function refreshFeed(int $feedId, string $url, PDO $pdo): array
+ public static function refreshFeed(int $feedId, string $url): array
{
$feedRetrieval = ParsedFeed::retrieve($url);
if (key_exists('error', $feedRetrieval)) return $feedRetrieval;
@@ -165,7 +153,7 @@ class Feed
if (!$feedDoc) return ['error' => 'Could not derive date last checked for feed'];
$lastChecked = date_create_immutable($feedDoc->checked_on ?? WWW_EPOCH);
- $itemUpdate = self::updateItems($feedId, $feed, $lastChecked, $pdo);
+ $itemUpdate = self::updateItems($feedId, $feed, $lastChecked);
if (key_exists('error', $itemUpdate)) return $itemUpdate;
$patch = [
@@ -174,12 +162,12 @@ class Feed
'checked_on' => Data::formatDate('now')
];
if ($url == $feed->url) $patch['url'] = $feed->url;
- Patch::byId(Table::FEED, $feedId, $patch, $pdo);
+ Patch::byId(Table::FEED, $feedId, $patch);
} catch (DocumentException $ex) {
return ['error' => "$ex"];
}
- return PURGE_TYPE == self::PURGE_NONE ? ['ok' => true] : self::purgeItems($feedId, $pdo);
+ return PURGE_TYPE == self::PURGE_NONE ? ['ok' => true] : self::purgeItems($feedId);
}
/**
@@ -196,18 +184,17 @@ class Feed
$feed = $feedExtract['ok'];
try {
- $pdo = Configuration::dbConn();
$fields = [Field::EQ('user_id', $_SESSION[Key::USER_ID]), Field::EQ('url', $feed->url)];
- if (Exists::byFields(Table::FEED, $fields, $pdo)) {
+ if (Exists::byFields(Table::FEED, $fields)) {
return ['error' => "Already subscribed to feed $feed->url"];
}
- Document::insert(Table::FEED, self::fromParsed($feed), $pdo);
+ Document::insert(Table::FEED, self::fromParsed($feed));
$doc = Find::firstByFields(Table::FEED, $fields, static::class);
if (!$doc) return ['error' => 'Could not retrieve inserted feed'];
- $result = self::updateItems($doc->id, $feed, date_create_immutable(WWW_EPOCH), $pdo);
+ $result = self::updateItems($doc->id, $feed, date_create_immutable(WWW_EPOCH));
if (key_exists('error', $result)) return $result;
return ['ok' => $doc->id];
@@ -226,12 +213,11 @@ class Feed
public static function update(Feed $existing, string $url): array
{
try {
- $pdo = Configuration::dbConn();
Patch::byFields(Table::FEED,
[Field::EQ(Configuration::$idField, $existing->id), Field::EQ('user_id', $_SESSION[Key::USER_ID])],
- ['url' => $url], $pdo);
+ ['url' => $url]);
- return self::refreshFeed($existing->id, $url, $pdo);
+ return self::refreshFeed($existing->id, $url);
} catch (DocumentException $ex) {
return ['error' => "$ex"];
}
@@ -262,10 +248,9 @@ class Feed
try {
$feeds = self::retrieveAll($_SESSION[Key::USER_ID]);
- $pdo = Configuration::dbConn();
$errors = [];
foreach ($feeds->items() as $feed) {
- $result = self::refreshFeed($feed->id, $feed->url, $pdo);
+ $result = self::refreshFeed($feed->id, $feed->url);
if (key_exists('error', $result)) $errors[] = $result['error'];
}
} catch (DocumentException $ex) {
@@ -284,7 +269,9 @@ class Feed
*/
public static function retrieveById(int $feedId): static|false
{
+ define('PDO_DOC_DEBUG_SQL', true);
$doc = Find::byId(Table::FEED, $feedId, static::class);
+ echo "Feed $feedId: " . ($doc ? 'found it' : 'not found');
return $doc && $doc->user_id == $_SESSION[Key::USER_ID] ? $doc : false;
}
}
diff --git a/src/lib/ItemList.php b/src/lib/ItemList.php
index 63b226b..5874353 100644
--- a/src/lib/ItemList.php
+++ b/src/lib/ItemList.php
@@ -2,13 +2,7 @@
namespace FeedReaderCentral;
-use BitBadger\PDODocument\Configuration;
-use BitBadger\PDODocument\Custom;
-use BitBadger\PDODocument\DocumentException;
-use BitBadger\PDODocument\DocumentList;
-use BitBadger\PDODocument\Field;
-use BitBadger\PDODocument\Parameters;
-use BitBadger\PDODocument\Query;
+use BitBadger\PDODocument\{Configuration, Custom, DocumentException, DocumentList, Field, Parameters, Query};
use BitBadger\PDODocument\Mapper\DocumentMapper;
/**
@@ -156,27 +150,26 @@ class ItemList
echo "
Error retrieving list:
$this->error";
return;
}
- $return = $this->returnURL == '' ? '' : '&from=' . urlencode($this->returnURL);
- $hasItems = false;
+ $return = $this->returnURL == '' ? '' : '&from=' . urlencode($this->returnURL);
echo '';
- foreach ($this->dbList->items() as $it) {
- $hasItems = true;
- echo '' . hx_get("/item?id=$it->id$return", strip_tags($it->title)) . '
';
- if ($this->showIndicators) {
- if (!$it->isRead()) echo 'Unread ';
- if ($it->isBookmarked()) echo 'Bookmarked ';
+ if ($this->dbList->hasItems()) {
+ foreach ($this->dbList->items() as $it) {
+ echo '
' . hx_get("/item?id=$it->id$return", strip_tags($it->title)) . '
';
+ if ($this->showIndicators) {
+ if (!$it->isRead()) echo 'Unread ';
+ if ($it->isBookmarked()) echo 'Bookmarked ';
+ }
+ echo '' . date_time($it->updated_on ?? $it->published_on) . '';
+ if ($this->linkFeed) {
+ echo ' • ' .
+ hx_get("/feed/items?id={$it->feed->id}&" . strtolower($this->itemType),
+ htmlentities($it->feed->title));
+ } elseif ($this->displayFeed) {
+ echo ' • ' . htmlentities($it->feed->title);
+ }
+ echo '';
}
- echo '' . date_time($it->updated_on ?? $it->published_on) . '';
- if ($this->linkFeed) {
- echo ' • ' .
- hx_get("/feed/items?id={$it->feed->id}&" . strtolower($this->itemType),
- htmlentities($it->feed->title));
- } elseif ($this->displayFeed) {
- echo ' • ' . htmlentities($it->feed->title);
- }
- echo '';
- }
- if (!$hasItems) {
+ } else {
echo '
There are no ' . strtolower($this->itemType) . ' items';
}
echo '
';
diff --git a/src/lib/ItemWithFeed.php b/src/lib/ItemWithFeed.php
index 3447225..c7f7ec5 100644
--- a/src/lib/ItemWithFeed.php
+++ b/src/lib/ItemWithFeed.php
@@ -2,14 +2,8 @@
namespace FeedReaderCentral;
-use BitBadger\PDODocument\Configuration;
-use BitBadger\PDODocument\Custom;
-use BitBadger\PDODocument\DocumentException;
-use BitBadger\PDODocument\Field;
-use BitBadger\PDODocument\Parameters;
-use BitBadger\PDODocument\Query;
-use BitBadger\PDODocument\Mapper\DocumentMapper;
-use BitBadger\PDODocument\Mapper\ExistsMapper;
+use BitBadger\PDODocument\{Configuration, Custom, DocumentException, Field, Parameters, Query};
+use BitBadger\PDODocument\Mapper\{DocumentMapper, ExistsMapper};
/**
* A combined item and feed (used for lists)
diff --git a/src/lib/User.php b/src/lib/User.php
index 7223e5d..4549ce2 100644
--- a/src/lib/User.php
+++ b/src/lib/User.php
@@ -2,14 +2,8 @@
namespace FeedReaderCentral;
-use BitBadger\PDODocument\Custom;
-use BitBadger\PDODocument\Document;
-use BitBadger\PDODocument\DocumentException;
-use BitBadger\PDODocument\Field;
-use BitBadger\PDODocument\Find;
+use BitBadger\PDODocument\{Custom, Document, DocumentException, Field, Find, Parameters, Query};
use BitBadger\PDODocument\Mapper\ExistsMapper;
-use BitBadger\PDODocument\Parameters;
-use BitBadger\PDODocument\Query;
/**
* A user of Feed Reader Central
diff --git a/src/util/db-update.php b/src/util/db-update.php
index 73917e5..0fa4088 100644
--- a/src/util/db-update.php
+++ b/src/util/db-update.php
@@ -1,16 +1,8 @@
query('SELECT * FROM old_user');
- if (!$users) throw new DocumentException('Could not retrieve users');
- while ($user = $users->fetch(PDO::FETCH_ASSOC)) {
- Document::insert(Table::USER, new User($user['id'], $user['email'], $user['password']), $pdo);
+ $users = Custom::list('SELECT * FROM old_user', [], new ArrayMapper());
+ if (!$users->hasItems()) throw new DocumentException('Could not retrieve users');
+ foreach ($users->items() as $user) {
+ Document::insert(Table::USER, new User($user['id'], $user['email'], $user['password']));
}
printfn('Migrating feeds...');
- $feeds = $pdo->query('SELECT * FROM old_feed');
- if (!$feeds) throw new DocumentException('Could not retrieve feeds');
- while ($feed = $feeds->fetch(PDO::FETCH_ASSOC)) {
+ $feeds = Custom::list('SELECT * FROM old_feed', [], new ArrayMapper());
+ if (!$feeds->hasItems()) throw new DocumentException('Could not retrieve feeds');
+ foreach ($feeds->items() as $feed) {
Document::insert(Table::FEED,
new Feed($feed['id'], $feed['user_id'], $feed['url'], $feed['title'], $feed['updated_on'],
- $feed['checked_on']), $pdo);
+ $feed['checked_on']));
}
printfn('Migrating items...');
- $items = $pdo->query('SELECT * FROM old_item');
- if (!$items) throw new DocumentException('Could not retrieve items');
- while ($item = $items->fetch(PDO::FETCH_ASSOC)) {
+ $items = Custom::list('SELECT * FROM old_item', [], new ArrayMapper());
+ if (!$items->hasItems()) throw new DocumentException('Could not retrieve items');
+ foreach ($items->items() as $item) {
Document::insert(Table::ITEM,
new Item($item['id'], $item['feed_id'], $item['title'], $item['item_guid'], $item['item_link'],
$item['published_on'], $item['updated_on'], $item['content'], $item['is_read'],
- $item['is_bookmarked']), $pdo);
+ $item['is_bookmarked']));
}
printfn('Dropping old tables...');
- Custom::nonQuery('DROP TABLE old_item', [], $pdo);
- Custom::nonQuery('DROP TABLE old_feed', [], $pdo);
- Custom::nonQuery('DROP TABLE old_user', [], $pdo);
+ Custom::nonQuery('DROP TABLE old_item', []);
+ Custom::nonQuery('DROP TABLE old_feed', []);
+ Custom::nonQuery('DROP TABLE old_user', []);
printfn(PHP_EOL. 'Migration complete!');
} catch (DocumentException $ex) {
printfn("ERR $ex");
diff --git a/src/util/refresh.php b/src/util/refresh.php
index 16078d9..49b26ea 100644
--- a/src/util/refresh.php
+++ b/src/util/refresh.php
@@ -1,11 +1,7 @@
items(), function (Feed $feed) use ($pdo, &$users) {
- $result = Feed::refreshFeed($feed->id, $feed->url, $pdo);
+ iterator_apply(Feed::retrieveAll()->items(), function (Feed $feed) use (&$users) {
+ $result = Feed::refreshFeed($feed->id, $feed->url);
$userKey = "$feed->user_id";
if (!key_exists($userKey, $users)) $users[$userKey] = Find::byId(Table::USER, $feed->user_id, User::class);
if (array_key_exists('error', $result)) {
diff --git a/src/util/search.php b/src/util/search.php
index 526baa8..6c2caab 100644
--- a/src/util/search.php
+++ b/src/util/search.php
@@ -1,9 +1,7 @@
id)], $db);
+ $feedCount = Count::byFields(Table::FEED, [Field::EQ('user_id', $user->id)]);
} catch (DocumentException $ex) {
printfn("$ex");
return;
@@ -192,9 +175,9 @@ function delete_user(string $email): void
$fields = [Field::EQ('user_id', $user->id, '@user')];
Custom::nonQuery(
'DELETE FROM ' . Table::ITEM . " WHERE data->>'feed_id' IN (SELECT data->>'id' FROM " . Table::FEED
- . ' WHERE ' . Query::whereByFields($fields) . ')', Parameters::addFields($fields, []), $db);
- Delete::byFields(Table::FEED, $fields, $db);
- Delete::byId(Table::USER, $user->id, $db);
+ . ' WHERE ' . Query::whereByFields($fields) . ')', Parameters::addFields($fields, []));
+ Delete::byFields(Table::FEED, $fields);
+ Delete::byId(Table::USER, $user->id);
printfn('%s deleted successfully', init_cap($displayUser));
} catch (DocumentException $ex) {
@@ -202,8 +185,6 @@ function delete_user(string $email): void
}
} catch (DocumentException $ex) {
printfn("$ex");
- } finally {
- $db->close();
}
}
@@ -214,13 +195,6 @@ function migrate_single_user(): void
{
global $argv;
- try {
- $db = Configuration::dbConn();
- } catch (DocumentException $ex) {
- printfn("ERR: Cannot obtain a connection to the database\n $ex");
- return;
- }
-
try {
if (!$single = User::findByEmail(Security::SINGLE_USER_EMAIL)) {
printfn('There is no single-user mode user to be migrated');
@@ -228,12 +202,10 @@ function migrate_single_user(): void
}
Patch::byId(Table::USER, $single->id,
- ['email' => $argv[2], 'password' => password_hash($argv[3], Security::PW_ALGORITHM)], $db);
+ ['email' => $argv[2], 'password' => password_hash($argv[3], Security::PW_ALGORITHM)]);
printfn('The single user has been moved to "%s", with password "%s"', $argv[2], $argv[3]);
} catch (DocumentException $ex) {
printfn("$ex");
- } finally {
- $db->close();
}
}