Как получить данные и отобразить их в браузере одновременно, используя PHP и Smarty?

Я использую PHP, MySQL, Smarty, jQuery, AJAX и т. д. для своего сайта. В настоящее время я извлекаю большое количество данных (соответствующие идентификаторы вопросов) из базы данных MySQL, выполняю их обработку, назначаю эти данные шаблону Smarty и печатаю их на веб-странице. Поскольку количество данных, которые необходимо извлечь, слишком велико и они находятся в процессе дальнейшей обработки, получение окончательных выходных данных занимает слишком много времени. В свою очередь, для отображения полных данных пользователю требуется слишком много времени.

У меня есть один подход в голове, но я не могу его реализовать. Мой подход состоит в том, чтобы запустить два процесса извлечения одного совпадающего question_id и одновременного отображения его в браузере и повторять этот цикл до тех пор, пока все совпадающие идентификаторы вопросов не будут выбраны и отображены. По мере отображения загруженных данных одной строки изображение загрузчика должно отображаться под этой отображаемой записью. Когда все данные будут напечатаны, изображение загрузчика должно исчезнуть.

Но основная проблема, с которой я сталкиваюсь, заключается в том, как я должен постоянно назначать данные шаблону Smarty и отображать шаблон, так как Smarty Template Engine сначала загружает все содержимое и только после того, как содержимое полностью получено, оно печатает его в браузере.

Для справки я помещаю ниже весь свой существующий код из контроллера, модели и представления:

PHP-код Controller (match_question.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")
?>

PHP-код Model(QuestionMatch.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 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>

Спасибо, что потратили свое драгоценное время на понимание моей проблемы.


person PHPLover    schedule 07.12.2013    source источник
comment
Если проблема в том, что скрипт истекает, вы можете попробовать использовать set_time_limit(): php.net/manual/en/function.set-time-limit.php   -  person Cyclonecode    schedule 07.12.2013
comment
@KristerAndersson: время ожидания сценария не истекло. Пользователю приходится долго ждать, пока все данные будут загружены. А я хочу выводить данные пошагово и не заставлять пользователя ждать. Моя проблема заключается в том, как печатать данные шаг за шагом, чтобы пользователь не ждал, пока будут загружены все данные (все записи).   -  person PHPLover    schedule 07.12.2013
comment
Я предлагаю вам изучить использование COL и COLGROUP и фиксированного макета таблицы, чтобы браузер знал, как макетировать таблицу, даже до того, как она будет полностью загружена. Вы можете проверить, является ли это вашим виновником, изменив свои таблицы на настройку на основе DIV и проверив, как она загружается. Обратите внимание, что очистка буфера (как предложил @halfer) может иметь решающее значение.   -  person SquareCat    schedule 16.12.2013


Ответы (5)


Я считаю, что узким местом является зацикливание 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.

Теперь вы можете создать простой SQL-запрос для поискового запроса 'question?' в NATURAL LANGUAGE MODE:

SQL:

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

Вы также можете добавить измерение релевантности в строку запроса. Существуют и другие режимы поиска, такие как логический. Прочитайте здесь и найдите лучшие решение.

Полнотекстовое индексирование быстрее и точнее в этих случаях использования.

person Mehdi    schedule 12.12.2013

Как правило, шаблонизаторы не загружают контент по частям — вам придется отправлять данные в браузер частями вручную, а flush между каждым битом. Библиотеки шаблонов обычно создают весь документ в памяти, а затем выгружают его в браузер за один раз. Однако на всякий случай стоит проверить руководство Smarty.

В качестве альтернативы вы можете отображать страницу без большого объема данных, а затем загружать ее по частям через AJAX. Хотя создание, скажем, 10 соединений AJAX последовательно добавляет небольшие дополнительные накладные расходы, похоже, что они будут минимальными по сравнению с вашим текущим временем рендеринга. Несмотря на то, что общее время рендеринга может быть немного больше, воспринимаемое пользователем время рендеринга будет намного быстрее, и, конечно, они получат преимущество, поскольку смогут видеть поступающие данные.

Я бы начал первую операцию AJAX в jQuery после domready, и когда каждая из них завершится, она может запустить другой запрос. Если ваш сервер может отвечать в JSON, а не в HTML, это позволит серверу вернуть логический флаг more_available, который вы можете использовать, чтобы определить, нужно ли вам выполнить еще одну выборку.

person halfer    schedule 07.12.2013

Предполагая, что вы хотите, чтобы ваш контент загружался в браузер, в то время как он все еще передается с сервера на клиент, если вы используете таблицы - как вы это делаете - вы можете столкнуться с проблемой браузера (из-за проблем с макетом) не возможность отображать таблицу до тех пор, пока все данные не будут загружены.

Вы можете ознакомиться с этими советами по созданию быстро загружаемых HTML-страниц и узнайте о таблицах в соответствующем разделе.

Некоторые важные моменты:

Если браузер может сразу определить высоту и/или ширину ваших изображений и таблиц, он сможет отображать веб-страницу без необходимости перекомпоновки содержимого. Это не только ускоряет отображение страницы, но и предотвращает раздражающие изменения в макете страницы, когда страница завершает загрузку. По этой причине, когда это возможно, для изображений следует указывать высоту и ширину.

А также:

Таблицы должны использовать комбинацию CSS selector:property:

макет таблицы: фиксированный;

... и должен указывать ширину столбцов с помощью HTML-тегов COL и COLGROUP.

Так же как:

Таблицы по-прежнему считаются допустимой разметкой, но их следует использовать для отображения табличных данных. Чтобы браузер быстрее отображал вашу страницу, избегайте вложения таблиц.

Вы также можете изучить методы потокового вывода из PHP.

Подробности см. в этом вопросе< /а>.

person SquareCat    schedule 07.12.2013

Ваш текущий запрос к базе данных и последующий smarty->assign не позволят ленивой загрузке данных ускорить процесс.

В этой ситуации вы можете определить максимальный набор строк из вашего запроса, который может быть быстро отображен пользователю. Как только вы определите максимальный набор строк, которые вы можете отображать, и при этом поддерживать быстрое время отклика, вы можете изменить свою систему запросов и шаблонов, чтобы отразить настройку нескольких запросов. По сути это пагинация. Вместо разбивки на страницы вы будете выполнять первоначальную загрузку строк, а затем через jquery загружать последний набор строк, пока все «страницы» данных не будут успешно загружены.

Для match_question.php

  • Сначала запросите свой набор данных, чтобы узнать, сколько всего строк данных у вас есть.
  • Разделите эти строки на общее количество строк, которые вы можете отобразить, сохраняя быстроту приложения. Это даст вам общее количество «страниц» или «запросов», которые вы запустите.
  • Например: предположим, что ваши тесты дают 100 строк в качестве оптимального быстрого ответа. Вы должны выполнить COUNT (*) для ожидаемого набора данных, который возвращает 2021 год. Вы разделите это количество строк на ваши оптимальные 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)

  • выведите значение «page_count» в элементе html, возможно, значение html5 страниц данных и назначьте его элементу с уникальным идентификатором.
  • При загрузке страницы ваш ajax инициализируется и получает значение страниц данных.
  • вызовите свой php-документ через ajax с помощью ?page=&subject_id=&topic_id=
  • прокручивайте вызов ajax, используя количество страниц данных, начиная со страницы = 2, пока вы не запросите максимальное количество страниц.
  • добавьте возвращенный html, где это необходимо, на каждой итерации.

Надеюсь, эта идея поможет вам найти решение. Ваше здоровье!

person Pynt    schedule 09.12.2013
comment
Я не знаком с Smarty, но хороший и содержательный ответ, Pynt. - person halfer; 10.12.2013

Не вдаваясь в конкретные детали вашего кода, похоже, что вы ищете что-то похожее на систему, используемую Facebook, под названием BigPipe, достаточно подробно описанную в эту заметку на Facebook Engineering.

По сути, то, что они пытаются сделать, это как можно скорее отправить браузеру ответ, содержащий базовый макет страницы и элементы-заполнители, которые позже будут содержать контент, загрузка которого занимает больше времени — они называют эти страницы. После сброса исходного ответа каждый пейджлет загружается по очереди, включая загрузку данных из баз данных и т. п., а также отправляется клиенту — по-прежнему являясь частью одного и того же HTTP-запроса. Javascript используется для вставки содержимого в правильный заполнитель.

В компании, в которой я работаю, мы некоторое время экспериментировали с этим и получили отличные результаты. Существует сторонняя реализация BigPipe PHP/Javascript на GitHub с открытым исходным кодом, которую мы использовали в качестве отправной точки. Хотя это далеко не тривиально, чтобы настроить и, что более важно, заставить работать действительно хорошо, я считаю, что это отличное решение именно для той проблемы, с которой вы сталкиваетесь.

person Aistina    schedule 15.12.2013