I am working on building a basic forum (inspired by laracasts.com/discuss). When a user posts a reply to a thread:
I'd like to direct them to the end of the list of paginated replies with their reply's anchor (same behavior as Laracasts).
I'd also like to return the user to the correct page when they edit one of their replies.
How can I figure out which page a new reply will be posted on (?page=x) and how can I return to the correct page after a reply has been edited? Or, from the main post listing, which page the latest reply is on?
Here is my current ForumPost model (minus a few unrelated things) -
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class ForumPost
*
* Forum Posts table
*
* #package App
*/
class ForumPost extends Model {
/**
* Post has many Replies
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function replies()
{
return $this->hasMany('App\ForumReply');
}
/**
* Get the latest reply for a post
* #return null
*/
public function latestReply()
{
return $this->replies()->orderBy('created_at', 'desc')->first();
}
}
UPDATE
Take a look at this and let me know what you think. It's a bit weird in how it works but it's returning the correct page for a given reply ID and it's just one method:
public function getReplyPage($replyId = null, $paginate = 2)
{
$id = $replyId ? $replyId : $this->latestReply()->id;
$count = $this->replies()->where('id', '<', $id)->count();
$page = 1; // Starting with one page
// Counter - when we reach the number provided in $paginate, we start a new page
$offset = 0;
for ($i = 0; $i < $count; $i++) {
$offset++;
if ($offset == $paginate) {
$page++;
$offset = 0;
}
}
return $page;
}
Fundamentally you are working with two values: first, what the index of a reply is in relation to all the replies of a post, and second the number of replies in on a page.
For example, you might have a reply with an id of 301. However, it is the 21st reply on a specific post. You need to some way to figure out that it is the 21st reply. This is actually relatively simple: you just count how many replies are associated with that post but have smaller ids.
//get the number of replies before the one you're looking for
public function getReplyIndex($replyId)
{
$count = $this->replies()->where('id', '<', $replyId)->count();
return $count;
}
That method should return the index of the reply you are looking for based- assuming, of course, that your replies are using auto-increment ids.
The second piece of the puzzle is figuring out which page you need. This is done using integer division. Basically you just divide the number normally, but don't use the remainder. If you are looking at the 21st reply, and you have 10 replies to a page, you know it should be on the third page (page 1: 1-10, page 2: 11-20, page 3: 21-30). This means you need to integer divide your reply index by your replies-per-page, then add 1. This will give us 21/10+1, which, using integer division, gives us 3. Yay!
//divides where we are by the number of replies on a page and adds 1
public function getPageNumber($index, $repliesPerPage)
{
$pageNumber = (int) ($index/$repliesPerPage+1);
return $pageNumber;
}
Alright, so now you just need to pull that page. This simply requires a method where you specify what page number you need, and how many replies to a page there are. That method can then calculate the offset and the limit, and retrieve the records you need.
public function getPageOfReplies($pageNumber, $repliesPerPage)
{
$pageOfReplies = $this->replies()->offset($pageNumber*$repliesPerPage)->limit($repliesPerPage)->get();
return $pageOfReplies;
}
For good measure, though, we can build a method to get the index of the final reply.
public function getLastReplyIndex()
{
$count = $this->replies()->count();
return $count;
}
Great! Now we have all the building blocks we need. We can build some simple methods that use our more general-purpose ones to easily retrieve the data we need.
Let's start with a method that gets the entire page of replies on which a single reply resides (feel free to change the names (also I'm assuming there are 10 replies per page)):
public function getPageThatReplyIsOn($replyId)
{
$repliesPerPage = 10;
$index = $this->getReplyIndex($replyId);
$pageNumber = $this->getPageNumber($index, $repliesPerPage);
return $this->getPageOfReplies($pageNumber, $repliesPerPage);
}
For good measure, we can make a method that gets the page of final replies.
public function getFinalReplyPage()
{
$repliesPerPage = 10;
$index = $this->getLastReplyIndex();
$pageNumber = $this->getPageNumber($index, $repliesPerPage);
return $this->getPageOfReplies($pageNumber, $repliesPerPage);
}
You could build a variety of other methods to use our building block methods and jump around pages, get the pages after or before a reply, etc.
A couple notes
These all go in your ForumPost model, which should have a one-to-many relationship with your replies.
These are a variety of methods that are meant to provide a wide array of functionality. Don't be afraid to read through them and test them individually to see exactly what they are doing. None of them are very long, so it shouldn't be difficult to do that.
Here is what I came up with. If anyone has any suggestions to improve on this, PLEASE let me know. I'm really wondering if there is a more Laravel way to do this and I would really appreciate Jeffrey Way sharing his secret, since he is doing this exact thing over at Laracasts.
/**
* Get Reply Page
*
* Returns the page number where a reply resides as it relates to pagination
*
* #param null $replyId Optional ID for specific reply
* #param bool $pageLink If True, return a GET parameter ?page=x
* #param int $paginate Number of posts per page
* #return int|null|string // Int for only the page number, null if no replies, String if $pageLink == true
*/
public function getReplyPage($replyId = null, $pageLink = false, $paginate = 20)
{
// Find the page for a specific reply if provided, otherwise find the most
// recent post's ID. If there are no replies, return null and exit.
if (!$replyId) {
$latestReply = $this->latestReply();
if ($latestReply) {
$replyId = $latestReply->id;
} else {
return null;
}
}
// Find the number of replies to this Post prior to the one in question
$count = $this->replies()->where('id', '<', $replyId)->count();
$page = CEIL($count / $paginate +1);
// Return either the integer or URL parameter
return $pageLink ? "?page=$page" : $page;
}
Related
I'm at a stage where I'm refactoring my code, and I've come across an interesting conundrum.
In my ArticleController I have a bog standard store method for storing an article in my articles database table.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(StoreArticle $request)
{
$article = new Article();
$defauultPublished = "draft";
$IntranetOnly = false;
$isFeatured = false;
$isFeatured = ($request->get('featuredArticle') == "1" ? true : false);
$IntranetOnly = ($request->get('IntranetOnly') == "1" ? true : false);
$article->title = $request->get('title');
$article->slug = str_slug($request->get('title'));
$article->author = $request->get('author');
$article->category = $request->get('category');
$article->excerpt = $request->get('excerpt');
$article->content = clean($request->get('content'));
$article->featuredImage = $request->get('featuredImage');
$article->featuredVideo = $request->get('featuredVideo');
$article->readingTime = $this->calculateReadTime($request);
$article->featuredArticle = $isFeatured;
$article->IntranetOnly = $IntranetOnly;
$article->published = $defauultPublished;
$article->save();
$article->handleTags($request);
return redirect('editable/news-and-updates')->with('success', 'Article has been added');
}
I also have a function for calculating read time:
/**
* Calculate a rough reading time for an articles by counting the words present
* These words are then divided by a given reading time and rounded to the nearest whole number
* Reading time average is roughly 267 words per minute, so this also accounts for relatively slow readers
*
* #param Request $request
* #return void
*/
public function calculateReadTime(Request $request)
{
$readingSpeed = 200;
$title = str_word_count(strip_tags($request->get('title')));
$excerpt = str_word_count(strip_tags($request->get('excerpt')));
$content = str_word_count(strip_tags($request->get('content')));
$words = ($title + $excerpt + $content);
$minutes = round($words / $readingSpeed);
return $minutes . ' minute' . ($minutes == 1 ? '' : 's');
}
My question is should these methods be moved to the Article model?
Controller should be as slim as possible. Following a resourceful approach (which you seem to be doing), the store() method in your ArticleController class should strive as much as possible to look like this:
class ArticleController extends Controller
{
public function store(CreateArticleRequest $request)
{
$article = Article::create($request->validated());
// Redirect with success message
}
}
Here, your request data is validated in a form request class before it even reaches the controller method; and then an Article model instance is created from that validated data.
A couple of other notes…
Statements like ($data['featuredArticle'] == "1" ? true : false) are overly verbose. You’re doing a condition check which will evaluate to true or false; you don’t need to manually return each value in a ternary operator. So this could be slimmed down to $data['featuedArticle'] == '1'. Furthermore, if you pass a value of 0 by default, then you could just get rid of the check entirely. If in your Blade template you put a hidden input before your checkbox:
<input type="hidden" name="featuredArticle" value="0" />
<input type="checkbox" name="featuredArticle" value="1" />
Then 1 will be send if the checkbox is checked (as it overrides the hidden input’s value, or 0 sent if the checkbox isn’t checked).
Also, try to stick to Laravel conventions to make your life easier. If you use snake_case for your input names, then it just makes life easier matching them up to model attribute and table column names. So use featured_article, have an attribute in your model with the same name, which maps to a database column with the same name again. This allows you to do shorthand calls like create() (as per my controller example) and update().
Finally, methods like calculating reading time definitely belong on your model. Models represent something in your application. It therefore follows that you can do things with your models. Calculating the time to read an Article model instance therefore lends itself to having a calculateReadingTime() method on the Article model.
A bit long-winded, but hopefully there should be some helpful pointers for you in the above. I’ve been working on Laravel projects for around five years now and have found that this approach and conventions is what works best.
Your controller's store article is fine, because it fills your article instance based on request data. It could use some refactoring and you could encapsulate more logic into your Article (for example, assign slug field inside your Article model whenever title is changed and so on).
But the line $article->handleTags($request); is a suspect, because your model should never operate with requests - it will quickly polute your model code with very specialized dependencies that you don't want (what happens when you receive your tags from cache and don't have a request instance? What happens if other type of request contains tags differently? and so on). Your model shouldn't have knowledge about requests or other parts of your app. Your controller is connecting the dots between them, so make sure your handleTags takes some basic abstract types/structures as a parameter (for example, an array) and make sure your controller takes and transforms data from request accordingly before feeding it to your article.
As for your calculateReadTime dilemma, it should definitely be inside your model. Think about it this way - do you have everything you need to calculate read time of your article inside your Article model? The answer is yes, it's a property of an article object, doesn't matter if you store it in DB or calculate it off other properties. Make getReadTime method. You don't want a controller to compute something about your model because it will tie that logic to a specific place in your app which is bad (what happens when you need to calculate read time of an article in other controller? Other model? and so on).
Make sure you read about has and is concepts regarding object-oriented design, it will help you immensely.
I think you should move those assignments to a Service Class. You could also go ahead and create a repository class. This would thus become your code structure:
Controller -> Service -> Repository -> Model.
Doing this $article = new Article(); is bad. You will have a had time when writing a test for your controller store method.
I would suggest you do this:
Create a Service class, say ArticleService.php. Define a store method in it.
ArticleService.php
use Article;
class ArticleService {
protected $article;
public function __construct(Article $article){
$this->article = $article;
}
public function store(array $data){
$defauultPublished = "draft";
$IntranetOnly = false;
$isFeatured = false;
$isFeatured = ($data['featuredArticle'] == "1" ? true : false);
$IntranetOnly = ($data['IntranetOnly'] == "1" ? true : false);
$this->article->title = $data['title'];
$this->article->slug = str_slug($data['title']);
$this->article->author = $data['author'];
$this->article->category = $data['category'];
$this->article->excerpt = $data['excerpt'];
$this->article->content = clean($data['content']);
$this->article->featuredImage = $data['featuredImage'];
$this->article->featuredVideo = $data['featuredVideo'];
$this->article->readingTime = $data['reading_time'];
$this->article->featuredArticle = $isFeatured;
//Capital letter I? You should be consistent with your naming convention
$this->article->IntranetOnly = $IntranetOnly;
$this->article->published = $defauultPublished;
if($this->article->save()){
$this->article->handleTags($request);
return true;
}
return false;
}
}
And your Controller now becomes:
class ArticleController{
protected $articleService;
public function __construct(ArticleService $articleService){
$this->articleService = $articleService;
}
public function store(Request $request){
//Some Validation Logic
$readingTime = $this->calculateReadTime($request)
$data = array_merge(['reading_time' => $readTime], $request->all());
return $this->articleService->store($request->all());
}
}
I also see that you are not validating the incoming Request. You should always do that because you can/should never trust your users to always provide/input the right data. It is your duty to force them to do that. e.g I as your user might decide to enter my name in your email field. If you don't validate that data, you will end up with wrong data.
There is also the issue of individually assigning your request parameter to their corresponding Model attribute. I decided to leave it that way so as not to overload you with information.
In summary, just take a look at the following resources for more insight.
https://laravel.com/docs/5.1/quickstart-intermediate
https://laravel.com/docs/5.6/validation
In short, read up the whole Laravel documentation! Goodluck!
I am trying to create a random slug based on a dictionary of about ~100 words. I have come up with the following solution but I have been thinking about it too long and can't tell if it is efficient and cannot figure out how to properly test it.
class SlugModel
{
/**
* Get random words from the slug_words table
* #param int $limit how many words should be fetched
* #return array the array of random words
*/
public static function getRandomSlugWords($limit)
{
// returns random words in an array. Ex:
return array('pop', 'funk', 'bass', 'electro');
}
/**
* Generate a random slug based on contents of slug_words
* #return str the slug
*/
public static function generateSlug($limit=4)
{
do {
$words = self::getRandomSlugWords($limit);
$slugified = implode('-', $words);
if(PlaylistModel::doesSlugAlreadyExist($slugified)) // returns true or false
{
// try a few different combinations before requesting new words from database
for ($i=0; $i < $limit; $i++)
{
$words[] = array_shift($words); // take the first and shift it to the end
$slugified = implode('-', $words);
if(!PlaylistModel::doesSlugAlreadyExist($slugified)) // break only if it does NOT exist
break;
}
}
} while (PlaylistModel::doesSlugAlreadyExist($slugified));
return $slugified;
}
}
I think this code works but I also think it can be made more efficient or I may be overthinking it. I could also have it as simple as
do {
$words = self::getRandomSlugWords($limit);
$slugified = implode('-', $words);
} while (PlaylistModel::doesSlugAlreadyExist($slugified));
But I am trying to test different combinations of the same words before pinging the database with another request for different words (I am using RAND() to get randomized results and trying to minimize this).
Any insight is appreciated! Thank you!
I think doing a single query for getting limit+N random words is a better (faster, resource friendly) approach. For example, whenever we need a slug made up from 4 elements, let us select 8 words.
SELECT slug FROM `slugs` ORDER BY RAND() LIMIT 8;
In this case, we will shuffle an array containing twice the items we will use - there are 24 permutations of 4 by 4, while there are 1680 permutations of 8 by 4 -, leaving us with more probable valid slugs with less queries.
In that case, we would have a
public static function getRandomSlugs($limit)
{
shuffle(self::$slugs); // 8 words from database
return array_slice(self::$slugs, 0, $limit); // we only need 4
}
and make our way in using a
public function generateSlug($limit = 4)
{
do {
$slugs = self::getRandomSlugs($limit); // Get N slugs
$mySlug = implode('-', $slugs); // Join the elements
} while ($this->slugExists($mySlug));
return $mySlug;
}
Checking if a slug is already taken can be done in a couple ways; selecting from database where the column is your slug, or setting up the table as unique and checking the return on inserting the data.
Anyhow, remember to keep the methods short, precise, and clean.
I am working on a web project in php that could potentially have a lot of users. From an administrative point of view, I am attempting to display a table containing information about each user, and seeing as the users table could grow extremely large, I would like to use pagination.
On the backend, I created a service that expects a limit and an offset parameter that will be used to query the database for records within the appropriate range. The service returns the total count of records in the table, along with the records matching the query
public static function getUsersInfo($limit = 50, $offset=1)
{
$users_count = Users::count(
array(
"column" => "user_id"
)
);
$users_info = array();
$users = Users::query()
->order('created_at')
->limit($limit, $offset)
->execute()
->toArray();
foreach ($users as $index => $user) {
$users_info[$index]['user_id'] = $user['user_id'];
$users_info[$index]['name'] = $user['first_name'] . " " . $user['last_name'];
$users_info[$index] ['phone'] = $user['phone'];
$users_info[$index] ['profile_image_url'] = $user['profile_image_url'];
}
$results = array(
'users_count' => $users_count,
'users_info' => $users_info
);
return !empty($results) ? $results : false;
}
On the frontend, what I would like to achieve ideally is, have the navigation displayed at the bottom of the table, with the typical previous, next buttons, and additionally a few numbers that allow the user to quickly navigate to a desired page if the page number displayed. This is what I have so far for the UsersController, with no pagination.
class UsersController extends ControllerBase
{
public function indexAction()
{
$usersObject = new Users();
$data = $usersObject->getUsers();
if ($data['status'] == Constants::SUCCESS) {
$users = $data['data']['users_info'];
$users_count = $data['data']['users_count'];
$this->view->setVar('users', $users);
}
echo $this->view->render('admin/users');
}
public function getUsersAction()
{
echo Pagination::create_links(15, 5, 1);
}
}
I don't have any working pagination yet, but I was thinking a good way to go would be to create a Pagination library with a create_links function that takes the
total_count of records in the database, so I know how many pages are expected
limit so I know how many records to collect
cur_page so I know where to start retrieving from
So when that function is called with the correct parameters, it would generate the html code to achieve the pagination, and that in turn can then be passed to the view and displayed.
I have never done this before, but from the research I have done so far, it seems like this might be a good way to approach it. Any guidance, suggestions, or anything at all really, regarding this would be greatly appreciated.
It looks like you are you using some bespoke MVC-ish framework. While it does not answer your question exactly I have a few points:
If you are looking at a lot of users, pagination is the least of your problems. You need to consider how the database is indexed, how the results are returned and much more.
Without understanding the underlying database abstract layer / driver you are using it is difficult to determine whether or not your ->limit($limit, $offset) line will work correctly. The offset should probably default to 0, but without knowing the code it is hard to say.
The ternary operator in your first method (return !empty($results) ? $results : false;) is currently valueless, because the statement before it will mean the variable will always be an array.
Avoid echo statements in controllers. They should return to a templating engine to output a view.
You Users class would be better named User, as the MVC framework implies that the 'Model' is a singular entity.
While it is not a general rule, most pagination systems I have used have worked on a zero-index system (Page 1 is Page 0), so calculating the limit range is simple:
$total_records = 1000;
$max_records = 20;
$current_page = 0;
$offset = $max_records * $current_page;
$sql = "SELECT * FROM foo LIMIT $offset, $max_records";
Whenever a user searches, I get this error:
2012-06-26 11:05:21.671 [NOTICE] [208.69.120.120:48175-0#hostname] [STDERR] PHP Fatal error: Call to undefined method Mage_Catalog_Model_Resource_Product_Flat::getEntityTablePrefix() in /chroot/home/SITENAME/DOMAIN.COM/html/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php on line 505
And instead of the user's results appearing, they get a blank white page- no error on their end, no UI, just white. This was the first issue I noticed, but on the same day the following issues started coming up:
White Search Results
Sub-category product count for all sub-categories in layered nav is showing 0.
Some customers can not view orders from their front-end UI when logged in.
Our order export script is returning blank fields (1.7mb instead of 4.3).
Our "Made-in-the-usa" and "best sellers" pages are returning more products than they should.
Now, I know these are all incorrect because if I reindex the entire site, for some period while it is processing the index, all of the above works. However, when the index is complete, it all breaks again. The same day this happened we had an error page appear that stated one of the tables had broken and should be repaired. We ran PHPMyAdmin's repair and optimize functions on all tables and it fixed that error- but all of these are still broken.
Any ideas at all? Any ideas of what could be tried to fix this? I cant find this error anywhere- and the guys over at Nexcess haven't been able to find anything for this, either.
Thank you for your time.
As per the comments above, Magento's telling you that it's trying to call the method getEntityTablePrefix on an object whose classes don't have that method defined. Specifically in this method
#File: app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php
public function getBackendTable()
{
if ($this->_dataTable === null) {
if ($this->isStatic()) {
$this->_dataTable = $this->getEntityType()->getValueTablePrefix();
} else {
$backendTable = trim($this->_getData('backend_table'));
if (empty($backendTable)) {
$entityTable = array($this->getEntity()->getEntityTablePrefix(), $this->getBackendType());
$backendTable = $this->getResource()->getTable($entityTable);
}
$this->_dataTable = $backendTable;
}
}
return $this->_dataTable;
}
Given this happens from the following class
Mage_Catalog_Model_Resource_Product_Flat
It says to me that you have an extension and/or customization done that assumes you're not using the flat catalog data tables and wasn't coded to work with the flat table.
Dropping in a debugging call like this
if(!is_callable(array($this->getEntity()),'getEntityTablePrefix'))
{
mageDebugBacktrace();
//debug_print_backtrace();
exit;
}
right before the offending call (in a local code pool override, of course), will print out a call-stack that should point the offending code.
The seems that problem is in Mage_CatalogSearch_Model_Resource_Search_Collection::_getSearchEntityIdsSql that is not compatible with using the product flat index.
You can rewrite class Mage_CatalogSearch_Model_Resource_Search_Collection and do two little modifications.
1) Add new function _getSearchEntityIdsSqlUsingFlatIndex to rewrited class. This new function (I hope) does exactly the same thing as original _getSearchEntityIdsSql, but with using the product flat index.
2) Modify function _getSearchEntityIdsSql so that it calls new _getSearchEntityIdsSqlUsingFlatIndex if the catalog product flat index is enabled and built.
See source code:
class VENDOR_MODULE_Model_PATHTOREWRITECLASS extends Mage_CatalogSearch_Model_Resource_Search_Collection {
/**
* Retrieve SQL for search entities using product flat index.
*
* #param $query
* #return Varien_Db_Select
*/
protected function _getSearchEntityIdsSqlUsingFlatIndex($query)
{
/* #var $coreHelper Mage_Core_Model_Resource_Helper_Abstract */
$coreHelper = Mage::getResourceHelper('core');
$likeOptions = array('position' => 'any');
$flatTableName = $this->getTable('catalog/product_flat').'_'.$this->getStoreId();
/** #var Varien_Db_Select $select */
$select = $this->getConnection()
->select()
->from($flatTableName, array('entity_id'));
foreach ($this->_getAttributesCollection() as $attribute) {
/** #var Mage_Catalog_Model_Entity_Attribute $attribute */
if ($this->_isAttributeTextAndSearchable($attribute)) {
$attributeCode = $attribute->getAttributeCode();
$dbFieldName = in_array($attribute->getFrontendInput(), array('select', 'multiselect'))
? $attributeCode.'_value'
: $attributeCode;
if ($this->getConnection()->tableColumnExists($flatTableName, $dbFieldName)) {
$select->where($coreHelper->getCILike($dbFieldName, $this->_searchQuery, $likeOptions));
} else {
Mage::log(__METHOD__.": Attribute '$attributeCode' is missing in flat index.", Zend_Log::NOTICE);
}
}
}
return $select;
}
/**
* Retrieve SQL for search entities
*
* #param unknown_type $query
* #return string
*/
protected function _getSearchEntityIdsSql($query)
{
// HACK - make compatibility with flat index
/** #var Mage_Catalog_Helper_Product_Flat $flatHelper */
$flatHelper = Mage::helper('catalog/product_flat');
if ($this->getStoreId() > 0
&& $flatHelper->isEnabled($this->getStoreId())
&& $flatHelper->isBuilt($this->getStoreId())
) {
return $this->_getSearchEntityIdsSqlUsingFlatIndex($query);
}
// END HACK
return parent::_getSearchEntityIdsSql($query);
}
}
Using Zend Paginator and the paginator cache works fine, but the same cached pages are returned for everything. Ie. I first look at a list of articles, when i go to view the categories the articles list is returned. How can I tell the paginator which result set i am looking for?
Also, how can I clear the paginated results without re-querying the paginator. Ie. I am updated a news article therefore the pagination needs to be cleared.
Thanks
Zend_Paginator uses two methods to define cache ID: _getCacheId and _getCacheInternalId. Second function is calculating cache ID based on two parameters: the number of items per page and special hash of the adapter object. The first function (_getCacheId) is calculating cache ID using result from _getCacheInternalId and current page.
So, if you are using two different paginator objects with 3 same internal parameters: adapter, current page number and the number of items per page, then your cache ID will be the same for these two objects.
So the only way I see is to define you own paginator class inherited from Zend_Paginator and to re-define one of these two internal functions to add a salt to cache ID.
Something like this:
class My_Paginator extends Zend_Paginator {
protected $_cacheSalt = '';
public static function factory($data, $adapter = self::INTERNAL_ADAPTER, array $prefixPaths = null) {
$paginator = parent::factory($data, $adapter, $prefixPaths);
return new self($paginator->getAdapter());
}
public function setCacheSalt($salt) {
$this->_cacheSalt = $salt;
return $this;
}
public function getCacheSalt() {
return $this->_cacheSalt;
}
protected function _getCacheId($page = null) {
$cacheSalt = $this->getCacheSalt();
if ($cacheSalt != '') {
$cacheSalt = '_' . $cacheSalt;
}
return parent::_getCacheId($page) . $cacheSalt;
}
}
$articlesPaginator = My_Paginator::factory($articlesSelect, 'DbSelect');
$articlesPaginator->setCacheSalt('articles');
$categoriesSelect = My_Paginator::factory($categoriesSelect, 'DbSelect');
$articlesPaginator->setCacheSalt('categories');