Szöveges állományok
Alapfogalmak
Ebben a leckében azzal foglalkozunk, hogy a PHP-vel hogyan lehet hozzáférni a szerveren tárolt szöveges fájlokhoz, hogyan lehet írni és olvasni őket. A szöveges fájlok a Windows operációs rendszer alatt általában txt kiterjesztésűek, de tulajdonképpen bármilyen fájl kezelhető szöveges fájlként. Gyakori szöveges fájl kiterjesztések például az ini vagy a dat. De szöveges fájlként kezelhető pl egy html fájl is, sőt akár bináris fájlok (pl egy képfájl, mondjuk jpg, vagy akár egy exe fájl) is, bár ezeket beolvasva nem leszünk sokkal okosabbak. Hasonló a helyzet a formázásokat is tartalmazó szövegeket tároló állományokkal (ilyen pl az rtf, doc, pdf), ezeket beolvasva is alapesetben csak kriksz-krakszokat kapunk. Ezeknek a fájloknak meghatározott szerkezetük van, ezt a szerkezetet figyelembe véve lehet értelmes dolgokat kiolvasni belőlük. Itt csak egyszerű szöveges fájlok beolvasásával fogunk foglalkozni, a bináris fájlok olvasására és írására előre megírt, kifejezetten erre a cálra szolgáló függvényeket szoktak használni.
Mielőtt még nekiesnénk a txt fájloknak, meg kell ismernünk a kezelésük technikáját. Tegyük fel, hogy be szeretnénk olvasni egy ilyen fájlt. Mielőtt elkezdjük olvasni, meg kell nyitnunk a fájlt. A megnyitáskor kapunk egy állományleírónak nevezett dolgot, ami az adott állományra hivatkozik. Ez a PHP-ben egy változó, az olvasást és írást végző függvények ennek segítségével érik el a fájlt. Az olvasást végző függvénynek tudnia kell, hogy honnan olvassa az állományt. Például van olyan függvény, ami egy karaktert olvas be a fájlból, van amelyik egy meghatározott hosszúságú karakterláncot. Hogy ez hol kezdődik, azt az állománymutató határozza meg. Az állománymutató a szöveges fájl valamelyik karakterére, vagy az utolsó karakter utáni helyre mutat, hogy pontosan hova, azt a megnyitásakor határozhatjuk meg. Az író és olvasó függvények mindig ott dolgoznak, ahol az állománymutató van. Ha például be szeretnénk olvasni az egész fájlt, érdemes úgy megnyitnunk, hogy az állománymutató az első karakterre mutasson, mert akkor az olvasást végző függvény innentől fogja beolvasni a karaktereket. Ha szeretnénk az állomány végéhez egy karakterláncot hozzáfűzni, akkor pedig az utolsó karakter utáni helyre kell hogy mutasson az állománymutató.
Az olvasást és írást végző függvények az állománymutatót mozgatják, például ha kezdetben az első karakterre mutatott, és mi beolvasunk egy karaktert, akkor a művelet után az állománymutató a következő karakterre fog mutatni. Írás esetén hasonló a helyzet, ha az utolsó utáni karakter utáni helyre írunk valamit, az állománymutató a művelet után a hozzáfűzött karakterek utáni helyre fog mutatni. Emiatt ritka az olyan eset, hogy az állománymutatót nekünk kellene manuálisan mozgatni, ez az írás és olvasás során automatikusan történik.
Megnyitás és bezárás
Egy szöveges fájlt a fopen() függvénnyel tudunk megnyitni. Ezután végezhetünk rajta olvasási és írási műveleteket.
fopen()
A függvénynek két paramétere van. Az első egy karakterlánc, ahol a fájl elérési útját kell megadni, a második szintén egy karakterlánc, ami a megnyitás módját határozza meg. Ez dönti el azt is, hogy hova fog kezdetben mutatni az állománymutató. Példa:
$file = fopen("adatok/nevek.txt", "r");
A függvény visszatérési értéke az állományleíró aminek a típusa resource (erőforrás, ld. Alapok 14. lecke), vagyis közvetlenül nem tudjuk használni, az írást és olvasást végző függvényeknek kell majd átadni paraméterként. A második paraméter "r", ami azt jelenti, hogy olvasásra (read) nyitjuk meg az állományt, az állománymutató ebben az esetben a fájl elejére kerül (vagyis az első karakterre mutat). A fontosabb megnyitási módok:
| művelet | állománymutató kezdeti helyzete | |
|---|---|---|
| r | fájl megnyitása olvasásra | fájl eleje |
| w | fájl megnyitása írásra (létrehozás/ürítés) | fájl eleje |
| a | fájl megnyitása hozzáfűzésre (létrehozás) | fájl vége |
Az állománymutató helyzete esetén a fájl eleje az első karaktert jelenti, a fájl vége pedig az utolsó karakter utáni helyet. Ha a fájl üres, akkor természetesen ez a két hely ugyanazt jelenti. A "létrehozás" azt jelenti, hogy ha ezzel a móddal hívjuk a fopen() függvényt, akkor amennyiben a fájl nem létezik, létrejön. Tehát a fopen() használható fájl létrehozására is. Az "ürítés" jelentése, hogy ha a fájl létezik, akkor ilyen megnyitási mód (w) esetén a fájl teljes tartalma törlődik. Az olvasási mód (r) esetén, ha a fájl nem létezik, warning-ot kapunk, a visszatérési érték pedig false lesz.
Ezt elkerülhetjük a hibaelnyomó operátorral (@), de a későbbi olvasási kísérletek sokkal nagyobb problémákat okozhatnak (pl. fatal error), ezért jobb, ha leellenőrizzük a fájl létezését, ha fennáll ennek a veszélye. Egy fájl létezését ellenőrizhetjük a file_exists() függvénnyel, aminek a paramétere a fájl elérési útja, a visszatérési érték pedig true, ha létezik a fájl, false ha nem.
A mód befolyásolja a használható műveletek körét is. Ez azt jelenti, hogy ha például olvasási módban nyitjuk meg a fájlt, nem engedhetünk rá olyan függvényt, ami írni akarja, és fordítva is igaz. Ha olvasni és írni is akarunk a fájlba, akkor nyissuk meg kétféle módban:
$file_r = fopen("file.txt", "r");
$file_w = fopen("file.txt", "a");
Itt a kétféle állományleíró ugyanarra a fájlra hivatkozik, de az egyinél hívhatjuk az olvasó, a másiknál az író műveleteket (vagyis például egy író függvénynek a $file_w állományleírót kell paraméterként átadnunk).
fclose()
Ha a fájlban elvégeztük a műveleteket amiket akartunk, és nem akarjuk tovább bántani, akkor be szokás zárni az fclose() függvénnyel. Egy paramétere van, az állományleíró, ami a fájlra hivatkozik. Ha meghívjuk, az állományleíró megszűnik, így nem tudjuk tovább használni a fájlt, hacsak meg nem nyitjuk újra. Csak megnyitott fájlt érdemes bezárni, mert ha nem létező állományleírót adunk neki, megint warningokban gyönyörködhetünk.
Ha az író és olvasó műveletek szétválaszthatóak (legtöbbször így van), akkor jobb, ha a két művelet között bezárjuk a fájlt, és utána nyitjuk meg egy másik móddal. Vagyis a fenti kód helyett inkább az utóbbi javasolt (ellenőrzéssel kiegészítve):
if (file_exists("file.txt")){
$file = fopen("file.txt", "r");
// olvasás
fclose($file);
}
$file = fopen("file.txt", "a");
// írás
fclose($file);
Olvasás és írás
Most, hogy kedvünk szerint tudjuk nyitogatatni és csukogatni a fájlokat, próbáljuk meg olvasni és írni őket. Olvasásra és írásra sok függvény használható, én csak a legegyszerűbben használhatókat mutatom meg.
fread()
A függvény fájl olvasására használható. Az első paraméter az állományleíró, a második egy egész szám, amiben azt kell megadni, hogy hány bájtot szeretnénk beolvasni. Ez általában megegyezik a karakterek számával, mivel egy karakter általában 1 bájtot foglal. A visszatérési érték a beolvasott karakterlánc.
A leggyakoribb eset természetesen az, hogy az egész fájlt be szeretnénk olvasni, ekkor második paraméterként a fájl méretét kell átadnunk. Ezt lekérdezhetjük a filesize() függvénnyel, aminek a paramétere a fájl elérési útja, visszatérési értéke pedig a fájl mérete bájtokban. Így egy szöveges fájlt az alábbi módon tudunk beolvasni:
$file = fopen("file.txt", "r");
$content = fread($file, filesize("file.txt"));
fclose($file);
A fájl tartalma az $content változóba kerül. A fájl létezését itt is erősen ajánlott ellenőrizni, mivel a filesize() is nehezen viseli az ilyen helyzeteket. Így egy jobb példa fájl beolvasására az alábbi módon néz ki:
function file_read($filepath){
if (file_exists($filepath)){
$file = fopen($filepath, "r");
$content = fread($file, filesize($filepath));
fclose($file);
}
else{
$content = false;
}
return $content;
}
Ez a függvény a paramétereként kapott fájlt (a fájl elérési útja) megpróbálja beolvasni. Ha nem sikerül, false-t ad vissza, egyébként meg a fájl tartalmát.
fwrite()
Ezzel a függvénnyel fájlba tudunk írni. Használata nagyon egyszerű, első paraméterként át kell adni neki az állományleírót, második pedig a karakterlánc, amit a fájlba szeretnénk beírni. Visszatérési értéke egy egész szám, ami a fájlba írt bájtok számát adja meg. Példa:
$file = fopen("file.txt", "a");
fwrite($file, "Ez egy tengeri T-rex");
fclose($file);
Ebben az esetben az történik, hogy ha a fájl nem létezik, létrejön file.txt néven és belekerül az "Ez egy tengeri T-rex" karakterlánc. Ha már létezett, akkor az eredeti tartalma megmarad, és ugyanez a karakterlánc a végéhez lesz hozzáfűzve. Ha az eredeti tartalmat le akarjuk cserélni erre a szövegre, akkor "w" módban kell megnyitnunk, így megnyitáskor a fájl tartalma törlődik.
Példa
A bemutatott függvényekkel már elvileg bármilyen olvasási és írási műveletet el tudunk végezni egy szöveges fájllal, sőt, létre is tudunk hozni szöveges fájlokat. Nézzünk erre egy egyszerű, űrlappal összekötött példát. Tegyük fel, hogy a nevek.txt-ben neveket tárolunk mégpedig úgy, hogy a nevek egy | karakterrel vannak elválasztva egymástól. Például legyen ilyen a tartalma:
Hörb|Ismeretlen arc|DaniL|Gipsz Jakab
Legyen egy űrlap, ami bekér egy nevet, és elküldés után bővíti a nevek listáját a beírt névvel, és kilistázza a neveket. Akkor hozzuk létre az urlap.php-t az alábbi tartalommal:
<form method="post" action="nevek.php"> Írd be a neved: <input type="text" name="nev" value="" /> <input type="submit" value="OK" /> </form>
A nevek.php lesz a feldolgozó oldal. Ennek tahát az a dolga, hogy beírja a fájlba az új nevet, amit a felhasználó megadott, majd listázza ki az addig beírt neveket. A működésében nincs semmi különös, egyszerűen csak használni kell az imént megismert függvényeket:
<?php
$filename = "nevek.txt";
// fájl írása
$file = fopen($filename, "a");
fwrite($file, "|".$_POST["nev"]);
fclose($file);
// fájl olvasása
$file = fopen($filename, "r");
$nevek = fread($file, filesize($filename));
fclose($file);
// kiírás
$nevtomb = explode("|", $nevek);
foreach ($nevtomb as $nev){
print $nev.", ";
}
?>
Mivel a 4. sorban biztosan létrejön a fájl, akkor is ha nem létezik, az olvasás előtt fölösleges ezt megvizsgálni. A kiíratásnál ebben az esetben az explode()-dal (ld. Alapok 17. lecke) szétdaraboljuk a beolvasott karakterláncot tömbbé, majd egy ciklussal a kívánt formában kiírjuk. A legtöbb esetben szükség van erre, de ebben az egyszerű példában elegendő lett volna az alábbi művelet is:
// kiírás
$nevek = str_replace("|", ", ", $nevek);
print $nevek;
Az str_replace() függvény (ld. Űrlapok 3. lecke) lecseréli a | karaktereket vesszőkre, sőt, ez a kimenet még szebb is, mert az utolsó név után nem lesz vessző.
A fájl jelenleg egy nevekből álló listát tárol, melyben az egyes neveket | karakterek választanak el egymástól. Ha a nevekhez egyéb, hozzájuk kapcsolódó adatokat is tárolni szeretnénk ebben a fájlban, akkor azokat egy másik elválasztó karakterrel kell jelölnünk. Pl:
Hörb;admin;norb,sikerweb,hu|Ismeretlen arc;user;|DaniL;admin;email@email.hu
Ilyen esetben elvárás lehet, hogy valamennyire tudjuk olvasni is a txt fájlt, mondjuk azért, hogy ha szükséges, manuálisan is bele tudjunk nyúlni. Ekkor a | helyett sokkal jobb elválasztó karakter lenne az újsor-karakter, vagyis az egyes nevek és a hozzájuk tartozó adatok külön sorokban szerepelnének:
Hörb;admin;norb,sikerweb,hu
Ismeretlen arc;user;
DaniL;admin;email@email.hu
Az újsor-karakterrel azonban adódhatnak problémák, melyekről külön említést kell tenni ahhoz, hogy probléma nélkül tudjunk használni a fájlt a fenti formában.
Az újsor-karakter
Az újsor-karakterrel vigyázni kell, mert háromféle "változata" is létezik. A három legelterjedtebb operációs rendszer (Windows, Linux, Macintosh) három különféle módon jelöli egy szöveges fájlban a sortörést. A Linux a \n escape-karaktert használja erre (egyéb nevei: LF, line feed, soremelés).
A Macintosh viszont a \r karaktert (CR, carriage return, kocsivissza). A Windows szerint pedig az újsor így néz ki: \r\n, vagyis egy kocsivissza és egy soremelés karakter egymás után írva. Ez azt jelenti, hogy ha Windows alatt létrehozunk egy txt fájlt, majd PHP-vel beolvassuk, akkor minden sor végén egy "\r\n" (két karakterből álló) karakterlánc lesz. Az egyszerűség kedvéért nézzük az alábbi fájlt:
Hörb
Ismeretlen
DaniL
Ezt Windows alatt hoztuk létre. Ha az utolsó sor után nincs sortörés, akkor PHP-vel beolvasva ezt a karakterláncot kapjuk:
"Hörb\r\nIsmeretlen\r\nDaniL"
Ezért ha hozzá akarunk írni egy újabb nevet a fájlhoz, akkor a fenti páldában így érdemes csinálnunk:
fwrite($file, "\r\n".$_POST["nev"]);
Szétszedni pedig értelemszerűen így kell:
$nevtomb = explode("\r\n", $nevek);
Azért kell így csinálni, mert ha manuálisan belenyúlunk a fájlba Windows alatt, egy sortörés leütése esetén a szövegszerkesztő egy "\r\n" karakterláncot helyez el a fájlban. Mivel a netre feltöltött fájl általában Linux környezetben fut (a szervereken Linux szokott futni, mivel a Windows ehhez általában nem elég stabil), ezért számítani kell arra, hogy ha valahonnan letöltünk egy txt fájlt, akkor abban nem feltétlenül Windows-stílusú újsor-karakterek lesznek. Ha Notepad2-vel megnyitjuk a fájlt, ezek a karakterek láthatóvá tehetők a "Nézet/Sorlezárások mutatása" menüponttal, illetve átkonvertálhatjuk az újsor-karaktereket a nekünk megfelelő formájúra a "Fájl/Sorlezárás" menüponttal.
Házi feladat
1.) A példaprogram nem működik megfelelően, ha kezdetben nincs név a nevek.txt fájlban. Ha így futtatjuk le, az első név előtt is lesz | karakter. Próbáljuk meg ezt kiküszöbölni!
Azt kell megoldani, hogy ha eredetileg üres a fájl, akkor a név elé ne írjunk ki | karaktert. Azt hogy egy létező fájl üres, többféleképpen is kideríthetjük. Például megnyitjuk és beolvassuk a tartalmát az fread()-del, és ha ez egy üres karakterláncot ad vissza, akkor a fájl üres. Ennél sokkal egyszerűbb, ha megnézzük az filesize() függvény visszatérési értékét. Ha ez 0, akkor a fájl üres. Használjuk inkább ezt, mert ilyenkor nincs szükség a fájl olvasási módban történő megnyitására:
$filename = "nevek.txt";
// fájl írása
$file = fopen($filename, "a");
if (filesize($filename) === 0){
fwrite($file, $_POST["nev"]);
}
else{
fwrite($file, "|".$_POST["nev"]);
}
fclose($file);
// ...
A fájl olvasása és kiírása ugyanolyan mint a feljebb lévő példában. Azért használtam a === operátort, mert a filesize() false értékkel tér vissza hiba esetén, és ez a == operátor szempontjából a 0-val megegyezik.
2.) Írjuk meg az urlap.php és a nevek.php fájlt (tehát a fenti példaprogramot) arra az esetre, amikor a nevek.txt fájl így néz ki:
Hörb;admin;norb,sikerweb,hu
Ismeretlen arc;user;
DaniL;admin;email@email.hu
Az űrlap elküldése után megjelenő kimenet legyen ilyen:
Hörb (rang: admin, e-mail: norb,sikerweb,hu)
Ismeretlen arc (rang: user, e-mail: )
DaniL (rang: admin, e-mail: email@email.hu)
Az urlap.php fájl lehet például ilyen:
<form method="post" action="nevek.php"> Név: <input type="text" name="nev" value="" /><br /> Rang: <input type="text" name="rang" value="" /><br /> E-mail: <input type="text" name="email" value="" /><br /> <input type="submit" value="OK" /> </form>
Az nevek.php-ben először beszúrjuk az új arc adatait, majd kiírjuk az addig összegyűjtött adatokat:
<?php
$filename = "nevek.txt";
// fájl írása
$file = fopen($filename, "a");
fwrite($file, "\r\n".$_POST["nev"]);
fclose($file);
// fájl olvasása
$file = fopen($filename, "r");
$adatok = fread($file, filesize($filename));
fclose($file);
// kiírás
$adattomb = explode("\r\n", $adatok);
foreach ($adattomb as $sor){
$adat = explode(";", $sor);
print $adat[0]." (rang: ".$adat[1].", e-mail: ".$adat[2].")<br />";
}
?>
Tulajdonképpen csak annyi a különbség, hogy az elválasztó karakter a Windows újsor-karaktere (\r\n) és mivel ez az egyes sorokat választja el, a foreach ciklus a sorokon megy végig. Egy sort szintén szét kell szedni a foreach magjában, ahol a pontosvessző az elválasztó, hogy egyenként tudjuk kiírni az adatokat.







