Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: PHP: Tietokantapohjainen sessiokäsittelijä

Sivun loppuun

goala [13.08.2005 03:04:32]

#

PHP:n session käsittely on perin yksinkertainen. Yksinkertaisesti se vain lukee ja kirjoittaa sessio-dataa tiedostoista. Toimiihan se pienessä skaalassa hyvin, mutta kun useat serverit astuvat kuvaan ei se toimi.

Sikäli kun sessio-data pitää tallentaa tiedostoihin, pitää tiedostojen sijaita jossakin keskitetyssä pisteessä - ei kovin ideaalista suorituskyvyn kannalta. Mutta jos data sijaitsee tietokannassa, voidaan sitä kutsua kaikista koneista serverin clusterista.

Tässä minun, aika perinteinen, ratkaisu (kiitos PHP:n joustavasta sessio-tallennusjärjestelmästä).

<?php

/**
 * Tämä ohjelma on vapaa; tätä ohjelmaa on sallittu levittää edelleen ja muuttaa
 * GNU yleisen lisenssin (GPL lisenssin) ehtojen mukaan sellaisina kuin Free
 * Software Foundation on ne julkaissut; joko Lisenssin version 2, tai (valinnan
 * mukaan) minkä tahansa myöhemmän version mukaisesti.
 *
 * Tätä ohjelmaa levitetään siinä toivossa, että se olisi hyödyllinen, mutta ilman
 * mitään takuuta; ilman edes hiljaista takuuta kaupallisesti hyväksyttävästä
 * laadusta tai soveltuvuudesta tiettyyn tarkoitukseen. Katso GPL lisenssistä lisää
 * yksityiskohtia.
 *
 * Tämän ohjelman mukana pitäisi tulla kopio GPL lisenssistä; jos näin ei ole,
 * kirjoita osoitteeseen Free Software Foundation Inc., 59 Temple Place – Suite
 * 330, Boston, MA 02111-1307, USA.
 *
 * CREATE TABLE sessions (ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, sessId CHAR(26), data TEXT DEFAULT '', dateTouched INT);
 *
 * @author Atte Backman <atte_backman@hotmail.com>
 * @copyright © 2005 Atte Backman
 * @version 1.00.000
 *
 * $Id: sessionHandler.class.php,v 1.00.000 2005/07/21 18:07:04 backmat Exp $
 */

/**
 * cSessionHandler
 *
 * @access public
 */
final class cSessionHandler
{
    /**
     * @var
     */
    private $socket;

    /**
     * @var
     */
    private $sessionPath;

    /**
     * @var
     */
    private $sessionName;

    /**
     * cSessionHandler::__construct()
     *
     * @return void
     */
    public function __construct()
    {
        $this->sessionPath = '';
        $this->sessionName = '';

        $this->socket = new mysqli('host', 'uname', 'pwd', 'db');

        /**
         * Jokainen käsittelijä tunnistetaan taulukosta joka sisältää
         * itseis-referenssin instanssiin (huomaa &) ja merkkijonon joka
         * määrittää metodin.
         *
         * Ainiin. Muista, että new-operaattori palauttaa objektin kopion.
         * Eli pitää käyttää uutta referenssiä kun luodaan instanssi:
         *
         * $sessionManager = & new cSessionHandler();
         *
         * Muulloin käsittelijät liitetään instanssiin jonka new-operaattori
         * loi ja eikä kopioon joka on tallennettuna muuttujaan $sessionManager.
         */
        session_set_save_handler(array(& $this, 'open'),
                                 array(& $this, 'close'),
                                 array(& $this, 'read'),
                                 array(& $this, 'write'),
                                 array(& $this, 'destroy'),
                                 array(& $this, 'garbageCollect'));
    }

    /**
     * cSessionHandler::__destruct()
     *
     * @return void
     */
    public function __destruct()
    {
        $this->socket->close();
    }

    /**
     * cSessionHandler::open()
     *
     * Tätä metodia kutsutaan aivan ensiksi.
     *
     * @param string $sessionPath Sessio-datan tallennuslokaatio.
     * @param string $sessionName Session nimi.
     * @return bool
     */
    public function open($sessionPath, $sessionName)
    {
        $this->sessionPath = $sessionPath;
        $this->sessionName = $sessionName;

        // Kaikista metodeista palautetaan true paitsi funktiosta read().
        return true;
    }

    /**
     * cSessionHandler::close()
     *
     * @return bool
     */
    public function close()
    {
        return true;
    }

    /**
     * cSessionHandler::read()
     *
     * Lukee tietokannasta sessio-datan.
     *
     * @param string $sessId Sessio-tunnus.
     * @return string
     */
    public function read($sessId)
    {
        // Luetaan sessio-data tietokannasta, jos id on olemassa.
        $result = $this->socket->query("SELECT data FROM sessions WHERE sessionId = '$sessId';");
        $uts    = time(); // Returns UNIX timestamp.

        if ($result->num_rows == 0) {
            /**
             * Tehdään tyhjä rivi jotta myöhemmin ei tarvitse kuin käyttää
             * UPDATEa. Myöskään ei tarvitse huolehtia siitä onko rivi
             * jo olemassa samalla id:llä.
             */
            $this->socket->query("INSERT INTO sessions (sessionId, dateTouched) VALUES ('$sessId', $uts);");

            /**
             * Jos me palautamme boolean-arvon true tai false, on todennäköistä
             * että PHP kaatuu. Joten aina palauta sessio- tai tyhjä
             * merkkijono.
             */
            return '';
        } else {
            // Dataa löytyi, otetaan se talteen ja palautetaan funktion mukana.
            extract($result->fetch_array(MYSQLI_ASSOC), EXTR_PREFIX_ALL, 'sess');

            $result->close();

            // Päivitetään session aikaleima, jotta myöhempi roskankeräys onnistuisi.
            $this->socket->query("UPDATE sessions SET dateTouched = $uts WHERE sessionId = '$sessId';");

            return $sess_data;
        }
    }

    /**
     * cSessionHandler::write()
     *
     * Aika nimensä mukainen metodi. Tallentaa tiedon joka annetaan
     * $_SESSION muuttujaan.
     *
     * @param string $sessId Sessio-id.
     * @param string $data Session tiedot.
     * @return bool
     */
    public function write($sessId, $data)
    {
        $uts = time(); // Palauttaa UNIX-aikaleiman.

        $this->socket->query("UPDATE sessions SET data = '$data', dateTouched = $uts WHERE sessionId = '$sessId';");

        return true;
    }

    /**
     * cSessionHandler::destroy()
     *
     * @param $sessId Sessio-id.
     * @return bool
     */
    public function destroy($sessId)
    {
        $this->socket->query("DELETE FROM sessions WHERE sessionId = '$sessId';");

        return true;
    }

    /**
     * cSessionHandler::garbageCollect()
     *
     * Tätä metodia kutsutaan sattumanvaraisesti silloin tällöin, jotta
     * kaikki turhanpäiväinen vanhentunut roska poistettaisiin. php.ini
     * tiedostoa kannattaa kyllä sen verran muuttaa, että sattumanva-
     * raisesta tehdäät joka kerta.
     *
     * @param int $sessionMaxLifeTime Session elinikä.
     * @return bool
     */
    public function garbageCollect($sessionMaxLifeTime)
    {
        $uts = time(); // Palauttaa UNIX-aikaleiman.

        $this->socket->query("DELETE FROM sessions WHERE dateTouched + $sessionMaxLifeTime < $uts;");

        return true;
    }
}

?>

config.default.php

<?php

/**
 * Tämä ohjelma on vapaa; tätä ohjelmaa on sallittu levittää edelleen ja muuttaa
 * GNU yleisen lisenssin (GPL lisenssin) ehtojen mukaan sellaisina kuin Free
 * Software Foundation on ne julkaissut; joko Lisenssin version 2, tai (valinnan
 * mukaan) minkä tahansa myöhemmän version mukaisesti.
 *
 * Tätä ohjelmaa levitetään siinä toivossa, että se olisi hyödyllinen, mutta ilman
 * mitään takuuta; ilman edes hiljaista takuuta kaupallisesti hyväksyttävästä
 * laadusta tai soveltuvuudesta tiettyyn tarkoitukseen. Katso GPL lisenssistä lisää
 * yksityiskohtia.
 *
 * Tämän ohjelman mukana pitäisi tulla kopio GPL lisenssistä; jos näin ei ole,
 * kirjoita osoitteeseen Free Software Foundation Inc., 59 Temple Place – Suite
 * 330, Boston, MA 02111-1307, USA.
 *
 * Yleiset asetukset.
 *
 * @author Atte Backman <atte_backman@hotmail.com>
 * @copyright © 2005 Atte Backman
 * @version 1.00.000
 *
 * $Id: config.default.php,v 1.00.000 2005/07/21 18:07:04 backmat Exp $
 */

require_once('sessionHandler.class.php');

$sessionHandler = & new cSessionHandler();

?>

example.php

<?php

/**
 * Tämä ohjelma on vapaa; tätä ohjelmaa on sallittu levittää edelleen ja muuttaa
 * GNU yleisen lisenssin (GPL lisenssin) ehtojen mukaan sellaisina kuin Free
 * Software Foundation on ne julkaissut; joko Lisenssin version 2, tai (valinnan
 * mukaan) minkä tahansa myöhemmän version mukaisesti.
 *
 * Tätä ohjelmaa levitetään siinä toivossa, että se olisi hyödyllinen, mutta ilman
 * mitään takuuta; ilman edes hiljaista takuuta kaupallisesti hyväksyttävästä
 * laadusta tai soveltuvuudesta tiettyyn tarkoitukseen. Katso GPL lisenssistä lisää
 * yksityiskohtia.
 *
 * Tämän ohjelman mukana pitäisi tulla kopio GPL lisenssistä; jos näin ei ole,
 * kirjoita osoitteeseen Free Software Foundation Inc., 59 Temple Place – Suite
 * 330, Boston, MA 02111-1307, USA.
 *
 * Esimerkki meidän uuden sessiokäsittelijän toiminnasta.
 *
 * @author Atte Backman <atte_backman@hotmail.com>
 * @copyright © 2005 Atte Backman



 * @version 1.00.000
 *
 * $Id: example.php,v 1.00.000 2005/07/21 18:07:04 backmat Exp $
 */

require_once('config.default.php');

session_start();

$tmp             = '';
$_SESSION['foo'] = 'bar';
$tmp             = $_SESSION['foo'];

session_destroy();

?>

arcatan [14.08.2005 16:13:58]

#

Kun näin, että vinkki on GPL-lisenssoitu, en uskaltanut lukea sitä läpi. Yleisesti ottaen koodivinkit on tarkoitettu muille opiksi ja avuksi. GPL-lisenssi kuitenkin pakottaa lisenssoimaan tuotteensa GPL-lisenssin alle. Tämä vie jutusta koko pointin. Uskon nimittäin, että merkittävä osa vinkin lukijoista ja potenttiaalisista käyttäjistä eivät ole aikeissa käyttää GPL-lisenssiä.

GPL-lisenssi on mainio juttu, kun kyseessä on esimerkiksi kokonainen ohjelma tai kenties kirjasto, joka on ainoa laatuaan. Koodivinkit, kuten tämäkään vinkki, eivät yleensä ole mitenkään ainutlaatuisia. Olisi järkevämpää käyttää koodivinkeissä lisenssiä, jos sellaista nyt on pakko käyttää, joka mahdollistaa vinkin hyödyntämisen mahdollisimman useissa olosuhteissa. Mielestäni usein olisi järkevämpää jättää vinkki kokonaan lisensoimatta ja julistaa se julkiseksi pääomaksi. Näin se palvelee tarkoitustaan paremmin.

Lisäksi kyseenalaistan, onko siinä järkeä, että lisenssi on yhtä pitkä kuin koodivinkki, johon sitä sovelletaan.

tsuriga [14.08.2005 16:19:04]

#

Njuu samat kuin yllä, muuten hyvää työtä. Tuohon config.default.php sivun kommenttiin voisi laittaa, että siinä otetaan käyttöön tuo oma sessiokäsittelijä(?).

goala [14.08.2005 19:04:06]

#

Jep. Onhan arcatanin komentti asiallista. Tosin olen tottumuksesta julkaissut koodini aina GPL-lisenssin alla. Mutta, periaatteellinen maailmankatsomukseni sotii vahvasti riistokapitalistista maailmanvaltaa vastaan.

goala [14.08.2005 19:13:32]

#

Sitä paitsi, tämä on vinkki oppimistarkoitukseen.

phadej [14.08.2005 21:12:31]

#

nii ja sit tä on PHP5:sta ja käyttää mysqli:a (mysql >= 4.1)
ja constructi olis voinut alottaa session.

Ihan hyvä vinkki, jos ei tiennyt että niinkin voi tehdä, mutta lukiessa https://www.php.net/manual-lookup.php?pattern=uts&lang=en kommentteja, löytyi sitä sun tätä tsydeemiä, joista sais pienillä muokkauksilla melko saman.
Niin ja voi käyttää mysqlin NOW() funktiota aikaan. (ei se muuten palauta uts-aikaa edes ;)
Myös se voisi escapettaa $data-stringin, session-id kun näyttää olevan md5 hash jostain (32 merkkiä eikä 26 muuten, saatan olla väärässä) (ei pitäisi olla mitään erikoismerkkejä).

ajv [14.08.2005 23:54:45]

#

Eikös affected_rows palauta muutettujen rivien lukumäärän, eikä SELECT-lauseella haettujen rivien lukumäärää?

Koodivinkkinä kyllä hyvä ja mielenkiintoinen, varsinkin kun tuo olio-ohjelmointi ei ole vielä ihan 100% hanskassa.

goala [15.08.2005 03:38:36]

#

ajv: Jep, oikeassa olet. Muutinpa ton num_row:siksi.
phadej: Voihan sen konstruktoriinki laittaa, jätän kuitenkin pois. Saapahan aloittaa session sitten kun haluaa, vaikka erillisessä konffaus tiedostossa on jo määritelty handleri.

kilotavu [19.12.2006 17:35:41]

#

Ei toimi minulla:

Notice: Trying to get property of non-object in /AAA/sessionHandler.class.php on line 134

Warning: session_start() [function.session-start]: Cannot send session cache limiter - headers already sent (output started at /AAA/sessionHandler.class.php:134) in /AAA/example.php on line 10

Warning: mysqli::query() [function.mysqli-query]: Couldn't fetch mysqli in /AAA/sessionHandler.class.php on line 175

Rivillä 134 on:

if ($result->num_rows == 0) {

ja rivillä 175 on:

$this->socket->query("UPDATE sessions SET data = '$data', dateTouched = $uts WHERE sessionId = '$sessId';");

tsuriga [26.10.2007 10:39:22]

#

PHP vitosessa oliota luodessa palautetaan automaagisesti viittaus, ja =& on täten deprekoitu merkintä.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta