Przechowywanie zdjęć w bazie danych

yakhub

Celem artykułu jest zademonstrowanie, jak przechowywać zdjęcia w bazie MySQL, dlatego też cały tekst koncentruje się tylko i wyłącznie na tym zadaniu. Dla większej przejrzystości tekstu, zamieszczone fragmenty kodu koncentrują się wyłącznie na tym zagadnieniu. Należy mieć na uwadze, że zamieszczony kod co prawda będzie działał, ale nie jest on kompletny, ani optymalny. Może zawierać uchybienia dotyczące bezpieczeństwa, zaś generowane przez niego dokumenty nie zawsze będą zgodne z normami.
Pisząc ten tekst, przyjąłem, że Czytelnik zna podstawy PHP i wie, jak komunikować się z bazą danych

Wstęp

Podstawową cechą stron generowanych dynamicznie jest ich interaktywność – użytkownicy mają wpływ na treści dostępne na stronie, a często wręcz są ich twórcami. „Twórczość” użytkowników trzeba w jakiś sposób przechowywać po stronie serwera – teoretycznie można to robić, operując na plikach, znacznie wygodniejsze do tego celu są jednak bazy danych.
Tradycyjnie, w BD przechowywane są dane tekstowe, lub pokrewne. Niemal zawsze dotyczy to bardzo niewielkich porcji danych, często liczonych w pojedynczych bajtach. Można jednak w nich przechowywać także duże ilości binarnych danych.

Konstrukcja tablicy bazodanowej

Potrzebna nam będzie najprostsza tabela, w której możemy przechowywać dane binarne. Do tego celu stosujemy typy danych BLOB. Dostępne są typy: BLOB, MEDIUMBLOB, TINYBLOB i LONGBLOB.

Różnią się między sobą maksymalną długością - dokładne wielkości każdego z nich znajdziesz w dokumentacji bazy. My użyjemy typu MEDIUMBLOB. Aby zdjęcia rozróżniać między sobą, dodamy jeszcze pole id typu INTEGER - będzie to klucz główny naszej tablicy. Dla uproszczenia kodu PHP, pole to będzie miało atrybut AUTO_INCREMENT. Definicja naszej tablicy wygląda następująco:

CREATE TABLE zdjecia (
	id INTEGER AUTO_INCREMENT PRIMARY KEY,
	zdjecie MEDIUMBLOB NOT NULL
)

Formularz HTML - dodawanie zdjęcia do bazy (dodawajka.php)

Tutaj wszelkie komentarze są chyba zbędne. Pierwszy plik, jaki utworzymy, będzie po prostu statycznym formularzem. (Patrz uwagi u góry strony!)

plik: dodawajka.php

<FORM ACTION="upload.php" METHOD="POST" ENCTYPE="multipart/form-data">
Zdjęcie: </td><td><INPUT type="file" name="zdjecie">
<input type="submit" name="ok" value="Wyślij zdjęcie do bazy"/>
</FORM>

Dodawanie pliku do bazy (plik upload.php)

Zaczynamy od połączenia z bazą:

<?
// Rzecz jasna wszystkie te dane zależą od naszej konkretnej bazy!
$adres = "localhost";
$uzytkownik = "user";
$haslo = "pass";		
$db_name = "base";

$link = mysql_connect( $adres, $uzytkownik, $haslo);
mysql_select_db($db_name);

Następnie musimy otworzyć do odczytu plik, który nadesłał nam Użytkownik. Jego zawartość zawartość zapisujemy w zmiennej $content:

$fhandle = fopen($_FILES['zdjecie']['tmp_name'], "r");
$content = base64_encode(fread($fhandle, $_FILES['zdjecie']['size']));
fclose($fhandle);

I wreszcie - właściwe dodanie danych do bazy:

$zapytanie = mysql_query("INSERT INTO zdjecia (zdjecie) VALUES (\"" . mysql_real_escape_string($content) . "\")";

Na koniec - informujemy użytkownika, jaki adres ma jego zdjęcie. Oczywiście w miejsce ADRES_STRONY wstawiamy jej adres. Plik showimage.php stworzymy później.

	$adres = "ADRES_STRONY/showimage.php?id=" . mysql_insert_id()
	echo "Twoje zdjęcie otrzymało adres: <br/>" . htmlentities($adres);

Oraz - aby upewnić się, że wszystko przebiegło poprawnie (a przy okazji zademonstrować, jak to zrobić) - wyświetlamy właśnie dodane zdjęcie:

	echo "<br/><img src=\"" . htmlentities($adres) . "\"/>";
?>

Wyświetlanie zdjęcia (plik showimage.php)

Przede wszystkim - chcemy, aby ten plik był traktowany przez przeglądarkę, jak zdjęcie. Musi mieć więc właściwy nagłówek:

<?php
header("Content-type: image/jpg;");

Zaczynamy od połączenia z bazą i zadania zapytania:

	$uzytkownik = "user"; 	//
	$haslo = "pass";		// Rzecz jasna wszystkie te dane zależą od naszej konkretnej bazy!
	$db_name = "base";  	//
	$adres = "localhost"; 	//
	
	$link = mysql_connect( $adres, $uzytkownik, $haslo);
	mysql_select_db($db);
	
	$result = mysql_query("SELECT zdjecie FROM zdjecia WHERE id=".$_GET['id']);

Następnie - po upewnieniu się, że cokolwiek w bazie zostało znalezione - wyciągamy z wyniku zapytania jedyny wiersz, i wyświetlamy go... A właściwie - wysyłamy użytkownikowi.

	if (mysql_num_rows($result) != 0)
	{
		$row = mysql_fetch_assoc($result);
		echo base64_decode($row['zdjecie']);
	}

Uzupełnienie serwisu

Rzecz właściwie nie mająca już związku z tematem, ale będąca ładnym uzupełnieniem całości - w głównym katalogu na serwerze w pliku .htaccess dopisujemy regułę:

RewriteEngine On
RewriteRule ^photo([0-9][0-9][0-9]).jpg showimage.php?id=$1

W ten sposób w pełni będziemy udawać, że nasze zdjęcia są prawdziwymi .jpgami, a nie są generowane dynamicznie. Taka reguła sprawi, że adres NASZASTRONA/photo123.jpg będzie przez serwer interpretowany tak samo, jak adres NASZASTRONA/showimage.php?id=123.

8 komentarzy

Jak zmodyfikować ten kod aby można było dodać kilka zdjęć jednocześnie ?

obrazek nie wyswietla się
Jak daje sekcje header z jpg to mam wysyp znaków
jak dam gifa to wyskakuje okienko z pytaniem czy zapisać czy nie.- wyświetla się owszem ale małe okienko z czerwonym x coś nie tak z tym skryptem może ktoś pomoże

Ciekawy artykuł. Dzięki!

Jednakże konstrukcja:

$result = mysql_query("SELECT zdjecie FROM zdjecia WHERE id=".$_GET['id']);

daje duże możliwości nieautoryzowanego dostępu nawet do innych tabel w bazie. Proponuje zmienną id przechowywać w postaci zmienne liczbowej (konwertować ją z $_GET['id'] na liczbę).

Jeszcze $db zamiast $db_name. [wiem, że mógłbym sam wyedytować, ale nie mam teraz czasu na to].

Poprawiłem. Na swoją obronę dodam, że ten kod jest kawałkami wycinany z większej całości, więc miejscami pozostawały "śmieci".

Ok, ale dane do łączenia i wybrania bazy nadal niepoprawne. Co do mysql_query - aha, nie wiedziałem, że wykorzysta ostatnio otwarte połączenie, jeśli się zadeklaruje $xxx = mysql_connect (myślałem, że tylko przy "samym" mysql_connect). Jeśli tylko jedno połączenie to można zamiast:

$link = mysql_connect(...);
mysql_select_db($db, $link); 

dać:

mysql_connect(...);
mysql_select_db($db);

I nie bardzo rozumiem, po co tutaj ten sprintf:

$result = mysql_query(sprintf("SELECT zdjecie FROM zdjecia WHERE id=".$_GET['id']));

oraz po co w ogóle zmienna $query (przecież nic z nią nie robimy):

$query = sprintf("insert into zdjecia (zdjecie) values (\"%s\")" , $content);
$zapytanie = mysql_query("INSERT INTO zdjecia (zdjecie) VALUES (\"".$content."\")";

"mysql_query() wysyła zapytanie do aktywnej bazy na serwerze skojarzonym z podanym identyfikatorem połączenia. Jeżeli identyfikator_połączenia nie został podany, wykorzystywane jest ostatnio otwarte połączenie. " - cytuję za dokumentacją.

Tablice $HTTP_cośtam faktycznie są przestarzałe - zaraz to zmienię.

Co do obsługi plików - po prostu przyzwyczajony jestem z języka C, że się to robi tak, a nie inaczej. Pewnie inaczej się da - ale chyba fopen() i fread() nie są nieprawidłowe?

Małe literówki - próbujesz się łączyć z hostem $ip, powinno być $adres. Tak samo wybierasz bazę $db, a chodziło o $db_name.

Ponadto mysql_query musi przyjąć jako drugi parametr: $link oraz zamiast $HTTP_POST_FILES należy używać $_FILES i zamiast $HTTP_GET_VARS - $_GET. Nie rozumiem też, po co tutaj zmienna $query? I zamiast fopen, fread, fclose, myślę, że wystarczy file_get_contents:

$content = base64_encode(file_get_contents($_FILES['zdjecie']['tmp_name']));