PHP, fájlkiterjesztések és kódszervezés

A fájlkiterjesztések kérdéséről azt gondolhatja mindenki, hogy ez egyértelmű, pedig nem az. Legalábbis sokaknak valószínűleg nem. Megpróbálom tehát tisztázni, hogy egyáltalán mi a jelentősége a témának, főként PHP programozás esetén, de általában véve is. Kitérek arra, hogy mikor, milyen kiterjesztést érdemes választani, illetve mikor, milyent nem. Ha pedig nem javasolt egy megoldás, akkor mi az, ami a követendő út.

Tartalomjegyzék

Mindent lehet, de nem mindent szabad

Aki már valamennyire ismeri a PHP-t, annak feltűnhetett, hogy viszonylag kevés a megkötés. PHP 7 előtt gyakorlatilag lehetőség sem volt arra, hogy a primitív típusokat kikényszeríthessük függvények paramétereiben. Az pedig, hogy egy objektum tulajdonságánál is kikössük, hogy csak integer típust vehet fel, csak 7.1-ben lesz implementálva. Ettől függetlenül ez még csak opció és nem kötelező.

Azt talán már kevesebben tudják, hogy az is opcionális, milyen kiterjesztésű fájlokat adjon át egy webszerver a PHP-nak értelmezésre. Tehát, ha ahhoz van kedvem, akkor az "index.piroszsiguli" nevű fájl is PHP-ként lesz értelmezve. Sőt, az sem kötelező, hogy egy statikus HTML fájlnak ténylegesen html kiterjesztése legyen. Akár egy txt fájlt is meg lehet jeleníteni html-ként.

Ha viszont ekkora hatalmas szabadságunk van, akkor már csak a képzelet szabhat határt. Ugye? Azt azonban nem árt tudni, hogy mi, milyen következményekkel jár. Ha én azt szeretném, hogy amit elkészítek, a lehető legtöbb környezetben működjön, többször is meg kell gondolnom, mielőtt valami egyedi ötlettel állok elő. A legjobb, ha utánanézek annak, hogyan csinálják a tapasztaltabbak, illetve van-e valamilyen javasolt, a fejlesztői közösség által elismert megoldás. A jó hír az, hogy van. Erre is kitérek majd.

Kiterjesztések jelentősége

Általában, de főleg Windows rendszereken szerepe van annak, hogy egy fájlnév mire végződik. A Windows átnevezéskor fel is szokta hívni a figyelmet, hogy esetleg később nem fogom tudni megnyitni a fájlt. Ez tehát nem csak a felhasználóknak jelzés, hanem azoknak a programoknak, amik majd dolgozni fognak a fájllal. Beleértve az operációs rendszert is.

Fejlesztésnél sincs ez másképp. A fájl tartalma alapján azokhoz tartozik egy tartalomtípus. Angolul "content type" vagy "mime type". A böngésző valójában ezt a típust figyeli, amikor eldönti, mit kezdjen egy betöltött fájllal. Ezt a típust viszont a szerver állítja be, ami fájlkiterjesztések szerint konfigurálható. Még tovább folytatva a gondolatmenetet, egy PHP programból is meghatározható, hogy az általa generált tartalom milyen típusú legyen. Így lehet például akár képet is megjeleníteni.

Az, hogy egy fájl a webszerveren átmegy-e a PHP értelmezőn függhet a tartalomtípustól is, de akár más logikától is. Közvetlenül a fájlkiterjesztéstől, teljes névtől vagy akár mappanévtől is. Az viszont szinte kivétel nélkül elmondható, hogy a ".php"-re végződő fájlokat feldolgozza az értelmező, amennyiben a szerver helyesen van konfigurálva.

PHP-nál használt kiterjesztések

Ahogy már említettem, a webszerverek beállíthatók bármilyen nevű fájl feldolgozására. Ennek megfelelően olyan kiterjesztéseket is látni szerverekben konfigurálva, mint php3, php4, php5, phtml. Tehát a PHP főverzióját is tartalmazzák, vagy éppen jelzik, hogy az adott PHP fájl valami sablonfájl, ami sok HTML-t tartalmaz. Lehet találkozni továbbá az "inc" végződéssel is. Ezt olyan esetekben szokták alkalmazni, amikor a fájl csak más PHP fájlban require-rel vagy include-dal (innen a név) kerül betöltésre, de közvetlenül a böngészőből nem cél, hogy elérhető legyen. Szót érdemel még a ".class.php" kettős kiterjesztés, ahol a "class" azt hivatott jelezni, hogy a fájl egy osztályt tartalmaz. Utóbbinak az értelmezés szempontjából nincs jelentősége, mivel a php végződés miatt úgyis a feldolgozandók közé kerül. Ehhez nagyon hasonló a ".tpl.php", ami viszont a phtml-nek nevezhető a biztonságosabb testvérkéjének, mivel a valódi kiterjesztése a mindig működő "php".

Ebből tehát kiderült, hogy vannak olyan fájlnevek, amik egyáltalán nem is ".php"-re végződnek, így a szerverbeállításra van bízva, hogy működnek-e és vannak azok, amiknél a valódi kiterjesztés előtt egy második ad információt a fájl jellegéről. De mi az oka ezek használatának? Miért van, illetve miért nincs szükségünk íly módon is megkülönböztetni a fájlokat? Erre több válasz is van. Valaki azért teszi, hogy ránézésre könnyebben meg tudja mondani, melyik fájlban kell keresse a megjelenítést, az osztályt vagy egyéb jellegű tartalmakat. Valaki azért, mert így szerverszinten korlátozhatja egyes fájlok elérését. Esetleg azért, mert a program egy mappában tartalmaz különböző célra írt fájlokat, amik közül csak néhányat kell tudni kódból listázni, betölthetővé tenni, stb. És hogy miért nincs ezekre szükség? Az is rögtön sorra kerül, bár mindennek meg lehet a helye. Gyakran mégis kódszervezési hiba téríti el a fejlesztőket.

Miért ne?

Erre a kérdésre szinte már válaszoltam is. Aki figyelt, már megjósolhatja, mit fogok leírni. Először is, ha vannak osztályaim, sablonfájljaim és mondjuk az ezeket betöltő, web-ről elérhető fájljaim, hacsak nem 3 fájl az egész program, külön mappába kell kerüljenek. Azon belül akár almappákra is osztható a tartalom. A mappanévből pedig máris kiderül, amit a kiterjesztés is jelzett volna. Viszont sok fájl helyett egy mappa nevét befolyásolja. A mappa tartalma könnyen listázható, és nem kell fájlneveket ellenőrizni (persze biztonsági okokból az sosem árt). Ráadásul a szerveren az elérés mappaszinten még egyszerűbben korlátozható, mint fájlnév alapján.

A fentiek figyelembe vétele javasolt, de az már határozottan betartandó, hogy amennyiben nem kizárólag saját használatra, vagy olyan szerverre írjuk a programot, aminek konfigurációja a mi kezünkben van, olyan kiterjesztést nem választunk, ami nem biztos, hogy működni fog a célszerveren. Tehát nyílt forráskódú, nagyközönségnek szánt szoftvernél az inc és phtml kiterjesztés kilőve. Attól függően persze, hogy mekkora közönségnek szánom, ha tudok biztosítani virtuális gépet, esetleg valami konténeres megoldást, szükség esetén akár az összes szabályt megszeghetném. De az eddig tárgyalt problémák nem abba a kategóriába tartoznak, amikért érdemes.

Régen ingyenes tárhelyek nem támogatták a htaccess-ben egyes konfigurációk felülírását. Nem végeztem kutatást, hogy ma milyen a helyzet, de ettől függetlenül nem árt minimumon tartani az attól való függést.

A különböző kódszerkesztők, azaz IDE-k működése sem elhanyagolható szempont. A php-t biztosan ismeri egy PHP nyelvhez készült verzió. Jó eséllyel ugyan az inc-t is, vagy legalább beállítható, de ne felejtsük el, hogy ez egy általános elnevezés, vagyis más nyelvek is használhatják, ami újabb problémákat szülhet egy arra fel nem készült IDE-ben. Projektszintű beállításokat bár az EditorConfig segítségével is meg lehet oldani, inkább a kód formázására vonatkozik, mint a kiterjesztések és fájltípusok párosítására (javítsatok ki, ha tévedek)

Javasolt út

Egy két fájlnál ugyan nincs nagy jelentősége, de egy jól megtervezett projektstruktúrával sok probléma eltűnik. Említettem, hogy nem kell újra feltalálni semmit. Itt siet segítségünkre a PSR. Ez ugyan sok mindenről szól, de egyik eleme az osztályok automatikus betöltése és az azokat tartalmazó fájlok elnevezése. Eszerint Az osztályok bekerülnek egy mappába, ami rendszerint az "src" nevet kapja, de ez megválasztható. A fájlok neve megegyezik a bennük levő osztályok neveivel, beleértve a kis- és nagybetűk különbségét is. A fájl kiterjesztése pedig ".php". Az osztályoknak lehetnek névtereik. Ilyenkor minden névtér egy almappát jelent a fájlrendszeren. A névtereket elválasztó jel pedig megfelel a mappák elválasztásának, csak ugye a névterek elválasztása mindig backslash-sel ("\") történik.

A fentiek persze csak az osztályok elnevezéseire adnak választ, de ezt követve nyugodtan készíthetek egy template mappát is a sablonfájloknak. Azt persze semmi sem tiltja, hogy ennek ellenére kettős kiterjesztéssel lássam el a fájlt, de értelmét ekkor már nem látom. A következőkben megmutatom azt is, miért old meg több problémát is ez a módszer.

Szükségtelen kontextusellenőrzés

Régi, jól bevált módszer php programokban, hogy amikor egy fájl csak más fájlokban betöltendő kódot tartalmaz, akkor a betöltendő fájl elejére bekerül egy ehhez hasonló ellenőrzés:

if(!defined('APP_ROOT')) {
     exit('Access denied!');
}

Így, ha valaki közvetlenül böngészőben próbál megnyitni egy fájlt, amiben levő kód ott lefutva, a függőségek betöltése híján hibaüzeneteket eredményezne, vagy nem várt működést idézne elő, ezzel károkat okozva a programban, a felhasználó csak az "Access denied" üzenettel találkozik. Ha viszont a fájlt az alkalmazás töltötte be, előtte definiálta a vizsgált konstanst, tehát biztonságosan lefuthat a kód.

Ezzel több probléma is van. Minden fájlba be kell illeszteni ezeket a sorokat. Azt átnézni, hogy hol felejtettem ki, nem kis munka egy nagy projektben. Ráadásul az előzőekben leírtak alapján a legtöbb esetben nincs is rá szükség. Hiszen, ha én függvényekbe, osztályokba szervezem a kódot, a legtöbb fájl gyakorlatilag ezek definícióját tartalmazza, tehát nem ad semmilyen kimenetet. Szintaktikai hibát leszámítva még hibaüzenetet sem, hisz nincs semmi lefuttatva helyben. A szintaktikai hibán pedig még a konstans ellenőrzése sem segít.

Ha pedig a programlogika nagy része osztályokban van a PSR ajánlása szerint, azokat automatikusan be lehet tölteni akkor, és csak akkor, amikor szükség van rájuk. Tehát, ahol korábban a fejlesztő az "include" vagy "require" sorát írta, ott csak az osztály vagy objektum metódusait hívja. Az osztályt tartalmazó fájl pedig az első alkalommal, amikor azt használni próbálom, betöltődik.

Ezzel pedig akár egyetlen egy php fájlra korlátozható, amit a böngészőn keresztül el kell tudni érni és minden betöltött fájlban elérhető lesz minden szükséges függőség.

Hozzáférés korlátozása

Szép és jó, hogy megfelelő kódszervezéssel a php fájlok elérését nem is nagyon kell korlátozni, de ez addig jó, amíg biztos lehetek abban, hogy semmilyen általam használt, de más által írt program nem követ más elveket. Ezt pedig 100 százalékosan nem lehet garantálni. Ráadásul nem csak php fájlok lesznek a projektben, hanem bármilyen egyéb json vagy akár xml, amik főként olyan konfigurációkat tartalmaznak, amiket nem szeretnék a nagyközönség elé tárni. De még ha csak PHP-ban gondolkodom, akkor is tartalmazhat egy programkönyvtár demo programot, ami lehet erőforrásigényes is. Például egy bonyolult kép generálása. Ezt pedig mégsem kéne futtathatóvá tenni. Ráadásul ezek nem is biztos, hogy behelyezhetők almappába. Ilyen lehet egy composer.json vagy más, természeténél fogva általában a projekt gyökerében elhelyezett fájl.

Ez az a pont, ahol hasonló tartalmú htaccess fájlokkal lehet találkozni:

<FilesMatch "(composer\.json|composer\.lock|.*\.config)$">
    Order Deny,Allow
    Deny from all
</FilesMatch>

Vagy Apache HTTPD 2.4 óta (mivel a 2.2 már elavult) már inkább így:

<FilesMatch "(composer\.json|composer\.lock|.*\.config)$">
    Require all denied
</FilesMatch>

Ezzel lehetne épp a ".inc" végű fájlokat is tiltani, de minek, ha van jobb mód? Vagy még lenne mód htaccess-ben az AddHandler-rel hozzáadni egy kiterjesztést az értelmezendő fájlokhoz, de ahhoz tudni kell, mi a neve a szerveren konfigurált handler-nek. És megint csak nem biztos, hogy egyáltalán működni fog.

A FilesMatch persze csak a fájlneveket tudja tiltani. De bármely mappába betéve egy htaccess-t a következő tartalommal, az adott mappa összes tartalmára érvényes a letiltás:

Require all denied

Ezzel nagyjából minden eddig felvetett gond megoldódott. Tehát, ha már htaccess függő a program, inkább íly módon függjön tőle, mint kockáztassuk, hogy egyáltalán futni fog-e. Mégis lehet, aki nem szívesen helyez szerte a projektben almappákba htaccess fájlokat. Sajnos a Directory és a Location csak szerverkonfiguráció szinten vethető be, htaccess-ben nem. Ezek pedig egy helyen bármely almappát tilthatnák. Van viszont egy jobb megoldás, aminek szépséghibája, hogy szintén nem minden szerveren alkalmaható.

A megoldás egy fordított logika. Nem azt tiltom le, amihez nem akarok hozzáférést adni, hanem csak azt teszem elérhetővé, amihez akarok. Ekkor lesz a projektben egy "web" vagy "www" esetleg "public" mappa, amiben minden olyan fájl kap helyet, ami böngészőből betölthető. Erre a mappára lesz állítva a szerveren a dokumentum gyökér, miközben a projekt gyökeréhez is lesz a PHP-nek joga, így a függőségeket változatlanul eléri.

Mivel ez utóbbi biztonsági kockázattal nem jár, ha ma nincs is felkészülve rá minden szolgáltató, nem kizárt, hogy ez változik. Ha pedig legalább a htaccess-ben a rewrite engine elérhető, még mindig megvan a lehetőség az eredetileg dokumentumgyökérre átirányítani minden kérést a böngészőtől, csak macerásabb módszer.

Összefoglaló

Kiderült tehát, hogy a kiterjesztések helyett inkább a projekt struktúrájával érdemes játszani. Arról is szó esett, hogy érdemes a htaccess-től való függést minimumon tartani és csak egy hasznos pluszként kezelni. Főleg, ha cél a szoftver nagyobb körben történő terjesztése. Javasolt követni a PSR ajánlásait, de persze különböző ajánlásoknak különböző jelentősége lehet. Ez alapján némelyiktől való eltérés talán nem vált ki közfelháborodást... A PSR-t támogató IDE-kkel viszont szorosabb barátságot köthet a kód és ezáltal a fejlesztő.

Ennyi szöveg után mégis a legfontosabbnak és a leginkább megjegyzendőnek azt tartom, mindig mérlegeljük a következményeket és a lehető legjobban csökkentsük a kockázatokat a program írása közben. Ez némi utánajárást igényelhet, de megéri.

Kategóriák: 
Megosztás/Mentés