From ca06c863282059a5e98ca76422fc0fcf2ce6dbdc Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 11:09:52 -0500 Subject: [PATCH] perf: consolidate article DB queries into single cached lookup (#38) - Add loadArticle() with static per-request cache for article data - Refactor getArticleDate(), getArticleAuthor() to use cached article - Refactor findImage() for com_content to use cached article - Pass cached article to JsonLdBuilder::buildArticle() to skip its query - Reduces article page DB queries from 5 to 1 for OG tag generation --- .../src/Extension/MokoOG.php | 69 +++++++++++-------- .../src/Helper/JsonLdBuilder.php | 43 +++++++----- 2 files changed, 65 insertions(+), 47 deletions(-) diff --git a/source/packages/plg_system_mokoog/src/Extension/MokoOG.php b/source/packages/plg_system_mokoog/src/Extension/MokoOG.php index 37a3321..5418449 100644 --- a/source/packages/plg_system_mokoog/src/Extension/MokoOG.php +++ b/source/packages/plg_system_mokoog/src/Extension/MokoOG.php @@ -220,7 +220,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface if ($option === 'com_mokoshop' && $view === 'product' && $id > 0) { $schema = JsonLdBuilder::buildProduct($id, $title, $description, $imageUrl); } elseif ($option === 'com_content' && $view === 'article' && $id > 0) { - $schema = JsonLdBuilder::buildArticle($id, $title, $description, $imageUrl); + $schema = JsonLdBuilder::buildArticle($id, $title, $description, $imageUrl, $this->loadArticle($id)); } else { $schema = JsonLdBuilder::buildWebPage($title, $description); } @@ -448,17 +448,10 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface // For Joomla articles, look at the intro/full image fields if ($option === 'com_content' && $id > 0) { - $db = Factory::getDbo(); - $query = $db->getQuery(true) - ->select($db->quoteName('images')) - ->from($db->quoteName('#__content')) - ->where($db->quoteName('id') . ' = ' . (int) $id); + $article = $this->loadArticle($id); - $db->setQuery($query); - $images = $db->loadResult(); - - if ($images) { - $imagesData = json_decode($images, true); + if ($article && !empty($article->images)) { + $imagesData = json_decode($article->images, true); if (!empty($imagesData['image_fulltext'])) { return $imagesData['image_fulltext']; @@ -514,6 +507,38 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface return rtrim(Uri::root(), '/') . '/' . ltrim($image, '/'); } + /** + * Load and cache a full article record with author for the current request. + * + * @param int $id Article ID + * + * @return object|null + */ + private function loadArticle(int $id): ?object + { + static $cache = []; + + if (isset($cache[$id])) { + return $cache[$id]; + } + + $db = Factory::getDbo(); + $query = $db->getQuery(true) + ->select($db->quoteName([ + 'a.title', 'a.introtext', 'a.fulltext', 'a.images', + 'a.created', 'a.modified', 'a.publish_up', 'a.metadesc', + ])) + ->select($db->quoteName('u.name', 'author_name')) + ->from($db->quoteName('#__content', 'a')) + ->join('LEFT', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('u.id') . ' = ' . $db->quoteName('a.created_by')) + ->where($db->quoteName('a.id') . ' = ' . $id); + + $db->setQuery($query); + $cache[$id] = $db->loadObject(); + + return $cache[$id]; + } + /** * Get a date field from an article. * @@ -524,16 +549,9 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface */ private function getArticleDate(int $id, string $field): string { - $db = Factory::getDbo(); - $query = $db->getQuery(true) - ->select($db->quoteName($field)) - ->from($db->quoteName('#__content')) - ->where($db->quoteName('id') . ' = ' . $id); + $article = $this->loadArticle($id); - $db->setQuery($query); - $date = $db->loadResult(); - - return $date ?: ''; + return $article->$field ?? ''; } /** @@ -545,16 +563,9 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface */ private function getArticleAuthor(int $id): string { - $db = Factory::getDbo(); - $query = $db->getQuery(true) - ->select($db->quoteName('u.name')) - ->from($db->quoteName('#__content', 'a')) - ->join('LEFT', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('u.id') . ' = ' . $db->quoteName('a.created_by')) - ->where($db->quoteName('a.id') . ' = ' . $id); + $article = $this->loadArticle($id); - $db->setQuery($query); - - return $db->loadResult() ?: ''; + return $article->author_name ?? ''; } /** diff --git a/source/packages/plg_system_mokoog/src/Helper/JsonLdBuilder.php b/source/packages/plg_system_mokoog/src/Helper/JsonLdBuilder.php index 5d1b448..9c92d85 100644 --- a/source/packages/plg_system_mokoog/src/Helper/JsonLdBuilder.php +++ b/source/packages/plg_system_mokoog/src/Helper/JsonLdBuilder.php @@ -20,31 +20,36 @@ class JsonLdBuilder /** * Build Article schema for a com_content article. * - * @param int $articleId Article ID - * @param string $title Page title - * @param string $description Page description - * @param string $image Image URL (absolute) + * @param int $articleId Article ID + * @param string $title Page title + * @param string $description Page description + * @param string $image Image URL (absolute) + * @param object|null $cachedArticle Pre-loaded article data (avoids duplicate query) * * @return array|null */ - public static function buildArticle(int $articleId, string $title, string $description, string $image): ?array + public static function buildArticle(int $articleId, string $title, string $description, string $image, ?object $cachedArticle = null): ?array { if ($articleId <= 0) { return null; } - $db = Factory::getDbo(); - $query = $db->getQuery(true) - ->select($db->quoteName([ - 'a.created', 'a.modified', 'a.publish_up', - 'u.name', - ])) - ->from($db->quoteName('#__content', 'a')) - ->join('LEFT', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('u.id') . ' = ' . $db->quoteName('a.created_by')) - ->where($db->quoteName('a.id') . ' = ' . $articleId); + $article = $cachedArticle; - $db->setQuery($query); - $article = $db->loadObject(); + if (!$article) { + $db = Factory::getDbo(); + $query = $db->getQuery(true) + ->select($db->quoteName([ + 'a.created', 'a.modified', 'a.publish_up', + ])) + ->select($db->quoteName('u.name', 'author_name')) + ->from($db->quoteName('#__content', 'a')) + ->join('LEFT', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('u.id') . ' = ' . $db->quoteName('a.created_by')) + ->where($db->quoteName('a.id') . ' = ' . $articleId); + + $db->setQuery($query); + $article = $db->loadObject(); + } if (!$article) { return null; @@ -60,10 +65,12 @@ class JsonLdBuilder 'dateModified' => $article->modified ?: $article->created, ]; - if (!empty($article->name)) { + $authorName = $article->author_name ?? ''; + + if (!empty($authorName)) { $schema['author'] = [ '@type' => 'Person', - 'name' => $article->name, + 'name' => $authorName, ]; }