MongoDB / MemCached / MyPaginator support inside QCubed? A few suggestions for the new release.
I am well aware of the fact the QCubed is a SQL based and PHP-Only Web dev framework. However, there was a discussion about memcached code going into the Qcubed_query_methods.tpl here:
http://qcu.be/content/proposal-future-qcubed-qcubed-community#comment-6342
and here:
http://qcu.be/content/using-memcached
Since that CAN improve performance to large extent, is the community thinking to include that as well? I would say the admins SHOULD.
Also, MOngoDB is a great Database without hassles and supports PHP officially (unlike Cassandra and HBase who rely on REST and Thrift Libraries). Again, MongoDB can be used to query the DB collections much the same way we do in SQL, mapping should not be too difficult; at least not as much as SQL is. If possible, do include Mongo as one of the databases which can be used in QCubed. Adds value and improves performance and being a NoSQL solution, it gives a lot of freedom from schemas and a lot better speed! Do consider this for the future, if not for the immediate release!
Anyway I did create a control of my own to allow GOOGLE run through the DataRepeating (both QDataGrid and QDataRepeater) controls. Since both those controls actually work based on Form State which need a 'click' event fire on the related paginator controls, using PURE QCubed logic will result in Google indexing a very very small amount of content from a QCubed-based web site because google looks for 'links' and never fires 'events' on pages it crawls. I did mention my work here:
http://qcu.be/content/my-very-own-paginator
However, I do not know how to (and I don't have the time to) create a Plugin for that control I created. Believe me, something like that is going to benefit the community a LOT. I am reposting the entire content here again. PLEASE DO THINK ABOUT THIS. I made this coz I needed it the most and I think others will need it too.
<?php
/*
* The default paginator available in QCubed works great. However,
* it uses FormStateData to do what it does - Pagination. This is
* great but not ideal as we live in a world where search engines
* need to look on our content and make it available to users when
* they search for it.
*
* This paginator gets the job done, in QCubed style while making
* sure that the pages are actually crawlable by the search engines
* by making the 'previous' and 'next' links on the page which the
* search engine can crawl. Unlike the QPaginatior, it is however,
* needed that you put effort for managing the display.
*
* You can use it like any other QControl and after doing the basic
* setup, call 'Render' on it. This keeps it easy =)
* ----------------------
* Author: Vaibhav Kaushal
* Date: Aug 28, 2011
*/
class MyPaginator extends QControl {
// Variables
// Total Page count
protected $TotalPageCount;
// Total Item count
protected $TotalItemCount;
// Items per page
protected $ItemsPerPage;
// Current page number
protected $CurrentPageNum;
// The Get variable against which the control will create the links
// You get the value of this var using QApplication::QueryString func.
protected $ActionVar;
// The base file path against which the links are to be created
protected $BaseFilePath;
// The Query String which is to be adjusted
protected $QueryString;
// Lables for previous and next
protected $lblPrevPage;
protected $lblNextPage;
// The list box containing the number of pages.
protected $lstPageNum;
// Button to get to the page selected in the list box.
protected $btnGoToPage;
// Fallback redirection page
protected $ErrorPageUri;
// Are we using Ajax?
protected $blnUseAjax = true;
public function __construct($objParentObject, $ActionVar, $strControlId = null) {
// First, call the parent to do most of the basic setup
try {
parent::__construct($objParentObject, $strControlId);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
// Set ActionVar for future use
$this->ActionVar = $ActionVar;
// Previous / Next lables
$this->lblPrevPage = new QLabel($this);
$this->lblPrevPage->HtmlEntities = false;
$this->lblPrevPage->Text = "« Previous «";
$this->lblNextPage = new QLabel($this);
$this->lblNextPage->HtmlEntities = false;
$this->lblNextPage->Text = "» Next »";
// Textbox for page number entry
$this->lstPageNum = new QListBox($this);
$this->lstPageNum->Width = 60;
$this->lstPageNum->CssClass = "mypaginator-page-list";
// Go button
$this->btnGoToPage = new QButton($this);
$this->btnGoToPage->CssClass = "mypaginator-go-button";
$this->btnGoToPage->Text = "Go";
// Add action to the button
if($this->blnUseAjax)
{
$this->btnGoToPage->AddAction(new QClickEvent(), new QAjaxControlAction($this, 'btnGoToPage_click'));
}
else
{
$this->btnGoToPage->AddAction(new QClickEvent(), new QServerControlAction($this, 'btnGoToPage_click'));
}
}
/*
* The reason why we do not put this code in the constructor is that
* the constructor sets the basics. However, it is needed to set the
* ActionVar, CurrentPageNum etc before you render. This must be done
* before you call render but after creating the paginator.
*
* This function cannot be called from anywhere except the GetControlHtml
* for the same reason.
*/
protected function setup ()
{
/*
* This function will set up all the controls.
*/
// Setup the number of pages
$this->TotalPageCount = ceil($this->TotalItemCount / $this->ItemsPerPage);
if($this->TotalPageCount == 0)
{
$this->TotalPageCount = 1;
}
// Invalid CurrentPageNum
if($this->CurrentPageNum < 0 || $this->CurrentPageNum > $this->TotalPageCount)
{
// Something wrong. Fallback
QApplication::Redirect($this->ErrorPageUri);
}
// Create the variable to hold the array format of query string
$parsed = "";
// Get into array
parse_str($this->QueryString, $parsed);
// now, see if the variable was already there in the query.
// If it wasn't, make sure that you create it and set it.
if (!array_key_exists($this->ActionVar, $parsed) || $parsed[$this->ActionVar] == "")
{
$parsed[$this->ActionVar] = 1;
}
// If it was but not numeric, still set it to one (should have been some error or user mischief)
if ($parsed[$this->ActionVar] && (!is_numeric($parsed[$this->ActionVar]) | $parsed[$this->ActionVar] < 0))
{
$parsed[$this->ActionVar] = 1;
}
// Create the previous page link
/*
* When its the first page, disable the previous link.
* One may want to do nothing, leaving the lable as text,
* instead of a link but display it. I prefer to hide it.
*/
if($this->CurrentPageNum == 1)
{
$this->lblPrevPage->Display = false;
}
// If its something in between
elseif($this->CurrentPageNum > 1 & $this->CurrentPageNum <= $this->TotalPageCount)
{
// Copy the parsed array for creating the next and previous page links
$parsed_copy = $parsed;
$parsed_copy[$this->ActionVar] -= 1 ;
$this->lblPrevPage->Text = '<a href="' .
$this->BaseFilePath .
'?' . http_build_query($parsed_copy) .
'">« Previous</a>';
}
// Create the next link
/*
* When its the last page, disable the next link.
* One may want to do nothing, leaving the lable as text,
* instead of a link but display it. I prefer to hide it.
*/
if($this->CurrentPageNum == $this->TotalPageCount)
{
$this->lblNextPage->Display = false;
}
// If its something in between
elseif ($this->CurrentPageNum >= 1 & $this->CurrentPageNum < $this->TotalPageCount )
{
// Copy the parsed array for creating the next and previous page links
$parsed_copy = $parsed;
$parsed_copy[$this->ActionVar] += 1 ;
$this->lblNextPage->Text = '<a href="' .
$this->BaseFilePath .
'?' . http_build_query($parsed_copy) .
'">Next »</a>';
}
// Set up the list box
for ($i = 1; $i <= $this->TotalPageCount ; $i++)
{
if($this->CurrentPageNum != $i)
{
$this->lstPageNum->AddItem($i, $i);
}
else
{
$this->lstPageNum->AddItem($i, $i, true);
}
}
}
// When the user clicks on the 'Go' button.
public function btnGoToPage_click($strFormId, $strControlId, $strParameter)
{
// Create the variable to hold the array format of query string
$parsed = "";
// Make the query array
parse_str($this->QueryString, $parsed);
// now, see if the variable was already there in the query.
// If it wasn't, make sure that you create it and set it.
if (!array_key_exists($this->ActionVar, $parsed) || $parsed[$this->ActionVar] == "")
{
$parsed[$this->ActionVar] = 1;
}
// If it was but not numeric, still set it to one (should have been some error or user mischief)
// Caller should make sure that is has set the correct values.
if ($parsed[$this->ActionVar] && !is_numeric($parsed[$this->ActionVar]))
{
$parsed[$this->ActionVar] = 1;
}
// If the page was not changed, do nothing
if($this->lstPageNum->SelectedValue == $this->CurrentPageNum)
{
return;
}
// else make the query array
$parsed[$this->ActionVar] = $this->lstPageNum->SelectedValue;
// build the complete query
$redir_uri = $this->BaseFilePath . '?' . http_build_query($parsed);
// GO!
QApplication::Redirect($redir_uri);
}
// Render :)
protected function GetControlHtml() {
// Setup first. Without this, nothing works.
$this->setup();
//Set the styles which the subcontrols will use
$strOuterStyleClass = "my-paginator-outer";
$strPrevNextClass = "my-paginator-prev-next";
$strPageListClass = "my-paginator-jumper";
$strOffsetStrClass = "my-paginator-offset-str";
// Lets get the rendered subcontrols -- remember to use FALSE for "blnDisplayOutput"
$intLastItemNum = ($this->TotalItemCount > ($this->CurrentPageNum * $this->ItemsPerPage))? ($this->CurrentPageNum * $this->ItemsPerPage) : $this->TotalItemCount;
if($this->TotalItemCount <= 0)
{
$strOffsetInTotal = "Nothing found!";
}
else
{
$strOffsetInTotal = "Showing " . ((($this->CurrentPageNum - 1) * $this->ItemsPerPage)+1) . ' - ' . $intLastItemNum . ' out of ' . $this->TotalItemCount . " results";
}
$strPrev = $this->lblPrevPage->Render(false);
$strNext = $this->lblNextPage->Render(false);
$strPageNumList = $this->lstPageNum->Render(false);
$strGoButton = $this->btnGoToPage->Render(false);
// Let's render it out
return sprintf('
<div id="%s" class="%s">
<div class="%s">
%s
</div>
<div class="%s">
%s
</div>
<div class="%s">
%s %s
</div>
<div class="%s">
%s
</div>
</div>',
$this->strControlId, $strOuterStyleClass,
$strOffsetStrClass,
$strOffsetInTotal,
$strPrevNextClass,
$strNext,
$strPageListClass,
$strPageNumList, $strGoButton,
$strPrevNextClass,
$strPrev
);
}
public function __get($strName) {
switch ($strName) {
case 'TotalItemCount': return $this->TotalItemCount;
case 'ItemsPerPage': return $this->ItemsPerPage;
case 'TotalPageCount': return $this->TotalPageCount;
case 'CurrentPage': return $this->CurrentPageNum;
case 'ActionVar': return $this->ActionVar;
case 'ButtonText': return $this->btnGoToPage->Text;
case 'ErrorPageUri': return $this->ErrorPageUri;
case 'BaseFilePath': return $this->BaseFilePath;
case 'QueryString': return $this->QueryString;
default:
try {
return parent::__get($strName);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
public function __set($strName, $mixValue) {
// Whenever we set a property, we must set the Modified flag to true
$this->blnModified = true;
try {
switch ($strName) {
case 'ItemsPerPage': return ($this->ItemsPerPage = QType::Cast($mixValue, QType::Integer));
case 'TotalItemCount': return ($this->TotalItemCount = QType::Cast($mixValue, QType::Integer));
case 'CurrentPage': return ($this->CurrentPageNum = QType::Cast($mixValue, QType::Integer));
case 'ActionVar': return ($this->ActionVar = QType::Cast($mixValue, QType::String));
case 'ButtonText': return ($this->btnGoToPage->Text = QType::Cast($mixValue, QType::String));
case 'ErrorPageUri': return ($this->ErrorPageUri = QType::Cast($mixValue, QType::String));
case 'BaseFilePath': return ($this->BaseFilePath = QType::Cast($mixValue, QType::String));
case 'QueryString': return ($this->QueryString = QType::Cast($mixValue, QType::String));
default:
return (parent::__set($strName, $mixValue));
}
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
// All functions MUST implement ParsePostData
public function ParsePostData() {}
/*
* May be this is what should be doing the checks but then,
* saving a few function calls is not bad :D
*/
// All functions MUST implement Validate
public function Validate() {return true;}
}
?>and here is how I use it:
<?php
// Make the Paginators
$pagenum = QApplication::QueryString('page');
if (!$pagenum | $pagenum == "" | !is_numeric($pagenum) | $pagenum < 0)
{
$pagenum = 1;
}
$this->objMyPaginatorTop = new MyPaginator($this, 'page');
$this->objMyPaginatorBottom = new MyPaginator($this, 'page');
// Extract the URI and the Query String
$this->objMyPaginatorBottom->BaseFilePath = $this->objMyPaginatorTop->BaseFilePath = $_SERVER['PHP_SELF'];
$this->objMyPaginatorBottom->QueryString = $this->objMyPaginatorTop->QueryString = $_SERVER['QUERY_STRING'];
// Total number of items and items per page
$this->objMyPaginatorBottom->TotalItemCount = $this->objMyPaginatorTop->TotalItemCount = $this->intTotalItemCount;
$this->objMyPaginatorBottom->ItemsPerPage = $this->objMyPaginatorTop->ItemsPerPage = $this->intItemsPerPage;
// Fallback page
$this->objMyPaginatorBottom->ErrorPageUri = $this->objMyPaginatorTop->ErrorPageUri = 'notfound.php';
// Set Current page
$this->objMyPaginatorBottom->CurrentPage = $this->objMyPaginatorTop->CurrentPage = $pagenum;
$this->intOffset = ($pagenum - 1) * $this->intItemsPerPage;
$this->dtrThreads = new QDataRepeater($this);
?>The binder function would be like:
<?php
protected function dtrThreads_Bind() {
// This function defines how we load the data source into the Data Repeater
// Total item count cannot be set INTO the DTR because we are not using Form-state based pagination control. Hence commenting
// $this->dtrThreads->TotalItemCount = $this->intTotalItemCount; //DiscussionThread::CountByProdId($this->productId);
$this->dtrThreads->DataSource = DiscussionThread::LoadArrayByProdId($this->productId, (QQ::Clause(QQ::LimitInfo($this->intItemsPerPage, $this->intOffset),
QQ::OrderBy(QQN::ProductPictures()->PostTimestamp)
)
));
}
?>Also, including the ILIKE as a built-in plugin or an extended class would not hurt:
http://qcu.be/content/addition-new-qquery-qqcondition-class
A Bug (/ lack of a feature) I deal with daily on QCubed is that 'Timestamp' data types are not recognized as fields by QCubed during code generation and I have to handle all such fields' data manually (Thank God, Loading works :) , but saving produces error ). I am using PostgreSQL (version 8.4.8, currently)
I hope I have mentioned a few good points which admins can bring in into the new release (I am excited about it and waiting for a new release) :)
Regards,
Vaibhav

Vaibhav,
Thank you for your contributions, they are great.
Is the timestamp problem you are referring to the same as the bug you reported in #738? If so, the fix has already been committed to trunk and will be part of the upcoming release.
Rest assured your contributions are not going unnoticed. The best way to make sure your posts will not be forgotten is to open tickets and simply refer to the forum posts. That way they will show up in roadmaps and when someone finds more time to work on QCubed they will be among the tickets to pick from.
Supporting MangoDB would be a great addition to QCubed. Unfortunately I personally don't have much experience with it. So if you can contribute anything, it would help a lot. Same goes for MemCached.
Being busy with the upcoming release, I will probably not have time to make plugins for ILIKE and your search engine friendly datagrid control. But I definitely will get to them, once the new release is out of the door. Please create tickets for these as well, if you have time.
Thanks
Yes, it was the same bug. Also, I had created the solution but forgot to tell guys here.
Vaibhav,
Since you're using PostgreSQL, we could really use your help on a ticket:
http://trac.qcu.be/projects/qcubed/ticket/637
Even if you can't test if the patch is fixing the issue reported, could you at least confirm that it doesn't break the PostgreSQL adaptor and functionality in any obvious way? That way we can commit the fix as experimental but at least with assurance that it doesn't break old code.
If you don't have time, that's ok; just let us know, so we don't hold the release for too long for this ticket.
Thanks.
Patching fails (I dunno why...may be I did not do it right :( ) Thanks to traq, I can see the changes on the website itself.
Made the changes and its all working fine for me as of now, also the issue in the ticket is done with. All pages working fine. I would say push it into the new release. Should work. My application is working fine.
The final decision is to be of the admins.
Thank you very much Vaibhav.
I'll commit these changes tonight.
My pleasure :)
Delete the above comment please.
Done!