有没有什么方法可以将PDO查询结果“流”到输出缓冲区中,而不是将其存储到字符串中?

时间:2011-09-27 15:42:41

标签: php mysql pdo

如果您看到此问题的标题,则几乎没有添加内容。

我有一个从MySQL表中检索单行的查询,我对特定列感兴趣,这是一个BLOB。我希望PHP将它写入输出缓冲区,而不是将~500 KB存储到一个字符串中(,而且我不确定它是二进制安全的)。

PDOStatement功能如:

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

不要帮助我。

你能帮助至少给我一个方向吗?提前谢谢。

P.S。:我知道在DB表中存储~500 KB的东西并不好,但这不是我的选择,我只需要坚持下去。

3 个答案:

答案 0 :(得分:2)

this page。这会将数据加载到流中,然后可以与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中使用它,测试和工作的方式;它包括许多额外的缓存处理,可以加速你的客户端,只需要你跟踪blob的Last-Modified时间:

<?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,但这也会阻止客户端直接在浏览器中查看图像(尽管这可能是一件好事,具体取决于您的情况)。

答案 1 :(得分:2)

我坚信使用Doctrine进行批处理或使用MySQL(PDO或mysqli)进行任何迭代都只是一种错觉。

@ dimitri-k提供了一个很好的解释,特别是关于工作单元。问题是导致错过:&#34; $ query-&gt; iterate()&#34;它并没有真正迭代数据源。已经完全获取数据源的只是一个\ Traversable wrapper

一个例子表明即使从图片中完全删除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

答案 2 :(得分:-4)

看看下面的php函数。 ob_start() ob_end_clean()ob_end_flush()