Lühidalt: PHP on dünaamiliselt tüpiseeritud: väärtuse tüüp selgub alles täitmisajal ja ilma lisanõueteta teisendab PHP väärtusi vajaduse korral automaatselt. Alates PHP 7-st saab parameetritele, tagastusväärtustele ja omadustele siiski tüübid anda ning declare(strict_types=1) muudab kontrolli rangeks. Kes tunneb reegleid ja kasutab rangeid võrdlusi operaatoriga ===, väldib tervet klassi loogika- ja turvavigu.
Dünaamiliselt tüpiseeritud, kuid mitte tüübita
PHP-s ei pea kirjas $preis = 19.90; tüüpi märkima. PHP tuvastab, et tegu on ujukomaarvuga. Kui väärtus seotakse hiljem sõnega, teisendab PHP selle. Seda käitumist nimetatakse Type Juggling'uks ehk automaatseks tüübiteisenduseks. See teeb alustamise lihtsaks, kuid võib viia üllatavate tulemusteni.
Otsustajale on see oluline, sest paljud vanemad PHP rakendused on kirjutatud ilma tüübimärgeteta. Sellist koodi on raskem kontrollida ja moderniseerida. Uuemad projektid kasutavad tüüpe järjekindlalt, mis toob vead varem nähtavale ja teeb staatilise analüüsi tööriistadega nagu PHPStan või Psalm alles päriselt mõjusaks.
PHP tüübid ülevaatlikult
PHP käsiraamat nimetab üheksa sisemist tüüpi, mis igal avaldisel võivad olla. Tavaliselt jaotatakse need kolme rühma:
| Rühm | Tüübid | Näide |
|---|---|---|
| Skalaarsed tüübid | bool, int, float, string |
true, 42, 3.5, "Text" |
| Liittüübid | array, object, callable |
[1, 2], new DateTime() |
| Erilised tüübid | null, resource |
null, avatud fail |
Tüübikontrolliks soovitab käsiraamat is_*() funktsioone nagu is_int(). Uurimiseks sobib get_debug_type(), mis annab alates PHP 8.0.0-st tavalised tüübinimed, samal ajal kui vanem gettype() tagastab näiteks "integer" ja "double".
Automaatse teisenduse kõrval on olemas ka selgesõnaline: castiga nagu (int) $wert või funktsiooniga settype() määrab kood ise, mis tüüpi väärtus olema peab. See on vormisisestuste puhul tavaline, sest kõik, mis tuleb sisse $_GET või $_POST kaudu, on esialgu sõne või massiiv. Cast ei kontrolli siiski, kas väärtus oli mõistlik: „abc“ muutub vaikselt nulliks 0. Sisendite puhul on seetõttu enamasti parem valik päris kontroll funktsiooniga filter_var().
Ka klassid ja liidesed on PHP-s tüübid. Funktsioon võib seega nõuda, et parameeter oleks teatud klassi objekt või täidaks teatud liidest. Kuidas klassid, liidesed ja enumid koos töötavad, selgitab leht Objektorienteeritus PHP-s.
Tüübideklaratsioonid: PHP 7.1-st 8.3-ni
Tüübideklaratsioonid määravad, milliseid väärtusi funktsioon vastu võtab ja tagastab. Tüübisüsteem on kasvanud paljude versioonide jooksul. Käsiraamatu muudatuste logi järgi lisandusid muu hulgas järgmised võimalused:
| Versioon | Uus |
|---|---|
| 7.1.0 | nullable tüübid (?int), void, iterable |
| 7.2.0 | object |
| 7.4.0 | tüpiseeritud klassi omadused |
| 8.0.0 | Union-tüübid (mitu lubatud tüüpi ühes märkes), mixed, tagastustüüp static |
| 8.1.0 | Intersection-tüübid (A&B), tagastustüüp never |
| 8.2.0 | DNF-tüübid, literaaltüüp true, null ja false eraldi tüüpidena |
| 8.3.0 | tüpiseeritud klassi-, liidese-, trait'i- ja enumi konstandid |
DNF tähendab disjunktiivset normaalkuju ja lubab kombinatsioone nagu (A&B)|null. Kes PHP versioone planeerib, näeb sellest tabelist ka seda, milliseid keelevahendeid rakendus eeldab.
strict_types: teisendus või TypeError
Ilma lisamärketa töötab PHP teisendavas režiimis. Funktsioon, mille parameetrid on int, võtab siis vastu ka 1.5 ja teeb sellest 1; alates PHP 8.1-st on see kadudega teisendus märgitud iganenuks. Kui faili alguses on declare(strict_types=1), viskab PHP selle asemel vea TypeError.
<?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, vaikselt 3 asemel
Kaks peensust on olulised. Esiteks kehtib strict_types väljakutsetele failist, milles see on, mitte seal defineeritud funktsioonidele. Kui strict_types'ita fail kutsub sama funktsiooni välja, siis teisendatakse. Teiseks on täisarv seal, kus oodatakse ujukomaarvu, lubatud ka ranges režiimis.
Lõdvad ja rangad võrdlused
Operaator == võrdleb pärast tüübiteisendust, === võrdleb väärtust ja tüüpi ilma teisenduseta. Käsiraamat soovitab enamikul juhtudel === ja !==, sest teisendusreeglid ei ole alati ilmselged.
<?php
var_dump(0 == "a"); // PHP 7: true, PHP 8: false
var_dump("1" == "01"); // true, mõlemad on numbrilised sõned
var_dump("1" === "01");// false, sama tüüp, kuid erinev sõne
var_dump(1 == true); // true
var_dump(1 === true); // false
PHP 8.0-ga muutusid arvude ja mittenumbriliste sõnede võrdlused mõistlikumaks. Vanemad rakendused võivad uuenduse tõttu käituda teisiti, näiteks switch-plokkides. Samuti PHP 8.0-ga lisandunud avaldis match võrdleb alati rangelt. Kuidas lõdvad võrdlused viivad turvaaukudeni, näiteks tokeni- või paroolikontrollides, kirjeldab artikkel Type Juggling: lõdvad võrdlused turvariskina.
Tüübid, uuendused ja PHP2Shell
Range tüübisüsteem muudab rakenduse koodi töökindlamaks, kuid ei kaitse vigade eest interpretaatoris endas. PHP2Shelli turvaaugud CVE-2026-109618 ja CVE-2026-109619 asuvad PHP mälu- ja päringuhalduses. Parandatud on need versioonides PHP 8.5.12, 8.4.27, 8.3.36 ja 8.2.35; pärast seda peavad kõik PHP protsessid uuesti käivituma.
Tüübisüsteemil on siiski oma roll, nimelt uuendamisel. Rakendused, mis on veel PHP 8.1 või vanema külge kinni, ei saa PHP projektilt enam ametlikke uuendusi. Hästi tüpiseeritud koodi on tunduvalt lihtsam tõsta toetatud versioonile, sest tööriistad leiavad sobimatused varakult. Vanemate projektide puhul toetab meie üleminek toetatud PHP versioonile.
Aktiveerige strict_types järk-järgult uutes ja ümbertehtud failides. Nii kasvab tüübiturvalisus, ilma et vana kood korraga katki läheks.
Kuidas PHP SOS konkreetselt aitab?
- Kontrollime, millist PHP versiooni teie rakendus vähemalt vajab, ja võrdleme seda parandatud versioonidega.
- Laseme koodil käia staatilise analüüsi läbi ja märgime kohad, kus turvakontrollides on lõdvad võrdlused.
- Planeerime uuendused nii, et muutunud võrdlusreeglid saavad eelnevalt testitud.
- Paigaldame PHP uuenduse, käivitame kõik workerid uuesti ja dokumenteerime seisu.
Korduma kippuvad küsimused
Kas PHP on tugevalt või nõrgalt tüpiseeritud?
PHP on dünaamiliselt tüpiseeritud: muutuja tüüp selgub alles täitmisajal. Ilma strict_types'ita teisendab PHP väärtusi vajaduse korral. Tüübideklaratsioonide ja strict_types'iga saab koodi siiski väga rangeks muuta.
Mida teeb declare(strict_types=1)?
See lülitab väljakutsete jaoks vastavast failist välja skalaarsete väärtuste automaatse teisendamise. Kui väärtus ei sobi deklareeritud tüübiga, viskab PHP vea TypeError. Ainus erand on täisarv seal, kus oodatakse ujukomaarvu.
Miks on 0 == "a" PHP 8-s väär, PHP 7-s aga tõene?
Kuni PHP 7-ni teisendati mittenumbriline sõne arvuga võrdlemisel arvuks, „a“ muutus nulliks. Alates PHP 8.0-st ei võrdle PHP sellisel juhul enam numbriliselt ja tulemus on false.
Kas tüübid kaitsevad PHP enda turvaaukude eest?
Ei. Tüübid takistavad vigu teie rakenduse koodis. Interpretaatori turvaauke nagu PHP2Shell suleb ainult PHP uuendus. Kaasaegsed tüübifunktsioonid eeldavad siiski ajakohaseid PHP versioone ja on seega hea põhjus uuendamiseks.