Есть ли способ, которым я могу «направить» результат запроса PDO «в» выходной буфер, вместо того, чтобы сохранить его в строку? - PullRequest
6 голосов
/ 27 сентября 2011

Добавим немного, если вы видите заголовок этого вопроса.

У меня есть запрос, который извлекает одну строку из таблицы MySQL, и меня интересует конкретный столбец, который является BLOB. Я хотел бы, чтобы PHP записал его в выходной буфер, вместо того, чтобы хранить ~ 500 КБ в строку (, что, к тому же, я не уверен, будет бинарно-безопасным ).

Функции PDOStatement, такие как:

string PDOStatement::fetchColumn ([ int $column_number = 0 ] )

не помогите мне.

Можете ли вы помочь дать мне хотя бы направление? Заранее спасибо.

П.С .: Я знаю, что хранить ~ 500 КБ вещей внутри таблицы БД нехорошо, но это не мой выбор, я просто должен придерживаться этого.

Ответы [ 3 ]

2 голосов
/ 24 августа 2012

См. эту страницу . Это загружает данные в поток, который затем можно использовать с функциями f *, включая вывод непосредственно в браузер с помощью fpassthru. Вот пример кода с этой страницы:

<?php
$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2');
$stmt = $db->prepare("select contenttype, imagedata from images where id=?");
$stmt->execute(array($_GET['id']));
$stmt->bindColumn(1, $type, PDO::PARAM_STR, 256);
$stmt->bindColumn(2, $lob, PDO::PARAM_LOB);
$stmt->fetch(PDO::FETCH_BOUND);

header("Content-Type: $type");
fpassthru($lob);
?>

Ключевым моментом здесь является то, что после $stmt->execute() вы вызываете $stmt->bindColumn('columnName', $stream, PDO::PARAM_LOB);, затем вызываете $stmt->fetch(PDO::FETCH_BOUND), чтобы получить строку (где значения хранятся в связанных переменных PHP). Вот как я использовал его в Drupal, протестировал и работал; он включает в себя много дополнительной обработки кэша, которая должна ускорить работу ваших клиентов и требует только отслеживания времени последнего изменения ваших больших двоичных объектов:

<?php
$rfc2822_format = 'D, d M Y H:i:s e';

// This is basically the Drupal 7 way to create and execute a prepared
// statement; the `->execute()` statement returns a PDO::Statement object.
// This is the equivalent SQL:
//   SELECT f.fileType,f.fileSize,f.fileData,f.lastModified
//   FROM mfiles AS f WHERE fileID=:fileID
// (with :fileID = $fileID)
$statement = db_select('mfiles', 'f')
  ->fields('f', array('fileType', 'fileSize', 'fileData', 'lastModified'))
  ->condition('fileID', $fileID, '=')
  ->execute();
// All of the fields need to be bound to PHP variables with this style.
$statement->bindColumn('fileType', $fileType, PDO::PARAM_STR, 255);
$statement->bindColumn('fileSize', $fileSize, PDO::PARAM_INT);
$statement->bindColumn('fileData', $fileData, PDO::PARAM_LOB);
$statement->bindColumn('lastModified', $lastModified, PDO::PARAM_STR, 19);

$success = false;

// If the row was fetched successfully...
if ($statement->fetch(PDO::FETCH_BOUND)) {
  // Allow [public] caching, but force all requests to ask the server if
  // it's been modified before serving a cache [no-cache].
  header('Cache-Control: public no-cache');

  // Format the Last-Modified time according to RFC 2822 and send the
  // Last-Modified HTTP header to aid in caching.
  $lastModified_datetime = DateTime::createFromFormat('Y-m-d H:i:s',
    $lastModified, new DateTimeZone('UTC'));
  $lastModified_formatted = $lastModified_datetime->format($rfc2822_format);
  header('Last-Modified: ' . $lastModified_formatted);

  // If the client requested If-Modified-Since, and the specified date/time
  // is *after* $datetime (the Last-Modified date/time of the API call), give
  // a HTTP/1.1 304 Not Modified response and exit (do not output the rest of
  // the page).
  if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
    // Ignore anything after a semicolon (old browsers sometimes added stuff
    // to this request after a semicolon).
    $p = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'], 2);
    // Parse the RFC 2822-formatted date.
    $since = DateTime::createFromFormat($rfc2822_format, $p[0]);

    if ($lastModified_datetime <= $since) {
      header('HTTP/1.1 304 Not Modified');
      exit;
    }
  }

  // Create an ETag from the hash of it and the Last-Modified time, and send 
  // it in an HTTP header to aid in caching.
  $etag = md5($lastModified_formatted . 'mfile:' . $fileID);
  header('ETag: "' . $etag . '"');

  // If the client requested If-None-Match, and the specified ETag is the
  // same as the hashed ETag, give a HTTP/1.1 304 Not Modified response and
  // exit (do not output the rest of the page).
  if (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER) && $etag ==
      str_replace('"', '', stripslashes($_SERVER['HTTP_IF_NONE_MATCH']))) {
    header('HTTP/1.1 304 Not Modified');
    exit;
  }

  // Set the content type so that Apache or whatever doesn't send it as
  // text/html.
  header('Content-Type: ' . $fileType);

  // Set the content length so that download dialogs can estimate how long it
  // will take to load the file.
  header('Content-Length: ' . $fileSize);

  // According to some comments on the linked page, PDO::PARAM_LOB might
  // create a string instead of a stream.
  if (is_string($fileData)) {
    echo $fileData;
    $success = true;
  } else {
    $success = (fpassthru($fileData) !== false);
  }
}
?>

В сторону: если вам нужно указать имя файла, быстрое и грязное решение - добавить имя файла к фактическому URL, ссылающемуся на файл; для http://example.com/fileurl.php используйте http://example.com/fileurl.php/filename.jpg. Это может не сработать, если что-то уже интерпретирует информацию о пути (например, Drupal); «лучшее» решение - отправить заголовок Content-Disposition: attachment; filename=filename.jpg, но это также не позволяет клиентам просматривать изображение непосредственно в браузере (хотя это может быть полезно в зависимости от вашей ситуации).

2 голосов
/ 19 июня 2017

Я твердо верю, что пакетная обработка с Doctrine или любые другие итерации с MySQL (PDO или mysqli) - всего лишь иллюзия.

@ dimitri-k дал хорошее объяснение, особенно в отношении единицы работы. Проблема заключается в промахе: « $ query-> iterate () », который на самом деле не перебирает источник данных. Это просто \ Traversable оболочка вокруг уже полностью извлеченного источника данных.

Пример, демонстрирующий, что даже после полного удаления слоя абстракции Doctrine из картинки мы все равно столкнемся с проблемами с памятью :

echo 'Starting with memory usage: ' . memory_get_usage(true) / 1024 / 1024 . " MB \n";

$pdo  = new \PDO("mysql:dbname=DBNAME;host=HOST", "USER", "PW");
$stmt = $pdo->prepare('SELECT * FROM my_big_table LIMIT 100000');
$stmt->execute();

while ($rawCampaign = $stmt->fetch()) {
    // echo $rawCampaign['id'] . "\n";
}

echo 'Ending with memory usage: ' . memory_get_usage(true) / 1024 / 1024 . " MB \n";

Выход:

Starting with memory usage: 6 MB 
Ending with memory usage: 109.46875 MB

Здесь разочаровывает getIterator () метод:

namespace Doctrine\DBAL\Driver\Mysqli\MysqliStatement

/**
 * {@inheritdoc}
 */
public function getIterator()
{
    $data = $this->fetchAll();

    return new \ArrayIterator($data);
}

Вы можете использовать мою маленькую библиотеку для на самом деле потоковой передачи тяжелых таблиц с использованием PHP Doctrine или DQL или просто чистого SQL. Однако вы находите подходящим: https://github.com/EnchanterIO/remote-collection-stream

0 голосов
/ 27 сентября 2011

Посмотрите на следующие функции php. ob_start() ob_end_clean() и ob_end_flush()

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...