Práce s obrázky v databázi MySQL

24. října 2002

Víte, jak pomocí skriptu php bezpečně nahrát obrázek do databáze MySQL a jak pomocí dotazu na databázi tento obrázek zpětně načíst do prohlížeče? Pokud nikoli, využijte následujících řádků. Mechanismus lze použít například v internetových obchodech, kdy patří ke standardní prezentaci nabídky fotografie zboží načítané z databáze.

Spojení správné fotografie a dané položky lze dosáhnout v podstatě dvěma způsoby. U prvního způsobu je v příslušném poli tabulky adresa obrázku uloženého na serveru, obrázek se tedy načte jednoduchým dotazem na databázi, například:

// proměnná $zaznam[‚obrazek‘] je přímá adresa obrázku odpovídajícího dané položce
$dotaz = mysql_query(„SELECT obrazek FROM Polozky WHERE ID_polozka = 30“);
if($dotaz):
  $zaznam = mysql_fetch_array($dotaz);
  <img src=“<? echo $zaznam[‚obrazek‘] >“>
endif;

Tento způsob má nespornou výhodu v minimálním zatížení databáze, nevýhodou však je komplikované zajištění samotného nahrání obrázku na server, například chceme-li jako autor aplikace internetového obchodu správu této aplikace, včetně editace obrázků k položkám zboží, předat přímo majiteli obchodu.

V tomto článku se budu věnovat, druhému způsobu, tedy uložení obrázku do pole typu BLOB tabulky databáze. Aby zde popsaný skript fungoval, je nutné zajistit podporu knihovny php_gd.dll, kterou budu potřebovat pro funkce zjišťující pixelové velikosti nahrávaného obrázku. Název knihovny je nutno odkomentovat v konfiguračním souboru php.ini (sekce Dynamic Extensions) a knihovnu umístit do adresáře, který je v php.ini nadefinován jako adresář pro dynamické moduly (sekce Paths and Directories, direktiva extension_dir, pozor také na direktivu enable_dl v téže sekci, která musí být nastavena na On). Knihovna nepodporuje bohužel od verze 1.3 formát obrázků GIF. Situace se snad změní po 19. červnu 2003, kdy vyprší patent společnosti UNISYS na druh komprese používaný tímto formátem. Prozatím se spokojíme s podporou formátů JPEG a PNG, což ale v uvedeném příkladě využití pro fotografie zboží položek internetového obchodu zase tolik nevadí.

Nejdříve si musíme vytvořit tabulku v databázi mySQL, do které budeme ukládat obrázky jako binární soubory. Tabulku nazveme „obrazek“ a pole typu BLOB, do kterého se obrázek uloží, nazveme „foto“. Další pole „nazev“, „velikost“, „typ“, „sirka“ a „vyska“ poslouží k uložení informací o nahraném obrázku, tedy o jeho názvu, velikosti v bytech, typu (image/jpeg nebo image/png), šířce a výšce v pixelech. Primárním klíčem tabulky je pole „id“, které ve zmíněném příkladu internetové prodejny slouží k propojení tabulky „obrazek“ s další tabulkou, uchovávající všechny ostatní údaje o položce, jako její název, popis, cena a podobně. Při načtení a manipulaci s nabídkou obchodu se tak vyhneme zbytečnému zatěžování databáze, pole typu BLOB se načte jen na přímé přání zákazníka obchodu, například po kliknutí na odkaz „fotografie“ k příslušné položce zboží obsahující parametr id. Pro účely tohoto skriptu si do tabulky načteme jeden cvičný záznam s hodnotou pole id = 1.

CREATE TABLE `obrazek` (
`id` INT UNSIGNED DEFAULT ‚0‘ NOT NULL ,
`foto` BLOB NOT NULL ,
`nazev` VARCHAR( 20 ) NOT NULL ,
`velikost` INT UNSIGNED NOT NULL ,
`typ` VARCHAR( 20 ) NOT NULL ,
`sirka` TINYINT UNSIGNED DEFAULT ‚0‘ NOT NULL ,
`vyska` TINYINT UNSIGNED DEFAULT ‚0‘ NOT NULL ,
PRIMARY KEY ( `id` )
);
INSERT INTO `obrazek` ( `id` , `nazev` , `velikost` , `typ` )
VALUES (
‚1‘, “, ‚0‘, “
);

Skript php, řešící nahrání obrázku do tabulky „obrazek“, nazveme „obrazek_upload.php“, skript, řešící načtení obrázku uloženého v této tabulce do prohlížeče, nazveme „obrazek_vypis.php“.

První částí skriptu „obrazek_upload.php“ je formulář, kterým můžeme vybrat libovolný soubor uložený na svém počítači. K formuláři snad jen to, že jeho atribut „method“ musí být nastaven na multipart/form-data a musí obsahovat element typu file, který umožní výběr souboru, a jehož jméno bude ve skriptu použito pro přístup k tomuto souboru.

Součástí formuláře je také element typu hidden se jménem MAX_FILE_SIZE a s hodnotou udávající maximální akceptovatelnou velikost souboru v bytech. Tento element musí být ve formuláři zařazen před elementem typu file. Do formuláře jsem přidal ještě další dva elementy typu hidden. První se jménem „id“, jehož hodnota odpovídá hodnotě pole „id“ záznamu tabulky „obrazek“, do kterého chceme nahrát obrázek. Protože jsem do tabulky „obrazek“ vložil pouze jeden cvičný záznam s hodnotou pole „id“ = 1, je i hodnota tohoto elementu formuláře nastavena na 1. Druhý element jsem nazval „action“ a slouží jako kontrola odeslání formuláře. Zjistí-li skript, že je inicializována proměnná $action, pozná, že došlo k odeslání formuláře a začne zpracovávat předaný soubor. Příklad jednoduchého formuláře uvádím ve výpisu:

<form name=“upload“ action=“obrazek_upload.php“ method=“post“ enctype=“multipart/form-data“>
<input type=“hidden“ name=“id“ value=“<? echo $id ?>“>
<input type=“hidden“ name=“MAX_FILE_SIZE“ value=“30000″>
<input type=“hidden“ name=“action“ value=“true“>
<table border=“0″ cellpadding=“6″ cellspacing=“0″ align=“center“>
<tr>
<td colspan=“2″>
podporovány pouze formáty JPEG/JPG a PNG<br>
maxim. povolený rozměr 200×200 pixelů<br>maxim. povolená velikost 30 000 bytů
</td>
</tr>
<tr>
<td colspan=“2″>
<input type=“file“ name=“binFile“>
&nbsp;&nbsp;
<input type=“submit“ value=“upload“>
</td>
</tr>
</table>
</form>

Ve formuláři je uvedeno, že podporované formáty jsou JPEG a PNG, maximální povolený rozměr obrázků je 200 x 200 pixelů a maximální velikost souboru nesmí překročit 30 000 bytů. Těžištěm skriptu „obrazek_upload.php“ není samotná operace načtení obrázku do databáze, což provedeme jednoduchým příkazem UPDATE s příslušnou syntaxí, ale kontrola správnosti vybraného souboru. K informacím o nahraném souboru budeme přistupovat přes pole $HTTP_POST_FILES (od verze 4.1.0 použijeme pole $_FILES, u verzí do 4.0.3 je nutno v konfiguračním souboru php.ini zkontrolovat, zda direktiva track_vars je nastavena na On). Na soubor jako takový se odvoláváme prostřednictvím $HTTP_POST_FILES[‚binFile‘][‚tmp_name‘], kde ‚binFile‘ je jméno elementu file výše popsaného formuláře. Obdobným způsobem se můžeme odvolat na velikost poslaného souboru ($HTTP_POST_FILES[‚binFile‘][‚size‘]), jeho jméno ($HTTP_POST_FILES[‚binFile‘][‚name‘]) a typ ($HTTP_POST_FILES[‚binFile‘][‚type‘]). Celý soubor příkazů php skriptu následuje v komentovaném výpisu, ze kterého bude celý mechanismus kontroly a načtení souboru obrázku do tabulky „obrazek“ dostatečně zřejmý:

// předpokládám připojení $pripoj k databázi mySQL obsahující tabulku obrazek
// inicializace proměnných + bezpečnostní ošetření proměnné $id
if(!isset($id)):
  $id=0;
else:
  $id+=0;
endif;
if(!isset($chyba)):
  $chyba=““;
endif;
if(isset($action)):
// formulář byl odeslán
  if(trim($HTTP_POST_FILES[‚binFile‘][‚name‘])==““):
// musím vybrat nějaký soubor
    $chyba=“vyberte platný soubor“;
  elseif($HTTP_POST_FILES[‚binFile‘][‚size‘]==0):
// vybraný soubor musí mít nějakou velikost
    $chyba=“vybraný soubor má nulovou velikost“;
  elseif($HTTP_POST_FILES[‚binFile‘][‚size‘]>30000):
// soubor nesmí být větší než 30 000 bytů (POZOR!, element MAX_FILE_SIZE formuláře lze snadno obejít)
    $chyba=“soubor je příliš velký -<br>maxim. velikost nesmí překročit 30 000 bytů“;
  elseif(!is_uploaded_file($HTTP_POST_FILES[‚binFile‘][‚tmp_name‘])):
// zkontroluji, zda zpracovávaný soubor není podvržen (potenciální útočník může zmást skript např. tím, že za nahrávaný soubor bude vydávat soubor již umístěný na serveru)
    $chyba=“přístup odepřen“;
  endif;
  if($chyba==““):
// vybraný soubor zatím splňuje požadovaná kritéria
    $typ=strrchr($HTTP_POST_FILES[‚binFile‘][‚name‘], „.“);
// ze jména souboru zjistím jeho formát
    if($typ!=“.jpg“ && $typ!=“.png“):
// mohu nahrát pouze soubory *.jpg nebo *.png
      $chyba=“můžete použít pouze formát JPEG (JPG) nebo PNG“;
    else:
      if($typ==“.jpg“):
// soubor je formátu *.jpg
        $typ_file=“image/jpeg“;
// do tabulky obrazek soubor uložím jako image/jpeg – viz skript obrazek_vypis.php v dalším výpisu
        $im=imagecreatefromjpeg($HTTP_POST_FILES[‚binFile‘][‚tmp_name‘]);
// pomocí funkce imagecreatefromjpeg() z knihovny php_gd.dll získám identifikátor obrázku $im
        if(imagesx($im)>200 || imagesy($im)>200):
// pomocí funkcí imagesx() a imagesy() z knihovny php_gd.dll zkontroluji šířku a délku obrázku v pixelech
          $chyba=“obrázek je příliš velký – jeho šířka ani výška nesmí překročit 200 pixelů“;
        else:
// hodnoty udávající skutečnou šířku a výšku obrázku zjištěné pomocí funkcí imagesx() a imagesy() načtu do proměnných $width a $height, které uložím do příslušných polí tabulky obrazek
          $width=imagesx($im);
          $height=imagesy($im);
        endif;
        imagedestroy($im);
// uvolním paměť rezervovanou pro obrázek
      else:
// soubor je formátu *.png, následující příkazy jsou obdobné jako u obrázku formátu *.jpg, jen s tím rozdílem, že do tabulky obrazek soubor uložím jako image/png
        $typ_file=“image/png“;
        $im=imagecreatefrompng($HTTP_POST_FILES[‚binFile‘][‚tmp_name‘]);
        if(imagesx($im)>200 || imagesy($im)>200):
          $chyba=“obrázek je příliš velký – jeho šířka ani výška nesmí překročit 200 pixelů“;
        else:
          $width=imagesx($im);
          $height=imagesy($im);
        endif;
        imagedestroy($im);
      endif;
    endif;
    if($chyba==““):
// poslaný soubor splňuje všechna požadovaná kritéria a mohu ho nahrát do tabulky obrazek
      $binFile=str_replace(„;“, „“, $HTTP_POST_FILES[‚binFile‘][‚tmp_name‘]);
// v tomto případě poněkud paranoidní bezpečnostní opatření – odstraním všechny možné výskyty ; z nahrávaného souboru
      $obrazek = addslashes(fread(fopen($binFile, „r“), filesize($binFile)));
// soubor načtu jako binární text
      $aktual = mysql_query(„UPDATE obrazek SET foto=’$obrazek‘, nazev='“.$HTTP_POST_FILES[‚binFile‘][‚name‘].“‚, velikost=“.$HTTP_POST_FILES[‚binFile‘][‚size‘].“, typ=’$typ_file‘, vyska=$width, sirka=$height WHERE id=$id“);
// do pole foto příslušného záznamu definovaného pomocí proměnné $id tabulky obrazek uložím binární text souboru, do pole nazev jméno souboru, do pole velikost jeho velikost v bytech, do pole typ jeho nadefinovaný typ a do polí sirka a vyska jeho skutečné rozměry v pixelech
      if($aktual):
        if(mysql_affected_rows()==1): ?>
// aktualizace proběhla v pořádku – mohu provést načtení obrázku zpět do prohlížeče
          <table border=“0″ cellpadding=“5″ cellspacing=“0″ align=“center“>
          <tr align=“center“>
          <td><br><strong>obrázek byl úspěšně nahrán</strong></td>
          </tr>
          <tr align=“center“>
// vypíši jméno souboru
          <td>jméno souboru: <? echo $HTTP_POST_FILES[‚binFile‘][‚name‘] ?></td>
          </tr>
          <tr align=“center“>
// vypíši velikost souboru
          <td>velikost souboru v bytech: <? echo $HTTP_POST_FILES[‚binFile‘][‚size‘] ?></td>
          </tr>
          <tr align=“center“>
// vypíši typ souboru
          <td>typ souboru: <? echo $typ_file ?></td>
          </tr>
          <tr>
          <td align=“center“>
// pomocí skriptu obrazek_vypis.php s předaným parametrem id vygeneruji obrázek, ke skriptu obrazek_vypis.php viz dále
          <img src=“obrazek_vypis.php?id=<? echo $id ?>“ border=“0″ width=“<? echo $width ?>“ height=“<? echo $height ?>“>
          </td>
          </tr>
          </table>
// link na úvodní formulář
          <br><div align=“center“><a href=“obrazek_upload.php“>zpět</a></div>
// ukončení této větve skriptu
          </body>
          </html>
          <? MySQL_Close($pripoj);
          die();
        else:
// poslaný soubor je totožný s již uloženým souborem
          $chyba=“obrázek nebyl aktualizován“;
        endif;
      else:
// k aktualizaci nedošlo
        $chyba=“porucha v komunikaci s databází – obrázek se nepodařilo nahrát!“;
      endif;
    endif;
  endif;
endif;
// výpis chybové hlášky
if($chyba!=““): ?>
  <br><br><div align=“center“><strong><? echo $chyba ?></strong></div>
<?
endif; ?>

Obrázek tedy vygenerujeme pomocí skriptu „obrazek_vypis.php“. Aby se z tabulky „obrazek“ načetl záznam se správným obázkem, je nutno tomuto skriptu předat proměnnou $id s hodnotou odpovídající hodnotě klíčového pole „id“ daného záznamu. Obrázek můžeme po připojení k příslušné databázi umístit kdekoliv na stránce *.php pomocí kódu <img src="obrazek_vypis.php?id=<? echo $id ?>" width="<? echo $width ?>" height="<? echo $height ?>">. Hodnota proměnné $id je volitelná, hodnoty proměnných $width a $height získáme dotazem na pole „sirka“ a „vyska“ příslušného záznamu tabulky.

Samotný skript „obrazek_vypis.php“ je velice jednoduchý, skládá se vlastně jen z příslušného dotazu na databázi a z definice HTTP hlaviček, které prohlížeči řeknou, že mu místo obvyklého html kódu posíláme obrázek. Z tohoto důvodu je také typ ukládaných souborů definován jako image/jpeg, respektive image/png, což jsou definované hodnoty hlavičky „Content-type“ (takzvaný MIME typ přijímaných dat). Komentovaný výpis skriptu:

// předpokládám připojení $pripoj k databázi mySQL obsahující tabulku obrazek
// bezpečnostní ošetření předaného parametru
$id+=0;
// dotaz na databázi
$nacti=mysql_query(„SELECT foto, velikost, typ, nazev FROM obrazek WHERE id=$id“);
if(!$nacti):
  echo „došlo k poruše v komunikaci s databází“;
  MySQL_Close($pripoj);
  die();
endif;
$zaznam=mysql_fetch_array($nacti);
// načtení údajů z databáze do proměnných
$foto=$zaznam[„foto“];
$velikost=$zaznam[„velikost“];
$typ=$zaznam[„typ“];
$nazev=$zaznam[„nazev“];
// vytvoření příslušných hlaviček
header(„Content-type: $typ“);
header(„Content-length: $velikost“);
header(„Content-Disposition: attachement; filename=$nazev“);
header(„Content-Description: PHP Generated Data“);
// vypsání binárního textu z pole typu BLOB tabulky obrazek způsobí, že v prohlížeči se tento text zobrazí jako obrázek
echo $foto;

A jedna rada na závěr. Nahrávání souborů je vždy riziková záležitost z hlediska možného útoku hackera. Skript obsahující možnost nahrání čehokoliv na server byste tedy měli zpřístupnit jen autentifikovaným uživatelům (tj. jen po zadání loginu a hesla) a v každém případě kontrolovat velikost, typ a jméno nahrávaného souboru a otestovat ho na přítomnost rizikových znaků jako je středník nebo anglická uvozovka.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Předchozí článek Toulky po webu 11.
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *