如何使用PHP和Smarty同时获取数据并在浏览器中显示?

时间:2013-12-07 09:22:59

标签: php mysql ajax smarty output-buffering

我在我的网站上使用PHP,MySQL,Smarty,jQuery,AJAX等。目前,我从MySQL数据库中获取大量数据(匹配问题ID),对其进行处理,将此数据分配给Smarty模板并将其打印在网页上。由于要获取的数据量太大而且正在进行进一步处理,因此获取最终输出数据需要花费太多时间。反过来,向用户显示整个数据需要花费太多时间。

我有一种方法,但无法实现它。我的方法是运行两个过程:获取单个匹配question_id并同时将其显示到浏览器并重复此循环,直到获取并显示所有匹配的问题ID。当显示单行的加载数据时,加载器图像应显示在该显示的记录下。当打印完所有数据后,加载器图像应该消失。

但我面临的主要问题是我应该如何继续将数据分配给Smarty模板并显示模板,因为Smarty模板引擎首先加载所有内容,并且只有在完全拥有内容后才能将其打印到浏览器。

供您参考我将所有现有代码放在Controller,Model和View下面:

Controller(match_question.php)的PHP代码如下:

<?php 
  require_once("../../includes/application-header.php");

  $objQuestionMatch  = new QuestionMatch();

  $request = empty( $_GET ) ? $_POST : $_GET ;


  if($request['subject_id']!="") 
    $subject_id = $request['subject_id'];
  if($request['topic_id']!="") 
    $topic_id = $request['topic_id'];

  if($subject_id !='' && $topic_id !='')
    $all_match_questions = $objQuestionMatch->GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id);

  $smarty->assign('all_match_questions', $all_match_questions);
  $smarty->display("match-question.tpl")
?>

模型(QuestionMatch.php)的PHP代码如下:

<?php
  class QuestionMatch {

    var $mError = "";
    var $mCheck;
    var $mDb;
    var $mValidator;
    var $mTopicId;
    var $mTableName;

    function __construct() {
      global $gDb;
      global $gFormValidation;

      $this->mDb        = $gDb; 
      $this->mValidator = $gFormValidation;
      $this->mTableName = TBL_QUESTIONS;
    }
/**
     * This function is used to get all the questions from the given subject id and topic id
         */
    function GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id) {

            /*SQL query to find out questions from given subject_id and topic_id*/
            $sql  = " SELECT * FROM ".TBL_QUESTIONS." WHERE question_subject_id=".$subject_id;
            $sql .= " AND question_topic_id=".$topic_id;

            $this->mDb->Query($sql);
            $questions_data = $this->mDb->FetchArray(); 
            /*Same array $questions_data is assigned to new array $questions to avoid the reference mismatching*/
            $questions      = $questions_data;

      /*Array of words to be excluded from comparison process
       *For now it's a static array but when UI design will be there the array would be dynamic
            */
            $exclude_words = array('which','who','what','how','when','whom','wherever','the','is','a','an','and','of','from');  

      /*This loop removes all the words of $exclude_words array from all questions and converts all 
       *converts all questions' text into lower case
      */
      foreach($questions as $index=>$arr) {
        $questions_array = explode(' ',strtolower($arr['question_text']));
        $clean_questions = array_diff($questions_array, $exclude_words);
        $questions[$index]['question_text'] = implode(' ',$clean_questions);
      }      

      /*Now the actual comparison of each question with every other question stats here*/
            foreach ($questions as $index=>$outer_data) {

        /*Logic to find out the no. of count question appeared into tests*/
        $sql  = " SELECT count(*) as question_appeared_count FROM ".TBL_TESTS_QUESTIONS." WHERE test_que_id=";
        $sql .= $outer_data['question_id'];

        $this->mDb->Query($sql);
        $qcount = $this->mDb->FetchArray(MYSQL_FETCH_SINGLE); 

        $question_appeared_count = $qcount['question_appeared_count'];
        $questions_data[$index]['question_appeared_count'] = $question_appeared_count;
        /*Crerated a new key in an array to hold similar question's ids*/
        $questions_data[$index]['similar_questions_ids_and_percentage'] = Array(); 

        $outer_question = $outer_data['question_text'];

        $qpcnt = 0;     
        //foreach ($questions as $inner_data) {
        /*This foreach loop is for getting every question to compare with outer foreach loop's 
        question*/
        foreach ($questions as $secondIndex=>$inner_data) { 
            /*This condition is to avoid comparing the same questions again*/
          if ($secondIndex <= $index) {
            /*This is to avoid comparing the question with itself*/
              if ($outer_data['question_id'] != $inner_data['question_id']) {

              $inner_question = $inner_data['question_text'];  

                /*This is to calculate percentage of match between each question with every other question*/
                similar_text($outer_question, $inner_question, $percent);
                $percentage = number_format((float)$percent, 2, '.', '');

                /*If $percentage is >= $percent_match only then push the respective question_id into an array*/
                if($percentage >= 85) {
                $questions_data[$index]['similar_questions_ids_and_percentage'][$qpcnt]['question_id']       = $inner_data['question_id'];
                $questions_data[$index]['similar_questions_ids_and_percentage'][$qpcnt]['percentage']        = $percentage;
                /*$questions_data[$secondIndex]['similar_questions_ids_and_percentage'][$qpcnt]['question_id'] = $outer_data['question_id'];
                $questions_data[$secondIndex]['similar_questions_ids_and_percentage'][$qpcnt]['percentage']    = $percentage;*/

                /*Logic to find out the no. of count question appeared into tests*/
                $sql  = " SELECT count(*) as question_appeared_count FROM ".TBL_TESTS_QUESTIONS." WHERE test_que_id=";
                $sql .= $inner_data['question_id'];

                $this->mDb->Query($sql);
                $qcount = $this->mDb->FetchArray(MYSQL_FETCH_SINGLE); 

                $question_appeared_count = $qcount['question_appeared_count'];
                $questions_data[$index]['similar_questions_ids_and_percentage'][$qpcnt]['question_appeared_count'] = $question_appeared_count;
                $qpcnt++;
            }
          }
        }   
      }
    }    //}    
    /*Logic to create the return_url when user clicks on any of the displayed matching question_ids*/
    foreach ($questions_data as $index=>$outer_data) {
      if(!empty($outer_data['similar_questions_ids_and_percentage'])) { 
        $return_url  = ADMIN_SITE_URL.'modules/questions/match_question.php?';
        $return_url .= 'op=get_question_detail&question_ids='.$outer_data['question_id'];

        foreach($outer_data['similar_questions_ids_and_percentage'] as $secondIndex=>$inner_data) {
          $return_url = $return_url.','.$inner_data['question_id'];
        }      
        $questions_data[$index]['return_url'] = $return_url.'#searchPopContent';
      }
    }     
          /*This will return the complete array with matching question ids*/
      return $questions_data;
      }
}
?>

查看(match-question.tpl)的代码如下:

<table width="100%" class="base-table tbl-practice" cellspacing="0" cellpadding="0" border="0">
  <tr class="evenRow">
    <th width="33%" style="text-align:center;" class="question-id">Que ID</th>
    <th width="33%" style="text-align:center;" class="question-id">Matching Que IDs</th>
    <th width="33%" style="text-align:center;" class="question-id">Percentage(%)</th>
  </tr>
{if $all_match_questions}
  {foreach from=$all_match_questions item=qstn key=key}   
    {if $qstn.similar_questions_ids_and_percentage}
      {assign var=counter value=1}
  <tr class="oddRow">
    <td class="question-id" align="center" valign="top">
      <a href="{$qstn.return_url}" title="View question" class="inline_view_question_detail">QUE{$qstn.question_id}</a>{if $qstn.question_appeared_count gt 0}-Appeared({$qstn.question_appeared_count}){/if}
    </td>
      {foreach from=$qstn.similar_questions_ids_and_percentage item=question key=q_no}
        {if $counter gt 1}
    <tr class="oddRow"><td class="question-id" align="center" valign="top"></td>
        {/if}
    <td class="question" align="center" valign="top">

        {if $question.question_id!=''}
      <a href="{$qstn.return_url}" title="View question" class="inline_view_question_detail">QUE{$question.question_id}</a>{if $question.question_appeared_count gt 0}-Appeared({$question.question_appeared_count}){/if}
        {if $question.question_appeared_count eq 0}
      <a id ="{$question.question_id}" href="#" class="c-icn c-remove delete_question"  title="Delete question"> Delete</a>{/if}
        {/if}

    </td>

    <td class="question" align="center" valign="top">
        {if $question.percentage!=''}{$question.percentage}{/if}
        {assign var=counter value=$counter+1}
    </td>
  </tr>
      {/foreach}               
    {/if}
  {/foreach}
{else}
  <tr>
    <td colspan="2" align="center"><b>No Questions Available</b></td>
  </tr>
{/if}
</table>

感谢你花了一些宝贵的时间来理解我的问题。

5 个答案:

答案 0 :(得分:10)

我认为瓶颈在于循环SQL查询。有一种标准方法可以在MySQL上对搜索结果进行排名。您可以简单地实现全文搜索。

首先,您需要创建一个类似search_results的表:

<强> SQL:

CREATE TABLE `search_results` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `result_title` varchar(128) CHARACTER SET utf8 NOT NULL,
  `result_content` text CHARACTER SET utf8 NOT NULL,
  `result_short_description` text CHARACTER SET utf8,
  `result_uri` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `result_resource_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `result_title` (`result_title`,`result_content`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

您必须将questions表中的所有有用数据(包括问题,主题,答案以及您想要搜索的任何内容)插入到result_title和result_content中,(此时还会更新此表)需要更新)。 result_resource_id上的相应表的原始记录也有回溯。使用预定义的URI result_uri指向您网站中结果的已定义URL,您可以更快地完成所有操作。您不需要每次都创建URL。

现在,您可以在'question?'中为搜索查询NATURAL LANGUAGE MODE创建一个简单的SQL查询:

<强> SQL:

SELECT `result_title`, `result_content`, `result_uri`
FROM `search_results` WHERE MATCH(result_title, result_content) AGAINST('question?');

您还可以将相关性度量添加到查询字符串中。还有其他搜索模式,如布尔值。阅读the documents here并找到最佳解决方案。

在这些用例中,

Full-text indexing更快,也更准确。

答案 1 :(得分:7)

假设您希望在浏览器中将内容从服务器流式传输到客户端时加载内容,如果您正在使用表格 - 就像您一样 - 您可能会遇到浏览器问题(由于布局问题) )在加载所有数据之前无法呈现表。

您可以查看these tips for authoring fast-loading HTML pages并了解相应部分中的表格。

一些关键点:

  

如果浏览器可以立即确定图像和表格的高度和/或宽度,则可以显示网页而无需重排内容。这不仅可以加快页面的显示速度,还可以在页面完成加载时防止页面布局出现恼人的变化。因此,应尽可能为图像指定高度和宽度。

  

表应使用CSS selector:property组合:

     

table-layout:fixed;

     

...并且应使用COL和COLGROUP HTML标记指定列的宽度。

以及:

  

表仍被视为有效标记,但应用于显示表格数据。为了帮助浏览器更快地呈现页面,您应该避免嵌套表格。

您可能还想查看PHP的流输出方法。

See this question for details

答案 2 :(得分:7)

通常,模板引擎不会零碎地加载内容 - 您需要手动将数据发送到浏览器,并在每个位之间flush。模板库通常将整个文档组合在内存中,然后一次性将其转储到浏览器中。值得检查Smarty手册,以防万一。

作为替代方案,您可以在没有大量数据的情况下呈现页面,然后通过AJAX将其加载到各个部分中。虽然制作10个AJAX连接会连续增加额外的开销,但与当前的渲染时间相比,这听起来很小。即使您的总渲染时间可能稍微长一些,用户感知的渲染时间也会快得多,当然他们也可以看到数据到达。

我会在domready中启动jQuery中的第一个AJAX操作,当每个操作完成时,它可以触发另一个请求。如果您的服务器可以用JSON而不是HTML回答,它将允许服务器返回more_available布尔标志,您可以使用该标志来确定是否需要进行另一次提取。

答案 3 :(得分:6)

您当前的数据库查询和后续的smarty-&gt; assign将不允许延迟加载数据以加快处理速度。

在这种情况下,您可以从查询中识别可以快速显示给用户的最大行集。确定可以显示的最大行集并仍然保持快速响应时间后,您可以修改查询和模板系统以反映多个查询设​​置。这基本上是分页。您将执行初始的行加载,而不是分页,然后通过jquery加载后一组行,直到所有&#34;页面&#34;数据已成功加载。

对于match_question.php

  • 首先查询您的数据集,以查看您拥有的总数据行数。
  • 将这些行除以您可以显示的总行数,同时保持快速应用程序。这将为您提供&#34;页面的总数&#34;或&#34;查询&#34;你会跑。
  • 例如:假设您的测试产生100行作为最佳最快响应。您将在预期的数据集上执行COUNT(*),返回2021.您可以将该行数除以最佳的100个结果,这将产生20.21或21&#34;页&#34;,或者在您的情况下为21查询。您的第一个初始查询,以及另外20个ajax查询。
  • 这会对您的数据库产生大量查询,但这会使页面加载时间对最终用户更有效。因此,您应该测量机器上的负载与最终用户的易用性。

    $limit = 100;
    $page = 1;
    ...
    
    if($request['page'] != '')
       $page = $request['page'];
    
    ...
    
    if($subject_id !='' && $topic_id !=''){
       $count_matched_questions = $objQuestionMatch->GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id, true);
    
       $page_count = ceil($count/$limit) //round up if decimal for last page;
    
       $paged_match_questions = $objQuestionMatch->GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id, false, $limit, $page)
    }
    
    $smarty->assign( 'all_match_questions', $paged_match_questions
                    ,'page_count', $page_count);
    //cache each result page separately to support multiple subject/topic/page combinations to properly utilize the cache mechanism
    $smarty->display("match-question-".$subject_id."-".$topic_id."-".$page.".tpl")
    

对于QuestionMatch.php

  • 调整查询功能(示例):

    function GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id, $count = false, $limit = 0, $page = 0 ) {
        if($count)
        {
           $sql  = " SELECT COUNT(*) FROM ".TBL_QUESTIONS." WHERE question_subject_id=".$subject_id;
        }
        else
        {
           $sql  = " SELECT * FROM ".TBL_QUESTIONS." WHERE question_subject_id=".$subject_id;
        }
        $sql .= " AND question_topic_id=".$topic_id;
    
        if($page > 0 && $limit > 0)
        {
           $sql .= " LIMIT = " . ($limit*$page)-$limit . ", " . ($limit*$page);
        } 
    
    } 
    

For View(match-question.tpl)

  • 输出&#39; page_count&#39; html元素中的值,可能是数据页面html5值,并将其分配给具有唯一ID的元素。
  • 在页面加载时,您的ajax初始化并获取数据页面值。
  • 使用?page =&amp; subject_id =&amp; topic_id =
  • 通过ajax调用您的php文档
  • 使用从page = 2开始的数据页量来循环访问ajax,直到您查询了最大页面。
  • 在每次迭代时将返回的html附加到适当位置。

希望这个想法可以帮助您找到解决方案。干杯!

答案 4 :(得分:6)

在没有深入了解代码的具体细节的情况下,您所寻找的内容与Facebook使用的名为BigPipe的系统类似,在this note on Facebook Engineering中有详细描述。

基本上,他们尝试做的是尽快向浏览器发送回复,其中包含页面的基本布局,以及稍后将包含需要更长时间加载的内容的占位符元素 - 他们称之为这些小页面。刷新初始响应后,依次加载每个pagelet,包括从数据库或类似程序加载数据,并发送到客户端 - 仍然是同一HTTP请求的一部分。 Javascript用于将内容插入到正确的占位符中。

在我工作的公司,我们试验了一段时间,并取得了很好的成果。我们使用了一个开源第三方PHP/Javascript BigPipe implementation on GitHub作为起点。虽然远非琐碎的设置,更重要的是,工作得非常好,但我相信这对于你所遇到的那种问题来说是一个很好的解决方案。