Vasta nyt huomasi, että PHP:ssäkin on mahdollisuus käyttää luokkia ja olioita ja kun asiasta en ole ainakaan aikaisemmin nähnyt keskustelua, niin päätin kysyä, että onko kannattavaa käyttää PHP:een olioita ja käytetäänkö niitä yleisesti PHP-skripteissä? Eli kannattaako niiden käyttö opetella?
Kyllä kannattaa opetella. OOP-tukea remontoitiin PHP:n vitosversiossa rankalla kädellä ja tämän vuoksi olioita käytetään järkevästi lähinnä projekteissa, jotka on aloitettu PHP:n vitosversion ilmestymisen jälkeen. 5.3.0 versiosta etiäpäin PHP:ssä on myös namespacet (ohlala).
EDIT: Kannattaa selailla softien sorsia ja miettiä samalla, millaista koodia lähtisit itse mieluiten muokkaamaan.
Olioita kannattaa käyttää, jos niistä tuntuu olevan itselle hyötyä. Kuitenkin kaiken mahdollisen voi toteuttaa vaikeuksitta ilman olioita, joten olioiden käyttö ei tuo PHP:hen mitään uusia mahdollisuuksia, vaan kyseessä on vain vaihtoehtoinen merkintätapa.
Jos englanti on hanskassa ja hanska kädessä niin SitePointilla on asiasta asiaa.
Antti Laaksonen kirjoitti:
Kuitenkin kaiken mahdollisen voi toteuttaa vaikeuksitta ilman olioita, joten olioiden käyttö ei tuo PHP:hen mitään uusia mahdollisuuksia, vaan kyseessä on vain vaihtoehtoinen merkintätapa.
Tuosta kyllä rohkenen olla täysin eri mieltä. Ilman olioita olisi monen PHP -kieleen nojautuvan järjestelmän toiminta hyvin vaikeaa, jos ei jopa mahdotonta. Oliot tuovat julmetun paljon lisää mahdollisuuksia ja osa asioista on jopa ehkä mahdotonta tehdä ilman olioita.
Ehkä Antti ei ole riittävän hyvin selvillä PHP:n olioihin liittyvistä asioista?
-W-
Voitko antaa esimerkin asiasta, jonka toteutus on vaikeaa ilman olioita mutta helppoa olioita käyttäen?
Oliot on aina mahdollista korvata taulukoilla ja tavallisilla funktioilla, joille annetaan taulu parametrina.
<?php // Oliot $olio = new Olio(); $olio->jokin = 123; $olio->tulosta($parametri); // Taulut $taulu = taulu_new(); $taulu["jokin"] = 123; taulu_tulosta($taulu, $parametri); ?>
Kun vielä tallennetaan funktiotkin taulukkoon, saadaan toteutettua perintä tarvittaessa aivan täysin. Mitä vielä puuttuu?
Jollain ne oliotkin on aina ohjelmoitu, joten ei voi väittää, etteikö tällä matalamman tason järjestelmällä voisi toteuttaa aivan samoja asioita kuin olioilla. Pohjalla on kuitenkin aina konekieli, ja siitä ei ainakaan löydy PHP:n tai muunkaan kielen olioita.
Sanoisin että kannattaa kyllä opetella ne oliot ainakin sen verran että ymmärtää pääpiirteissään mistä niissä on kysymys. Ja olen osittain samaa mieltä Wizardin kanssa että tietyissä tapauksissa ne ovat melkeinpä välttämättömyys (ei siksi etteikö asiat muuten onnistuisi, vaan koska Olioilla se on merkittävästi helpompaa/järkevämpää). Kun yksin tekee koodia mitä ei koskaan näe muut kuin sinä itse, proseduraalisella lähestymistavalla pärjää varmasti ihan tyydyttävästi. Mutta jos projektiin osallistuu useita henkilöitä, tai vain halutaan ohjelmoida jotain kokonaisuuksia mitä on helppo käyttää muissakin projekteissa, tarjoaa Olio-toteutus merkittäviä etuja.
Kyllä itekin kannustan OOP-koodiin. Alotin tässä omankin projektin olioilla nyt ja samalla opettelen vähän parempaa koodausta. Kyllä on niin paljon helppolukuisempaa koodi, että ei tee mieli enää yhtään vaihtaa takaisin proseduraaliseen.
Luokat vastaavat niin ihanasti tietokannan tauluja, että oleen kyllä aivan myyty <3
Nyt kun tota PHP:n olio-koodia olen katsellut, niin olen ihmetellyt sitä, että eikö siinä käytetä olion ja metodin välillä piste-operaattoria, vaan tuollaista nuolta -> ?
Käytetään, aivan kuten C++:n osoitinten kanssakin. (Miksi sitten kysyt, kun kerran olet asian jo koodista nähnyt?)
Antti Laaksonen kirjoitti:
Voitko antaa esimerkin asiasta, jonka toteutus on vaikeaa ilman olioita mutta helppoa olioita käyttäen?
MVC -arkkitehtuuri. Tarvitsetko jotain muuta esimerkkiä? ;)
-W-
Wizard kirjoitti:
MVC -arkkitehtuuri.
Kerro toki vielä, miksi se olisi ilman olioita niin hankala. Itse en näe mitään ongelmaa.
Luulenpa, että asiassa on nyt lähinnä kyse siitä, että kun käyttää olioita edes suunnilleen OOP-periaatteiden mukaisesti, ajattelu on väkisinkin tietyllä tavalla modulaarista. Mikään ei estä soveltamasta samanlaista ajattelumallia ohjelmointiin ilmankin olioita. Koska proseduraalinen ohjelmointitapa ei pakota ajattelua samaan suuntaan, useimmat käyttävät sitä eri tavalla, jolloin syntyy illuusio, ettei sitä voi käyttää kuin tällä yhdellä tavalla.
Linux on ohjelmoitu C:llä, C:ssä ei ole olioita. Silti Linux on ainakin minusta hyvin pitkälti MVC-mallin mukainen, sikäli kuin niin laaja järjestelmä voi olla. Kontrolleriahan siinä ei itsessään paljon ole, vaan sitä varten on erillisiä ohjelmia.
Metabolix kirjoitti:
Kerro toki vielä, miksi se olisi ilman olioita niin hankala. Itse en näe mitään ongelmaa.
Luulenpa, että asiassa on nyt lähinnä kyse siitä, että kun käyttää olioita edes suunnilleen OOP-periaatteiden mukaisesti, ajattelu on väkisinkin tietyllä tavalla modulaarista. Mikään ei estä soveltamasta samanlaista ajattelumallia ohjelmointiin ilmankin olioita. Koska proseduraalinen ohjelmointitapa ei pakota ajattelua samaan suuntaan, useimmat käyttävät sitä eri tavalla, jolloin syntyy illuusio, ettei sitä voi käyttää kuin tällä yhdellä tavalla.
Linux on ohjelmoitu C:llä, C:ssä ei ole olioita. Silti Linux on ainakin minusta hyvin pitkälti MVC-mallin mukainen, sikäli kuin niin laaja järjestelmä voi olla. Kontrolleriahan siinä ei itsessään paljon ole, vaan sitä varten on erillisiä ohjelmia.
Tähän voisin heittää vaikka mitä, mutta otan nyt vaikka yhden esimerkin suoraan manuskoista jonka voit sitten kääntää preseduuliseen muotoon. Voitaisiin ottaa vaikka esimerkki vielä niin, että tuossa metodissa getCommentForm voisi olla kutsuja validaattoriin ja muutamia silmukoita jne.
<?php class IndexController extends Zend_Controller_Action { public function indexAction() { $form = $this->getCommentForm(); if ($this->getRequest()->isPost()) { if ($form->isValid($_POST)) { $comment = $form->getValue('comment'); $this->view->comment = $comment; } } $this->view->form = $form; } /** * This function returns a simple form for adding a comment */ public function getCommentForm() { $form = new Zend_Form(array( 'method' => 'post', 'elements' => array( 'comment' => array('textarea', array( 'required' => true, 'label' => 'Please Comment:' )), 'submit' => array('submit', array( 'label' => 'Add Comment' )) ), )); return $form; } } ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Zend Framework Quick Start</title> </head> <body> <h1>Basic Blog</h1> <?php if(isset($this->comment)) { ?> <p> You just submitted the following comment: <?= $this->escape($this->comment); ?> </p> <?php } ?> <?= $this->form; ?> </body> </html>
Edit: esimerkki ei ole ihan nyt mitä haen, mutta kun ratkaisut muuttuvat monimutkaisiksi, proseduulinen tapa ei enää vain toimi tai bugien määrä on rajaton. Olioiden kanssa toistorakenteet ovat helpompia, koodi paljon lyhyempää jne. Lisäksi tulee vastaan sellainen pieni asia kuin tietoturva.
Itse olen ollut tekemässä sellaista pikkujärjestelmää kuin www.finlex.fi ja voin sanoa, että moni asia siellä olisi jäänyt tekemättä ilman olioita.
-W-
Esimerkkisi on sikäli huono tähän tilanteeseen, että käytät siinä jo ulkopuolisia luokkia. Jos nyt kuitenkin otan valtuudet muuttaa myös ne samalla, ylemmästä koodista voisi tulla esimerkiksi tällainen:
<?php function index_controller_action(&$self) { $form = index_controller_get_form(); if (request_is_post($self['request'])) { if (form_is_valid($form)) { $comment = form_get_value($form, 'comment'); $self["view"]["comment"] = $comment; } } $self["view"]["form"] = $form; } function index_controller_get_form() { return form_create(array(/* samat kamat */)); } ?>
Tietenkin ulkopuolista rajapintaa voi käyttää sen omalla tavalla, eli turha tehdä sille uutta välirajapintaa, jos sen tarjoamat keinot kelpaavat sellaisinaan.
Wizard kirjoitti:
kun ratkaisut muuttuvat monimutkaisiksi, proseduulinen tapa ei enää vain toimi tai bugien määrä on rajaton.
Ei suinkaan. Tässä on kyse nyt juuri tuosta, mitä aiemmin selitin. Mahdollisuus bugeihin on molemmilla merkintätavoilla aivan yhtäläinen, vai pystytkö esittämään jonkin (mielekkään) bugin, jonka luokat erityisesti estävät?
Wizard kirjoitti:
Lisäksi tulee vastaan sellainen pieni asia kuin tietoturva.
En näe tällä mitään yhteyttä olioihin.
No joo, tässä lienee parempi esimerkki eli haluaisin tietää kuinka teette tämän proseduurisella tavalla:
<?php /** * Define a custom exception class */ class MyException extends Exception { // Redefine the exception so message isn't optional public function __construct($message, $code = 0) { // some code // make sure everything is assigned properly parent::__construct($message, $code); } // custom string representation of object public function __toString() { return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; } public function customFunction() { echo "A Custom function for this type of exception\n"; } } /** * Create a class to test the exception */ class TestException { public $var; const THROW_NONE = 0; const THROW_CUSTOM = 1; const THROW_DEFAULT = 2; function __construct($avalue = self::THROW_NONE) { switch ($avalue) { case self::THROW_CUSTOM: // throw custom exception throw new MyException('1 is an invalid parameter', 5); break; case self::THROW_DEFAULT: // throw default one. throw new Exception('2 isnt allowed as a parameter', 6); break; default: // No exception, object will be created. $this->var = $avalue; break; } } } // Example 1 try { $o = new TestException(TestException::THROW_CUSTOM); } catch (MyException $e) { // Will be caught echo "Caught my exception\n", $e; $e->customFunction(); } catch (Exception $e) { // Skipped echo "Caught Default Exception\n", $e; } // Continue execution var_dump($o); echo "\n\n"; // Example 2 try { $o = new TestException(TestException::THROW_DEFAULT); } catch (MyException $e) { // Doesn't match this type echo "Caught my exception\n", $e; $e->customFunction(); } catch (Exception $e) { // Will be caught echo "Caught Default Exception\n", $e; } // Continue execution var_dump($o); echo "\n\n"; // Example 3 try { $o = new TestException(TestException::THROW_CUSTOM); } catch (Exception $e) { // Will be caught echo "Default Exception caught\n", $e; } // Continue execution var_dump($o); echo "\n\n"; // Example 4 try { $o = new TestException(); } catch (Exception $e) { // Skipped, no exception echo "Default Exception caught\n", $e; } // Continue execution var_dump($o); echo "\n\n"; ?>
Kyseessä siis ihan yksinkertainen virheidenkäsittely jota tarvitaan aina.
-W-
Oliot eivät toki ole mitenkään välttämättömiä, mutta ne ovat aika usen käteviä.
Perinnällä ja/tai instanssien luonnilla voi ryhmitellä uudelleenkäytettäviä asioita helposti. Wizardin ensimmäisessä esimerkissä voitaisiin tehdä Zend_Controller_Actionin ja omien *Controller-luokkien väliin oma yläluokka, jossa on jotain yhteistä toiminnallisuutta tai käteviä apumetodeja. Yläluokkaan voidaan tehdä korvattavia metodeja, joilla konfiguroidaan jotain yläluokan toiminnallisuutta. Näin moniin samankaltaisiin alaluokkiin ei välttämättä tule kuin pari muutaman rivin metodia yläluokkaan ohjeistamaan.
Proseduraalisessa tavassa "yläluokan" toiminnallisuutta joudutaan kutsumaan aina käsin, tai perintää joudutaan simuloimaan jollain override-systeemillä, jossa funktioita etsitään ensin spesifisestä paikasta ja sitten yleisestä.
Myös olioiden tiedonpiilotusominaisuuksia (private, protected) voi olla hankala simuloida siististi proseduraalisesti. Oliot nyt vain ovat kovin kätevä tapa ryhmitellä asioita.
Olioiden uudelleen käytettävyys tiettyihin tehtäviin on korvaamaton. Muuten pitäisi naputella aina sama koodi mikä on turhauttavaa ja aikaa vievää. Helpottaa erinomaisesti myös muistamista ja hyvin nimetyt oliot ovat helposti käytettäviä ja niistä ymmärtää heti ilman kommentointia mitä tekee. Lyhyissä ohjelmissa olioiden tekeminen on ajan hukkaa.
offtopic(C++): Olioista voi tehdä omia pikakirjastoja.
Pollapoju kirjoitti:
— —
Mikään näistä asioista ei liittynyt erityisesti olioihin, vaan kaikki pätee yhtä lailla kaikkeen hyvin kirjoitettuun koodiin. Mitään ei todellakaan tarvitse kirjoittaa moneen kertaan sen enempää ilman olioita kuin niiden kanssakaan. Muutakin kuin olioita voi nimetä hyvin, muutakin koodia voi kirjoittaa ymmärrettävästi.
map_ puhuu sinänsä asiaa, toki oliot kielestä riippuen enemmän tai vähemmän piilottavat oliomaisen toiminnallisuuden käytännön toteutuksen. Sehän niiden tarkoitus onkin. Kuitenkin kannattaa muistaa, että tämä keskustelun viimeisin suuntaus, oliokoodin muuntaminen suoraan toiseen merkintätapaan, on melko nurinkurinen ajattelutapa. Tietenkin proseduraalinen ohjelmointi on hankalampaa kuin olio-ohjelmointi, jos sen yrittää väkisin ahtaa tiukasti olio-ohjelmoinnin muottiin. Jos sen sijaan toteutetaan jokin kokonainen järjestelmä, lopputulos voi olla yhtä toimiva, yhtä selvä ja yhtä lyhyt molemmilla tavoilla, vaikka monet asiat olisikin jäsennetty aivan eri tavalla.
Viimeiseksi mainittakoon, että koodin luettavuuden kannalta muiden seikkojen vaikutus on todella paljon suurempi kuin tämän oliokysymyksen. Looginen jäsennys ei vaadi olioita, mutta todistettavasti myöskään oliot eivät vaadi läheskään loogista jäsennystä, nimim. Töissä PHP:stä kärsivä.
Uskallan väittää, että viimeisintä esimerkkiäni ei voi kääntää proseduuliseen muotoon vaan se vaatii ehdottomasti olion.
-W-
Wizard kirjoitti:
Uskallan väittää, että viimeisintä esimerkkiäni ei voi kääntää proseduuliseen muotoon vaan se vaatii ehdottomasti olion.
Tarkoitatko ettei virheenkäsittelyä voi toteuttaa ilman poikkeuksia? Vai ettei poikkeuksia voi käyttää ilman poikkeuksia?
Virheitä voi hallita esimerkiksi niin, että funktio palauttaa ennalta määritellyn arvon, joka yksilöi virheen, tai virhetilanne voidaan merkitä johonkin globaaliin muuttujaan, jonka sisältö tarkistetaan erikseen.
Siisti proseduraalinen koodi näyttää käytännössä matkivan aika usein olio-ohjelmointia. On tietueita ja on niitä muokkaavia funktioita (sekä liuta sekalaisia apufunktioita) jäsenneltynä moduleiksi.
PHP:n suoritustapa eroaa "tavallisista" MVC-ohjelmista aika paljon, joten olio-ohjelmointi ei siinä pääse mielestäni täysin oikeuksiinsa. PHP-skriptin elinkaari on vain yksi pyyntö, joten olioiden elinkaarista ei kauheasti tarvitse huolehtia. Aika moni olio luodaan käytännössä vain kerran (kuten tuo aikaisemman Zend Framework-esimerkin kontrolleri) ja sen tilaa sotketaan sopivilla tavoilla kunnes saadaan haluttu tuloste ulos, eikä skriptin loputtua tarvitse enää huolehtia mistään.
Yksi mielenkiintoinen ohjelmointitapa löytyy JavaScriptistä. Siinähän modulaarisuutta ja tiedon piilotusta saadaan aikaan olioiden lisäksi sulkeumilla. Siis suunnilleen näin:
function lisaaMerkkilaskuri(textarea, maxMerkit) { var laskuri = document.createElement('div'); // ... lisää laskuri vaikka textarean perään ... textarea.onkeydown = function() { laskuri.innerHTML = 'Merkkejä jäljellä: ' + (maxMerkit - /* textarean merkit */) } }
Merkkilaskurin sisäinen tila ja toiminnallisuus on siis kivasti kapseloitu funktion sisään.
Chiman kirjoitti:
Virheitä voi hallita esimerkiksi niin, että funktio palauttaa ennalta määritellyn arvon, joka yksilöi virheen, tai virhetilanne voidaan merkitä johonkin globaaliin muuttujaan, jonka sisältö tarkistetaan erikseen.
Juuri näiden tapojen välttämiseksi poikkeukset keksittiin.
Molemmat tavat ovat virhealttiita ja työläitä, koska kutsujan on aina muistettava tarkistaa virhearvo. Globaali taas on hyvinkin ongelmallinen monisäikeisissä sovelluksissa, ellei siitä erikseen tee säiespesifistä.
Käsittääkseni kääntäjät voivat myös optimoida poikkeusten käsittelyn paremmin kuin eksplisiittiset tarkistukset.
Ihan yleisellä tasolla poikkeuksien viskely ei sinänsä vaadi olioita. Ihan hyvin voitaisiin viskata poikkeus joka ei olisikaan olio vaan vaikka array, tai jossain muussa kielessä vaikka struct. Tietenkin jos kielen määrittelyssä poikkeukset viskataan olioina, niin silloin oliota on pakko käyttää.
map_ kirjoitti:
Juuri näiden tapojen välttämiseksi poikkeukset keksittiin.
Molemmat tavat ovat virhealttiita ja työläitä, koska kutsujan on aina muistettava tarkistaa virhearvo.
Olen osin samaa mieltä ja käytänkin poikkeuksia mielelläni. Samoin luokkia, aina kun se on mielekästä. Virhearvon tarkistaminen ei kuitenkaan ole välttämättä työläämpää kuin poikkeuskäsittelyn kirjoittaminen. Poikkeusten käsittelykin on aina muistettava tehdä, jos aikoo selvitä virhetilanteista.
Tarkoitukseni oli luonnollisesti vain todeta, ettei poikkeusten käyttö ole pakollista virheiden hallinnassa. Wizardin esimerkkikoodi on osa ratkaisua ongelmaan, joka pitäisi nähdä, ennen kuin tilalle voisi ehdottaa järkevää proseduraalista vastinetta.
Tämä jääköön minun puolestani irralliseksi teoreettiseksi pointiksi, koska en ole olio- tai poikkeusvastainen.
Muoks: lisäsin tarkennusta alkuun
Aihe on jo aika vanha, joten et voi enää vastata siihen.