Lazy Loading

Školení, která pořádám

Lazy Loading je postup, kdy data nenahráváme ihned, ale až když jsou potřeba. Jak tento postup využít u jednoduchého ORM? V PHP stačí správně využít přetěžování:

<?php
class ORM {
    protected $id;
    protected $table;
    protected $data = array();
    
    function __construct($table, $id = null) {
        $this->table = $table;
        $this->id = $id;
    }
    
    function __get($name) {
        $this->load();
        return $this->data[$name];
    }
    
    function __isset($name) {
        $this->load();
        return isset($this->data[$name]);
    }
    
    function __set($name, $value) {
        $this->data[$name] = $value;
    }
    
    function __unset($name) {
        unset($this->data[$name]);
    }
    
    protected function load() {
        if ($this->data) {
            return false;
        }
        if (isset($this->id)) {
            $this->data = mysql_fetch_assoc(mysql_query("
                SELECT *
                FROM $this->table
                WHERE id = " . intval($this->id) . "
            "));
        } else {
            $result = mysql_query("EXPLAIN $this->table");
            while ($row = mysql_fetch_assoc($result)) {
                $this->data[$row["Field"]] = $row["Default"];
            }
            mysql_free_result($result);
        }
    }
    
    function save() {
        $set = array();
        foreach ($this->data as $key => $val) {
            $set[] = "$key = " . (isset($val) ? "'" . mysql_real_escape_string($val) . "'" : "NULL");
        }
        if (isset($this->id)) {
            if (!$set) {
                return 0;
            }
            mysql_query("
                UPDATE $this->table
                SET " . implode(", ", $set) . "
                WHERE id = " . intval($this->id) . "
            ");
        } else {
            mysql_query("INSERT INTO $this->table " . ($set
                ? "SET " . implode(", ", $set)
                : "() VALUES ()"
            ));
            $this->id = mysql_insert_id();
            $this->data["id"] = $this->id;
        }
        return mysql_affected_rows();
    }
    
    function delete() {
        if (isset($this->id)) {
            mysql_query("DELETE FROM $this->table WHERE id = " . intval($this->id));
            $this->id = null;
            return mysql_affected_rows();
        }
        return 0;
    }
}
?>

Kód není myšlen jako kompletní ORM, ale jen jako ukázka toho, že data není nutné načítat dříve, než je to nezbytně nutné. SQL příkazy předpokládají, že v tabulce bude existovat Auto Increment sloupec s názvem id. Tomuto záměru je kód podřízen, takže např. o neexistenci záznamu se dozvíme až v momentě, kdy se z něj pokusíme načíst hodnotu (a ne hned při vytvoření objektu) – je to kvůli aktualizacím, při kterých není potřeba původní záznam načítat. Ukázka použití:

<?php
// vytvoření uživatele
$uzivatel = new ORM("uzivatel");
$uzivatel->jmeno = "Jakub";
$uzivatel->save();

// načtení stávajícího uživatele
$uzivatel = new ORM("uzivatel", 1);
echo $uzivatel->jmeno;
$uzivatel->jmeno = "Franta";
$uzivatel->save();
?>
Jakub Vrána, Seznámení s oblastí, 11.12.2009, diskuse: 9 (nové: 0)

Diskuse

Mr.Vain:

Az nato, ze v tom druhom bloku malo byt load. A na druhej strane je to dost blbost. ;)

ikona Jakub Vrána OpenID:

Zjevně jsi článek vůbec nepochopil. Metoda load() se volá automaticky, když je to potřeba. Zvenku objektu zavolat ani nejde.

paranoiq:

lazy loading je samozřejmě dobrá věc, ale je třeba zvážit, kde má jeho využití rozumný konec

mě se třeba vůbec nelíbí představa, že vytvářím objekt za účelem updatu, který reprezentuje NEEXISTUJÍCÍ řádek v databázi, aby při prvním pokusu s jeho obsahem cokoliv udělat selhal - měl selhat už v konstuktoru!

// načtení stávajícího uživatele
$uzivatel = new ORM("uzivatel", 1);

pokud uživatel s id=1 neexistuje, vede to k situaci, která je absurdní. není to čisté řešení a je mi jedno, kolik výkonu bych tím ušetřil. pokud načítám řádek z databáze a konstruuji z něj objekt, musím ověřit alespoň jeho existenci

ikona Jakub Vrána OpenID:

Ano, v článku je uvedeno, že lazy loadingu je kód podřízen. Také vidím stejný potenciální problém, nicméně ve většině případů nemusí být nijak zásadní, protože o neexistenci záznamu se dozvím při prvním přístupu k vlastnosti. Naopak to dovoluje dělat rychlé UPDATE bez potřeby načítat data.

Ve svém projektu bych ale takhle extrémní lazy loading asi taky nepoužil.

ikona v6ak:

Jo a je tu vlastně možnost zjistit při update, že ten řádek neexistuje? Nepočítám nějaký hack s nastavením a_number = -a_number, které by (pokud by bylo nenulové) zajistilo updatování vždy.

ikona Jakub Vrána OpenID:

V PHP bohužel jedině parsováním mysql_info().

ikona v6ak:

Hmm, v dibi asi ne, že?

ikona Jakub Vrána OpenID:

Ale jo, dělá to metoda getInfo().

Vojta:

Tohle Tvé řešení se mi líbí, ještě jsem nic podobného nezkoušel a myslím, že jsem si trochu rozšířil obzory :). Já používám "modely" - malé třídy, které neumí nic jiného než si pamatovat pár definovaných hodnot se "settery", "gettery" a vlastní validací. Tyto třídy pak načítám a ukládám do DB pomocé "handlerů", což jsou třídy s přístupem do databáze. Ten Tvůj jednoduchý ORM by bylo potřeba rozšířit v případě použití cizích klíčů - nabízí se tedy například vytvoření nějaké hiearchie ORM tříd (ani by nemusely být specifické pro každý projekt, stačilo by nějak chytře reprezentovat případy 1:1, 1:N a N:N).

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.