ugrás a tartalomhoz

A PHP munkamenet-kezelés buktatói

inf3rno · 2011. Dec. 30. (P), 19.38

PHP-ben a beépített munkamenet-kezelő átírásáról nem található elegendő információ a kézikönyvben, ezért gondoltam összeszedem az ismereteimet ezzel kapcsolatban, hogy másnak már könnyebben menjen. (Az itt leírtak PHP 5.2.17-re érvényesek.)

Ha meglévő kódhoz akarunk munkamenet-kezelőt írni, és meg akarjuk tartani a $_SESSION szuperglobális használatát, akkor problémák sorozatába ütközünk.

Munkamenetek tárolása

Kezdjük az elején: saját munkamenet-tárolót PHP-ben a session_set_save_handler() segítségével állíthatunk be. Ennek bemutatására csináltam egy kis adapter vázat:

class SessionStoreAdapter
{
    public function __construct() {
        session_set_save_handler(
            array($this, 'openSession'),
            array($this, 'closeSession'),
            array($this, 'readSession'),
            array($this, 'writeSession'),
            array($this, 'destroySession'),
            array($this, 'collectGarbage')
        );
        
        // Oldal generálása után a munkamenet mentése (hiba és kivétel fellépése esetén is lefut)
        register_shutdown_function('session_write_close');
    }
    
    public function openSession($savePath, $cookieName)
    {
        // Munkamenet nyitása
        // Egy session header automatikusan küldésre kerül (bufferrel lehetséges elfogni)
        // Kapcsolat nyitása a tárolást végző rendszerhez kerülhet ebbe a részbe
        
        return true;
    }
    
    public function closeSession()
    {
        // Munkamenet zárása
        // Kapcsolat zárása a tárolást végző rendszerhez, ami ide kerülhet
        
        return true;
    }
    
    public function readSession($id)
    {
        // Adatok kiolvasása az id alapján
        // Sztringet vár vissza a szerializált adatokról
        
        $data = array(
            'testBoolean' => true,
            'testString'  => 'test string',
            'testInteger' => 13
        );
        
        return $this->encode($data);
    }
    
    public function writeSession($id, $encodedData)
    {
        // Adatok tárolása az id alapján
        // Az adatokat szerializálva kapjuk
        
        $data = $this->decode($encodedData);
        
        return true;
    }
    
    public function destroySession($id)
    {
        // Adatok törlése az id alapján
        
        return true;
    }
    
    public function collectGarbage($lifeTime)
    {
        // Szemétgyűjtés
        // Adatok törlése, ha régebben módosultak, mint a lifeTime
        // A lifeTime – mint minden „time” végződésű változó – másodpercben értendő
        
        return true;
    }
    
    public function encode(array $data)
    {
        // Így működik a beépített szerializáló
        
        $encodedData = '';
        
        foreach ($data as $key => $value) {
            $encodedData .= $key . '|' . serialize($value);
        }
        
        return $encodedData;
    }
    
    public function decode($encodedData)
    {
        // Így csomagol ki a beépített szerializáló (a kézikönyvből másolt kód)
        
        $encodedDataSplitterPattern = '/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff^|]*)\|/';
        $entries = preg_split($encodedDataSplitterPattern, $encodedData, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
        
        $data = array();
        
        for ($index = 0, $entriesCount = count($entries); $index < $entriesCount; ++$index) {
            $key = $entries[$index];
            ++$index;
            $value = unserialize($entries[$index]);
            $data[$key] = $value;
        }
        
        return $data;
    }
}

Ez a kérdés elég egyszerűen megy, megkapjuk az adatokat, mentjük őket, aztán meg kiolvassuk, ha a következő oldalon újra lekérik őket. Már itt is látszik, hogy csak limitált eszköztárat kaptunk, mert eleve szerializálva kapjuk meg az adatokat, és ilyen formában is várja őket vissza a munkamenet-kezelő rendszer. Tetszőleges módon csak akkor szerializálhatjuk őket, ha használjuk az adapterben a decode() és encode() metódusokat. (Ezek rekurzióra nincsenek felkészítve.)

Munkamenetek kezelése

Most nézzük meg a beépített munkamenet-kezelő függvényeket, és azt, hogy ezek mit csinálnak.

A session_start() függvény állítja be a válaszban a munkamenethez kapcsolódó fejléceket. Az adapteren az openSession() és a readSession() metódusokat hívja; beolvassa az adatokat a $_SESSION szuperglobálisba. Ha a tartalom kirajzolása után próbáljuk indítani, akkor hibával tér vissza, hiszen nem lehet fejlécet küldeni, ha már a tartalmat írjuk. Egynél többszöri meghívása garantáltan hibaüzenethez vezet.

A session_id() nevű függvénnyel saját munkamenet-azonosítót állíthatunk be az aktuális munkamenetünknek. A beállítással kapcsolatban ami fontos, hogy a session_start() előtt kell megtenni, ellenkező esetben nem veszi figyelembe a munkamenet-kezelő. (Erről hibaüzenetet nem kapunk.)

A session_name() függvény a session_id()-hez hasonlóan működik, a session cookie nevét lehet beállítani vele.

A session_regenerate_id() új munkamenet-azonosítót állít be, újraküldi a fejlécet az új azonosítóval, hogy felülírja a régi fejlécet. Így a kimenetben mindkettő ott lesz egymás után. Ha azt szeretnénk, hogy a régi munkamenetre meghívja az adapteren a destroySession()-t, akkor session_regenerate_id(true) formában kell hívnunk. Ami érdekes, hogy az adapteren semmit nem hív, tehát semmi garancia nincs arra, hogy olyan azonosítót kapunk, ami még nincs bent a rendszerben, tehát kicsi az esélye, de elviekben simán megkaphatjuk akár mások munkamenetét is. (Ezzel a függvénnyel a session fixation támadásokat lehet kivédeni. Ha valaki fixált munkamenettel érkezik, akkor a bejelentkezéskor érdemes megváltoztatni az azonosítót, így a támadók által beállított nem kapja meg az áldozat jogait a bejelentkezéskor.)

A session_commit() és session_write_close() függvények gyakorlatilag azonosak. Mindkettő menti a munkamenetet, és bezárja azt, ennek megfelelően a writeSession() és a closeSession() metódusokat hívják egymás után az adapteren. A PHP fejlesztői szerintem nem voltak a helyzet magaslatán, amikor ezeket a függvényeket alkották, és ez abból is látszik, hogy két eltérő nevű függvény csinálja ugyanazt. Én úgy gondolom, hogy a session_commit()-nak nem lenne szabad bezárnia a munkamenetet, főleg nem úgy, hogy utána az adaptert újra be kelljen állítani újraindításkor. (Ha úgy hívjuk meg őket, hogy nincs nyitott munkamenet, akkor nem kapunk hibaüzenetet.)

A session_destroy() törli az aktuális munkamenetet. Az adapteren a destroySession() és a closeSession() metódusokat hívja. Persze a beállított session cookie-k így is kimennek. Törlés után nem lehet új munkamenetet indítani.

Összességében szerintem a beépített munkamenet.kezelőt nem érdemes használni a benne lévő biztonsági és logikai hibák miatt. Én éppen saját munkamenet-kezelőn dolgozom, ettől függetlenül csináltam adaptert a beépítetthez, hogy például egy eleve arra épülő oldalt könnyebb legyen refaktorálni.

 
1

Jövőre írok egy bejegyzést a

inf3rno · 2011. Dec. 31. (Szo), 07.21
Jövőre írok egy bejegyzést a szerializálásról is, mert elég szorosan összefügg a munkamenet kezeléssel, és abban is van mit tákolni a beépített megoldáson...
2

Hol vannak a problémák? Ha

pp · 2011. Dec. 31. (Szo), 08.58
Hol vannak a problémák?

Ha jól értem egyetlen egy gond az az, hogy serializálva kapod meg az adatokokat.

Tudnál mutatni egy példát, hogy miért nem jó ez?

Azt írod nem található elegendő információ a kézikönyvben, de én nem látom azt, hogy részletesebben foglalkoznál a kérdésekkel, mint az ott bemutatott példa és leírás. Vagy ez valaminek a kezdete?

Összességében szerintem a beépített munkamenet.kezelőt nem érdemes használni a benne lévő biztonsági és logikai hibák miatt.


Pontosan milyen biztonsági problémákról és logikai hibákról van szó?
4

A fő probléma, hogy

inf3rno · 2011. Dec. 31. (Szo), 09.42
A fő probléma, hogy rugalmatlan ez a rendszer, nem gondolták át eléggé, hogy mire lehet szükség:
- nem tudsz két munkamenetet párhuzamosan futtatni
- nem tudod több részletben menteni a munkamenet információit, mert egy mentés után lezárul (erre a commit-nál meg a write_close-nál utaltam)
- ha töröltél egy munkamenetet, nem tudsz a helyére nyitni egy másikat
- nem tudsz az általad megadott módon szerializálni (a következő blog bejegyzésemben, majd leírom, hogy erre mikor van szükség)
- ha elindult a munkamenet, akkor nem tudod beállítani a session id-t, csak a regenerate-el lehet új id-t megadni
- a regenerate meg nem nézi, hogy létezik e már session vagy sem a generált id alatt, tehát két munkamenet simán összeakadhat (ennek megoldását a statisztikára bízták, ami szerintem biztonsági szempontból nem jó megoldás)

Ha gondolod szívesen felvázolom, hogy szerintem milyennek kéne lennie egy session kezelő rendszernek. Hmm azt hiszem, ebből egy rövidke sorozat lesz, aminek következő részében a szerializálásról írok, utána meg arról, hogy ezt a kettőt hogyan lehet összehozni. Igazából erre nincs túl sok időm, meg amit ilyen elven építettem az használ még ezer másik osztályt, uh. ezekre a blogbejegyzésekre még várni kell.
7

egy kliens egy munkamenet

tiku I tikaszvince · 2011. Dec. 31. (Szo), 12.12
vesszek meg ha értem, hogy miért jó két/több munkamenetet futtatni egy kliensnek. Egy kliens-szerver kapcsolat az egy munkamenet.

A kiírás problémája számomra eddig nem volt probléma. Az elképzelés (szerintem) az mögötte, hogy a session_start() hívásától a script futás lehető legutolsó pillanatáig lehessen írni olvasni a $_SESSION-t. Ezt ugye úgy tudjuk elérni, hogy a leállítási folyamatban úgy történik meg a kiírás, hogy te neked azzal már ne kelljen foglalkozni.

Az alap regenerate új, pillanatnyilag nem használt ID-t generál, ennél fogva, nem is akadhat össze egy már érvénytelenített munkamenettel.

Tapasztalatom szerint, aki neki állt az alap session folyamatot újra gondolni, megjavítani, rengeteg problémát csinált magának. És nekem is, mert arra már nem tellett, hogy a session név helyett másikat keressen. Én meg a nulla dokumentáció mellett (szerintem jogosan) azt vártam, hogy minden megy a megszokott mederben. Ehhez képest random helyeken random problémák jöttek elő. Elkezdtek össze akadni munkamenetek. Egyszer ott volt a berakott adat, másszor nem, és sorolhatnám…

Az meg, hogy neked az alap szerializálási mód nem tetszik, lelked rajta. Ha készítesz egy saját munkamenet kezelőt akkor olyan formában írod és olvasod ki az adatokat ahogy jól esik.
9

vesszek meg ha értem, hogy

inf3rno · 2011. Dec. 31. (Szo), 13.34
vesszek meg ha értem, hogy miért jó két/több munkamenetet futtatni egy kliensnek. Egy kliens-szerver kapcsolat az egy munkamenet.

GC-nél néha jó kiszedni az adatokat a munkamenetből. Mondjuk ha van egy webshopod ami levonja a raktárkészletből, hogy mi van a kosárban, akkor a destroy-nál a megsemmisülő kosarakból ki kell szedni a termékeket a kosárból és visszatenni a raktárkészletbe... (Ezt kicsit félreérthetően írtam le, ehhez is több párhuzamos Session példány kell, de nem vezetünk több párhuzamos munkamenetet, csak az adatokat olvassuk ki belőlük.)

Az alap regenerate új, pillanatnyilag nem használt ID-t generál, ennél fogva, nem is akadhat össze egy már érvénytelenített munkamenettel.

Ha az alap regenerate egy szót sem szól a save handler-ben megadott függvényekhez, akkor hogyan garantálja, hogy pillanatnyilag nem használják a munkamenetet? Onnan kéne lekérdeznie neki, hogy létezik e már fájl a session id-hez...

Az meg, hogy neked az alap szerializálási mód nem tetszik, lelked rajta.

Az alap szerializálási móddal nincs különösebb baj, de azért néhány extra hiányzik belőle. Nekem konkrétan az hiányzik, hogy nem lehet a sleep-nek és a wakeup-nak paramétereket átadni. Szívesen átadnék mondjuk egy listát, hogy mit ne szerializáljon (pl adatbázis kapcsolat), visszaállításnál meg egy listát ami alapján a nem szerializált részeket vissza lehet állítani. Mondjuk erre lehet workaround-ot csinálni...
11

A PHP 16 bájtos véletlen

tgr · 2011. Dec. 31. (Szo), 19.54
A PHP 16 bájtos véletlen számokat használ session sütinek, vagyis kb. 10^38 féle értéket vehet fel a süti. Ha végtelen sokáig élnek a sessionjeid, és minden magyar állampolgár minden másodpercben meglátogatja az oldaladat, akkor durván százezer év múlva várható session ütközés. Nem tudom, te hogy vagy vele, én ilyen esélyekkel együtt tudnék élni. Ezzel szemben ha minden requestnél csinálsz egy plusz fájl lekérdezést (ráadásul mindig más névre, tehát még csak cache-elni sem tudja az oprendszer), azt csúcsidőben valószínűleg megérzed.

Egyet kell, hogy értsek tikuval, ha nincs nyomós okod rá, ne piszkálj hozzá a sessionkezeléshez, csak rontani fogsz rajta. (Pl. a te példakódod fatal errornál elveszti a sessiont.)
15

Ezzel szemben ha minden

inf3rno · 2012. Jan. 2. (H), 04.14
A statisztikai részével kapcsolatban nem akarok vitatkozni, valóban minimális az esélye, hogy ilyen történik, de attól még nem nulla. Én nem sajnos biztonság mániás vagyok. :D

Ezzel szemben ha minden requestnél csinálsz egy plusz fájl lekérdezést (ráadásul mindig más névre, tehát még csak cache-elni sem tudja az oprendszer), azt csúcsidőben valószínűleg megérzed.

Nem teljesen világos, hogy miért kéne minden request-re plusz egy fájl lekérdezést csinálni.

Egyet kell, hogy értsek tikuval, ha nincs nyomós okod rá, ne piszkálj hozzá a sessionkezeléshez, csak rontani fogsz rajta. (Pl. a te példakódod fatal errornál elveszti a sessiont.)


Ne haragudj, de nem értem, hogy milyen példakódról beszélsz. Amit kitettem az egy Adapter vázlat. Amit szintén nem értek, hogy miért kellene fatal error-ra felkészítenem bármit is?

Egyébként ha jól tudom, akkor ez teljesen független a session kezeléstől, a register_shutdown_function-ben megadottak hibától függetlenül mindig lefutnak.
41

A statisztikai részével

tgr · 2012. Jan. 4. (Sze), 00.05
A statisztikai részével kapcsolatban nem akarok vitatkozni, valóban minimális az esélye, hogy ilyen történik, de attól még nem nulla.


Minimális eséllyel mindig történhet probléma, például a memóriában a bitek időnként random módon átváltanak a kozmikus háttérsugárzás miatt, és ennek az esélye még crc-vel is sokkal nagyobb (10^-20 fölött van / óra), mint a véletlen session id ütközés (10.000 párhuzamos sessiont és másodpercenként egy új sessiont feltételezve ~ 10^-40). Praktikusan persze ezek soha be nem következő esélyek, miközben a programozói hiba esélye viszonylag nagy (jó minőségű, tesztelt kódban is kb. ezer sorra esik egy hiba), tehát arra érdemes optimalizálni (pl. nem piszkálni feleslegesen a session kezelő kódot).

Nem teljesen világos, hogy miért kéne minden request-re plusz egy fájl lekérdezést csinálni.


Hát ha minden requestnél generálsz egy session id-t, és meg kell nézned, hogy létezik-e már... (jó, minden session süti nélküli requestnél, de tipikusan a requestek nagy része olyan.)

Amit szintén nem értek, hogy miért kellene fatal error-ra felkészítenem bármit is?


Mert valószínűleg nem szeretnéd, ha a sessionbe írt adatok fatal errornál nem mentődnének.

Egyébként ha jól tudom, akkor ez teljesen független a session kezeléstől, a register_shutdown_function-ben megadottak hibától függetlenül mindig lefutnak.


PHP verziótól függően esetleg a garbage collection után, vagyis mire mentenéd a sessiont, már nincs meg az adapter objected.
43

Minimális eséllyel mindig

inf3rno · 2012. Jan. 4. (Sze), 09.16
Minimális eséllyel mindig történhet probléma, például a memóriában a bitek időnként random módon átváltanak a kozmikus háttérsugárzás miatt


A franc, akkor jobb, ha nem is csinálok semmit, mert a kozmikus háttérsugárzás úgyis keresztbe tesz :D Oké, akkor ezt így elfogadom. :-)

Hát ha minden requestnél generálsz egy session id-t, és meg kell nézned, hogy létezik-e már... (jó, minden session süti nélküli requestnél, de tipikusan a requestek nagy része olyan.)

Szerintem ez teljes mértékben oldal függő... Mondjuk ha egy webshopról van szó, akkor már az elején adni kell egy session-t, hogy lehessen mibe betenni a kosarat...

Mert valószínűleg nem szeretnéd, ha a sessionbe írt adatok fatal errornál nem mentődnének.

Nem tudom te hogy vagy vele, de én tesztelni szoktam a kódomat, és azért egy fatal error elég durván ki szokott jönni ezeken a teszteken...
45

Fatál?

H.Z. v2 · 2012. Jan. 4. (Sze), 09.33
Az nem fatal error, ha elfogy a memória a szerveren, megtelik a temporális fájlokat tartalmazó diszk, megszakad az adatbázis kapcsolat stb.?
46

De, gyanús :-) De továbbra

inf3rno · 2012. Jan. 4. (Sze), 09.40
De, gyanús :-) De továbbra sem értem... :-)

A session-t cookie alapján azonosítja, ha a böngészőben megmarad a cookie, akkor miért veszne el a munkamenet?

A hagyományos session kezelő ugyanúgy nincs felkészítve ilyen problémákra, és fatal error esetén ugyanúgy azért menti csak el a munkamenet változásait, mert a register shutdown function-ben megadtad a session write close-t... Ha meg elfogy a hely a vinyón, akkor meg ugyanúgy meghal az is... De kíváncsi vagyok továbbra is, hogy miben jobb az a megoldás, esetleg kihagytam valamit?
49

Passz...

H.Z. v2 · 2012. Jan. 4. (Sze), 10.13
Sajnos mostanra leépültem agyilag annyira, hogy már ne tudjam követni a társalgást, csak eszembe jutott a fatal errorról, hogy pl. megtelik a temp, annak lehetnek vonzatai a session kezelés környékén is.
Szóval pusztán arra akartam reagálni, hogy szerinted nem kell foglalkozni a fatal errorokkal, amit nem feltétlenül tartok jó ötletnek. ;)
54

Szóval pusztán arra akartam

inf3rno · 2012. Jan. 4. (Sze), 14.07
Szóval pusztán arra akartam reagálni, hogy szerinted nem kell foglalkozni a fatal errorokkal, amit nem feltétlenül tartok jó ötletnek. ;)

Azt hiszem ezt hívják az érveléstechnikában a vita manipulálásának. :D Én olyat nem mondtam, hogy nem kell foglalkozni a fatal error-ral, természetesen loggolni kell, már ha erre van lehetőség. Többet úgysem tehetsz... A fatal error-ból nem tudsz ErrorException-t csinálni, szóval max annyit tehetsz, hogy display errors off. Ennél többet szerintem senki nem tud csinálni vele... Elmondom harmadszor is, amit a register shutdown function függvénnyel beállítasz, az fatal error esetén is lefut, tehát ha van lehetőség a session mentésére, akkor teljesen mindegy, hogy milyen a session kezelőd, ha beállítod a register shutdown function-nel, hogy fusson le a session mentése, és erre lehetőség van, akkor az lefut.
50

Mi futottunk már ilyen

Hidvégi Gábor · 2012. Jan. 4. (Sze), 11.03
Mi futottunk már ilyen hibába, nem dob hibát, hanem egyszerűen nem menti el a session tartalmát fájlba. Elég sokat szenvedtünk, mire rájöttünk, mitől nem működik a rendszer...
51

Nekem szerencsém volt... :)

H.Z. v2 · 2012. Jan. 4. (Sze), 11.24
Én üzemeltetési oldalról kezeltem az ilyen dolgokat és a felügyelő program mindig riasztott, ha megtelt valamelyik diszk. ;)
52

Nálunk igazából a memória

Hidvégi Gábor · 2012. Jan. 4. (Sze), 12.04
Nálunk igazából a memória telt meg, emiatt nem tudta szerializálni és menteni az adatokat.
55

Az vicces :D Mondjuk ez a

inf3rno · 2012. Jan. 4. (Sze), 14.08
Az vicces :D
Mondjuk ez a része szerintem már abszolút üzemeltetési kérdés, és nem kódolási...
57

Mondjuk úgy, itt kezdődnek a

H.Z. v2 · 2012. Jan. 4. (Sze), 14.14
Mondjuk úgy, itt kezdődnek a komoly problémák... (nem, ez többnyire nem üzemeltetési kérdés, mert általában valamilyen programhiba miatt fogy el a memória - tapasztalataim szerint lényegesen ritkább az alulméretezett rendeszer)
61

Ez szerintem is programozói

Hidvégi Gábor · 2012. Jan. 4. (Sze), 14.26
Ez szerintem is programozói hiba, miután átgondoltuk az esetet, kiderült, eléggé pazarlóan bántunk az erőforrásokkal. De a lényeg a lényeg: a php-tól semmilyen hibaüzenetet nem kaptunk, hogy az adatok írása sikertelen volt.
62

Jah előfordul... Mondjuk

inf3rno · 2012. Jan. 4. (Sze), 17.19
Jah előfordul... Mondjuk akkor lehet, hogy kimaradt az optimalizálási fázis, vagy nem volt elég alapos... Hát igen, ha sürget a határidő... :D
71

A shutdown handler elején meg

tgr · 2012. Jan. 4. (Sze), 22.13
A shutdown handler elején meg kell nézni, hogy "Allowed memory size of"-fal kezdődik-e a hibaüzenet, és ha igen, ini_settel megnövelni 1-2 megabájttal. Mindent, ami memóriaigényes lehet, csak ezután kell meghívni.
72

Köszi, utána fogok járni.

Hidvégi Gábor · 2012. Jan. 4. (Sze), 22.21
Köszi, utána fogok járni. Egyébként a beépített munkamenetkezelőt használjuk.
73

Mondjuk ha egy webshopról van

tgr · 2012. Jan. 4. (Sze), 22.31
Mondjuk ha egy webshopról van szó, akkor már az elején adni kell egy session-t, hogy lehessen mibe betenni a kosarat...


A látogatók nagy része a kezdőlapról visszafordul, úgyhogy a látogatások nagy része session süti nélkül kezdődik akkor is, ha mindig adsz session sütit, amikor még nincsen.

Nem tudom te hogy vagy vele, de én tesztelni szoktam a kódomat, és azért egy fatal error elég durván ki szokott jönni ezeken a teszteken...


Egy részük ki szokott jönni, más részük meg benn szokott maradni, pl. mert ritkán használt kód-útvonalon van a hiba, vagy változók aktuális értékétől függ.

A session-t cookie alapján azonosítja, ha a böngészőben megmarad a cookie, akkor miért veszne el a munkamenet?


Maga a munkamenet nem veszik el, de amit az adott requestben módosítottál rajta, az igen. Ami elég nagy probléma lehet, ha olyan adatokat tárolsz a sessionben, amiket adatbázisban illene (a DB módosításaid elmentődnek, a session módosításaid nem, és inkonzisztens adataid lesznek), de anélkül is okozhat kellemetlenséget (pl. duplaposzt kivédésre raksz a sessionbe poszt azonosítót, user végez valami adatbázisműveletet, mentés után fatal errora fut -> white screen of death-t kap -> frissít -> duplán került az adatbázisba a művelet).

A hagyományos session kezelő ugyanúgy nincs felkészítve ilyen problémákra, és fatal error esetén ugyanúgy azért menti csak el a munkamenet változásait, mert a register shutdown function-ben megadtad a session write close-t...


A session_write_close-t a PHP alapból regisztrálja valami shutdown-handler-szerűségként, de nem ez a lényeg, hanem hogy a default session kezelő, vagy akár egy kézzel írt, de nem objektumorientált vidáman lefut ilyenkor, a te sessionkezelő objektumodat viszont eddigre már megette a garbage collector.

Egyébként vannak olyan - nem is különösebben súlyos - hibák, amiktől a PHP elszáll fatal errorral, és nem hívja meg a shutdown functiont (pl. ha egy null objektumnak próbálod meghívni egy függvényét), úgyhogy még csak arra sem lehet alapozni, hogy a shutdown handler mindig meghívódik.
74

a te sessionkezelő

inf3rno · 2012. Jan. 5. (Cs), 06.44
a te sessionkezelő objektumodat viszont eddigre már megette a garbage collector.

Fura, ezt én nem tapasztaltam. Nekem az a benyomásom a shutdown function-nel kapcsolatban, hogy a szemétgyűjtés előtt fut le, tehát még minden objektum érintetlen ilyenkor... (Fatal error-ral csak az adaptert próbáltam ki, megnézem, hogy a többi objektum megmarad e vagy sem, de az a gyanúm, hogy igen ...)

Kieg: Ironikus módon igazad lett, tényleg nem megy fatal error esetén a mentés, viszont nem a GC, hanem egy 2002 óta nem javított bug miatt: https://bugs.php.net/bug.php?id=19022
Érdekes hozzáállás, hogy 10 éve nincs feedback egy létező bughoz...

Az alapján, amiket olvasok nekem úgy tűnik, hogy fatal error esetén nem működik az adapteres megoldás, mert valamiért nem veszi figyelembe ilyenkor a php a session_set_save_handler-ben beállított dolgokat. Azt hiszem csinálok erről egy bug reportot... Még azért tesztelem előtte az újabb verziókkal is. (Tisztán adapter nélkül még nem próbáltam...) Egyébként azt írják, hogy auto_append_file-al megy, de azt még nem teszteltem...
75

Verziófüggő, én PHP 5.3-nál

tgr · 2012. Jan. 5. (Cs), 08.26
Verziófüggő, én PHP 5.3-nál tapasztaltam, hogy minden fatal error logbejegyzéshez társul egy további hibalog, hogy nem létező objektumra hivatkozott a sessionkezelő kód. Egyébként a session_set_save_handler php.net-es manualja is felhívja erre a problémára a figyelmet.

Érdekes hozzáállás, hogy 10 éve nincs feedback egy létező bughoz...


A PHP bugtrackert minden valószínűség szerint egy csapat szociopata tervezte, akik azon versenyeztek, ki tud jobban kib***ni a PHP közösséggel. Az egyik zseniális húzásuk az volt, hogy a bugs.php.net-re nem lehet regisztrálni, hanem egy jelszót kell megadnod a hiba jelentésekor, és csak annak birtokában lehet módosítani. Így aztán ha egy hibát no feedbackre tesznek, és az eredeti bejelentő nem aktív (vagy csak elfelejtette a jelszavát), az már örökre úgy marad. Láttam már egészen durva biztonsági hibától kezdve mindent, ami erre a sorsra jutott.
76

Fantörpikus :D Az igazi

inf3rno · 2012. Jan. 5. (Cs), 09.01
Fantörpikus :D Az igazi gyönyör az lenne, ha rátolnának egy duplicated-et egy report-ra, mert már van egy no feedback-es ugyanolyan téma... :D
77

Milyen verzió?

H.Z. v2 · 2012. Jan. 5. (Cs), 09.38
2002 óta nem javított bug miatt

Valamit nem értek: az általad linkelt bug még 1.3-as Apache + 4.2.2-es PHP alatt lett jelentve. Ugyanez Apache2.x és mondjuk 5.2-es v. 5.3-as PHP alatt is megvan? Vagy a PHP4-es sorozata még mindig támogatott lenne?
78

5.2.17 alatt volt, de nem

inf3rno · 2012. Jan. 5. (Cs), 09.47
5.2.17 alatt volt, de nem tudom biztosan, hogy teljesen ugyanazt jelentették e, én csak felületesen olvastam el. A hiba amiatt keletkezik, mert a session save path-ben megadott útvonal nincs bent az include path-ben, és ezért nem írható. Viszont az adapteres session kezelőmnek külön adtam meg egy másik útvonalat, ahova tud menteni, tehát a session save path-ben megadott útvonalat nem használja fel... Ebből nyilvánvaló, hogy a session kezelés visszaállt a fatal error hatására az adapteresről a beépített változatra. Ez szerintem nem kicsit gáz...
79

Na teszteltem, kiderült, hogy

inf3rno · 2012. Jan. 5. (Cs), 11.37
Na teszteltem, kiderült, hogy előbb volt egy hiba a kódban, minthogy beállítottam volna az adaptert, amiatt használta a beépített megoldást...

Amúgy teljesen jól működik az adapteres változat is:

ini_set('display_errors', true);

class AnotherClass {

    public function __destruct() {
        echo 'another destruct<br>';
    }

}

class SessionStoreAdapter {

    protected $another;

    public function __construct() {
        $this->another = new AnotherClass();
        session_set_save_handler(
                array($this, 'openSession'), array($this, 'closeSession'), array($this, 'readSession'), array($this, 'writeSession'), array($this, 'destroySession'), array($this, 'collectGarbage')
        );
        register_shutdown_function('session_write_close');
    }

    public function openSession($savePath, $cookieName) {
        echo 'open' . (isset($this->another) ? '1' : '0') . '<br>';
        return true;
    }

    public function closeSession() {
        echo 'close' . (isset($this->another) ? '1' : '0') . '<br>';
        return true;
    }

    public function readSession($id) {
        echo 'read' . (isset($this->another) ? '1' : '0') . '<br>';
        $data = array(
            'testBoolean' => true,
            'testString' => 'test string',
            'testInteger' => 13
        );

        return $this->encode($data);
    }

    public function writeSession($id, $encodedData) {
        echo 'write' . (isset($this->another) ? '1' : '0') . '<br>';
        $data = $this->decode($encodedData);

        return true;
    }

    public function destroySession($id) {
        echo 'destroy' . (isset($this->another) ? '1' : '0') . '<br>';
        return true;
    }

    public function collectGarbage($lifeTime) {
        echo 'gc' . (isset($this->another) ? '1' : '0') . '<br>';
        return true;
    }

    public function encode(array $data) {
        $encodedData = '';
        foreach ($data as $key => $value) {
            $encodedData .= $key . '|' . serialize($value);
        }
        return $encodedData;
    }

    public function decode($encodedData) {
        $encodedDataSplitterPattern = '/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff^|]*)\|/';
        $entries = preg_split($encodedDataSplitterPattern, $encodedData, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
        $data = array();
        for ($index = 0, $entriesCount = count($entries); $index < $entriesCount; ++$index) {
            $key = $entries[$index];
            ++$index;
            $value = unserialize($entries[$index]);
            $data[$key] = $value;
        }
        return $data;
    }

    public function __destruct() {
        echo 'destruct' . (isset($this->another) ? '1' : '0') . '<br>';
    }

}

new SessionStoreAdapter();
session_start();
throw new Exception('fatal error');
output:

open1
read1

Fatal error: Uncaught exception 'Exception' with message 'fatal error' in D:\creation\software developer\projects\test project\document root\index.php:92 Stack trace: #0 {main} thrown in D:\creation\software developer\projects\test project\document root\index.php on line 92
write1
close1
Ami furcsa, hogy a destruktorok nem hívódnak meg fatal error esetén...
80

A shutdown function működik a

inf3rno · 2012. Jan. 5. (Cs), 11.43
A shutdown function működik a sessiontől függetlenül is fatal error esetén, tehát nem igaz, hogy fatal error-nál nem tudod menteni saját session kezelővel a változásokat... A shutdown mindig a GC (és a destruktorok) előtt fut le.

<?php

ini_set('display_errors', true);

class AClass {

    public function __destruct() {
        echo 'destruct<br>';
    }

}

class AnotherClass {

    protected $a;

    public function __construct() {
        $this->a = new AClass();
        echo 'construct' . (isset($this->a) ? '1' : '0') . '<br>';
    }

    public function shutDown() {
        echo 'shutDown' . (isset($this->a) ? '1' : '0') . '<br>';
    }

    public function __destruct() {
        echo 'another destruct' . (isset($this->a) ? '1' : '0') . '<br>';
    }

}

register_shutdown_function(array(new AnotherClass(), 'shutDown'));

throw new Exception('fatal error');
output:

construct1

Fatal error: Uncaught exception 'Exception' with message 'fatal error' in D:\creation\software developer\projects\test project\document root\index.php:34 Stack trace: #0 {main} thrown in D:\creation\software developer\projects\test project\document root\index.php on line 34
shutDown1
12

Amit korábban írtál, abból az

tiku I tikaszvince · 2011. Dec. 31. (Szo), 20.09
Amit korábban írtál, abból az olvastam ki (és úgy nézem, hogy nem vagyok ezzel egyedül), hogy két session példányt akarsz egyszerre futtatni.

A boltos példádban említett két problémádra volna itt egy-egy megoldási javallat:

Vegyünk azt a nem túl bonyolult esetet, amikor a lehető legegyszerűbb módszerrel adatbázisban tárolod a munkamenet adatokat (session <id[char32]>, value<blob>, updated<datetime>). Csak a read és write handlereket kell megírnod, plusz egy takarító metódust, ami időről időre törli a session táblából a lejárt adatokat. Ehhez ugye elengedhetetlen, hogy minden alkalommal amikor egy session-t indítasz, akkor az utolsó módosítás dátumát frissítsd.

A problémádat úgy szoktuk hívni, hogy elhagyott kosár. Képzeld magad egy kiskereskedő helyébe, aki zárás után körbe járja az üzletét és hátul a sarokban talál egy pár teli pakolt kosarat. Szerinted mit fog kezdeni? Melyik módszer a hatékonyabb?
  • Visszarendeled az összes vásárlót és felsoroltatod velük, hogy ki melyik terméket tette a kosarába és amikor megvannak a bűnösök egyenként visszapakoltatod velük a termékeket?
  • Vagy te teszed vissza?

Ha ez a raktárkészlet csökkentés problémát jelent, akkor
  • a vásárlás végén, amikor már tuti, hogy elviszik azt a darabot, csökkentsd a raktárkészletet
  • Ne Session változóként tárold a kosár tartalmát, hanem pl egy kapcsoló táblában (session id, termék id, mennyiség) tárold a fogott darabokat. Így, ha a kapcsolat foreign key segítségével rendesen ki van építve, akkor egy session rekord törlésekor automatikusan törlődik a foglalás. Az aktuális raktárkészletből meg egy SUM SELECT-el egyszerűen ki tudod vonni a fogott darabokat
.

Ha meg a regeneratt-et is már felülírod, akkor generáltathatsz addig amíg nem állít elő egy olyat, ami még nincs az adatbázisban.

Lehet én értek valamit félre, de a __sleep megírásával nem épp azt mondod meg, hogy "ha ennek az osztálynak egy pédlányát valaki beküldené a serialize-ba, akkor csak ezt és ezt az objektum tulajdonságomat írd ki!"? A __wakeup-ban meg megírhatod a nem kiírt tagváltozók feltöltését.
16

Amit korábban írtál, abból az

inf3rno · 2012. Jan. 2. (H), 04.23
Amit korábban írtál, abból az olvastam ki (és úgy nézem, hogy nem vagyok ezzel egyedül), hogy két session példányt akarsz egyszerre futtatni.

=>
Ezt kicsit félreérthetően írtam le, ehhez is több párhuzamos Session példány kell, de nem vezetünk több párhuzamos munkamenetet, csak az adatokat olvassuk ki belőlük.


Úgy érzem nem érted a problémát a webshopos példánál. A munkamenet mondjuk fájlrendszerben vagy memóriában van, hogy ne nyírja ki az sql szervert. Az sql szerveren meg az adatbázisban van bent, hogy melyik termék van lefoglalva. Amikor kosárba kerül egy termék, akkor lefoglaljuk. Ezután kikerülhet úgy is a kosárból a termék, hogy a felhasználó törli meg úgy is kikerülhet, hogy a munkamenet semmisül meg, és maga a kosár törlődik. Ez utóbbit a GC csinálja. Na mármost valahogy össze kellene kötni a munkamenet törlését és az adatbázisban a termékek újra aktiválását. Kíváncsi vagyok te hogyan oldanád meg ezt?

Az én logikám szerint, amikor a vásárlók leadják a kosarukat, akkor megnézem, hogy maradt e benne valami, és ha igen, akkor mindent visszapakolok belőle a polcokra. Nálam meg a vásárlók nem hagyják el a kosarukat. ;-)

Lehet én értek valamit félre, de a __sleep megírásával nem épp azt mondod meg, hogy "ha ennek az osztálynak egy pédlányát valaki beküldené a serialize-ba, akkor csak ezt és ezt az objektum tulajdonságomat írd ki!"? A __wakeup-ban meg megírhatod a nem kiírt tagváltozók feltöltését.

Ez így van, de ez a rendszer rugalmatlan, mert nem lehet paraméterezni. A feltöltés része csak global-ból vagy singleton-ból mehet, mert csak onnan tudja kiolvasni a __wakeup, hogy mivel töltse fel őket. (A másik lehetőség, hogy a wakeup-ban példányosítasz mindent, ez viszont nem szerencsés az erőforrásoknál.) A __sleep-pel szintén az a baj, hogy nem automatizálható, ami meg könnyen kódismétlődéshez vezet. Sajnos ezekre csak erőforrás igényes megoldás van. :S
18

utolsó pillanatban

tiku I tikaszvince · 2012. Jan. 2. (H), 08.32
A fő baj az általad vázolt problémával, hogy A vásárló munkamenetében B, C és D felhasználó munkamenet adatai között fogsz turkálni. És ez változatos biztonsági kérdéseket vet fel.

Fentebb már leírtam a legegyszerűbb megoldást: a készletet csak akkor csökkentsd, amikor már tuti, hogy eladtad a darabot.

Ha a foglalásokat (már kosárba tett, de még nem megrendelt) is akarod rögzíteni, akkor azt továbbra se session változóba tedd, hanem adatbázisba. Minden kosár hivatkozzon egy session ID-ra. Munkamenet változóban a kosárral kapcsolatos minden adat maradhat, de a betett termékek azonosítója és a fogott darabszám egy központi ("nyilvános") helyre kerüljön. Az adatbázisban tárolt adatoknak természetesen egy utolsó aktivitás mező is kell, hogy a lejárt rekordokat automatikusan törölhesd. Ha az adatbázis terhelést csökkenteni szeretnéd, akkor legyen a munkamenetben egy flag, hogy van-e a kosárban termék. Ha nincs, akkor nem kell frissíteni a DB-ben tárolt adatokat.

<off>
Úgy érzem, az alap probléma megoldásához alapvetően rossz utat választottál. Olyan mintha a feladatod egy szeg beverése lenne, az eszközöd meg egy turmix gép. Amikor az első pár ütés után nem megy, neki állsz, hogy a kezedben levő eszközt alkalmassá tedd szeg beverésére. Pedig neked egy kalapácsra van szükséged. Esetleg egy szegbelövőre.
</off>
19

A fő baj az általad vázolt

inf3rno · 2012. Jan. 2. (H), 10.39
A fő baj az általad vázolt problémával, hogy A vásárló munkamenetében B, C és D felhasználó munkamenet adatai között fogsz turkálni. És ez változatos biztonsági kérdéseket vet fel.

Konkrétan a CRON/collectGarbage munkamenetében fogok B, C és D vásárló munkamenet adatai között turkálni. Ez milyen biztonsági kérdéseket vet fel?

Fentebb már leírtam a legegyszerűbb megoldást: a készletet csak akkor csökkentsd, amikor már tuti, hogy eladtad a darabot.

Ez egy konkrét webshopban egy megrendelői igény, amit meg kellett valósítanom...

Ha a foglalásokat (már kosárba tett, de még nem megrendelt) is akarod rögzíteni, akkor azt továbbra se session változóba tedd, hanem adatbázisba. Minden kosár hivatkozzon egy session ID-ra. Munkamenet változóban a kosárral kapcsolatos minden adat maradhat, de a betett termékek azonosítója és a fogott darabszám egy központi ("nyilvános") helyre kerüljön. Az adatbázisban tárolt adatoknak természetesen egy utolsó aktivitás mező is kell, hogy a lejárt rekordokat automatikusan törölhesd. Ha az adatbázis terhelést csökkenteni szeretnéd, akkor legyen a munkamenetben egy flag, hogy van-e a kosárban termék. Ha nincs, akkor nem kell frissíteni a DB-ben tárolt adatokat.

Értem, tehát azt javaslod, hogy minden egyes oldallekérésnél küldjem el az adatbázisnak, a frissítés időpontját, hogy ne törölje x idő után a termékeket a kosárból. Hát kösz, de nincs kedvem agyonterhelni az sql szervert...
21

Ez egy konkrét webshopban egy

H.Z. v2 · 2012. Jan. 2. (H), 10.52
Ez egy konkrét webshopban egy megrendelői igény, amit meg kellett valósítanom...

Bocs a kissé drasztikus fogalmazásért, de ha a megrendelő hülyeséget kér, esetleg meg lehet győzni róla, hogy nem úgy kellene megoldani, ahogy ő képzelte.
Anélkül, hogy a részletekrő bármit tudnék: ha félbeszakad egy vásárlás, te meg már lekönyvelted, hogy csökkent a készlet, abból elég csúf dolgok jöhetnek ki...
22

A kérés nem feltétlenül

Hidvégi Gábor · 2012. Jan. 2. (H), 10.59
A kérés nem feltétlenül hülyeség, ha belegondolsz, ha valaki kis raktárkészlettel dolgozik. A megvalósítás persze nem egyszerű.
24

Itt konkrétan egyedi termékek

inf3rno · 2012. Jan. 2. (H), 11.09
Itt konkrétan egyedi termékek vannak... Lehet, hogy speciálisak az igények, de nem tartom őket hülyeségnek...
28

Nem a konkrét kérésre írtam,

H.Z. v2 · 2012. Jan. 2. (H), 11.20
Nem a konkrét kérésre írtam, hogy hülyeség, mivel nem ismerem a körülményeket - bocs, ha félreérthető voltam. Csak kissé khm... drasztikusabb formában fogalmaztam meg, hogy ha a megrendelő olyat kér, ami a fejlesztő szerint nem jó, akkor meg lehet próbálni elmagyarázni neki, hogy ez így nem nyerő.
----
Egyébként ha valaki kis raktárkészlettel dolgozik, akkor sem fogom neki lekönyvelni a készletcsökkentést azért, mert valaki online a kosarába tett egy terméket.
A termék mellett bejegyzem, hogy kosárban van, de ez a kosár az a session megszűnésekor törlődik és így automatikusan visszakerül készletre a meg nem vett darab is.
26

Anélkül, hogy a részletekrő

inf3rno · 2012. Jan. 2. (H), 11.16
Anélkül, hogy a részletekrő bármit tudnék: ha félbeszakad egy vásárlás, te meg már lekönyvelted, hogy csökkent a készlet, abból elég csúf dolgok jöhetnek ki...

Egy terméknek több állapota van, pl: "szabad", "kosárban van", "elkelt", stb... A session destroy-ra (ill. GC-re) meg pont ezért kell egy eseménykezelő, ami visszaállítja "szabad"-ra a kosárban lévő termékek állapotát. Nem hiszem, hogy ebből bármi csúf dolog jöhetne ki, működni működik, ennyi. Ha esetleg az SQL nem fut le, akkor meg a GC elszáll exception-nel és nem törli a session fájlt, tehát a következő GC hívás javítja a hibát... Nyilván oda kell figyelni, hogy szinkronban legyenek a session fájlok és az adatbázis, de ez nem egy annyira bonyolult probléma...
30

Amire reagáltam, abban nem

H.Z. v2 · 2012. Jan. 2. (H), 11.24
Amire reagáltam, abban nem emlékszem, hogy szó lett volna arról, hogy mindenből csak egy darab van. Ettől függetlenül, amit írsz, az épp azt csinálja, amit mondok: nem könyveled el, hogy elvitték, csak bejelölöd, hogy ez már foglalt.

ui: hozzászólás után csak nekem ugrik rossz helyre a böngésző? Eddig az új hozzászólásra ugrott, most meg mintha véletlenszerűen kiválasztott helyre dobna vissza.
31

Abban igazad van, hogy a

inf3rno · 2012. Jan. 2. (H), 11.36
Abban igazad van, hogy a készlet fogyást csak a rendelés végén kell lekönyvelni. Azt hiszem egy kis kommunikációs zavar volt köztünk, legközelebb igyekszem pontosabban fogalmazni... Szóval nem a készlet fogyást könyvelem le, hanem azt, hogy változott a termék állapota. Nyilván a flag-ezés is ugyanezt csinálja. Már nehezen követem, hogy ki mit írt, mert olyan mélységben van a kérdés-válasz... :D Kezdek megijedni, hogy vitatkozással fogom tölteni az Újévet :D
32

Valami hasonló gondolataim

H.Z. v2 · 2012. Jan. 2. (H), 11.41
Valami hasonló gondolataim támadtak nekem is... már ami a vita és a legújabb év viszonyát illeti. :)
(úgy egyébként meg egyről beszélünk, csak rosszkor szóltam közbe ;) )
47

a probléma és az ő gyökere

tiku I tikaszvince · 2012. Jan. 4. (Sze), 10.06
Hát kösz, de nincs kedvem agyonterhelni az sql szervert...

Ezt DB agyonterhelési történetet azért jó lenne valami kísérlettel, meg mérésekkel ellenőrizni. De ha már kipróbáltad, akkor elhiszem neked.

Végig gondoltam a felvetett problémát, és a következőre jutottam:

Az én szememben a munkamenetben tárolt adatok kizárólag két szereplőre tartoznak, a kliensre és az aktuális PHP alkalmazás példányra. Az egész session pedig arra való, hogy az azonos kliens által, egymás után futtatott alkalmazás példányok között megosszak adatot, és nem arra, hogy A kliens információhoz jusson B, C és N felhasználó adataiból, még ha A kliens egy Cron futtatta takarító script is.

Ha egy kliens adatot másik kliens számára is láthatóvá szeretnék tenni, akkor azt nem teszem munkamenet változók közé, hanem egy nyilvános helyen fogom tárolni, és a session változókból hivatkozok erre a közös forrásra. A közös tároló hely pedig lehet DB, lehet valamilyen NoSQL megoldás, vagy bármi más.

Ha jól értelek, akkor számodra a munkamenet és a benne tárolt adatok nem számítanak ennyire tabunak.
53

Ha jól értelek, akkor

inf3rno · 2012. Jan. 4. (Sze), 14.01
Ha jól értelek, akkor számodra a munkamenet és a benne tárolt adatok nem számítanak ennyire tabunak.

Jól értesz. Nálam ugyanolyan adatok, mint az adatbázisban tároltak, vagy bármi más. Pl. az adatbázisban ugyanúgy érzékeny információk vannak, mint a munkamenetekben, és ugyanúgy az összes script tud bele írni ... Biztos van valami logikus magyarázat arra, hogy nálad ez miért ekkora tabu, lehet, hogy valahol már előjött probléma ezzel a megközelítéssel kapcsolatban, vagy így oktatták, passz. Én jelenleg nem tudok elképzelni olyan helyzetet, ahol ez különösebb veszélyforrás lehetne az általad felvázolt tárolási módhoz képest, de ha tudsz ilyet mondani, akkor elfogadom, hogy igazad van.
69

Általában jó ötlet, ha az

tgr · 2012. Jan. 4. (Sze), 22.01
Általában jó ötlet, ha az adatbázis önmagában konzisztens, és tartalmaz minden fontos adatot, sokkal egyszerűbb pl. egy költözést lebonyolítani, mint ha két teljesen különböző technológia között vannak megosztva az adataid. Plusz naplózás, hozzáférési jogok stb. szempontjából az adatbázis sokkal jobban menedzselhető, tehát védettebb a rendszer, ha komolyan manipulálni csak az adatbázis meghekkelésével lehet, mintha sima fájlműveleteknél is (bár ennek gyakorlati jelentősége csak nagy rendszereknél van).
82

Ahm, hát ha nem tisztán

inf3rno · 2012. Jan. 5. (Cs), 12.17
Ahm, hát ha nem tisztán adatbázist használunk, akkor ezek szerint érdemes betenni egy javító kódot, ami bizonyos időközönként lefut és szinkronban tartja a kettőt. Mondjuk ha a webshopos példánál maradunk, akkor bekerülhet a termék foglalásának dátuma, a javító kód meg visszaállítaná az 1 napja foglalt, de nem megrendelt termékeket elvihető állapotba. Szóval rá lehet tákolni az inkonzisztenciára (hú de szépen mondtam), bár ilyen szempontból tényleg jobb, ha tisztán adatbázis minden...

Itt igazából az a gond, hogy a session változás és az adatbázis változás mentése nincs egy tranzakcióban, így ha a session és adatbázis mentések között lép fel hiba, akkor inkonzisztens lesz a két adattároló... Ezen valamennyit segítene ha rögtön adatbázis mentés után mentenénk a session változásait is... (Ezért írtam korábban, hogy jobb lenne, ha a session_commit csak mentene, de nem zárná le a sessiont, ilyen esetben pl használható lenne...)
3

Alias

T.G · 2011. Dec. 31. (Szo), 09.10
Ha egy függvény egy másik álneve, akkor azokat miért kell összehasonlítani? :) Persze, hogy a commit ugyanazt csinálja, mint a write_close, merthogy a két függvény ugyanaz. Valószínűsítem a 4.4-nél gondolták azt a fejlesztők, hogy a commit szó jobban illik arra a folyamatra, amit a write_close csinál.
5

Nem összehasonlítottam,

inf3rno · 2011. Dec. 31. (Szo), 09.56
Nem összehasonlítottam, egyrészt szerintem ez az álnevezés marhaság, ha egy függvény egy dolgot csinál, akkor legyen egy neve. Ha több néven van ugyanaz, akkor az zavart okoz. A másik meg hogy az elnevezéseiket hasonlítottam össze, nem azt, amit csinálnak. A write_close-nak benne van a nevében, hogy lezárja a session-t, a commit-nak viszont nincs, ezért az lenne a logikus, hogy a commit csak menti a változásokat, de nem zárja le a munkamenetet. Gondolom a commit azért volt szimpi szó, mert a tranzakciókat szokták committálni. A session viszont nem a változásokat tárolja, hanem az összes adatot, ezért nem túl logikus egy commit után lezárni. Ha már itt tartunk, akkor a save és a load lennének a megfelelő szavak... (Sajnos ezt az elnevezést én is átvettem.)
6

Alias

Pepita · 2011. Dec. 31. (Szo), 11.49
egyrészt szerintem ez az álnevezés marhaság
Csak szerinted. Van olyan, hogy valaminek jobb neve lesz egy új név, ilyenkor a régebbi verzióval való kompatibilitás miatt kell az álnév. Egyébiránt csak "nekünk magyaroknak" érthetőbb a write_close, valójában a commit végleges befejezése valaminek, tehát szintén "benne van" a lezárás. Szerintem azért volt szimpi, mert csak egy szó, és szerintük jól kifejezi amit csinál. A save, load inkább fájlműveleteknél használatos. Persze lehet mondani: a süti is fájl, de azért a session az már nem.
8

$_SESSION ?

solkprog · 2011. Dec. 31. (Szo), 12.23
"...meg akarjuk tartani a $_SESSION szuperglobális használatát..."
Miért akarod megtartani? főleg ha:
"nem tudsz két munkamenetet párhuzamosan futtatni"
-t hiányolod.
10

Haha, kezdem azt hinni, hogy

inf3rno · 2011. Dec. 31. (Szo), 13.37
Haha, kezdem azt hinni, hogy senki sem olvasta végig, amit írtam :D Pedig nem hosszú... Az utolsó mondatban ott van, hogyha mondjuk kapsz egy kész rendszert, ami ezt használja, akkor könnyebb úgy átírni a sajátodra, hogy először egy adapteres változattal dolgozol, és csak utána térsz át a teljesen saját rendszerre. Így minden lépésnél lehet tesztelni, hogy történt e hiba vagy sem.
13

PHP verziószám?

T.G · 2012. Jan. 1. (V), 16.16
Miért lett kiemelve, hogy a fentiek 5.2.17-es verziónál érvényesek? Amennyiben eltekintünk a type hinting-től, akkor 5.0-val is már működne a mintakód, illetve amennyiben láthatóságtól is eltekintünk, akkor már PHP 4-ben is működne. Nem?

A felvetett problémák mennyire vannak a megadott verzióhoz kötve?

Még nem néztem az 5.4 újdonságait, az ott bevezetett SessionHandler fog változtatni ezen?
14

Azért, mert csak 5.2.17 alatt

inf3rno · 2012. Jan. 2. (H), 04.12
Miért lett kiemelve, hogy a fentiek 5.2.17-es verziónál érvényesek? Amennyiben eltekintünk a type hinting-től, akkor 5.0-val is már működne a mintakód, illetve amennyiben láthatóságtól is eltekintünk, akkor már PHP 4-ben is működne. Nem?


Tudtommal a zárójel nem kiemelés, de javíts ki, ha tévednék. Type hint nincs a kódban, tehát 5.0-val is menne, és gondolom 4-es verziónál is, ha a public-ot kiszednénk mindenhonnan.

Egyébként elég triviális oka van, hogy odaírtam: csak 5.2.17 alatt néztem meg, hogy hogyan működik. Egyébként remélem a SessionHandler majd változtat rajta (jó irányba).
17

Re: 5.2.17

T.G · 2012. Jan. 2. (H), 07.10
Ez meg mi? public function encode(array $data)

Nem a szavakon lovagolok, de ha valamire azt írják, hogy ezzel a verzióval érvényes, akkor arra gondolhatunk, hogy ezzel érvényes, de mással nem biztos. Triviálisan erre kérdeztem rá, hogy van-e, volt-e, lesz-e olyan PHP változás, ami érinti a fentieket?
Merthogy nem az volt odaírva, hogy ezzel teszteltem, de valójában a ma használatos stabil verzióval ugyanúgy érvényes lenne.

(zárójel, ami nem kiemelés: Szerintem ami a nyitóoldalon van bevezető szöveg az kiemelés. :)
23

Hát akkor át kéne írnom a

inf3rno · 2012. Jan. 2. (H), 11.00
Hát akkor át kéne írnom a nyitóoldalon a zárójeles részt. :-) Kösz, hogy megnézted, hogy a mai verzióval is megy! :-)

Valószínűleg előbb vagy utóbb lesz olyan változás, ami érinti. Nem tudom, hogy mikor, mert a PHP-sek eléggé visszafele kompatibilitás mániások, ami meg szerintem nem tesz jót a nyelvnek, mert nekem úgy tűnik nem tesztelik és nem gondolják át eléggé a dolgaikat, tehát bőven lenne még mit változtatni rajta. Amiért nekem saját session implementáció kellett, az az egyedi szerializálás és unszerializálás, amit mondjuk egy Serializer cserével meg lehetne oldani. A másik ami miatt kellett az a CRON-ból hívott GC. Ezt mondjuk ki lehetne váltani az ini_set-es gc beállításokban való trükközéssel (érdekes, hogy 2 változóval kell megadni egy arányt), de az se túl szép, jobban tetszik, ha direktbe lehet hívni a GC-t. Ezt mondjuk a SessionHandler osztályon meg is lehet tenni, de ha jól tudom az 5.4-es php-tól van csak.
25

visszafele kompatibilitás

Hidvégi Gábor · 2012. Jan. 2. (H), 11.14
visszafele kompatibilitás mániások, ami meg szerintem nem tesz jót a nyelvnek
Arról ne felejtkezz el, hogy nagyon komoly rendszereket is írtak PHP-ben, és egy verzióváltás esetén így is sok időbe tellhet átnézni a kódot, hogy minden működik-e, hát még ha a függvények paraméterezésén vagy akár a nevén is változtatnának.
27

Ez tény, de egy verzió váltás

inf3rno · 2012. Jan. 2. (H), 11.20
Ez tény, de egy verzió váltás tervezhető dolog, meg ha vannak unit test-ek a rendszerhez (már pedig egy nagyon komoly rendszerhez miért ne lennének), akkor jóval gyorsabb a hibák felderítése...
33

lefele kompatibilitás

T.G · 2012. Jan. 2. (H), 19.30
Ne becsüljük le a lefele kompatibilitás előnyeit, főleg, hogy script nyelvről beszélünk! Ugyanaz a kód futhat több szerveren, különböző környezetben. Véleményem szerint függvényt igenis csak nagyon indokolt esetben, megfelelő előkészítéssel szabad törölni. Ám ha rájövünk, hogy rosszul neveztünk el valamit, akkor adjunk neki új nevet! Szerintem ez teljesen rendben van így!
36

Hát olyan megkötések mellett,

inf3rno · 2012. Jan. 3. (K), 06.19
Ez rendben van, de szerintem csak zavart okoz, hogy két eltérő nevű függvény csinálja ugyanazt. Akkor már legalább az egyiket deprecated-re raktam volna a helyükben (az warningot tol, ha meghívják), aztán egy idő után kivezettem volna... Így az egész olyan rosszul karbantartott kód benyomását kelti, gyűlik benne a szemét... pl: mysql-hez 3 féleképpen lehet csatlakozni (már amiről én tudok), ez nem gáz szerintetek?
37

MySql vs. Session

T.G · 2012. Jan. 3. (K), 09.40
Érdekes, hogy pont a MySql-t említed ennél a cikknél.
Véleményem szerint a legtöbben nem kapnánk fel a fejünket, ha valaki azt írná, hogy ne az eredeti mysql_akármi függvényeket használjuk, hanem írjunk saját osztályt erre, elfedve a beépített függvényeket. Sőt, negatív példa lenne, ha nem így csinálnánk.
Ezzel szemben a session kezelés felülírása már messze nem ennyire egyértelmű. Még akkor, sem ha a felvetett problémák jogosak. Igaz, én nem érzem jelentős problémának, ha csak becslést tudunk adni a kosárban lévő termékek számáról.

(amúgy remélem, hogy a mysqlnd abba az irányba tett első lépés, hogy rendet tegyenek a MySql kapcsolat terén)
38

vagyis...

T.G · 2012. Jan. 3. (K), 10.30
Annyiban fals a fenti, hogy míg adatbázishoz csak egy új felületet adunk, addig itt egy teljesen új session kezelésről beszélünk... tehát ez mégsem összehasonlítható... na mindegy... inkább megvárjuk a cikk folytatását, hogy lássuk mire is ez a felhajtás. :)
20

A beépített PHP

Hidvégi Gábor · 2012. Jan. 2. (H), 10.39
A beépített PHP alapértelmezett munkamenetkezelésénél az okozhat problémát, hogy az adatokat tartalmazó fájl zárolása miatt az azonos klienstől érkező kéréseket nem lehet aszinkron kiszolgálni. Már korábban volt hasonló téma, ahol azt írták, hogy ha áthelyezzük adatbázisalapra, akkor jóval lassabb lesz a folyamat. Vajon megoldható-e a fájlra alapuló session lockolás nélkül?
29

Szerintem nem lenne gyorsabb,

inf3rno · 2012. Jan. 2. (H), 11.22
Szerintem nem lenne gyorsabb, mint az adatbázisos megoldás... Nem hiszem, hogy egyszerűen megoldható, a bonyolult megoldása meg egyenlő az adatbázissal...
34

Persze, hogy nem lehet,

Pethical · 2012. Jan. 2. (H), 23.45
Persze, hogy nem lehet, hiszen aszinkron módon érkező módosító kérések után melyik adat lesz a valós a sessionben? Amelyik "szerencsésebb" és a kérése lassabban ért oda? És, ha a másik lenne már a valós?
35

Tegyük fel, hogy a

inf3rno · 2012. Jan. 3. (K), 06.16
Tegyük fel, hogy a Session-ben két dolgot tárolsz: jogokat meg kosarat. A két párhuzamos kérés közül az egyik a jogokat módosítja, mondjuk bejelentkezel, a másik meg a kosarat, mondjuk hozzáad egy kiló kenyeret... Szerintem logikus lenne, hogy mindkét változás mentésre kerüljön.
39

Igen, ez logikus lenne.

Pethical · 2012. Jan. 3. (K), 13.02
Igen, ez logikus lenne. Azonban ugyanaz áll fenn, mint a threadeknél, ha nem thread safe a változód, akkor, ha konkurens threadek módosítják, meghatározhatatlan lesz az eredménye.
Nézzünk egy buta példát:
Max. 10 kg kenyeret lehet venni, és 9 van a kosaramban. Mint a villám rányomok 2x a gombra, ami hozzáad még 1 kg kenyeret. A két kérés egyszerre beérkezik, egyszerre le is kezeli a szerver. Ehhez a kód nagyon lebutítva kb. így néz ki:

if($_SESSION['kenyer']<10) $_SESSION['kenyer']++;
Na most, nézzük a folyamatot:
  1. első nyomás megnézi mennyi van a sessionben, 9 kg
  2. második nyomás megnézi mennyi van sessionben 9 kg (!!!!)
  3. első nyomás növel 1-el, 10 kg lesz
  4. második nyomás növel 1-el, 11 kg lesz


Azt meg aztán végképp nem kívánom senkinek, hogy tele legyenek ilyen problémákkal a fórumok, mert Pistike még nem hallott a threadekről.

Abban az esetben megvalósítható, ha lehetséges bizonyos feltételek mellett lock-ot tenni a sessionre. Ezt persze MySQL-el, vagy egyéb adatbázissal lehet saját úton.

És egy kérdés: Ha a kenyér hozzáadása hamarabb fut le, mint a login, kié lesz a kenyér a kosárban?
40

Egy anonim felhasználóé, aki

inf3rno · 2012. Jan. 3. (K), 14.43
Egy anonim felhasználóé, aki 1 másodperc múlva azonosítja magát :D Vagy az éhező afrikai árváknak adjuk :D

Amúgy jah, nem thread safe...
42

Egy anonim felhasználóé, aki

Pethical · 2012. Jan. 4. (Sze), 02.36
Egy anonim felhasználóé, aki 1 másodperc múlva azonosítja magát :D Vagy az éhező afrikai árváknak adjuk :D

Amúgy jah, nem thread safe...


Egyszóval használhatatlan? :-)
44

Maga a példa, mert ugyanazt a

inf3rno · 2012. Jan. 4. (Sze), 09.26
Hát ugye a loginnál meg szoktuk változtatni a session id-t, szóval ha egyszerre megy az id változtatás meg a kosárba tétel, akkor lehet, hogy a régi sessionbe kerülne a termék, az új session kosara meg üres maradna. Ez mondjuk elég gáz... :-) Jah, használhatatlan...

kieg:
Amúgy utánaolvastam kicsit ennek az ajaxos témának, és azt javasolják rá, hogy csináljunk egy queue-t, és csak akkor indítsuk a második ajax kérést, ha az első már lefutott... Így mondjuk nem aszinkron az aszinkron... :S

Érdemes lenne egy olyan session kezelőt készíteni, ami kiolvassa a fájlokat, de a mentés / felülírás csak akkor fut le, ha ténylegesen módosult is valami... Ez GC szempontjából nehezen megvalósítható, mert az meg a módosítás dátuma alapján gyűjtöget... :S :S :S Hát ez sem egy egyszerű probléma...
48

Na, így mindig csak adott

Pethical · 2012. Jan. 4. (Sze), 10.11
Na, így mindig csak adott művelet idejére fog lockolódni a file. Ez rohadt lassú lehet amúgy a sok file nyitás-zárás miatt. Csak az elve miatt írtam meg. Nem teszteltem. :-)

class Session implements ArrayAccess
{
    function __construct() 
    {
        ob_start();
    }
    
    function __destruct()
    {
        echo ob_get_flush();
    }
        
    function offsetSet($key, $value)
    {
        session_start();        
        $_SESSION[$key] = $value;
        session_write_close();
        return $value; 
    }
    
    function offsetGet($key)
    {
        session_start();        
        $ret = $_SESSION[$key];
        session_write_close();
        return $ret;
    }
    
    function offsetExists($key)
    {
        session_start();
        $ret = isset($_SESSION[$key]);
        session_write_close();
        return $ret;
    }
    
    function offsetUnset($key)
    {
        session_start();
        unset($_SESSION[$key]);
        session_write_close(); 
    } 
}

$_SESSION = new Session(); 

56

Hát nem tudom ez mennyivel

inf3rno · 2012. Jan. 4. (Sze), 14.11
Ez biztos, hogy nem menne, ha a write close-al lezárod a sessiont, akkor már nem tudod újranyitni. Egyébként sem oldaná meg a problémákat, mert ugyanúgy ütközhetnének az azonos időpontban elkövetett írások.
58

ha a write close-al lezárod a

Pethical · 2012. Jan. 4. (Sze), 14.14
ha a write close-al lezárod a sessiont, akkor már nem tudod újranyitni

Ezt ki mondta neked, mert hazudott. :-)
Nem ütköznek, mert amíg nyitva van, addig zárolja.
59

majd lesd meg milyen es hany

Tyrael · 2012. Jan. 4. (Sze), 14.21
majd lesd meg milyen es hany session sutit fog kikuldeni a PHP, ha tobb sessiont inditasz el egy requesten belul.

Tyrael
60

Azért furcsállom, mert

Pethical · 2012. Jan. 4. (Sze), 14.24
Azért furcsállom, mert használtam már hasonló módon, amikor hosszabb progresst mutattam a usernek oly módon, hogy ajax-al visszahívogattam az aktuális állapotért. Sosem indított új sessiont, pedig kellett a session_write_close ahhoz, hogy a folyamatot kiírja és a másik "szál" el tudja küldeni a jsnek.
63

Ezt nem mondták, hanem

inf3rno · 2012. Jan. 4. (Sze), 17.21
Ezt nem mondták, hanem kipróbáltam ... Többek között erről is szól ez a kis blog bejegyzés...

(Persze attól, hogy én 5.2.17-ben az én beállításaimmal errort kaptam még lehet, hogy másik verzióban működik, amit írsz...)
64

Megnéztem érdekességképpen a

Pethical · 2012. Jan. 4. (Sze), 17.27
Megnéztem érdekességképpen a session kezelés C forrását, és a write_close nem nullázza a session_id-t, ami meg, ha van, akkor a session_start azt indítja újra, tehát nem generál újat. Semmi olyat nem láttam a kódban, ami meggátolná, hogy újranyissam a kiírt sessiont.

Valójában elég régen volt már az, hogy ilyesmivel foglalkoztam phpban, így nem is emlékszem tisztán, ezért előfordulhat, hogy a régen működő kódom nem teljesen olyan, mint most emlékszem rá, de már meg sincsen nálam, olyan régen volt. :)
65

Hát nekem valami error-al

inf3rno · 2012. Jan. 4. (Sze), 17.38
Hát nekem valami error-al elszállt, ha megpróbáltam újraindítani... :-) Ennyi a tapasztalatom, inkább nem nézek bele a php kódjába, jobb az úgy nekem :D még a végén elkezdeném refaktorálni.... :D

kieg:
Közben megnéztem a beépített session kezelő tényleg lockolja a fájlt amíg él a session. Azt hiszem talán olyan session kezelőt lehetne írni, ami figyelembe veszi mindezt, és tol egy sleep-et magára, hogy kivárja a lockolás végét...
81

Tévedtem, újra teszteltem, és

inf3rno · 2012. Jan. 5. (Cs), 12.00
Tévedtem, újra teszteltem, és most nincs semmilyen hiba a session újraindításnál. Mondjuk most a beépített kezelővel csináltam a tesztet, nem az adapteres megoldással...

Oké, újabb érdekesség:
Minden session_start előtt be kell állítani a session_set_save_handler-el, hogy az adapter kezelje a session-t, ezért kaptam hibát. A blog bejegyzésben tehát nem igaz az, hogy nem lehet újraindítani a session-t, pusztán ilyen bugja van, hogy nem jegyzi meg a beállításokat...

Jó lenne ezt javítani a blog bejegyzésben is...
70

Ha valami nagyon párhuzamos

tgr · 2012. Jan. 4. (Sze), 22.07
Ha valami nagyon párhuzamos szolgáltatást csinálsz sok konkurrens AJAX requesttel, akkor a fájl alapú sessionkezelés eleve nem egy jó választás, valami gyors és jól párhuzamosítható tároló kell a sessionöknek (pl. memcached).
67

Ez nem egy shm-hez hasonló

Pethical · 2012. Jan. 4. (Sze), 17.48
Ez nem egy shm-hez hasonló megoldás?
Mindenesetre biztató, hogy egy daemon, így valószínű nem rossz az elve a dolognak.

OFF:
Érdekes, hogy mennyire fel tudta kelteni az érdeklődésemet ez a téma, hogy még a php kódjába is bele túrtam, pedig jelenleg nincs is rá szükségem, meg szerintem a közeljövőben se nagyon lesz. :-)