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

Posted

技术标签:

【中文标题】如何使用 PHP 和 Smarty 同时获取数据并在浏览器中显示?【英文标题】:How to fetch the data and display it in a browser simultaneously using PHP and Smarty? 【发布时间】:2013-12-24 17:44:01 【问题描述】:

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

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

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

为了您的参考,我将来自控制器、模型和视图的所有现有代码放在下面:

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")
?>

Model(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;
      

?>

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

<table  class="base-table tbl-practice" cellspacing="0" cellpadding="0" border="0">
  <tr class="evenRow">
    <th  style="text-align:center;" class="question-id">Que ID</th>
    <th  style="text-align:center;" class="question-id">Matching Que IDs</th>
    <th  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>

感谢您抽出宝贵的时间来了解我的问题。

【问题讨论】:

如果问题是脚本超时你可以尝试使用set_time_limit():php.net/manual/en/function.set-time-limit.php @KristerAndersson:脚本没有超时。用户必须等待很长时间才能加载所有数据。我想一步一步地显示数据,而不是让用户等待。我的问题是如何逐步打印数据,以便用户不应该等到整个数据(所有记录)被加载。 我建议您考虑使用COLCOLGROUP 以及固定的表格布局,以便浏览器在表格完全加载之前就知道如何布局表格。您可以通过将表更改为基于DIV 的设置并测试其加载方式来测试这是否是您的罪魁祸首。请注意,缓冲区刷新(如@halfer 建议的那样)可能至关重要。 【参考方案1】:

我相信瓶颈在于 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_titleresult_content,(也随时更新此表它需要更新)。在result_resource_id上也有指向对应表的原始记录的回溯指针。使用预定义的 URI result_uri 指向您网站中结果的定义 URL,您可以让一切变得更快。您无需每次都创建 URL。

现在,您可以在NATURAL LANGUAGE MODE 中为搜索查询'question?' 创建一个简单的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 在这些用例中更快、更准确。

【讨论】:

【参考方案2】:

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

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

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

【讨论】:

【参考方案3】:

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

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

一些关键点:

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

还有:

表格应使用 CSS selector:property 组合:

表格布局:固定;

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

还有:

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

您可能还想研究从 PHP 流式输出的方法。

See this question for details.

【讨论】:

【参考方案4】:

您当前的数据库查询和随后的 smarty->assign 将不允许延迟加载数据以加快进程。

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

对于 match_question.php

首先查询您的数据集,看看您总共有多少行数据。 将这些行除以在保持应用程序快速运行时可以显示的总行数。这将为您提供您将运行的“页面”或“查询”总数。 例如:假设您的测试产生 100 行作为最佳最快响应。您将对返回 2021 的预期数据集执行 COUNT(*)。您可以将该行数除以您的最佳 100 个结果,这将产生 20.21 或 21 个“页面”,或者在您的情况下为 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);
     

 

对于视图(match-question.tpl)

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

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

【讨论】:

我对 Smarty 不熟悉,但很好且实质性的答案,Pynt。【参考方案5】:

如果不深入了解您的代码的具体细节,听起来您正在寻找类似于 Facebook 使用的名为 BigPipe 的系统,在 this note on Facebook Engineering 中进行了合理的详细描述。

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

在我工作的公司,我们对此进行了一段时间的试验,并取得了很好的效果。我们以开源第三方PHP/Javascript BigPipe implementation on GitHub 为起点。虽然设置起来绝非易事,更重要的是,它可以很好地工作,但我相信它是解决您所面临问题的绝佳解决方案。

【讨论】:

以上是关于如何使用 PHP 和 Smarty 同时获取数据并在浏览器中显示?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 smarty 获取当前 URL

PHP数组中可否存放对象?如果可以,那么在smarty的模板中应当如何获取对象的属性值?

请教一下,smarty是如何判断数据更新来实现更新静态文件的 - PHP进阶讨论

Smarty:评估存储在 PHP 变量中的模板

使用Smarty 获取当前日期时间和格式化日期时间的方法详解

在smarty中通过php脚本获取smarty变量