PHP und MySQL: Bilder in Datenbank speichern
Bei statischen Webseiten legt man die zugehörigen Bilder zusammen mit den HTML-Seiten einfach als Datei ab. Werden die Texte dagegen in einer Datenbank gespeichert, macht es Sinn, auch die Grafiken in der Datenbank abzulegen. Dann enthält die Datenbank den gesamten Inhalt der Webseite, was Backup und Umzug der Webseite auf einen neuen Server erleichtert.
Datenstrukturen erweitern
Neben den Bilddaten selbst muss in der Datenbank auch der Typ des Bildes gespeichert werden. Denn den braucht der Browser als Information beim Laden, damit er die Grafik korrekt darstellen kann.
Folgender SQL-Befehl, den Sie beispielsweise über phpMyAdmin geben, erzeugt die benötigte Tabelle images:
CREATE TABLE images(
id INT( 11 ) AUTO_INCREMENT ,
imgdata LONGBLOB,
imgtype VARCHAR( 20 ) ,
PRIMARY KEY ( id )
);
Das Feld id, das von der Datenbank automatisch mit einer eindeutigen Nummer versehen wird, dient zur eindeutigen Identifizierung eines Bildes.
Mit dieser einfachen Tabellenstruktur können Sie Bilder Ihrer Webseite in der Datenbank statt im Dateisystem ablegen.
Bild hochladen
Mit normalen HTML-Formularen kann man nicht nur Textinformationen, sondern auch Binärdaten an einen Webserver verschicken. Dazu muss lediglich in das Tag
das zusätzliche Attribut enctype="multipart/form-data" aufgenommen werden und das Formular ein Eingabefeld vom Typ file bekommen. Das erzeugt dann bei der Darstellung die typische Kombination aus einem Feld für den Namen einer lokalen Datei und dem Button Durchsuchen, um die Datei bequem ansteuern zu können.
Schickt man das Formular ab, werden die Daten von PHP in einer temporären Datei auf dem Server gespeichert und die Systemvariable $_FILE enthält alle notwendigen Informationen, um die empfangenen Daten auszuwerten, wie etwa den Namen der temporären Datei oder den MIME-Typ der Datei, den der Browser zur Identifizierung mitschickt.
Die kompakte Version des Bilder-Uploaders, bei dem das Formular selbst und seine Auswertung im selben Skript realisiert sind, sieht so aus:
<?php
// img_up.php: Ein Bild hochladen
require_once 'connect.inc.php';
if (array_key_exists('img',$_FILES)) {
$tmpname = $_FILES['img']['tmp_name'];
$type = $_FILES['img']['type'];
$hndFile = fopen($tmpname, "r");
$data = addslashes(fread($hndFile, filesize($tmpname)));
$strQuery = "INSERT INTO images
(imgdata,imgtype) VALUES
('$data','$type')" ;
if (!mysql_query( $strQuery))
die(mysql_error());
}
?>
<html><body>
<h1>Bild hochladen</font></h1>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"
enctype="multipart/form-data">
Bilddatei:<br />
<input type="file" name="img" size="40"><p>
<input type="submit" name="submit" value="Abschicken">
</form></body></html>
Um das Skript nicht weiter aufzublähen, gehen wir davon aus, dass das Include-File connect.inc.php alle notwendigen Schritte unternimmt, um die Datenbankverbindung herzustellen. Danach prüft das Skript, ob im Array $_FILE ein Element img vorhanden ist. So haben wir das Input-Feld für den Upload genannt. Die if-Abfrage trifft also dann zu, wenn das Skript als Folge des Formularversands aufgerufen wird. Trifft die Abfrage zu, werden zuerst die temporäre Datei gelesen und dann die Daten über addslashes() so aufbereitet, dass man sie in die INSERT -Abfrage einbauen kann, die die Bildinformationen speichert. Ansonsten würden die Binärdaten zu einem SQL-Fehler führen, weil darin ja beispielsweise auch SQL-Steuerzeichen enthalten sein können.
Bild abrufen
Das Konzept von HTML sieht vor, dass ein Bild in einer separaten Aktion abgerufen wird. Im HTML-Quelltext steht ja dann so etwas wie <img src='bild.jpg'>, was der Browser auswertet und die Bilddaten von der genannten Quelle nachlädt.
Für unsere Datenbank-Bilder bedeutet das, dass beim Attribut src ein PHP-Skript referenziert wird, das sich so verhalten muss, wie ein statisch angesprochenes Bild.
Ein vollständiges Tag für ein Bild aus der Datenbank könnte so aussehen:
<img src='img_get.php?id=123'>
Dabei dient der Parameter id dazu, das Bild zu identifizieren.
Grafiktyp definieren
Was das Skript neben den übermittelten Bilddaten selbst liefern muss, ist eine Identifikation, um welche Bildart es sich handelt. Bei statischen Bildern erledigt dies der Webserver. Er ermittelt das verwendete Format und schickt als Teil des Headers den Inhaltstyp image mit dem Bildformat als Untertyp. So kann der Browser ohne aufwändige Analyse der Daten sich auf das verwendete Datenformat einstellen.
Für ein JPEG-Bild sieht die dazugehörige Header-Zeile beispielsweise so aus:
Content-Type: image/jpeg
Diese Information muss der PHP-Bilderlieferant auch liefern, was über den PHP-Befehl header() realisiert wird. Der Typ wurde beim Upload im Datensatz des Bildes abgelegt, muss also einfach aus dem imgtype abgerufen werden
<?php
// img_get.php: Bild abrufen
require_once 'connect.inc.php';
$id = $_GET['id'];
$strQuery= "select imgdata,imgtype
from images where id=$id";
$result=mysql_query($strQuery);
$row=mysql_fetch_assoc($result);
header("Content-type: {$row['imgtype']}");
echo $row['imgdata'];
?>
Weitere Daten speichern
Neben den Pixeldaten eines Bildes und seinem Typ wäre eine Speicherung einer Bildbeschreibung sehr sinnvoll. Diese könnte man dann im <IMG>-Tag für das Attribut ALT einsetzen. Wer aus technischen Gründen keine Bilder abrufen kann oder blind ist, kann damit wenigstens rudimentäre Informationen erfahren. Dann muss zusätzlich im Skript, das die HTML-Daten eines Beitrags anzeigt, ein Abruf aller zum Beitrag gehörenden Datensätze aus der Tabelle images erfolgen.
Bildverweis per Namen
Recht unpraktisch am Beispielskript ist die Referenzierung eines Bildes über seine eindeutige Nummer, die man manuell eintragen muss. Praktischer wäre die Verwendung des beim Upload verwendeten Bildnamens. Dann allerdings besteht das theoretische Problem, dass bei verschiedenen Beiträgen derselbe Bildname verwendet wird. Man müsste daher in der Tabelle images zusätzlich noch die Nummer des Beitrags speichern und als Identifikationsdaten für den Abruf eines Bildes dann den Bildnamen und zusätzlich die Beitragsnummer angeben.
Die praktischste Variante wäre die Einführung eines speziellen, selbst gebastelten Tags in einem Textbeitrag, wie etwa <bild foto1> oder - syntaktisch sauber - <bild name='foto1.jpg'/>. Das würde das Skript für die Artikelanzeige durch Parsen des Textes finden, daraufhin die Bildtabelle nach dem passenden Datensatz durchsuchen und statt des Pseudotags das korrekte IMG-Tag mit dem Bildabruf-Skript als Quelle eintragen.
Trifft der Programmteil zum Eintragen eines Textbeitrags auf so ein Tag, fordert er beim Speichern den Benutzer dazu auf, das Bild über das Upload-Skript in der Datenbank abzulegen.
Fehler als Grafik
Klappt der Abruf eines Bildes nicht, etwa, weil ein Datenbankfehler auftritt, kann man das nicht einfach als eine Fehlermeldung ausgeben. Denn der Output des Skripts wird ja vom Browser als Bilddaten erwartet. Um eine Fehlermeldung zu realisieren, müssen Sie darum ein Bild erzeugen, das aus der Fehlermeldung besteht. Dazu gibt es die mit image... beginnenden PHP-Grafikfunktionen, die intern die GD-Bibliothek verwenden. Eine Minimalversion für die Ausgabe einer Fehlermeldung auf diese Weise könnte so aussehen:
<?php
$err=mysql_error();
$im = ImageCreate (250,50);
$color = ImageColorAllocate ($im, 0, 0, 0);
$bgcolor = ImageColorAllocate ($im, 255, 255, 255);
ImageString ($im, 2, 5, 5, $err, $color);
header("Content-type:image/png");
ImagePNG($im);
?>
Meiner Erfahrung nach empfiehlt es sich, die reinen BLOBs und die
Metadaten noch einmal zu trennen und 1:1 zu verknüpfen. Auf diese
Weise bleiben Abfragen über die Metadaten - z.B. um ein
Inhaltsverzeichnis auszugeben - schnell.
Weiters habe ich persönlich noch ein Caching-System eingebaut, dass
die Binärdaten bei der ersten Ausgabe auf die Platte schreibt und in
Zukunft von dort holt, was natürlich wesentlich performanter ist.
Dadurch hat man - zu Lasten des Speicherplatzverbrauchs - sowohl
Performanz als auch Integrität, da man bei Problemen / Änderung
einfach das Cache-Verzeichnis löschen kann und die Daten noch in der
Datenbank hat.
[Hadanite Marasek | 07.09.2007]
Antworten
Hallo,
hab das Ganze mal unter PostgreSQL ausprobiert (in abgewandelter
Form), beim Hochladen der Bilder kann ich in der Spalte imgdata aber
nur sehr kurze Strings (Referenzen?) speichern. Woran kann das liegen?
Beim Auslesen erhalte ich dann ein stilisiertes Piktogramm (als
Zeichen, dass der Browser das Bild nicht auslesen kann).
Wahrscheinlich werden die Bildinformationen nicht (vollständig) in der
Datenbank abgespeichert.
//Skript zum Hochladen
//Skript zum Herunterladen
Für einen Tipp wäre ich sehr dankbar.
Sven
[Sven Steglich | 13.05.2009]
Antworten
Klappt bei mir nicht. Er gibt als Fehlermeldung von img_get.php aus:
"Warning: Cannot modify header information - headers already sent by
(output started at /www/htdocs/w0069920/php/img_get.php:2) in
/www/htdocs/w0069920/php/img_get.php on line 11"
line 2 ist der Start des php-Scripts, line 11 ist
"header("Content-type: {$row['imgtype']}");"
Die Infos kommen aber an, mit echo bekomme ich den korrekten MIME-Typ
(z.B. "image/jpeg" oder "image/gif")
[Chnutz | 10.02.2010]
Antworten
habe das gleiche Prob wie Chnutz . ;-(
[RalfBollermann | 24.04.2011]
Antworten
Bedingungen für die Kommentareingabe
Hinweis