Statické proměnné v metodách

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

Co vypíše následující kód?

<?php
class C {
    function f() {
        static $a = 0;
        $a++;
        return $a;
    }
}

class D extends C {
}

$c1 = new C;
echo $c1->f() . "\n";
$c2 = new C;
echo $c2->f() . "\n";
$d = new D;
echo $d->f() . "\n";
?>

Přiznám se, že jsem si to myslel špatně. Myslíte si, že víte? A co když by se definice změnila takhle?

<?php
class C {
    private function g() {
        static $a = 0;
        $a++;
        return $a;
    }
    function f() {
        return $this->g();
    }
}

class D extends C {
}
?>
Jakub Vrána, Výuka, 10.10.2012, diskuse: 33 (nové: 0)

Diskuse

Hrach:

Uhuh, zajimave.

ikona Zemistr:

Hmm...čekal jsem 1 2 3...výsledek mě opravdu překvapil.
Myslíš, že by jsi to mohl nějak vysvětlit?

ikona Miloslav Ponkrác:

Jakmile se v jazyce začnou objevovat podezřelé kousky jako „late static bindings“, je jasné, že se to podezřele začne chovat i jinde.

Jakmile jsem začal číst o „late static bingings“, říkal jsem si, oni snad budou muset začít kopírovat statické metody tak, že každá třída dostane nezávislou kopii. Že statické metody budou jenom metametody (tedy šablony metod řečeno jazykem C++), ze kterých PHP kompilátor vygeneruje  do každé třídy konkrétní kopii statické metody.

Tedy statické metody se staly jen „vzorem“ pro to, co PHP tiše vygeneruje do každé třídy.

Nicméně to se může změnit. O několik verzí dále se autoři PHP rozhodnout optimalizovat, a tak budou kopírovány a generovány statické metody jenom tam, kde je použito „late static bindings“. Takže se pak vzorový příklad bude chovat v každé situaci jinde – někde bude statická proměnná sdílená, v jiných se zase rozkopírují.

PHP je jednoduše katastrofálně nedomyšlený jazyk v mnoha směrech. Zaplaťpámbů za logicky postavené jazyky, kde existuje psaná norma, a kde se diskutovalo o věcech.

Sázím se jak dlouho bude trvat, než bude situace v PHP neudržitelná. Protože na rozdíl od jiných jazyků se tam 90 % věcí chová neintuitivně, nečekaně a ještě se příležitostně mění podle toho, jak se to autorům PHP podaří nastavit. Manuál k PHP stojí za starou bačkoru, popisuje jen zlomek chování PHP a i tak špatně a s mnoha chybami a nepřesnostmi.

Budu-li mít někdy vlastní server, s velkou radostí to bahno a nesmysl zvaný PHP nechám za sebou.

Miloslav Ponkrác

ikona Zemistr:

Velmi zajímavý názor na PHP.
Je pravda, že PHP není nejpromyšlenější jazyk a jeho konzistence není úplně nejlepší. Pokud, ale hledáte něco, v čem naprogramovat jednoduchý nebo rozsáhlý web, tak nejčastěji lidé sáhnou právě po PHP. V dnešní době je tady ještě ASP nebo Ruby. Ve svém okolí neznám nikoho, kdo když začínal, tak nezačínal na PHP. Dokumentace PHP se mi zdá velice dobrá. Vše co jsem hledal, jsem v ní našel a co jsem tam nenašel jsem si otestoval.

Teď mě tak napadlo.
V čem vy vlastně programujete? (jestli programujete)

ikona Miloslav Ponkrác:

V čem programuji? V čem je potřeba.

Před 25 lety jsem začínal na kombinaci Pascal, Simula, C, Smalltalk.

Dnes se mi ukazuje jako nejpoužitelnější C++ pro projekty, které mě dnes živí. Spolu s ARM assemblerem.

Jazykem C++ jsem si vydělal v životě největší peníze. Je to velmi bohatý, nicméně logický, konzistentní a intuitivní jazyk. A jde v něm napsat naprosto všechno a velmi rychle.

---

Otestovat si v PHP samozřejmě můžete cokoli, ovšem kromě toho, že to bude fungovat v dané verzi PHP, kde jste to otestoval, to nic jiného neříká.

Zrovna statické proměnné a jejich implementace – tak PHP experimentuje. V PHP 4 se snažili implementovat static proměnné pomocí referencí. To se ukázalo jako problematické. V PHP 5 je to jinak.

Snaha zavést v PHP 5.3 závislost na třídě do statickým metod vedla k tomu, že statické metody se staly pouhými metametodami a že PHP vygeneruje pro každou třídu speciální instanci metody – což je výsledek toho, čemu se v článku diví.

Juraj:

Vieš o tom, že keď napíšeš meno do formulára už sa nemusíš na konci podpisovať?

ikona Miloslav Ponkrác:

Vím

Miloslav Ponkrác :-)

Lupen:

To je ten, co se enormě na root.cz vztekal, že neexistuje HTML5 (http://www.root.cz/zpravicky/plan-na-dokonceni-…-roku-2014/), co?

ikona Jakub Vrána OpenID:

Chyby v PHP manuálu rád opravím. Jen bych je potřebova specifikovat.

kucix OpenID:

Řekl bych, že statická proměnná se váže ke třídě, resp. "hlavní třídě", tady tedy v prvních dvou případech ke třídě C a v posledním ke třídě D, tedy je to jiná proměnná...
Ale nečekal jsem to... čekal jsem taky 1,2,3...
It's a bug or feature?

ikona Zemistr:

Udělal jsem testovací jízdu a dle výsledků soudím, že pro každého potomka se tzv. všechno hodí do počátečního stavu.

<?php
header
('Content-Type: text/plain; charset=UTF-8');

class
C {
    function f() {
        static $a = 0;
        $a++;
        return $a;
    }
}

class
D extends C {
}
class
E extends D {
}

$c1 = new C;
echo
'C1 - '. $c1->f() . "\n";
$c2 = new C;
echo
'C2 - '. $c2->f() . "\n";
$d1 = new D;
echo
'D1 - '. $d1->f() . "\n";
$d2 = new D;
echo
'D2 - '. $d2->f() . "\n";
$e = new E;
echo
'E - '. $e->f() . "\n";
$e = new E;
echo
'E - '. $e->f() . "\n";
$e = new E;
echo
'E - '. $e->f() . "\n";
$e = new E;
echo
'E - '. $e->f() . "\n";
$e = new E;
echo
'E - '. $e->f() . "\n";
?>

ikona Zemistr:

jo...tu je výsledek ;)

C1 - 1
C2 - 2
D1 - 1
D2 - 2
E - 1
E - 2
E - 3
E - 4
E - 5

ikona stekycz:

Mě se zdá toto chování nakonec vcelku logické, ačkoli jsme to také nečekal.
Aktuálně se taková proměnná chová jako privátní statický atribut třídy. Bohužel to by v některých případech znamenalo v každé třídě napsat metodu znova a to v tomto případě nutné není.
Pokud bychom chtěli pro třídu a všechny její potomky společný čítač, tak hlavní předek by měl mít getter (proč jinak bychom chtěli počítat?) a metodu f(). Atribut bychom mohli nastavit ne jako privátní, ale jako protected a getter není potřeba.
Pokud by tedy takováto proměnná fungovala jako privátní/protected atribut, tak by se podle mě jednalo o duplicitu funkcionality jazyka.

Podbi:

Nemyslím si, že by to bylo zase tak nelogické. V PHP prostě patří statický atribut dané třídě, potomek má tedy svůj vlastí...
Nicméně je fakt, že v jiných jazycích je chování statických atributů v dědičnosti odlišné.

bene:

Mě příjde chování logické a vyhovuje mi. Jak by se to mělo chovat, pokud bych metodu v potomkovy přetížil a změnil. To by pořád měla přístup ke statické proměnné, která ji vlastně nepatří? Pro sdílení statických proměnných slouží třídní proměnné.

lopata:

Tahle se to chová odjakživa, late static binding na tom nic nezměnil - http://www.php.net/manual/en/language.variables.scope.php#42955

v6ak:

Já jsem nad tím přemýšlel: co je vlastně static v nestatické metodě? V rámci čeho je to statické? Nejvíc by to dávalo smysl v rámci instance, když jde o nestatickou metodu. Výstup by pak byl 1 1 1.

V případě Scaly, kdyby tam podobná feature byla, bych za to snad i dal ruku do ohně bez čtení dokumentace. V případě PHP jsem to prostě zkusil bez favorita.

Mohu mít jeden takový dotaz: K čemu je dobrá static proměnná uvnitř metody? Statické proměnné ve funkci jsem kdysi použil (dohromady asi jednou - pro obdobu singletonu), ale v metodách?

Marek:

treba rychla "cache" ;)

v6ak:

Ale pokud můžu použít statickou proměnnou třídy (IMHO přehlednější)... Smysl to mělo snad v PHP 5.2.*-, kde nebylo late static binding.

ikona Jakub Vrána OpenID:

Proměnným rád dávám minimální možnou viditelnost. Takže pokud s proměnnou pracuje více metod (nebo charakterizuje vlastnost objektu), tak patří do třídy. Ale pokud je součástí interní implementace jedné metody, tak patří jen do této metody. Takže tento kód:

<?php
class C {
    function loadA($id) {
        static $results = array();
        if (!isset($results[$id])) {
            $results[$id] = ; // Slowly load A
        }
        return $results[$id];
    }
}
?>

Považuji za lepší než tento:

<?php
class C {
    private static $cachedA = array();
    function loadA($id) {
        if (!isset(self::$cachedA[$id])) {
            self::$cachedA[$id] = ; // Slowly load A
        }
        return self::$cachedA[$id];
    }
}
?>

Třídě je houby do toho, jestli si metoda něco kešuje, to je její interní implementace, která nemusí prosakovat dál, než je to nutné.

v6ak:

Ano, je fakt, že je ta proměnná méně viditelná. Možná jsem zatím nevěděl, jak toto přesně funguje, možná to je vlivem jiných jazyků, možná určitým (malým) nádechem magie a možná jsem dělal příliš málo statických metod pracujících s globálním stavem.

Franta:

To je sice pravda, ale statické proměnné jsou dost zvěrstvo samy o sobě, takže je celkem jedno, jestli jsou vidět z celé třídy nebo jen z konkrétní metody.

v6ak:

Nebral bych to až tak dogmaticky. Je účelné statické proměnné omezit, ale na použití vhodného Singletonu (byť mám k němu spíš odpor) jako je https://gist.github.com/319827 (dvakrát totéž, liší se hlavně jazyk) bych se jim nebránil.

ikona Petr Jirásek:

K zamysleni dobry, ale jinak mi to prijde dost nehezky a doufam, ze to nikdo nepouziva a jestli jo, tak bych teda skutecne rad vedel, proc vubec a jak.

Filip Procházka:

https://github.com/nette/nette/blob/master/…/Presenter.php#L817

msx:

Čakal som 1 1 1. Ale opäť som o niečo múdrejší.

ikona Jakub Vrána OpenID:

Teď jsem přišel na výbornou věc:

<?php
class C {
    private function f() {
        static $class;
        if (!$class) {
            $class = get_class($this);
        }
        return $class;
    }

    function x() {
        return $this->f();
    }
}

class
D extends C {
}

class
E extends C {
}

$d = new D;
$e = new E;
var_dump($d->x());
var_dump($e->x()); // Vypíše D!
?>

Když se private změní na protected, tak se vypíše E. Fuj tajksl!

ikona Jakub Vrána OpenID:

Tohle se nakonec ještě dá pochopit – privátní metoda patří třídě, ve které je definovaná. Ale jak vysvětlíte tohle?

<?php
class C {
    function __call($name, $args) {
        static $class;
        if (!$class) {
            $class = get_class($this);
        }
        return $class;
    }
}

class
D extends C {
}

class
E extends C {
}

$d = new D;
$e = new E;
var_dump($d->x());
var_dump($e->x()); // Vypíše D!
?>

v6ak:

Co jiného by se mělo stát? Vysvětlím to asi stejně jako předchozí. Když je metoda veřejně viditelná (protected či public), zkopíruje se do potomka a udělá si zvláštní static field u své třídy. Ten je oddělený od předka i sourozenců. Když je private, nemá k tomu důvod.

Vzal jsem Tvůj první příklad a jen přehodilpořadí dumpů (a upravil komentáře):
<?php
class C {
    private function f() {
        static $class;
        if (!$class) {
            $class = get_class($this);
        }
        return $class;
    }

    function x() {
        return $this->f();
    }
}

class
D extends C {
}

class
E extends C {
}

$d = new D;
$e = new E;
// přehodil jsem pořadí výpisu
var_dump($e->x()); // Vypíše E!
var_dump($d->x()); // Vypíše E!
?>

Hned se chová jinak.

ikona Jakub Vrána OpenID:

Vždyť jsem psal, že se to dá pochopit. Jak bys vysvětlil chování public __call()?

v6ak:

Aha, omlouvám se, můj fail.

Ale mám jeden postřeh: při náhradě var_dump($d->x()); za var_dump($d->__call('x', array())); se změní chování. Takže asi interní volání magické metody call funguje jinak než její přímé volání a volá se metoda z předka. Zavání to nějakým porušením DRY v implementaci PHP.

omelkes:

Magická metoda také patří ke třídě ve které je definovaná. U public nemagické metody si PHP vytvoří vlastní tabulku metod, u magické, nebo private metody se odkazuje na rodičovskou třídu.

Asi chápu jak je to udělané, při volání metody se interpret:
1) podívá do tabulky metod (kde jsou zapsané metody dané třídy a public metody rodičů)
2) pokud tam není definovaná metoda, tak se zkontroluje definici magických metod.
3) Není-li definována u třídy ani magická ani normální metoda, zkontroluje třídu rodiče.
4) V rodiči existuje magická metoda, tak v prvním průchodu inicializuje statickou proměnnou, a tu dále použivá v kontextu rodiče.

Tohle chování dává smysl, protože interpret nemůže použít magickou metodu v kontextu volající třídy, už kvůli možnému volání privatní metody rodiče (a tím zachování konzistentního chování)

To na co upozorňuje v6ak je přímé volání public funkce, a to se chová jako jakékoli jiné volání public funkce u třídy. Sama třída si vytvoří svůj statický prostor.

v6ak:

Ony jsou oba přístupy pochopitelné, ale je trošku divné, že u volání funkce __call ze skriptu se to chová jinak než u přímého volá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.