Interval.cz
Prßce s obrßzky v databßzi MySQL

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 200x200 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.



Ondra Marek (24.10. 2002)

Redakce Interval.cz |  Inzerce na Interval.cz |  Hledßme novΘ autory ISSN 1212-8651 
 ⌐ Zoner software, s.r.o., vÜechna prßva vyhrazena, tento server dodr₧uje prßvnφ p°edpisy o ochran∞ osobnφch ·daj∙.