Kirjoittaja: Antti Laaksonen (2011).
Kaikissa ohjelmointikielissä on omat hyvät ja huonot puolensa. Vaikka PHP:ssä on paljon hyvää, siinä on myös asioita, jotka aiheuttavat ohjelmoijille usein harmaita hiuksia ja joista on hyvä olla tietoinen etukäteen.
Jotkut ohjelmoijat suhtautuvat PHP-kieleen hyvin kielteisesti. Toisinaan taustalla on ylimielinen asenne: kansan syvät rivit käyttävät PHP:tä, joten se ei voi olla hyvä. Toisaalta osa PHP:n saamasta kritiikistä on myös oikeutettua.
Perusongelma ohjelmointikielen kehittämisessä on, että kerran valittua suunnitteluratkaisua voi olla vaikeaa muuttaa myöhemmin. Myös PHP:ssä monet ongelmat ovat historiallisia, ja jos kielen saisi suunnitella uudestaan puhtaalta pöydältä, ne voisi korjata helposti. Näin ei voida kuitenkaan tehdä, koska olemassa olevat PHP-sovellukset luottavat kielen nykyisiin piirteisiin eivätkä toimisi kielen uudistuksen jälkeen.
Onko olemassa asioita, joita ei voi tehdä PHP:llä vaan on pakko siirtyä toiseen kieleen? Ohjelmoinnin teorian näkökulmasta vastaus on yksinkertainen: PHP pystyy kaikkeen samaan kuin muutkin kielet.
Syynä tähän on, että PHP on Turing-täydellinen kieli. Turingin kone on yksinkertainen tietokoneen malli, joka koostuu muistinauhasta ja sitä käsittelevästä laitteesta. Ohjelmointikieli on Turing-täydellinen, jos sillä voi simuloida Turingin konetta. PHP:ssä tämä onnistuu helposti muutaman kymmenen rivin koodilla, joten PHP on Turing-täydellinen. Kiinnostava asia on, että tähän mennessä ei ole kehitetty ohjelmointikieltä, jonka laskentakyky ylittäisi Turingin koneen. Niinpä PHP on samalla viivalla muiden kielten kanssa.
PHP:ssä tiedon tyyppi on häilyvä käsite, koska PHP tekee runsaasti automaattisia tyyppimuunnoksia. Esimerkiksi seuraavassa koodissa muuttujat $a
ja $b
ovat merkkijonoja, mutta ne muuttuvat automaattisesti luvuiksi kertolaskussa.
<?php $a = "5"; $b = "7"; echo $a * $b; ?>
Tyyppien huoleton käsittely ei ole yksiselitteisesti hyvä tai huono asia. Toisaalta se mahdollistaa joustavan ohjelmoinnin, kun muuttujan sisältö tulkitaan tilanteesta riippuen sopivalla tavalla. Välillä kuitenkin automaattisten tyyppimuunnosten seuraukset voivat olla odottamattomia.
Funktio strpos
tarjoaa hyvän esimerkin tyyppimuunnosten ongelmista. Funktion tarkoituksena on kertoa, missä kohtaa merkkijonoa toinen merkkijono esiintyy ensimmäisen kerran. Esimerkiksi kutsu strpos("esimerkki", "me")
tuottaa arvon 3, koska merkkijono "me" esiintyy kohdassa 3 merkkijonossa "esimerkki".
Jos merkkijonon esiintymää ei ole, strpos
palauttaa arvon false
. Seuraava koodi vaikuttaa siis toimivalta:
Seuraava esimerkki paljastaa kuitenkin koodissa olevan ongelman:
Vaikka merkkijono "esi" esiintyy merkkijonossa "esimerkki", koodi tulostaa "Merkkijonoa ei löytynyt!" Ongelmana on, että "esi" esiintyy heti merkkijonon alussa eli kohdassa 0. PHP tulkitsee kuitenkin, että luku 0 vastaa totuusarvoa false
, minkä vuoksi ehto pitää paikkansa.
Ratkaisu ongelmaan on käyttää vertailussa merkintää ===
, joka vaatii, että verrattavat arvot ovat myös tyypiltään samat. Tämän muutoksen jälkeen koodin toiminta on oikea:
PHP sisältää paljon hyödyllisiä funktioita, mutta funktiokirjastoa ei voi pitää huolellisesti suunniteltuna. Ongelmia ovat:
str_replace
alkuosan str
jälkeen on alaviiva mutta funktiossa strtoupper
ei ole alaviivaa.implode
ja join
toimivat täsmälleen samalla tavalla.str_rot13
, joka tekee merkkijonolle ROT13-muunnoksen eli siirtää kaikkia kirjaimia 13 kirjainta eteenpäin aakkosissa.Toisin kuin useimmissa nykyaikaisissa kielissä PHP:ssä kaikki funktiot ovat suoraan käytettävissä. Esimerkiksi satunnaisen luvun voi arpoa seuraavasti:
Python-kielessä vastaava funktio on kirjastossa random
, josta se täytyy ottaa erikseen käyttöön import
-rivillä:
import random print random.randint(1, 10)
Monien mielestä funktioiden jakaminen erillisiin kirjastoihin olisi selkeämpi ratkaisu kuin PHP:n käytäntö.
Seuraava PHP-koodi ei toimi oikein, koska koodin alussa muuttujan nimenä on virheellisesti $etunini
eikä $etunimi
:
Vaikka muuttujaa $etunimi
ei ole olemassa, tämä ei keskeytä koodin suoritusta. PHP:n asetukset määrittävät, tuleeko puuttuvasta muuttujasta virheilmoitusta. Jos virheilmoitukset ovat käytössä, sivulle tulee seuraava ilmoitus määrittelemättömästä muuttujasta:
Notice: Undefined variable: etunimi in testi.php on line 4
Kuitenkin jos virheilmoitukset on piilotettu, ainoa seuraus kirjoitusvirheestä muuttujan nimessä on, että ensimmäinen echo
-komento ei tulosta mitään.
PHP:n virheilmoitukset kannattaa pitää käytössä koodin toteutusvaiheessa, koska muuten monien virheiden korjaaminen ja jopa huomaaminen on vaikeaa. Virheilmoitukset saa yleensä näkyviin kirjoittamalla koodin alkuun seuraavat rivit:
<?php ini_set("display_errors", 1); ini_set("error_reporting", E_ALL | E_STRICT); // koodi tulee tähän ?>
Rivit muuttavat tiedoston suorituksen ajaksi PHP:n asetuksia display_errors
ja error_reporting
, jotka vaikuttavat virheilmoitusten näyttämiseen. Asetuksia pystyy myös muuttamaan pysyvästi tiedostosta php.ini
, jos tiedoston muuttaminen on mahdollista.
PHP:n alkuaikoina taulukot $_GET
, $_POST
, $_COOKIE
ja vastaavat olivat tuntemattomia. Niiden sijasta esimerkiksi lomake- ja evästetiedot olivat suoraan käytettävissä samannimisinä muuttujina.
Seuraavassa on sama koodi kirjoitettuna vanhalla ja uudella tyylillä:
<?php // vanha tyyli echo "Hei {$nimi}, ikäsi on siis {$ika} vuotta!"; ?>
<?php // uusi tyyli $nimi = $_GET["nimi"]; $ika = $_GET["ika"]; echo "Hei {$nimi}, ikäsi on siis {$ika} vuotta!"; ?>
Vanhan tyylin ongelmana on, että hyökkääjä pystyy määrittelemään mielensä mukaan PHP:n muuttujia. Tämä voi aiheuttaa tietoturvaongelman, jos tärkeitä muuttujia ei määritellä koodissa kaikissa tilanteissa. Tarkastellaan esimerkiksi seuraavaa tiedostoa vaara.php
:
<?php include("funktiot.php"); if (sisalla()) { $kayttaja = hae_kayttaja(); } if (isset($kayttaja)) { echo "salainen sisältö"; } ?>
Ideana on, että muuttuja $kayttaja
sisältää kirjautuneen käyttäjän nimen. Kuitenkin hyökkääjä voi mennä sivulle vaara.php?kayttaja=aapeli
, jolloin muuttujan $kayttaja
sisällöksi tulee "aapeli" riippumatta funktioista sisalla
ja hae_kayttaja
.
Taulukoiden käyttäminen samannimisten muuttujien sijasta on ollut jo pitkään yleinen käytäntö PHP-ohjelmoinnissa. Kuitenkin joillakin palvelimilla samannimisiä muuttujia voi käyttää edelleen, koska jotkin vanhat PHP-koodit olettavat niiden toiminnan. PHP:n asetus register_globals
säätelee, voiko samannimisiä muuttujia käyttää.
Opassarjan osassa 13 esitelty SQL-injektio on tyypillinen PHP-koodissa oleva tietoturvaongelma. Aiemmin PHP:ssä oli usein käytössä ominaisuus, joka pyrki estämään SQL-injektion automaattisesti lisäämällä lomaketietoihin kenoviivoja.
Ominaisuudesta on hyötyä esimerkiksi seuraavassa koodissa:
<?php $nimi = $_GET["nimi"]; $sql = "SELECT * FROM kayttajat WHERE nimi = '$nimi'"; // jne. ?>
Koodissa näyttää päältä päin olevan SQL-injektion mahdollisuus, koska lomakemuuttujan nimi
arvo tulee sellaisenaan osaksi kyselyä. Kuitenkin jos automaattiset kenoviivat ovat käytössä, jokaisen muuttujassa olevan heittomerkin eteen ilmestyy automaattisesti kenoviiva, mikä estää SQL-injektion. Esimerkiksi jos muuttujan arvona on Aap'eli
, se muuttuu automaattisesti muotoon Aap\'eli
.
Vaikka automaattiset kenoviivat voivat estää turvallisuusaukkoja ohjelmoijan huomaamatta, ominaisuus aiheuttaa myös ongelmia. Jos esimerkiksi koodi tulostaa lomakemuuttujan sisällön suoraan sivulle, siinä saattaa näkyä häiritseviä ylimääräisiä kenoviivoja. Joillakin palvelimilla automaattiset kenoviivat voivat olla edelleen käytössä, jolloin nykyaikainen koodi ei toimi oikein. PHP:n asetus magic_quotes_gpc
säätelee, ovatko automaattiset kenoviivat käytössä.
Antti Laaksonen kirjoitti:
Esimerkiksi funktiossa str_replace alkuosan str jälkeen on alaviiva mutta funktiossa strtoupper ei ole alaviivaa.
Taitaa johtua siitä että strreplace:ssa olisi kaksi ärrää peräkkäin eikä se vaikuta järkevältä. str_replace on myös helpommin luettava.
Hyvä pointti, mutta esimerkiksi funktiossa str_shuffle on alaviiva ja funktiossa strstr ei ole, vaikka molemmissa etuliitteen jälkeen tulee kirjain s.
Antti Laaksonen kirjoitti:
ErroR++ kirjoitti:
Antti Laaksonen kirjoitti:
Esimerkiksi funktiossa str_replace alkuosan str jälkeen on alaviiva mutta funktiossa strtoupper ei ole alaviivaa.
Taitaa johtua siitä että strreplace:ssa olisi kaksi ärrää peräkkäin eikä se vaikuta järkevältä. str_replace on myös helpommin luettava.
Hyvä pointti, mutta esimerkiksi funktiossa str_shuffle on alaviiva ja funktiossa strstr ei ole, vaikka molemmissa etuliitteen jälkeen tulee kirjain s.
Menee vähän offtopicin puolelle, mutta tässä puhuttiinkin r-kirjaimesta, ei ässästä.
Hengilö, oppaassa taas ei puhuta r-kirjaimesta, joten vaikka ErroR++ on keksinyt yhdelle alaviivalle selityksen, muissa PHP:n funktioissa vastaava selitys ei päde, kuten Antin esimerkki osoittaa. Sitä paitsi onhan olemassa myös r-kirjaimellisia vastaesimerkkejä kuten strrev ja strripos.
Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.