Navigace

Hlavní menu

 

PHP pro pokročilé - znovu třídy a objekty

V tomto článku o třídách a objektech si ukážeme další zajímavé možnosti při práci s OOP v PHP. Budeme se zabývat využitím takzvané serializace a magických funkcí __sleep() a __wakeup() - vytvoříme instanci třídy, uložíme ji do souboru a nakonec celý objekt předáme v session.

Serializace a unserializace

Vše zajišťují funkce serialize() a unserialize(), které převedou do takzvaného byte řetězce proměnné objektu. Proces tohoto "převodu" se nazývá serializace a opačný postup unserializace. Na vstupu funkce serialize() je objekt a na výstupu již zmiňovaný byte řetězec, u funkce unserialize() je tomu naopak.

Pro další experimentování si nadefinujeme pokusnou třídu do souboru "trida.php" se kterou budeme dále pracovat:

<?
/*
Soubor trida.php ve kterém je definice pokusné třídy
*/

class clsTrida {
//pomocí konstruktoru nastavíme hodnoty vlastností
function clsTrida($vl1, $vl2) {
 $this->vlastnost1=$vl1;
 $this->vlastnost1=$vl2;
}
//funkce vypis() vypíše hodnoty obou vlastností
function vypis() {
 echo "1. vlastnost: ".$this->vlastnost1."<br />";
 echo "2. vlastnost: ".$this->vlastnost2."<br />";
 }
}
?>

Nejprve si ukážeme postup uložení objektu do souboru:

<?
/*
Soubor "a.php"
V prvním souboru náš objekt zeserializujeme a uložíme ho do souboru "data"
*/

//naincludujeme soubor s pokusnou třídou
include ('trida.php');
//vytvoříme instanci pokusné třídy a nastavíme vlastností, které se zeserializují
$objekt = new clsTrida ("Vlastnost 1", "Vlastnost 2");
//zde vytvoříme byte řetězec
$bytestring=serialize($objekt);
//otevřeme soubor a byte řetězec do něj uložíme
$f = fopen("data", "w"); //vytvoření a otevření souboru pro zápis
fputs($f, $bytestring); //uložení bytestringu do souboru
fclose($f); //zavření souboru
?>

V souboru s unserializací se již nevytváří žádná instance, protože unserializovaný objekt je schopen sám poznat svoji třídu. Ta ale ve skriptu definována být musí.

<?
/*
Soubor "b.php"
V druhém souboru otevřeme soubor "data", kde je serializovaný objekt a unserializujeme ho
*/

//naincludujeme soubor s pokusnou třídou
include ("trida.php");
//otevření souboru a přečtení jeho obsahu
$bytestring = implode("", @file("data"));
$UnserializovanyObjekt=unserialize($bytestring);
//necháme si vlastnosti objektu vypsat, abyste viděli, že uložení do souboru "přežili"
$UnserializovanyObjekt->Vypis();
?>

Předání objektu pomocí session je v podstatě stejné jako při ukládání do souboru, jen zde místo funkcí pro uložení souboru použijeme funkce pro předávání session:

<?
/*
Soubor "sesstrida1.php"
*/

session_start(); //nastartovani session
//naincludujeme soubor s pokusnou třídou
include ("trida.php");
//vytvoříme instanci pokusné třídy
$objekt = new clsTrida ("Vlastnost 1", "Vlastnost 2");
//zde vytvoříme byte řetězec
$bytestring=serialize($objekt);
//vytvoření session
$_SESSION["serializovanyobjekt"]=$bytestring;
//napíšeme odkaz na další stránku
echo '<a href="sesstrida2.php">Další stránka</a>';
?>
<?
/*
Soubor "sesstrida2.php"
*/

session_start(); //nastartovani session
//naincludujeme soubor s pokusnou třídou
include ("trida.php");
//zde unserializujeme byte řetězec
$UnserializovanyObjekt=unserialize($_SESSION["serializovanyobjekt"]);
//opět si vypíšeme hodnoty obou vlastností
$UnserializovanyObjekt->vypis();
?>

Magické funkce __sleep() and __wakeup()

Při (un)serializaci mohou být velmi užitečné "magické" funkce __sleep() a __wakeup(), které se umisťují do definice třídy. Funkce __sleep() se spustí na začátku serializace. Můžeme do ní vložit kód například pro ukončení spojení s databází. Důležité je, aby funkce __sleep() vrátila pole, které obsahuje proměnné objektu, ze kterých se vytvoří byte řetězec. Naproti tomu funkce __wakeup() se spustí na začátku unserializace. Lze ji využít například pro obnovení spojení s databází.

V následujícím příkladu si vytvoříme třídu, která si pamatuje datum své serializace a při unserializaci tuto hodnotu vypíše. Byte řetězec v tomto příkladu budeme ukládat do souboru. Funkce get_object_vars() byla popsána v předchozím článku.

<?
/*
Soubor trida.php ve kterém je definice pokusné třídy
*/
class clsTrida {

 //funkce vypis() vypíše hodnoty obou vlastností
 function vypis() {
  echo "1. vlastnost: ".$this->vlastnost1."<br />";
  echo "2. vlastnost: ".$this->vlastnost2."<br />";
 }

 function __sleep() {

  //do vlastnosti "datum" uložíme aktuální datum = datum serializace
  $this->datum=Date("Y-m-d");

  //vytvoříme pole vlastností objektu. Jestliže nějakou vlastnost vynecháme, nebude serializovaná.
  $vlastnosti = get_object_vars($this);

  foreach ($vlastnosti as $klic => $hodnota) {
   $pole[] = $klic;
  }

  //vrácení pole vlastností třídy
  return $pole;
 }

 function __wakeup() {

  //jestliže jsme uložili datum, tak ho vypíšeme
  if (isset($this->datum)) {
    echo "Datum serializace: ".$this->datum."<br />";
  }
 }

}
?>

Výše uvedený příklad je pouze jednoduchou ukázkou magických funkcí a velké praktické využití asi nemá. V následujícím příkladu si tedy vytvoříme třídu, která využívá databázi. Funkce __sleep() v tomto případě spojení s ní ukončí a vrátí údaje potřebné k přihlášení do databáze, funkce __wakeup() spojení opětovně naváže. Výhody tohoto postupu jsou zřejmé.

<?
/*
Soubor trida.php ve kterém je definice pokusné třídy
*/
class clsTrida {

 //konstruktor třídy
 function clsTrida($server,$uzivatel,$heslo,$databaze){

  $this->datserver=$server;
  $this->datuzivatel=$uzivatel;
  $this->datheslo=$heslo;
  $this->datjmeno=$databaze;

   $this->Pripojeni();
 }

 //naváže spojeni s databází
 function Pripojeni() {
  MySQL_Connect($this->datserver,$this->datuzivatel,$this->datheslo);
  MySQL_Select_DB($this->datjmeno); //vybrání databáze
 }

 //funkce vypis() vypíše obsah tabulky; je zde samozřejmě pouze pro demonstraci práce s databází
 function Vypis() {
  $dotaz=MySQL_Query("SELECT sloupec1, sloupec2 FROM tabulka") or Die(MySQL_Error());
  while ($data = MySQL_Fetch_Array($dotaz)){
   echo $data[sloupec1]." ".$data[sloupec2];
   echo "<br />";
  }
 }

 //ukončíme spojení s databází a vrátíme přihlašovací údaje
 function __sleep() {

  MySQL_Close();

  //vytvoříme pole vlastností objektu
  $vlastnosti = get_object_vars($this);

  foreach ($vlastnosti as $klic => $hodnota) {
   $pole[] = $klic;
  }

  //vrácení pole vlastností třídy
  return $pole;
 }

 //opět navážeme spojení s databází
 function __wakeup() {
  $this->Pripojeni();
 }

}
?>
<?
/*
Soubor databaze1.php, kde dochází k serializaci
*/
include("trida.php");

$objekt = new clsTrida("Localhost", "MojeJmeno", "TajneHeslo", "PokusnaDAT");
//vypíšeme obsah tabulky
$objekt->vypis();

$bytestring=serialize($objekt);
//otevřeme soubor a byte řetězec do něj uložíme
$f = fopen("data", "w"); //vytvoření a otevření souboru pro zápis
fputs($f, $bytestring); //uložení bytestringu do souboru
fclose($f); //zavření souboru
?>
<?
/*
Soubor databaze2.php, kde dochází k unserializaci
*/
//naincludujeme soubor s pokusnou třídou
include ("trida.php");
//otevření souboru a přečtení jeho obsahu
$bytestring = implode("", @file("data"));
$UnserializovanyObjekt=unserialize($bytestring);
//vypíšeme obsah tabulky
$UnserializovanyObjekt->Vypis();
?>

Ještě se musím zmínit o jednom bezpečnostním nedostatku zmíněného příkladu. Pokud předáváte byte řetězec v session, data jsou zde v nezašifrované formě, takže je může případný útočník velmi jednoduše odchytit a pokud zná strukturu byte řetězce (která není nijak složitá, zkuste si otevřít soubor, do kterého ukládáte byte řetězec, v libovolném textovém editoru), nebude problém si je přečíst a dostat se tak do naší databáze. Podmínkou je tedy použití například zabezpečeného protokolu SSL.

Všechny příklady z článku si můžete stáhnout. Až na příklad s databází, kde si musíte upravit přihlašovací údaje, budou fungovat bez problémů.

Heller, Petr (16. 4. 2004)
webmaster PeHe.net