Lyhyesti: PHP on dynaamisesti tyypitetty: arvon tyyppi selviää vasta ajon aikana, ja ilman lisämääräyksiä PHP muuntaa arvoja tarvittaessa automaattisesti. Versiosta PHP 7 lähtien parametreille, paluuarvoille ja ominaisuuksille voi kuitenkin määrittää tyypit, ja declare(strict_types=1) tekee tarkistuksesta tiukan. Kun säännöt tuntee ja käyttää tiukkoja vertailuja ===, vältyy kokonaiselta luokalta logiikka- ja tietoturvavirheitä.
Dynaamisesti tyypitetty, mutta ei tyypitön
PHP:ssä lauseessa $preis = 19.90; ei tarvitse ilmoittaa tyyppiä. PHP tunnistaa, että kyseessä on liukuluku. Jos arvo myöhemmin yhdistetään merkkijonoon, PHP muuntaa sen. Tätä toimintaa kutsutaan nimellä type juggling eli automaattinen tyypinmuunnos. Se tekee aloittamisesta helppoa mutta voi johtaa yllättäviin tuloksiin.
Päättäjälle tällä on merkitystä, koska monet vanhemmat PHP-sovellukset on kirjoitettu ilman tyyppimerkintöjä. Sellaista koodia on vaikeampi tarkastaa ja modernisoida. Uudemmat hankkeet käyttävät tyyppejä johdonmukaisesti, jolloin virheet tulevat näkyviin aiemmin ja staattinen analyysi työkaluilla kuten PHPStan tai Psalm pääsee kunnolla oikeuksiinsa.
PHP:n tyypit pähkinänkuoressa
PHP-käsikirja mainitsee yhdeksän sisäistä tyyppiä, jotka millä tahansa lausekkeella voi olla. Ne jaetaan yleensä kolmeen ryhmään:
| Ryhmä | Tyypit | Esimerkki |
|---|---|---|
| Skalaarityypit | bool, int, float, string |
true, 42, 3.5, "Text" |
| Yhdistetyt tyypit | array, object, callable |
[1, 2], new DateTime() |
| Erikoistyypit | null, resource |
null, avattu tiedosto |
Tyyppitarkistuksiin käsikirja suosittelee is_*()-funktioita, kuten is_int(). Tutkimiseen sopii get_debug_type(), joka palauttaa yleiset tyyppinimet versiosta PHP 8.0.0 lähtien, kun taas vanhempi gettype() palauttaa esimerkiksi "integer" ja "double".
Automaattisen muunnoksen rinnalla on nimenomainen muunnos: castilla kuten (int) $wert tai funktiolla settype() koodi päättää itse, minkä tyyppinen arvon pitää olla. Se on tavallista lomakkeiden syötteissä, sillä kaikki, mikä tulee kautta $_GET tai $_POST, on aluksi merkkijono tai taulukko. Cast ei kuitenkaan tarkista, oliko arvo järkevä: merkkijonosta "abc" tulee hiljaa 0. Syötteille aito tarkistus funktiolla filter_var() on siksi yleensä parempi valinta.
Luokat ja rajapinnat ovat PHP:ssä myös tyyppejä. Funktio voi siis vaatia, että parametri on tietyn luokan olio tai täyttää tietyn rajapinnan. Miten luokat, rajapinnat ja enumit toimivat yhdessä, selitetään sivulla Olio-ohjelmointi PHP:ssä.
Tyyppimääritykset: versioista PHP 7.1 versioon 8.3
Tyyppimääritykset kertovat, mitä arvoja funktio ottaa vastaan ja palauttaa. Tyyppijärjestelmä on kasvanut monen version aikana. Käsikirjan muutoslokin mukaan mukaan tulivat muun muassa seuraavat mahdollisuudet:
| Versio | Uutta |
|---|---|
| 7.1.0 | nullable-tyypit (?int), void, iterable |
| 7.2.0 | object |
| 7.4.0 | tyypitetyt luokan ominaisuudet |
| 8.0.0 | unioni-tyypit (useita sallittuja tyyppejä yhdessä määrityksessä), mixed, paluutyyppi static |
| 8.1.0 | leikkaustyypit (A&B), paluutyyppi never |
| 8.2.0 | DNF-tyypit, literaalityyppi true, null ja false omina tyyppeinään |
| 8.3.0 | tyypitetyt luokka-, rajapinta-, trait- ja enum-vakiot |
DNF tarkoittaa disjunktiivista normaalimuotoa, ja se sallii yhdistelmiä kuten (A&B)|null. PHP-versioita suunnitteleva näkee tästä taulukosta myös, mitä kielen ominaisuuksia sovellus edellyttää.
strict_types: muunnos vai TypeError
Ilman lisämääräystä PHP toimii muuntavassa tilassa. Funktio, jolla on int-parametreja, ottaa silloin vastaan myös arvon 1.5 ja tekee siitä 1; versiosta PHP 8.1 lähtien tämä tietoa menettävä muunnos on merkitty vanhentuneeksi. Kun tiedoston alussa on declare(strict_types=1), PHP heittää sen sijaan TypeError-virheen.
<?php
declare(strict_types=1);
function summe(int $a, int $b): int
{
return $a + $b;
}
echo summe(1, 2); // 3
echo summe(1.5, 2.5); // TypeError, statt stillschweigend 3
Kaksi hienoisuutta on tärkeitä. Ensinnäkin strict_types koskee kutsuja siitä tiedostosta, jossa se on, ei siellä määriteltyjä funktioita. Jos tiedosto ilman strict_types-määrystä kutsuu samaa funktiota, arvot muunnetaan. Toiseksi kokonaisluku on sallittu myös tiukassa tilassa paikassa, jossa odotetaan liukulukua.
Löysät ja tiukat vertailut
Operaattori == vertaa tyypinmuunnoksen jälkeen, === vertaa arvoa ja tyyppiä ilman muunnosta. Käsikirja suosittelee useimmissa tapauksissa operaattoreita === ja !==, koska muunnossäännöt eivät aina ole ilmeisiä.
<?php
var_dump(0 == "a"); // PHP 7: true, PHP 8: false
var_dump("1" == "01"); // true, molemmat ovat numeerisia merkkijonoja
var_dump("1" === "01");// false, gleicher Typ, aber andere Zeichenkette
var_dump(1 == true); // true
var_dump(1 === true); // false
Versiossa PHP 8.0 lukujen ja ei-numeeristen merkkijonojen vertailut muuttuivat järkevämmiksi. Vanhemmat sovellukset voivat päivityksen jälkeen toimia eri tavalla, esimerkiksi switch-lohkoissa. Myös versiossa PHP 8.0 käyttöön tullut match-lauseke vertaa aina tiukasti. Miten löysät vertailut johtavat tietoturva-aukkoihin, esimerkiksi tunnisteiden tai salasanojen tarkistuksessa, kuvaa artikkeli Type juggling: löysät vertailut tietoturvariskinä.
Tyypit, päivitykset ja PHP2Shell
Tiukka tyyppijärjestelmä tekee sovelluskoodista kestävämpää, mutta se ei suojaa virheiltä itse tulkissa. PHP2Shell-haavoittuvuudet CVE-2026-109618 ja CVE-2026-109619 sijaitsevat PHP:n muistin- ja pyynnönhallinnassa. Ne on korjattu versioissa PHP 8.5.12, 8.4.27, 8.3.36 ja 8.2.35; sen jälkeen kaikkien PHP-prosessien on käynnistyttävä uudelleen.
Tyyppijärjestelmällä on silti rooli, nimittäin päivityksessä. Sovellukset, jotka ovat yhä versiossa PHP 8.1 tai vanhemmassa, eivät saa PHP-projektilta enää virallisia päivityksiä. Hyvin tyypitetty koodi on selvästi helpompi nostaa tuettuun versioon, koska työkalut löytävät yhteensopimattomuudet ajoissa. Vanhemmissa hankkeissa apuna on siirtymämme tuettuun PHP-versioon.
Ottakaa strict_types käyttöön vaiheittain uusissa ja uudistetuissa tiedostoissa. Näin tyyppiturvallisuus kasvaa ilman, että vanha koodi hajoaa kerralla.
Miten PHP SOS auttaa konkreettisesti?
- Selvitämme, mitä PHP-versiota sovelluksenne vähintään tarvitsee, ja vertaamme sitä korjattuihin versioihin.
- Ajamme koodille staattisen analyysin ja merkitsemme kohdat, joissa tietoturvan kannalta olennaisissa tarkistuksissa on löysiä vertailuja.
- Suunnittelemme päivitykset niin, että muuttuneet vertailusäännöt testataan etukäteen.
- Asennamme PHP-päivityksen, käynnistämme kaikki worker-prosessit uudelleen ja dokumentoimme tilanteen.
Usein kysytyt kysymykset
Onko PHP vahvasti vai heikosti tyypitetty?
PHP on dynaamisesti tyypitetty: muuttujan tyyppi selviää vasta ajon aikana. Ilman strict_types-asetusta PHP muuntaa arvoja tarpeen mukaan. Tyyppimäärityksillä ja strict_types-asetuksella koodista voi kuitenkin tehdä hyvin tiukan.
Mitä declare(strict_types=1) tekee?
Se kytkee kyseisen tiedoston kutsuissa pois skalaariarvojen automaattisen muuntamisen. Jos arvo ei sovi ilmoitettuun tyyppiin, PHP heittää TypeErrorin. Ainoa poikkeus on kokonaisluku paikassa, jossa odotetaan liukulukua.
Miksi 0 == "a" on PHP 8:ssa epätosi mutta PHP 7:ssä tosi?
PHP 7:ään asti ei-numeerinen merkkijono muunnettiin vertailussa luvun kanssa luvuksi, ja "a" muuttui nollaksi. Versiosta PHP 8.0 lähtien PHP ei tässä tapauksessa vertaa enää numeerisesti, ja tulos on false.
Suojaavatko tyypit PHP:n omilta tietoturva-aukoilta?
Eivät. Tyypit ehkäisevät virheitä sovelluskoodissanne. Tulkin aukot, kuten PHP2Shell, suljetaan vain PHP-päivityksellä. Modernit tyyppiominaisuudet edellyttävät kuitenkin ajantasaisia PHP-versioita, ja siksi ne ovat hyvä syy päivittää.