Kirjoittaja: Metabolix
Kirjoitettu: 30.06.2008 – 19.04.2013
Tagit: grafiikka, koodi näytille, vinkki
Joskus Internet-sivuilla tarvitaan kuvia, jotka muuttuvat dynaamisesti käyttäjän tekojen mukaan. On melko rasittavaa tallentaa tällaisia kuvia jatkuvasti levylle, varsinkin, jos uutta kuvaa tarvitaan yleensä vain kerran tai kaksi. Resursseja — kuten GD-kuvia — ei kuitenkaan voi tallentaa sellaisenaan esimerkiksi istuntoihin, ja toisaalta kuvaa ei myöskään voi oikein säilyttää raakadatana, koska tällöin sen muokkaaminen on hyvin työlästä ellei jopa mahdotonta.
Istuntoon voi kuitenkin tallentaa olioita. Tallennuksen yhteydessä olio muutetaan tekstimuotoon serialize-funktiolla, joka kutsuu olion __sleep-metodia, ja vastaavasti lataamisen yhteydessä käytetään unserialize-funktiota, joka kutsuu __wakeup-metodia. Näissä metodeissa olio voi valmistella datansa tallentamista varten ja palauttaa taas tallennetun datan ennalleen.
Jotta kuvaresurssin voisi tallentaa, se täytyy muuttaa joksikin muuksi. Luonnollinen vaihtoehto on merkkijono. PHP:n kuvakirjasto ei tarjoa tähän valmista funktiota, mutta pienellä yhdistelyllä tämäkin ratkeaa: Kuvan voi tulostaa, ja tulostetta voi puskuroida. Puskuroidun tulosteen voi poimia talteen ja puskurin voi tyhjentää niin, ettei tulostettua kuvaa oikeasti tulostetakaan. Toiseen suuntaan pääseekin helpommin: ImageCreateFromString lataa kuvan suoraan merkkijonodatasta.
Tässä vinkissä on pieni luokka, joka huolehtii kuvaresurssin muuttamisesta istuntoon sopivaan muotoon ja palauttamisesta takaisin resurssiksi. Toki kaikkein järkevintä olisi saman tien liittää luokkaan muitakin mukavia ominaisuuksia kuten kuvan tulostus käyttäjälle, mutta yksinkertaisuuden vuoksi vinkki sisältää vain kaikkein olennaisimman osan. Esimerkkiskripti taas piirtää kuvaan uusia kaaria joka päivityksellä ja tulostaa kuvan käyttäjälle PNG-muodossa.
Menetelmää voi käyttää yhdessä ajv:n esittelemän tietokantaan tallentamisen kanssa.
<?php class Image { /** @var Resource GD-kuva */ private $res; /** * Image-luokan muodostin, voi asettaa oliolle GD-kuvaresurssin * * @param Resource $res Uusi kuva (GD) * @return void */ public function __construct($res = null) { $this->set($res); } /** * Asettaa olion GD-kuvan * * @param Resource $res Uusi kuva (GD) * @return void */ public function set($res = null) { if (is_null($res) || is_resource($res)) { $this->res = $res; } } /** * Hakee olion GD-kuvan * * @return Resource Kuva (GD) */ public function get() { return $this->res; } /** * Muuttaa kuvan tekstimuotoon serialize-kutsun yhteydessä * * @return Array<String> Tallennettavat kentät */ public function __sleep() { # Jos on kuva, tulostetaan se puskuriin ja tallennetaan puskurin sisältö if (is_resource($this->res)) { ob_start(); imagegd2($this->res); $this->res = ob_get_contents(); ob_end_clean(); } # Ilmoitetaan, että res-jäsen pitää tallentaa return array("res"); } /** * Muuttaa kuvan tekstimuodosta resurssiksi unserialize-kutsun yhteydessä * * @return void */ public function __wakeup() { # Jos res on string-tyyppinen, muutetaan se kuvaresurssiksi if (is_string($this->res)) { $this->res = imagecreatefromstring($this->res); } } } ?>
<?php # Käytetään äskeistä luokkaa. require_once("Image.class.php"); # Aloitetaan istunto. # Samalla kaikki istuntoon tallennetut oliot herätetään unserialize-funktiolla, joka kutsuu __wakeup-metodia. session_start(); # Tarkistetaan, että kuva on säilötty, tai luodaan tarvittaessa uusi if (!isset($_SESSION["image"])) { $_SESSION["image"] = new Image(imagecreatetruecolor(256, 256)); } $img = $_SESSION["image"]; # Piirretään hieman imagearc( $img->get(), rand() % 256, rand() % 256, rand() % 256, rand() % 256, rand() % 360, rand() % 360, imagecolorallocate( $img->get(), rand()%256, rand()%256, rand()%256 ) ); # Lähetetään kuva käyttäjälle header("Content-type: image/png"); imagepng($img->get()); # Lopuksi istuntoon tallennetut muuttujat käyvät automaattisesti serialize-funktion läpi, jolloin kutsutaan __sleep-metodia
Kätevää! __sleep():n ja __wakeup():n olemassaolon unohtaa kovin helposti, ja tässä on niille loistava sovellus.
Kovalevyllehän se kuva päätynee kuitenkin, sulloit sen sitten sessioon, tietokantaan tai suoraan kuvatiedostona? Ei myöskään välttämättä paras ratkaisu raskaammille nettisivuille.
Koodista kuitenkin plussaa; luettavaa ja mukavasti kommentoitua. Luokan serialisointi PHP:ssa tuli itselleni uutena asiana, eipä ole ennen ollut käyttöä :)
PHP:n oletusarvoinen sessiokäsittelijä käyttää siis tiedostoja. Jos ylikirjoittaa sessiokäsittelijän käyttämään tietokantaa... :). Huvitelma.
Sessioihin tallentaessa on se hyvä puoli, että dynaamiset kuvat tuhotaan automaattisesti käyttäjän poistuessa - semanttisesti ajateltuna sopiva malli käyttötarkoitukseensa.
tsuriga kirjoitti:
Sessioihin tallentaessa on se hyvä puoli, että dynaamiset kuvat tuhotaan automaattisesti käyttäjän poistuessa.
Juuri tämä onkin yksi menetelmän olennaisimmista käyttömotiiveista. Esimerkiksi "syötä kuvassa olevat kirjaimet" on näppärä hoitaa näin.
Turatzuro kirjoitti:
Kovalevyllehän se kuva päätynee kuitenkin, sulloit sen sitten sessioon, tietokantaan tai suoraan kuvatiedostona?
Ei suinkaan, on niitä muitakin sessionkäsittelijöitä kuin se tavallinen tiedostopohjainen, eikä tarvitse edes itse kirjoittaa. Lisäksi sessioiden jakamiseen klusterin palvelinten välillä on usein valmiit välineet, tiedostojen kanssa pitää säätää käyttöön toinenkin väline.
Metabolix kirjoitti:
Ei suinkaan, on niitä muitakin sessionkäsittelijöitä kuin se tavallinen tiedostopohjainen, eikä tarvitse edes itse kirjoittaa.
Siinä vaiheessa kun niitä sessioita tulee tarpeeksi, ei niitä voi oikein enää rammissakaan pitää. Lopulta ne päätyvät joko swappiin tai sitten viimeistään jollain muulla tavalla kovalevylle, oli käsittelijä mikä tahansa.
Mielestäni parempi ja nopeampi tapa on heittää tiedosto suoraan kovalevylle ihan normi tiedostona. Näin kuvadataa ei tarvitse käyttää PHP:n kautta, vaan palvelin voi lähettää kuvadatan itsenäisesti. Kuvadata voidaan myöhemmin tuhota vaikkapa tsekkaamalla vanhentuneet kuvat tietokannasta tms. jotakin skriptiä ajettaessa, ja/tai sitten yksinkertaisesti poistetaan tiedosto operaation loputtua. Niillä servereillä joilla on mahdollisuus, voidaan poisto tehdä vaikkapa ajamalla poistoskripti cronjobina.