7

我使用 PHP 将图像存储在 PostgreSQL 数据库中,列类型为 bytea。问题是每次我尝试在浏览器中加载图像时它都不会出现。Firefox 开发者控制台显示图像被截断或损坏。

PHP代码:

//code for inserting into the database
if(array_key_exists('submit_pic', $_POST)){
$user=$_SESSION['name'];
if(isset($_FILES['thumbnail'])&&$_FILES['thumbnail']['size']>0){
$fi =  $_FILES['thumbnail']['tmp_name'];
$p=fopen($fi,'r');
$data=fread($p,filesize($fi));
$data=addslashes($data);
$dat= pg_escape_bytea($data); 
$q="update userinfo set image='{$dat}' where email='$user'";
$e=pg_query($q)or die(pg_last_error());

// code for retreving from database
require_once('conn.php');
session_start();
$user=$_SESSION['name'];
pg_query('SET bytea_output = "escape";');
$lquery ="select image from userinfo where email='$user'";
$lq = pg_query($lquery)or die(pg_last_error());
$lqq=pg_fetch_row($lq,'image');
header("conent-type:image");
echo pg_unescape_bytea($lqq[0]);

我需要将上传的图像存储在数据库中-我实际上是在使用 heroku 谢谢

4

4 回答 4

15

TL;博士:

删除addslashes($data)。这里是多余的。

双重转义..两次

$data=fread($p,filesize($fi));
$data=addslashes($data);
$dat= pg_escape_bytea($data); 

您读取数据,将其转义为字符串文字,然后将其转换为 bytea 八进制或十六进制转义。即使pg_escape_bytea是理智的,它也永远不会那样工作,但事实并非如此。

PHPpg_escape_bytea似乎对输出进行了双重转义,因此可以将其插入字符串文字中。这非常难看,但似乎没有不做这种双重转义的替代方案,因此您似乎无法在 PHP 中对 bytea 使用参数化语句。对于其他所有事情,您仍然应该这样做。

在这种情况下,只需删除addslashes从文件中读取的数据行就足够了。

显示双重转义的测试用例pg_escape_bytea(并且总是使用旧的、低效的八进制转义):

<?php
# oh-the-horror.php
print pg_escape_bytea("Blah binary\x00\x01\x02\x03\x04 blah");
?>

跑:

php oh-the-horror.php

结果:

Blah binary\\000\\001\\002\\003\\004 blah

看到双反斜杠了吗?那是因为它假设你要把它作为一个字符串插入到 SQL 中,这是非常低效的、丑陋的,而且是一个非常坏的习惯。不过,您似乎没有其他选择。

这意味着:

pg_unescape_bytea(pg_escape_bytea("\x01\x02\x03"));

... 产生错误的结果,因为pg_unescape_bytea实际上不是pg_escape_bytea. 这也使得无法将pg_escape_byteainto的输出pg_query_params作为参数提供,您必须将其插入。

解码

如果您使用的是现代 PostgreSQL,它可能会默认设置bytea_outputhex。这意味着如果我将数据写入一个bytea字段然后将其取回,它将看起来像这样:

craig=> CREATE TABLE byteademo(x bytea);
CREATE TABLE
craig=> INSERT INTO byteademo(x) VALUES ('Blah binary\\000\\001\\002\\003\\004 blah');
INSERT 0 1
craig=> SELECT * FROM byteademo ;
                                     x                                      
----------------------------------------------------------------------------
 \x426c61682062696e6172795c3030305c3030315c3030325c3030335c30303420626c6168
(1 row)

“嗯,什么”,你可能会说?没关系,它只是 PostgreSQL 稍微更紧凑的bytea. pg_unescape_bytea将处理它并产生与输出相同的原始字节......如果你有一个现代 PHP 和libpq. 在旧版本上,你会得到垃圾,需要设置bytea_outputescapeforpg_unescape_bytea来处理它。

你应该做什么

使用 PDO。

它对bytea.

$sth = $pdo->prepare('INSERT INTO mytable(somecol, byteacol) VALUES (:somecol, :byteacol)');
$sth->bindParam(':somecol', 'bork bork bork');
$sth->bindParam(':byteacol', $thebytes, PDO::PARAM_LOB);
$sth->execute();

看:

您可能还想查看 PostgreSQL 的 lob(大对象)支持,它提供了一个仍然完全事务性的流式、可搜索接口。

现在,到我的肥皂盒上

如果 PHP 在“字节字符串”和“文本字符串”类型之间有真正的区别,那么您甚至不需要pg_escape_bytea,因为数据库驱动程序可以为您做到这一点。不需要这些丑陋的东西。不幸的是,PHP 中没有单独的字符串和字节类型。

请尽可能使用带有参数化语句的 PDO。

在你不能的地方,至少使用pg_query_params和参数化的语句。PHPaddslashes不是替代品,它效率低下、丑陋,并且不理解数据库特定的转义规则。如果您出于不良的历史原因不使用 PDO,您仍然必须手动转义bytea,但其他一切都应通过参数化语句。

有关以下方面的指导pg_query_params

于 2013-06-15T09:19:55.997 回答
3

如果您确实必须在数据库中存储图像,最好使用 postgres大对象。userinfo表而不是图像本身中,仅将指向它的链接存储为loid(大对象 id)。

将图像插入数据库:

    pg_query("begin");  // pg_lo functions need to be run in a transaction
    $loid = pg_lo_import('full_path_and_file_name');
    pg_query("update userinfo set loid=$loid where email='$user'");
    pg_query("commit");

从数据库中检索图像:

    $rs = pg_query("select loid from userinfo where email='$user'");
    $loid = pg_fetch_row($rs, 0)[0];
    pg_query("begin");
    $blob = pg_lo_open($loid, "r");
    header("Content-type: image");
    pg_lo_read_all($blob);
    pg_lo_close($blob);
    pg_query("commit");

loid字段是oid类型的(当然你可以随意命名)。

考虑使用扩展lo中的类型lo不是使用oid类型。Usinglo为您提供自动“孤立删除”,从表中删除一行将自动删除关联的大对象,因此它适用于表行“拥有”一个大对象的情况。

如果您多次使用一张图像,存储图像链接特别方便。但是,您应该注意从数据库中删除未使用的图像(PHP 函数 pg_lo_unlink())。

postgres 文档中的大对象。

PHP 手册:pg_lo_import。

于 2013-06-15T21:44:09.257 回答
0

由于您的数据源是文件系统中的一个文件,因此在我看来,在这里找到灵感很有效:

在您的数据库中创建一个辅助函数,以超级用户身份运行:

create or replace function bytea_import(p_path text, p_result out bytea) 
                   language plpgsql as $$
declare
  l_oid oid;
begin
  select lo_import(p_path) into l_oid;
  select lo_get(l_oid) INTO p_result;
  perform lo_unlink(l_oid);
end;$$
security definer;

在您的 php 中执行如下查询:

#make sure that postgres will have access to the file
chmod($_FILES['thumbnail']['tmp_name'], 0644);
pg_query("update userinfo set image=(select bytea_import('".$_FILES['thumbnail']['tmp_name']."')) where email='$user'");
于 2018-02-04T13:25:11.587 回答
0

我发现了一种在不使用 PDO 的情况下也能正常工作的奇怪方法。

在 postgresql 中使用文本字段而不是 bytea。在插入时,像这样准备您的数据:

$imgdta = pg_escape_string(bin2hex($filedata));

然后,当您想在查询后显示文件时,请使用:

echo pack("H*", $img["filedata"]);

我不会假装我明白为什么,但这对我有用!

于 2015-09-22T20:29:41.753 回答