public function getPaginationData($results, $page, $perPage, $total)
protected function getCommentsOnProfilePostPaginated(\XF\Entity\ProfilePost $profilePost, $page = 1, $perPage = null)
{
$perPage = intval($perPage);
if ($perPage <= 0)
{
$perPage = $this->options()->messagesPerPage;
}
$commentFinder = $this->setupCommentsFinder($profilePost);
$total = $commentFinder->total();
$this->assertValidApiPage($page, $perPage, $total);
$commentFinder->limitByPage($page, $perPage);
$postResults = $commentFinder->fetch()->toApiResults();
/** @var \XF\Repository\Attachment $attachmentRepo */
$attachmentRepo = $this->repository('XF:Attachment');
$attachmentRepo->addAttachmentsToContent($postResults, 'profile_post_comment');
return [
'comments' => $postResults,
'pagination' => $this->getPaginationData($postResults, $page, $perPage, $total)
];
}
Error: Call to undefined method XF\Db\Mysqli\Statement::limitByPage() in src\addons\AV\ThreadTitles\Admin\Controller\TitleController.php at line 65
limitByPage()
is a method of class XF\Mvc\Entity\Finder
but as can be seen from the error message, $db->query()
returns an instance of XF\Db\Mysqli\Statement
.limitByPage()
or total()
.limitByPage()
or groupBy()
isn't possible with "low level" DB access.Code:<xf:foreach loop="$duplicates" value="$duplicate"> <div class="block" style="margin-bottom: 3px"> <div class="block-container"> <div style="padding:10px;"> <h3 style="margin:0">{$duplicate.title}</h3> </div> </div> </div> </xf:foreach>
And I don't really understand why {$duplicate.title} works as $duplicates is array of arrays, not array of objects.
$duplicates = [
[
'title' => 'Some title',
'COUNT(title)' => 2,
],
[
'title' => 'Another title ',
'COUNT(title)' => 4,
],
];
$output = '';
foreach ($uplicates as $duplicate)
{
$output .= '<div class="block" style="margin-bottom: 3px">
<div class="block-container">
<div style="padding:10px;">
<h3 style="margin:0">' . $duplicate['title'] . '
</h3>
</div>
</div>
</div>';
}
$duplicates
is an array of arrays so foreach does work just fine.ArrayAccess
so it can be used in foreach
as well.\XF\Mvc\Entity\Entity
- which again implements ArrayAccess
.$db = $this->db();
$db = $this->em->getDb();
$db = \XF::em()->getDb();
$db = \XF::app()->em()->getDb();
$db = \XF::app()->em->getDb();
$db = \XF::db();
$db = \XF::app()->db;
$db = \XF::app()->db();
$app = \XF::app();
$app['db'];
$container = \XF::app()->container();
$db = $container['db'];
$db = $container->offsetGet('db');
$db = $container->__get('db');
// probably a dozen more variants ... I am too lazy to list them all :)
$db
you can perform raw queries using method query
.total()
) and build pagination based on that.COUNT()
is a grouping function you can GROUP BY
on the result of a grouping function, soSELECT title, COUNT(title) AS mycount
FROM xf_thread
GROUP BY mycount
HAVING mycount > 1
ORDER BY mycount DESC
yesare you extending the abstractcontroller?
<?php
namespace AV\ThreadTitles\Admin\Controller;
use XF\Admin\Controller\AbstractController;
class TitleController extends AbstractController
{
you should be able to use that.
public function actionDuplicates()
{
$db = $this->app->db();
$myQuery = $db->query('
SELECT title, COUNT(title) AS mycount
FROM xf_thread
GROUP BY title
HAVING mycount > 1
ORDER BY mycount DESC
');
$page = $this->filterPage();
$perPage = 20;
$total = $myQuery->total();
$duplicates = $myQuery->fetchAll();
$viewParams = [
'duplicates' => $duplicates,
'page' => $page,
'perPage' => $perPage,
'total' => $total,
];
return $this->view('AV\ThreadTitles:TitleController\Duplicates', 'av_thread_titles_duplicates', $viewParams);
}
the abstract controller has:
Code:public function getPaginationData($results, $page, $perPage, $total)
\XF\Api\Controller\AbstractController
has this method, not \XF\Pub\Controller\AbstractController
or \XF\Admin\Controller\AbstractController
!public function findDuplicateThreadTitles(int $page = 1, int $perPage = 20): array
{
$db = $this->db();
if ($page < 1)
{
$page = 1;
}
if ($perPage < 1)
{
$perPage = 20;
}
$offset = ($page - 1) * $perPage;
$duplicates = $db->fetchAll('
SELECT title, COUNT(title) AS dupes
FROM xf_thread
GROUP BY title
HAVING dupes > 1
ORDER BY dupes DESC
LIMIT ?, ?
', [$offset, $perPage]);
$total = $db->fetchOne('
SELECT COUNT(*)
FROM xf_thread
GROUP BY title
HAVING COUNT(title) > 1
');
return [
'duplicates' => $duplicates,
'total' => $total,
];
}
public function actionDuplicates()
{
$page = $this->filterPage();
$perPage = 20;
$duplicateThreadsRepo = $this->getDuplicateThreadsRepo();
$duplicates = $duplicateThreadsRepo->findDuplicateThreadTitles($page, $perPage);
$this->assertValidPage($page, $perPage, $duplicates['total'], 'duplicated-thread-titles');
$viewParams = [
'duplicates' => $duplicates['duplicates'],
'page' => $page,
'perPage' => $perPage,
'total' => $duplicates['total'],
];
return $this->view('AV\ThreadTitles:TitleController\Duplicates', 'av_thread_titles_duplicates', $viewParams);
}
and your controller code
$duplicateThreadsRepo = $this->getDuplicateThreadsRepo();
$duplicates = $db->query('
SELECT title, COUNT(title) AS mycount
FROM xf_thread
GROUP BY title
HAVING mycount > 1
ORDER BY mycount DESC
')->fetchAll();
$myDuplicates = array();
for ($x = 1; $x <= 100; $x++) {
foreach ($duplicates as $title => $mycount){
$myDuplicates[$title] = $mycount;
}
}
Well, I though you knew how to use a repository as your Add-on does have oneError: Call to undefined method AV\ThreadTitles\Admin\Controller\TitleController::getDuplicateThreadsRepo() in src\addons\AV\ThreadTitles\Admin\Controller\TitleController.php at line 60
getDuplicateThreadsRepo
does not exist.Pretty complicated approach, usingPHP:for ($x = 1; $x <= 100; $x++) { foreach ($duplicates as $title => $mycount) { $myDuplicates[$title] = $mycount; } }
array_slice
would be a lot easier.On a cufficiently large board (eg. a few 100K threads),and pass $myDuplicates to a template. and now I'm not afraid of thousands results so it will do just fine without a pagination.
Correct? Or I'm missing something?
$duplicates
could easily be a few 10K rows.didn't I ask that by posting a single line from your code following by error "call to undefined method"? )Now the question I would like you to answer is:
Why is it missing and how can this be fixed?
I'll google for slice. I guess it will drop items > limit I would indicate (say 100), so yeah, no need to run a loop 100 times. but I heard that php makes 1m operations in a second, so i guess the loop of 100 doesn't take much time.Pretty complicated approach, usingarray_slice
would be a lot easier.
(But IMHO you shouldn't like this in PHP anyway)
this part I don't understand much yet. if I understood correctly, I have to go though all the rows so $mycount will show actual numbers of duplicates. when I had limit 100 in my sql query it returned 100 items with $mycount == 2.On a cufficiently large board (eg. a few 100K threads),$duplicates
could easily be a few 10K rows.
That is a lot of data for a PHP array and requires quite a bit of RAM (+ network fransfer from DB + CPU).
IMHO this at least somewhat borderlines resource standards rule #8
Therefore I'd at least implement pagination with SQL, but even that might be too resource heavy - ideally the data should be aggregated somehow (maybe once/week or so via cron?) and only the aggregated data should be queries (via repository) from acp controllers.
You should be able to answer that yourselfdidn't I ask that by posting a single line from your code following by error "call to undefined method"? )
so why is it missing and how can this be fixed?
/**
* @return \XF\Repository\ErrorLog
*/
protected function getErrorLogRepo()
{
return $this->repository('XF:ErrorLog');
}
17K isn't that much so you probably won't feel that it is slow.my forum has 17k threads and a page "duplicates" opens as fast as any other xF page.
LIMIT 10
or LIMIT 1000
.Here is a a smiliar method from a XenForo Admin Controller that should give you an idea how to implement the missing method in your controller.
PHP:/** * @return \XF\Repository\ErrorLog */ protected function getErrorLogRepo() { return $this->repository('XF:ErrorLog'); }
I have to say that I read new things that I barely understand and - "well, probably this..." then I do and results are not what I expected so I make assumptions - "well, probably that". so now my head is fool of a mix of "probably this" and "probably that", instead of knowledge. )So I don't really understand " will show realistic $mycount if a board has less than 20k of threads and somewhat realistic $mycount for bigger ones".
ok, after digesting this information for some time I understand that that's how it should be.ideally the data should be aggregated somehow (maybe once/week or so via cron?) and only the aggregated data should be queries (via repository) from acp controllers.
We use essential cookies to make this site work, and optional cookies to enhance your experience.