2019. április 24., szerda

Gyorskeresés

PHP static constructor

Írta: | Kulcsszavak: php . static . constructor . autoload

[ ÚJ BEJEGYZÉS ]

Nem létezik.

Pedig van úgy, hogy az embernek kellene annak ellenére is, hogy a static elemek sokszor kilógnak az OO nézetek alól. Nézegettem a különböző workaround-okat (nem sok volt), egyik, ami használható:

<?php

class Foo {

public static function Init() {
/* class initialization logic */
}

}

Foo::Init();

Ebben a megoldásban az osztálydefiníciót tartalmazó fájl végére kerül be az Init meghívása, ami biztosítja, hogy az osztály használatakor meg legyen hívva. Nem rossz, de szerintem csúnya.

Ezzel végére is értünk a használható ötleteknek. :) Megmutatom, szerintem mi az elegáns, amit nem is láttam másoknál (javítsatok ki, ha vak voltam):

<?php

class Autoloader {

public function Load($className) {
$cl = $this->_load($className);

if (empty($cl)) {
throw new Exception("Class not found: " . $className);
}

require $cl;

if (method_exists($className, "__static")) {
call_user_func(array($className, "__static"));
}
}

protected function _load($className) {
/* class lookup logic */
return $fullname;
}
}

A megoldás lényege, hogy, ha már használunk autoloader-eket, akkor talán ez el is végezhetné a munkát számunkra minimális overhead-del. Valószínűleg meg sem érzi ezt a kis trükköt a rendszer, mégis kényelmesen használható marad anélkül, hogy bele kellene gányolnunk az osztályunk forrásfájljába, itt a megkötés csupán annyi, hogy a static ctor public static function __static() legyen.

A megkötés ellenőrzésében a method_exists segít nekünk, ezzel ellenőrizzük, hogy megvan-e az adott osztályban (vagy más alkalmazáskor objektumban) egy adott metódus. Joggal merülhet fel a kérdés, miért nem is_callable()-t használtam: ennek az az oka, hogy, ha véletlenül nem public módosítóval van ellátva a __static, akkor az is_callable()-t a külső context miatt false-sal tér vissza ahelyett, hogy a PHP dobna egy hátast amiatt, hogy nem public a metódus.

A névre külön megkötési workaround nem is kell, ahogyan a static mivoltra sem.

Hozzászólások

(#1) Athlon64+


Athlon64+
(őstag)

[ értesítő ]

(#2) modder


modder
(senior tag)

Ezekkel az egyedi szépnek tűnő megoldásokkal az a baj, hogy ha átviszed a kódot egy másik kontextusba, ahol nem a saját autoloadered van használva, a statikus inicializáló blokk nem fog lefutni, ellenben a "csúnya" megoldással, ahol mindig lefut.

De ha nem tetszik a csúnya megoldás, akkor keringenek még olyan javaslatok a neten, hogy privát konstruktor és factory metódus használata új objektum létrehozására, ahol a factory metódusban elrejtheted a statikus inicializáló részt.

(#3) Athlon64+ válasza modder (#2) üzenetére


Athlon64+
(őstag)

Azzal nem lenne senki sem kisegítve, akinek ilyenre van szüksége, hiszen static dolgok alapvetően nem öröklődnek (late static binding van PHP-ban, de ájjáj), no persze private ctor mellett meg aztán semmit sem tehetsz öröklődés terén.

(#4) modder válasza Athlon64+ (#3) üzenetére


modder
(senior tag)

"hiszen static dolgok alapvetően nem öröklődnek"
Eddig úgy tudtam, hogy a statikus metódusokat ugyanúgy megörökli a gyerek osztály.

Ha új statikus konstruktort akarsz a származtatott osztálynak, felülírhatod a régi metódust. Esetleg a call_user_func late bindingot használ?

A privát konstruktor lehet protected is, az részletkérdés, attól függ, hogy az osztályt öröklésre tervezed-e vagy sem.

(#5) Athlon64+ válasza modder (#4) üzenetére


Athlon64+
(őstag)

Igen, elérhető minden a gyerekosztályon keresztül is, de minden elég kretén módon történik a self:: és a static:: körül (szerintem csak problémát jelent ez a két kulcsszó ilyen használatban), érdemesebb talán úgy tekinteni a static elemekre, hogy adott osztályhoz tartoznak, az adott osztályon keresztül kell őket meghívni, ha épp metódusról van szó, és el kell felejteni, hogy a gyerekben is elérhetőek.

A call_user_func() kérdés egészen helyén való bizonyos értelemben: ha az aktuális osztályban nincs static function __static, akkor meghívja a szülő (fenti gondolatból következően) megfelelő nevű metódusát, ami probléma lehet, viszont a method_exists() megfogja (nem látja a szülőét!), így nem lesz kétszer vagy töbször meghívva a szülő static ctor-ja.
Late static binding-et nem használ, csak épp ki tudja szolgálni, mint egy pár más megoldás is, hiszen forward-olja a hívó osztályt (nyilván a szülő osztályon keresztülit nem).

Factory method-oknak a valamennyire is zárt konstruktorral megvan az a problémájuk, hogy nehéz tesztelni őket. Azonos autoloader-t használva nem lehet semmi ilyen jellegű problémája a fejlesztőnek. (Azt persze nem szabad elfelejteni, hogy a bevezetett static ctor-ba sem kerülhet olyan logika, ami csak ott létezik, máshogyan elérhetetlen.)

(#6) modder válasza Athlon64+ (#5) üzenetére


modder
(senior tag)

A "late static binding" eléggé erőltetettnek és kerülendőnek tűnik, főleg, hogy alapból úgy működnek a statikus metódusok PHPban, ahogy elvárható tőlük más programnyelvből kiindulva.

Én így látom:
Autoloaderes megoldás
Kényelmes az osztályok létrehozásakor, viszont method_exists() miatt nem fogja a szülő __static()-ját meghívni, ezért körültekintőnek kell lenni származtatásnál. Az osztály újrafelhasználásakor be kell regisztrálni egy külön autoloadert úgy, hogy az kompatibilis legyen a korábban regisztrált autoloaderekkel: azok ne találják meg az osztályt.

Statikus factory metódusok
Hordozható, de oda kell figyelni származtatásnál, hogy a factory metódusokat felülírjuk, ha több is van, ez kényelmetlen, és későbbi fejlesztő elfelejtheti. (igazából ezzel az erővel már a konstruktorban is meg lehetne hívni az statikus inicializáló metódust)

PHP class fájl végén hívott init
Legmegszokottabb megoldás, nem kötelező származtatott osztálynál meghívni vagy felülírni.

Érdekes megoldás, de amennyiben az osztály helyes működése függ a loadertől, maximum keretrendszerekben használnám. A statikus inicializáló blokkra egyébként is ritkán van szükség, kérdéses, megéri-e egy ilyen függőséget tenni az osztályokba.

[ Szerkesztve ]

(#7) Athlon64+ válasza modder (#6) üzenetére


Athlon64+
(őstag)

Semmi baj nincs belőle, hogy a szülő static ctor-ját nem hívja meg, majd meghívódik akkor, amikor a szülőt behúzza a PHP maga alá az adott loader-rel.
Keretrendszerben alkalmazom ezt a cuccot, nyilván egy olyan modulnál, ami külön is életképes, nem igazán lehet használni.

Factory method-októl nekem többnyire feláll a szőr a hátamon, de az még durvább lenne, ha egy instance létrehozásakor még el kellene látnom az osztály inicializálását, nekem ez a kettő nagyon külön áll.

A fájl végén található Init hívásról mindig az jut eszembe, kb. minden gondolkodás nélkül kitörölném a "class-t bezáró kapcsos zárójel plusz új sor" szerkesztési elvemmel. :DDD

Az a baj, hogy PHP-ban nem lehet semmihez sem kötni bizonyos elemek betöltését, C#-ban meg lehet oldani különböző Assembly-kre akasztható attribútumokkal, de itt meg semmi sincs.

További hozzászólások megtekintése...
Copyright © 2000-2019 PROHARDVER Informatikai Kft.