Dinamikus weboldalak előállítása szerveroldali technológiákkal

Horváth Győző, Visnovitz Márton

Belépő a tudás közösségébe

Informatika szakköri segédanyag

A kiadvány „A felsőoktatásba bekerülést elősegítő készségfejlesztő és kommunikációs programok megvalósítása, valamint az MTMI szakok népszerűsítése a felsőoktatásban” (EFOP-3.4.4-16-2017-006) című pályázat keretében készült 2017-ben.

ISBN 978-963-284-998-0

Tartalomjegyzék

  1. Bevezetés
  2. Szerveroldali dinamikus webprogramozás és eszközei
  3. A PHP programozási nyelv
  4. Adatok dinamikus megjelenítése
  5. Adatbázis használata
  6. Bemenet kezelése
  7. Kódszervezés
  8. Munkamenet-kezelés
  9. Fájlkezelés

Bevezetés

Dinamikus weboldalak

Manapság számítógépes tevékenységeink tekintélyes részét a böngészőprogram használata jelenti. Információkat keresünk, híreket olvasunk, videókat nézünk, kapcsolatot teremtünk az ismerőseinkkel, egyre több hivatalos ügyet el tudunk intézni online, egyszerűbb játékokat is játszhatunk. Mindezeket valamilyen webes alkalmazás segítségével tudjuk megtenni, így joggal mondhatjuk, hogy a böngészők a webes technológiákkal együtt modern alkalmazásfejlesztési platformmá nőtték ki magukat.

A webes alkalmazások – a web jellegéből fakadóan – kliens-szerver architektúrában működnek. A szerver közzéteszi az elérhető erőforrásokat (HTML dokumentumok, képek, stb.), ezeket pedig klienssel, azaz böngészővel kérhetjük el a szervertől. Dinamikus weboldalakról akkor beszélünk, ha a megjelenített dokumentum előállításához, működtetéséhez, módosításához valamilyen számítógépes programot használunk. Ez a program futhat szerveroldalon, ekkor a böngészőnek leküldendő tartalmat dinamikusan állítjuk elő ezzel a programmal; vagy futhat kliensoldalon, ekkor a böngészőbe már betöltött HTML oldal dinamikus működtetése a cél. Egy összetettebb webalkalmazásban mindkét oldalon használhatunk programot.

Kliens-szerver architektúra
Kliens-szerver architektúra

A tananyag célja

Ebben a tananyagban a szerveroldali webfejlesztésre fókuszálunk. Azt fogjuk megnézni, hogy hogyan használható a webszerver dinamikus, azaz bizonyos körülményektől (mint pl. felhasználói bemenettől vagy külső adatforrástól) függően változó tartalmú weboldalak előállítására. Az alkalmazás felhasználói felületét a böngésző jeleníti meg, szerkezetéhez HTML, megjelenéséhez CSS technológiát fogunk használni. A böngészőben megjelenő weboldal azonban nem lesz előre adott (mint a statikus oldalak esetén), hanem előállítását egy program fogja szerveroldalon elvégezni, ehhez pedig a PHP programozási nyelvet választottuk.

Szerveroldali alkalmazásokra elsősorban akkor van szükség, amikor több kliens között egy közös erőforrás (pl. adat, logika) megosztására van szükség, vagy ha nem szeretnénk érzékeny információt a kliensre juttatni. Tipikusan adatok központi tárolásakor vagy üzleti jellegű számítások elvégzésekor használják, amelyek igen gyakori feladatok.

A szerveroldali alkalmazásokat széles körben használják az egyszerű portáloktól kezdve a bank informatikáig. Az ebben a tananyagban megismert technológiát használják a legelterjedtebb tartalomkezelő rendszerek a Wordpress és a Drupal, de a Facebook webes alkalmazását is eredetileg PHP-ban írták.

A tananyag elsajátításával a diákok képesek lesznek olyan dinamikus oldalak elkészítésére, amelyek:

Űrlapkezelés és adatfeldolgozás
Űrlapkezelés és adatfeldolgozás
Kosár megvalósítása, adatbázisból érkező adatok
Kosár megvalósítása, adatbázisból érkező adatok
Regisztráció, bejelentkezés
Regisztráció, bejelentkezés
Fájl- és könyvtárkezelés
Fájl- és könyvtárkezelés

A tananyag felépítése

A tananyag több, egymásra épülő fejezetet tartalmaz. Minden fejezet elején röviden ismertetjük az adott témakör elméleti tudnivalóit, majd azok használatát több, kisebb feladaton keresztül mutatjuk be. A tananyagot végigkísérik nagyobb feladatok is, ezeket minden témakör végén adjuk meg. A fejezetek végén további gyakorló feladatok kapnak helyet.

A tananyagban a következő jelöléseket használjuk:

A tananyaghoz tartozó gyakorlati feladatokat, szemléltető szöveg- és kódrészleteket tartalmazó blokkba.

A feltételezett ismeretek gyors áttekintésére szolgáló blokk.

A törzsanyagon túlmutató, további részleteket, kapcsolódó érdekességeket bemutató vagy továbblépési lehetőségeket felvillantó ismereteket ilyen blokkban közöljük.

A tananyagot végigkísérő nagyobb feladathoz tartozó részfeladatokat tartalmazzák ezek a blokkok.

A letölthető anyagok ilyen blokkokban jelennek meg.

Szükséges előismeretek

Habár a tananyag kezdőknek szól, bizonyos mértékben épít korábbi tapasztalatokra, ismeretekre. A feltételezett előismeretek az alábbiak:

Szerveroldali dinamikus webprogramozás és eszközei

A világháló és technológiái

A világháló (angolul World Wide Web, röviden web) egy olyan információs rendszer, amelyben dokumentumokat és más erőforrásokat egységes címmel azonosítunk, ezeket hiperhivatkozásokkal kötjük össze, elérhetőségüket pedig internetre kötött szerverek segítségével biztosítjuk. A web több komponensből épül fel, működését számos szabvány, protokoll és technológia biztosítja:

Ezek az elemek szükségesek a web használatához. Ezeken kívül azonban “webesnek” hívunk minden olyan technológiát, amely a fenti elemek bármelyikéhez kapcsolódik. Általában a HTTP fölött zajló kommunikációval vagy a HTML-lel leírt dokumentumokkal kapcsolatos technológiák webesnek számítanak. (Ld. még a World Wide Web Consortium szabványait.)

Kliens-szerver architektúra és a dinamikus webprogramozás

A webes dokumentumok kiszolgálása kliens-szerver architektúrában történik. A böngésző kliensként egy HTTP kérést küld a szervernek. A szerver a kérésben foglalt információk alapján összeállítja a HTTP választ, és visszaküldi a böngészőnek. A böngésző a választ feldolgozza, ami általában a válaszban kapott dokumentum megjelenítését jelenti.

Kliens-szerver architektúra
Kliens-szerver architektúra

Statikus weboldalakról akkor beszélünk, ha az a tartalom, amit meg szeretnénk jeleníteni már a kérés pillanatában készen áll a szerveren, és a betöltődése után sem változik meg a szerkezete. Ilyenkor a szerver szempontjából is statikus az oldal, hiszen a kikeresett fájlt változatlan formában küldi vissza, és a kliens is statikus, hiszen a megjelenítés után a böngésző nem módosítja az oldal tartalmát.

Dinamikus weboldalakról akkor beszélünk, ha a megjelenített dokumentum előállításához, működtetéséhez, módosításához programot használunk. Mivel az architektúránkban két komponens van, ezért a dinamikusságot mindkét komponens szemszögéből vizsgálhatjuk. Szerveroldali dinamikus kiszolgálásról akkor beszélhetünk, ha szerveroldalon a HTML válasz egy program futásának eredményeképpen születik meg. Kliensoldali dinamizmus esetén a böngészőben futó program változtatja a megjelenített oldal állapotát.

Szerveroldali webprogramozás

Ez a tananyag dinamikus szerveroldali weboldalak programozásáról szól. Azt mutatja meg, hogy hogyan lehet a böngésző kérésére egy program segítségével HTML oldalakat előállítani. Szerveroldalon nagyon sok programozási nyelv közül választhatunk (Python, C#, Java, JavaScript), de ebben a tananyagban a PHP nyelvet használjuk, mert ezzel a nyelvvel lehet a legkevesebb beállítással és plusz koncepcióval dinamikus oldalakat generálni.

A HTTP kérésre választ adó programok alapvetően kétféle architektúrában működnek. Az egyik lehetőség, hogy egy meglévő webszerver funkcionalitását bővítjük úgy, hogy bizonyos kiterjesztésű kérések esetén a kikeresett forrásállományt először lefuttatja, és annak eredményét adja vissza a böngészőnek. A szkriptnyelvek – mint például a PHP, Python, Ruby, Perl – általában ezt a megközelítést használják. A webszerver és a program közötti kommunikációt az ún. Common Gateway Interface (CGI) protokoll vezérli. Ez írja le, hogyan kell a programot elindítani és milyen módon történik az adatcsere a szerver és a program között (standard bemenet és kimenet, valamint környezeti változók).

A másik lehetőség, hogy a programok nemcsak a HTML oldal generálására szolgálnak, hanem a HTTP kapcsolat figyelését és feldolgozását is ők maguk végzik. Ezt a koncepciót alkalmazásszervernek hívják, és például a Java, C#, JavaScript környezetek alkalmazzák.

Webszerver architektúrák
Webszerver architektúrák

A PHP-t tipikusan Apache webszerver moduljaként szokták telepíteni. Ebben az esetben az Apache webszerver kezeli a HTTP kommunikációt. Ha egy .php kiterjesztésű állomány kiszolgálására érkezik kérés, akkor a szerver kikeresi a fájlrendszerből az állományt, majd ahelyett, hogy azonnal leküldené a forráskódot, átadja a PHP értelmezőnek, ami lefuttatja a programot. A program az eredményét a szabványos kimenetére írja, amit figyel a webszerver, és az oda érkező tartalmat pedig HTTP válaszként továbbítja a böngésző felé.

Az Apache webszerver működésének sematikus ábrája
Az Apache webszerver működésének sematikus ábrája

A böngészők

A webes világban a böngészők szolgálnak a különböző webes erőforrások megjelenítésére, futtatására. Az erőforrások lehetnek HTML oldalak, képek, stílusállományok, JavaScript programfájlok. A böngésző a HTML oldalakat, képeket megjeleníti, a JavaScript kódot futtatja.

Sokféle böngészőprogram közül lehet választani, ezek közül néhány elterjedtebb:

A böngészőprogramok általában a felhasználói felületükben és az általuk nyújtott szolgáltatásokban térnek el egymástól. Fejlesztés szempontjából az a lényeges, hogy a HTML és CSS állományokat helyesen jelenítsék meg, a JavaScript kódot egységesen futtassák. Szerencsére manapság a böngészők között e tekintetben nincsenek nagy eltérések, ezért bármelyik választható.

Fejlesztői eszközök a böngészőkben

Szerveroldali fejlesztéshez csupán pár eszközre lehet szükségünk a böngészőkben. Az egyik az oldal forrásának megtekintése, amelyet általában a jobb egér gomb megnyomásával előugró menüben lévő menüpont vagy a Ctrl-U billentyűkombinációval tudunk előhívni. Mivel a szerveroldali programozás a megfelelő HTML szerkezet generálásáról szól, ezért legjobban a generált forráskód vizsgálatával tudunk meggyőződni programunk helyes működéséről.

A másik hasznos eszköz a böngészőprogram hálózati forgalmának figyelése. Ez a böngészőkbe épített webfejlesztési eszköztár része, és a legtöbb böngészőprogramban az F12 billentyű lenyomásával érhetjük el, de a menüben mindig található rá hivatkozás (pl. Google Chrome esetén További eszközök/Fejlesztői eszközök menüpont). A megfelelő fülön nyomon követhető, hogy a böngésző milyen HTTP kéréseket küld, és azokra milyen válaszokat kap a szervertől.

Szerveralkalmazások

A szerveroldalon is számos alkalmazásra lesz szükségünk:

Szerkesztők

Az alkalmazásfejlesztéshez megfelelő szerkesztőprogramra is szükség van. A webes dokumentumok forráskódjai egyszerű szövegfájlok. Olyan szerkesztő kell, amelyik képes HTML, CSS, PHP kódot kezelni, és kényelmes, fejlesztőbarát funkciókat nyújt, mint például kódszínezés, kódkiegészítés, automatikus behúzások, projekt kezelése, nyomkövetés, stb. Kétféle lehetőség közül választhatunk: vannak a kisebb méretű, de funkciókban gazdag kódszerkesztők, és vannak az ún. integrált fejlesztőkörnyezetek, amelyek általában nagyobbak, lassabbak, de rengeteg funkcióval rendelkeznek. Mindenki a maga preferenciái szerint választja ki az általa használt eszközt. A webes fejlesztők között az alábbi szerkesztőprogramok a legelterjedtebbek:

Ezeken kívül az olyan általános szerkesztőprogramok is használhatók, mint pl. a Notepad++.

Javasolt fejlesztői eszköztár

Lokális rendszer kialakítása

Egy egy gépen futó fejlesztői környezet kialakításához az alábbi szoftvereket javasoljuk:

A szerverkörnyezetet telepítve el kell indítani a webszervert (és az adatbázis-kezelő rendszert). Ehhez általában valamilyen vezérlőpulton van lehetőség. A szerverek tipikusan a 80-as porton figyelnek a beérkező kérésekre.

A forrásállományokat a feltelepített webszerver valamelyik alkönyvtárába kell elhelyezni. Ezeket helyben a szerkesztőprogramok segítségével lehet szerkeszteni. Megtekintéshez nem a forrásfájlt kell a böngészőben megnyitni, hanem a webszerveren keresztül a megfelelő URL-t (pl. localhost/pelda/alma.php).

Központi szerver használata

Ha a szerveralkalmazások egy központi szerveren kerülnek kialakításra, akkor a következő szoftverkomponensekre lesz szükség:

A fejlesztés egyik lehetséges folyamata a következő:

Szerver beállítási segédlet tanároknak

A PHP programozási nyelv

Célunk, hogy a következő pár fejezetekben megoldjuk nagyobb méretű feladatokat is. Ezeket a feladatokat önálló munkára szánjuk és ehhez hasonló blokkokkal jelöljük.

A szerver oldali programozásban számos nyelv elterjedt. A kiszolgálókon gyakran használják a többek között a PHP, Java, Python, Ruby, JavaScript nyelveket. Tananyagunkban a PHP nyelv segítségével mutatjuk be a legfontosabb szerveroldali technológiákat és alkalmazásfejlesztési módszereket, alapelveket.

A PHP nyelvről általában

A PHP egy úgynevezett általános célú interpretált szkriptnyelv, ami annyit tesz, hogy a programkód egy futtatókörnyezetben (például egy paranccssori értelmező) fut közvetlenül, fordítás nélkül. A PHP szintaxisában a “C-stílusú” nyelvekhez tartozik, így a vezérlési szerkezetek, nyelvi elemek nagyon hasonlóak a C, C++, Java és C# nyelvek azonos elemeihez.

// JavaScript
$x = 1;
for ($i = 2; $i < 10; $i++) {
  $x = $x * $i;
}
// C++
int x = 1;
for (int i = 2; i < 10; ++i) {
  x = x * i;
}

Első ránézésre is szembetűnő, hogy a PHP nyelvben a változók azonosítóját $ jel előzi meg. Másik érdekesség, hogy PHP-ban nem szükséges a változókat külön deklarálni, azok az első értékadásnál jönnek létre. Amennyiben olyan változót akarnánk használni, aminek még korábban nem adtunk értéket, azt a futtató környezet hibaüzenettel jelzi. A C-hez, C++-hoz hasonlóan az utasításokat ; zárja. Megjegyzéseket szintén az ezekből a nyelvekből ismerős // vagy /* */ módon írhatunk a kódba, de használhatjuk a Perl nyelvben használt # jelet is kommentek készítéséhez.

A nyelv további jellemzői:

A nyelvhez tartozó dokumentáció a weben elérhető számos nyelven (angol, német, francia, stb).

Típusok

Mint a legtöbb programozási nyelvben, a PHP-ben is definiálva vannak bizonyos alapvető típusok. Habár a változóink nem, csak az egyes értékek rendelkeznek típussal, mégis fontos típusokról beszélni. A nyelvben elérhető típusok feloszthatóak egyszerű, összetett és speciális típusokra:

Egyszerű típusok:

Összetett típusok:

Speciális típusok

Vezérlési szerkezetek

A PHP nyelv vezérlési szerkezetei szinte pontosan megegyeznek a más, hasonlóan C szintaxisú nyelvek azonos elemeivel:

for (/* kezdőérték */; /* feltétel */; /* cikluslépés */) {
  // utasítás
}

while (/* feltétel */) {
  // utasítás
}

do {
  // utasítás
} while (/* feltétel */);

if (/* feltétel */) {
  // utasítás
} else {
  // utasítás
}


switch (/* változó */) {
  case /* érték */: 
    // utasítás
    break;
  default:
    // utasítás
}

Ezeken kívül létezik még a foreach ciklus egy változata, mely egy tömb (precízebben tetszőleges felsorolható adatszerkezet) elemein halad végig egyesével.

foreach ($tomb as $elem) {
  // utasítás
}

Amennyiben szükségünk van a felsorolásnál az adott elem indexére is, akkor ez a foreach egy alternatív változatával elkérhető:

foreach ($tomb as $index => $elem) {
  // utasítás
}

Függvények

PHP-ban, mint szinte minden másik programozási nyelvben lehetőségünk van saját függvényeket létrehozni. A függvények létrehozása a function kulcsszóval történik.

// függvény megadása a `function` kucsszóval
function fuggveny($param1, $param2) {
  // utasítások
  return /* visszatérési érték */;
}

// például
function osszead($x, $y) {
  return $x + $y;
}

// használata
osszead(10, 32); // --> 42

A fenti példában, a függvények paramétereit nem láttuk el típussal, illetve a visszatérési érték típusát sem határoztuk meg. A PHP 7.0-ás verziójától kezdve van lehetőségünk megadni a függvényparaméterek és visszatérési értékek paramétereinek típusát is, ezzel biztonságosabbá téve a kódunkat.

function fuggveny(int $param1, string $param2) : string {
  /* ... */
}

// például
function osszead(int $x, int $y): int {
  return $x + $y;
}

Létezik a PHP-nak olyan változata, melyeben teljes mértékben jelen van a statikus típuskezelés, vagyis minden változónak, függvényparaméternek és visszatérési értéknek kötelezően definiált típusa van. Ezt a változatot Hack-nek hívják. Hátránya a Hack nyelvnek, hogy saját, speciális futtatókörnyezetre van szüksége a működéshez.

A PHP kipróbálása

A PHP nyelv kipróbálásához szükséges valamilyen futtatókörnyezet. Ez lehet helyi gépre telepített PHP értelmező, webszerver vagy valamilyen online “sandbox” eszköz. Amennyiben a gépre vagy a szerverre telepítve van a PHP értelmező azt könnyedén elindíthatjuk interaktív módban a php -a utasítás kiadásával.

Az egyik legegyszerűbb utasítás az egyszerű kiírása, amit az echo utasítással tehetünk meg.

echo "Hello világ";

Mivel az interaktív parancssori értelmezőbe egyszerre csak egy utasítást írhatunk be, ezért ha hosszabb kódokkal szeretnénk kísérletezni, akkor már érdemesebb a kódunkat egy fájlba elhelyezni. A fájlnak a .php kiterjesztést kell adni. Ügyeljünk arra, hogy csak azok a részek kerülnek értelmezésre programkódként, amik a <?php és ?> jelek közé kerülnek. Minden, ami ezeken a jeleken kívül található, az egyszerű szövegként kiírásra kerül. Ilyen “PHP blokkból” több is lehet egy fájlban. Ennek a megoldásnak az az oka, hogy a PHP képes sablonnyelvként is viselkedni, és ezzel a megoldással a statikus és dinamikus részek egymást válthatják egy oldalon belül.

Több olyan online eszköz is létezik, melyek segítségével azonnal futtathatjuk az általunk írt PHP kódot, melynek az eredménye is azonnal megjelenik. Ilyen eszköz például a Repl.it webalkalmazás.

Feladatok

  1. Készíts függvényt, ami beolvas egy számot és eldönti, hogy az páros vagy páratlan! Használd a maradék (%) operátort!

    function paros_e($szam) {
      return ($szam % 2 == 0);
    }
  2. Egy tömbben adott számoknak a sorozata, adjuk meg az összegüket!

    $tomb = [1, 4, 2, 6, 1, 23];
    $osszeg = 0;
    foreach ($tomb as $szam) {
      $osszeg += $szam;
    }
    echo $szam;
  3. Készíts függvényt faktorialis néven, ami egy ciklussal kiszámítja az n faktoriális értékét!

    function faktorialis($n) {
      $eredmeny = 1;
      for ($i = 2; $i <= $n; $i++) {
        $eredmeny *= $i;
      }
      return $eredmeny;
    }
  1. Készíts függvényt, ami egy tömb elemei közül megszámolja, hogy hány darab x érték található!

    function darab($tomb, $x) {
      $darab = 0;
      foreach ($tomb as $elem) {
        if ($elem === $x) {
          $darab++;
        }
      }
      return $darab;
    }

Adatszerkezetek ábrázolása

A PHP nyelv tömbje, egy nagyon sokféleképpen használható adatszerkezet. Lehet használni egyszerű tömbként, de akár veremként, sorként de használhatjuk tetszőleges kulcs-érték párok tárolására is. Ezeknél komplexebb adatszerkezetet is felépíthetünk, ha tömböket egymásba ágyazunk, vagyis ha a tömbünk egyik eleme maga is egy tömb.

Készítsünk egy adatszerkezetet, ami egy iskolának és annak osztályainak adatait tartalmazza!

Kiindulásképp vegyünk egy asszociatív tömböt, melynek a mezői az iskola alapvető adatait tartalmazzák:

[
  "nev" => "PHP Általános Iskola",
  "cim" => "1337 Világháló utca 404.",
  "om" => "528272478"
]

Ez az adatszerkezet tovább bővíthető, ha egy értéken belül egy tömbben eltároljuk, hogy milyen osztályok vannak az iskolában:

[
  "nev" => "PHP Általános Iskola",
  "cim" => "1337 Világháló utca 404.",
  "om" => "528272478",
  "osztalyok" => [
    "1.a",
    "1.b",
    "2.a",
    "2.b"
  ]
]

Ebben a példában csak az osztályok nevét tároljuk el, de semmi egyebet nem tudunk az osztályról. Ha további információkat szeretnénk tárolni (pl. osztálylétszám, osztályfőnök), akkor megtehetjük, hogy minden egyes osztályt egy újabb asszociatív tömb reprezentál, és így tovább tetszőleges mélységig bővítve az adatszerkezetünket.

A való életben, a komolyabb programokban az összetartozó értékeket (pl. egy iskola, vagy egy iskolai osztály adatai) objektumokban tárolják, melyekhez osztályokat hoznak létre. Ezzel a megoldással a bonyolult adatszerkezetek leírása egyszerű tömbök és objektumok egymásba ágyazott halmazaivá válik.

Feladatok

  1. Készíts egy olyan adatszerkezetet, amely egy könyv adatait írja le. A könyvnél a következő adatokat tároljuk:
    $konyv = [
      "szerzo"     => "J.R.R Tolien",
      "cim"        => "A gyűrűk ura",
      "kiadas_eve" => 1954,
      "kiado"      => "Allen & Unwin",
      "isbn"       => "ISBN 0-618-34099-7"
    ];
  2. Készíts olyan adatszerkezetet, melybe egy bevásárlólista információit tárolhatjuk. A listán többféle dolgot szeretnénk tárolni, és mindegyik elemről tudni szeretnénk, hogy mi az és mennyit szeretnénk belőle vásárolni, illetve, hogy ki a felelős a beszerzéséért.

Adatok dinamikus megjelenítése

Egy szerveroldali program elsődleges feladata a HTTP kérésnek megfelelő HTML tartalom előállítása. A programban így a kimenet generálás kitüntetett szerepet kap. Másképpen megközelítve: a szerveroldali programokra is jellemző az az általános felépítés, amit például a konzolos programoknál láthattunk, vagyis ezekben a programokban is be kell olvasni az adatokat, fel kell dolgozni őket, majd megfelelő formában (HTML) meg kell őket jeleníteni. Ez a fejezet ez utóbbira, a kimenet-generálásra, azaz a HTML oldal előállítására összpontosít.

Az előző fejezetben láthattuk, hogyan használható a PHP nyelv adatszerkezetek definiálására és feldolgozására. Ebben a fejezetben az így előálló adatokat adottnak tekintjük, és azt nézzük meg, hogy a PHP nyelv segítségével miként tudjuk azokat HTML formában a kliensre küldeni.

Ebben a megközelítésben az az elv tükröződik, hogy az adatok definiálását és feldolgozását minél határozottabban elválasszuk azok megjelenítésétől. Ebből az is következik, hogy a kiírás során új adat definiálására vagy bármiféle feldolgozásra, számításra, háttértárhoz fordulásra már nem kerül sor, így a kiíráshoz használt nyelvi elemkészlet is megfelelően kicsi lehet.

Mivel a generálandó HTML-ben óhatatlanul lesznek dinamikusan előállítandó részek, ezeket a részeket valamilyen módon jelölni kell. A HTML kódból egy szerveroldali alkalmazásban sablon lesz, amelybe majd behelyettesítődnek a dinamikus tartalmak. A kiírás során tehát a PHP sablonnyelvként fog szolgálni.

Kiírás PHP-val

A PHP kód a HTML kódba ágyazva jelenik meg. A HTML kódban <?php nyitó- és ?> záróelemek közé kell helyezni a PHP kódot. Egy oldalon belül ilyen elempárokból akárhova és akárhány elhelyezhető. PHP oldalról megközelítve: egy PHP fájlon belül mindaz a szövegrészlet, ami nincsen <?php ?> elemek között, automatikusan kiírásra kerül. PHP kódon belül kiírni az echo utasítással lehet.

<p>Automatikusan kiíródó szöveg</p>
<?php
echo "<p>Programmal kiírt szöveg</p>";
?>

HTML generálása

A HTML generálásakor két szempontot tartunk szem előtt:

  1. Csak a dinamikus részek előállításához van szükségünk programra. Statikus tartalmak kiírásához nem használunk PHP-t.
  2. Jelezvén, hogy a kiírás során a PHP sablonnyelvként szolgál, a kimenet generálásakor a PHP alternatív szintaxisát fogjuk használni (ld. az alábbi példákat).

Statikus részek generálása

Statikus HTML generálásához nincsen szükség PHP-ra, minden automatikusan mehet a program kimenetére úgy, ahogy van:

<h1>Hello világ!</h1>
<p>Ebben a részben semmi dinamikus tartalom nincsen</p>

Változó értékének kiírása

Változó kiírásához a <?= ?> formát használjuk.

<h1>Hello <?= $name ?>!</h1>

Ciklus

Gyűjtemények kiírásához ciklust használunk (foreach-endforeach).

<ul>
  <?php foreach($lista as $elem) : ?>
    <li><?= $elem ?></li>
  <?php endforeach ?>
</ul>

Elágazás

Feltételes kiíráshoz az if-else-endif párost használjuk.

<div>
  <?php if ($bejelentkezve) :?
    <p>Hello <?= $nev ?>!</p>
    <a href="logout.php">Kijelentkezés</a>
  <?php else: ?>
    <a href="login.php">Bejelentkezés</a>
  <?php endif ?>
</div>

Általános elvek

PHP sablon létrehozásakor mindig induljunk ki a generálás végeredményeként előállítandó statikus HTML-ből. Abban azonosítsuk azokat az elemeket, amelyek a program futása során változhatnak, és cseréljük le őket a fenti szerkezetekre!

Adott egy hibákat tartalmazó tömb. Jelenítsük meg a hibalistát felsorolásként!

Első lépésként magát a HTML listát hozzuk létre:

<ul>
  <li>hiba1</li>
  <li>hiba2</li>
  <li>hiba3</li>
</ul>

Miután meggyőződtünk, hogy ez hibátlanul megjelenik, akkor azonosítjuk benne a változó tartalmat:

  1. A listaelemek száma a hibatömb számosságátók függ (–> ciklus)
  2. A listaelemek tartalma az adott tömbelem értéke (–> kiírás)
<ul>
  <?php foreach($hibak as $hiba) : ?>
    <li><?= $hiba ?></li>
  <?php endforeach; ?>
</ul>

Feladatok

  1. Adott egy szótár pár bejegyzése. Jelenítsük ezt meg definíciós listaként!

    $szotar = [
      "alma"      => ["apple"],
      "azonnal"   => ["immediately", "instantly", "at once", "now"],
      "segít"     => ["help", "assist", "aid"],
    ];
  2. Adott tanulók adatai (név, tanulmányi azonosító, osztály, cím). Írassuk ki a tanulók listáját táblázatos formában!

Adatbázis használata

Szerveroldali programra akkor van szükség, amikor valamilyen közös erőforrást szeretnénk az alkalmazás használói között megosztani. Az egyik legfontosabb közös erőforrás az adat. Az adatot egy helyen tároljuk, és ebből szolgáljuk ki az egyes klienseket.

Adatbázisok fajtái

Az adatot alapvetően kétféleképpen tudjuk tárolni:

Adatok tárolását illetően az adatbázisok használatának számos előnye van:

Az adatbázisoknak is több fajtája van különböző szempontok szerint. Az adatok szerkezetét illetően megkülönböztetünk:

A tárolás módja szerint a következő kategóriák lehetnek:

Ebben a tananyagban az egyszerűség kedvéért a szélesebb körben ismert relációs adatmodellel működő és külön szervert nem igénylő SQLite adatbázisokat fogjuk használni.

Program és adatbázis kapcsolata általában

Egy bizonyos típusú adatbázis használatához egy tetszőleges programozási nyelvben adatbáziskezelő-specifikus függvények állnak rendelkezésre. Külön függvénykönyvtár szükséges a MySQL, PostgreSQL, SQLite, stb kezelésére. Ha ezeket feltelepítettük, akkor a programban általában a következő lépések követik egymást:

Adatbázisok használata PHP-ban

Kliens, szerver és adatbázis kapcsolata
Kliens, szerver és adatbázis kapcsolata

PHP-ban számos adatbázis-kezelő rendszer elérése támogatott a megfelelő függvénykönyvtárakon keresztül, amelyeket általában külön telepíteni szükséges. A PHP azonban biztosít egy olyan függvénykönyvtárat, amely egy egységes programozási interfészen keresztül teszi lehetővé a különböző adatbázis-kezelők használatát. Így nem kell megtanulnunk az egyes adatbáziskezelő-specifikus függvényeket, mi esetünkben az SQLite-hoz tartozókat, hanem csak ezt a szóban forgó interfészt. Ennek a függvénykönyvtárnak a neve PHP Data Objects, azaz PDO.

A PDO ugyanolyan műveletcsoportokat biztosít, mint a többi függvénykönyvtár. Segítségével kapcsolódni lehet egy adatbázishoz, SQL utasításokat lehet kiadni, az eredményeket lekérdezni, stb. A PDO ehhez két osztályt definiál:

Főbb utasításai:

Hibakezelés a következőképpen történhet:

Alapvető SQL utasítások

Az adatokkal általában tipikusan négyféle műveletet kell tudnunk elvégezni: lekérdezni, újat felvenni, módosítani és törölni. Az ezeknek megfelelő alapvető SQL utasításokat a következőkben tekintjük át.

Lekérdezés

SELECT <oszloplista>     -- oszlopok szűrése
FROM   <tábla>
WHERE  <feltételek>;     -- sorok szűrése

-- például
SELECT `nev`, `kor`
FROM   `dolgozok`
WHERE  `kor` > 60;

Beszúrás

INSERT INTO <tábla>
  (<oszloplista>)
VALUES
  (<értéklista>);

-- például
INSERT INTO `dolgozok`
  (`nev`, `kor`)
VALUES
  ('Buhera Vera', 55);

Módosítás

UPDATE <tábla>
SET <oszlop1> = <érték1>,
    <oszlop2> = <érték2>
WHERE <feltételek>;       -- sorok szűrése

-- például
UPDATE `dolgozok`
SET    `kor` = 56
WHERE  `id` = 231;

Törlés

DELETE FROM <tábla>
WHERE <feltételek>;       -- sorok szűrése

-- például
DELETE FROM `dolgozok`
WHERE  `kor` > 80;

Az adatbázis-használat tipikus esetei

Adatbázis előkészítése

Mielőtt PHP-ból elkezdenénk az adatbázist használni, készítsük elő! Hozzuk létre a táblákat, a kapcsolatokat, és ha szükséges, akkor töltsük fel példaadatokkal. Megtehetnénk ezt PHP segítségével is, de az egyszerűség kedvéért tegyük ezt meg külön a megfelelő adatbáziskliens használatával.

SQLite esetén használhatjuk például a DB Browser for SQLite alkalmazást. Ebben létrehozhatunk egy új adatbázist, majd elkészíthetjük a táblastruktúrát és feltölthetjük adatokkal.

Az alábbiakban egy zeneszámokat tároló adatbázist építünk. Két tábla lesz benne:

Az adatbázist az alábbi SQL utasítással létre is hozhatjuk:

CREATE TABLE `albumok` (
  `id`    INTEGER PRIMARY KEY AUTOINCREMENT,
  `egyuttes`  TEXT,
  `cim`   TEXT NOT NULL,
  `ev`    INTEGER NOT NULL
);
INSERT INTO `albumok` (`id`, `egyuttes`, `cim`, `ev`) VALUES (1,'Guns n'' Roses','Appetite for destruction',1986);
INSERT INTO `albumok` (`id`, `egyuttes`, `cim`, `ev`) VALUES (2,'Aerosmith','Get a grip',1993);

CREATE TABLE `zeneszamok` (
  `id`    INTEGER PRIMARY KEY AUTOINCREMENT,
  `szerzo`    TEXT,
  `cim`   TEXT NOT NULL,
  `hossz` INTEGER,
  `album_id`  INTEGER NOT NULL,
  FOREIGN KEY(`album_id`) REFERENCES `albumok`(`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
);
INSERT INTO `zeneszamok` (`id`, `szerzo`, `cim`, `hossz`, `album_id`) VALUES (1, NULL, 'Cryin', 309, 2);
INSERT INTO `zeneszamok` (`id`, `szerzo`, `cim`, `hossz`, `album_id`) VALUES (2, NULL, 'Crazy', 314, 2);
INSERT INTO `zeneszamok` (`id`, `szerzo`, `cim`, `hossz`, `album_id`) VALUES (6, NULL, 'Paradise City', 406, 1);
INSERT INTO `zeneszamok` (`id`, `szerzo`, `cim`, `hossz`, `album_id`) VALUES (7, NULL, 'Sweet Child o'' Mine', 355, 1);

Kapcsolódás

$pdo = new PDO("sqlite:./zene.sqlite");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Segédfüggvényben, felkészülve azokra az adatbázisokra, ahol felhasználónevet és jelszót is meg kell adni:

function kapcsolodas($kapcsolati_szoveg, $felhasznalonev = '', $jelszo = '') {
  $pdo = new PDO($kapcsolati_szoveg, $felhasznalonev, $jelszo);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  return $pdo;
}

// használata
$kapcsolat = kapcsolodas("sqlite:./zene.sqlite");

Egyszerű lekérdezések

Ezeknél a lekérdezéseknél nem jelenik meg paraméter.

$stmt = $kapcsolat->query("SELECT * FROM `albumok`");
$albumok = $stmt->fetchAll();
var_dump($albumok);

Paraméteres lekérdezések

Sokszor fordul elő, hogy az SQL utasításainkat paraméterekkel kell általánosítanunk. Ilyenkor biztonsági okokból soha ne szövegösszefűzéssel állítsuk elő az SQL utasítást, hanem használjunk előkészített SQL utasításokat és adatkötést.

Az előkészített SQL utasításokban speciális jelölőkkel jelezzük a paraméterek helyét. Ezt az utasítást az adatbázis-kezelővel előkészíttetjük, amely során az előzetesen feldolgozza az SQL utasítást és a paraméterek helyét előkészíti. Végül az így előkészített utasítást konkrét értékekkel lefuttatjuk. Az előkészítés alapvetően akkor hasznos, ha ugyanazt az SQL utasítást többször szeretnénk futtatni más és más paraméterekkel, de mi főleg azt használjuk ki, hogy így a paraméterek átadása biztonságos csatornán keresztül történik.

$stmt = $kapcsolat->prepare("SELECT * FROM `zeneszamok` WHERE `album_id` = :album_id");
$stmt->execute([
  "album_id" => 1
]);
$zeneszamok = $stmt->fetchAll();
var_dump($zeneszamok);

Természetesen az utasítások ezen csoportja paraméterek nélkül is működik. Így a lekérdezés egy segédfüggvényben általánosítható:

function lekerdezes($kapcsolat, $sql, $parameterek = []) {
  $stmt = $kapcsolat->prepare($sql);
  $stmt->execute($parameterek);
  return $stmt->fetchAll();
}

// használata
$zeneszamok = lekerdezes($kapcsolat, 
  "SELECT * from zeneszamok where album_id = :album_id", 
  [ "album_id" => 1 ]
);

var_dump($zeneszamok);

A szövegösszefűzés nagy hátránya, hogy alkalmazásunkat kitesszük az ún. SQL beszúrásos támadás (SQL Injection) veszélyének. A paraméterek ugyanis tipikusan a felhasználói felületről érkeznek. Ha ezeket nem vizsgáljuk vagy nem megfelelő módon készítjük elő, akkor egy rosszindulatú támadó olyan szövegrészletet küldhet az alkalmazásunkba, amely nem várt módon változtatja meg az SQL utasításunkat.

Adott a következő SQL utasítás:

$query = "SELECT `id`, `name`, `inserted`, `size` FROM `products`
          WHERE `size` = '${size}'";

Ha a $size értékeke a következő:

'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--

akkor a végső SQL utasítás ez lesz:

SELECT `id`, `name`, `inserted`, `size` FROM `products`
      WHERE `size` = ''
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--'

ami a termékek listázása mellett felhasználóneveket és jelszavakat is visszaad.

Módosító utasítások (beszúrás, módosítás, törlés)

Paraméter nélküli módosító utasításokat a PDO::exec metódusával lehet kiadni. Általában azonban paraméteres SQL utasításokat kell itt is írni, amiben az előkészített utasítások vannak segítségünkre. Az alábbi példa azt is megmutatja, hogy az utoljára beszúrt sor azonosítóját hogyan lehet lekérni a PDO::lastInsertId segítségével.

$kapcsolat
  ->prepare("
    INSERT INTO `albumok` (`egyuttes`, `cim`, `ev`) VALUES (:egyuttes, :cim, :ev)
  ")
  ->execute([
    "egyuttes"  => "Roxette",
    "cim"       => "Joyride",
    "ev"        => 1991
  ]);

$id = $kapcsolat->lastInsertId();
var_dump($id);

Természetesen ez a logika is általánosítható:

function vegrehajtas($kapcsolat, $sql, $parameterek = []) {
  return $kapcsolat
    ->prepare($sql)
    ->execute($parameterek);
}

// használata
vegrehajtas($kapcsolat,
  "INSERT INTO albumok (egyuttes, cim, ev) values (:egyuttes, :cim, :ev)",
  [
    "egyuttes"  => "Roxette",
    "cim"       => "Joyride",
    "ev"        => 1991
  ]
);

$id = $kapcsolat->lastInsertId();
var_dump($id);

Hibakezelés

try {
  $kapcsolat = kapcsolodas("sqlite:./zene.sqlite");

  vegrehajtas($kapcsolat, 
    "INSERT INTO `zeneszamok` (`cim`) VALUES (:cim)",
    [ "cim" => "Joyride" ]
  );
}
catch (PDOException $e) {
  var_dump($e->errorInfo);
}

Segédfüggvények

function kapcsolodas($kapcsolati_szoveg) {
  $pdo = new PDO($kapcsolati_szoveg);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  return $pdo;
}

function lekerdezes($kapcsolat, $sql, $parameterek = []) {
  $stmt = $kapcsolat->prepare($sql);
  $stmt->execute($parameterek);
  return $stmt->fetchAll();
}

function vegrehajtas($kapcsolat, $sql, $parameterek = []) {
  return $kapcsolat
    ->prepare($sql)
    ->execute($parameterek);
}

Jelenítsük meg az albumokat felsorolásban! A lista fölé tegyünk egy szűrőmezőt, amit kitöltve és elküldve az együttesek nevében keres! A fenti segédfüggvények jelenlétét feltételezzük!

<?php
$szuro = $_GET["szuro"] ?? "";

$kapcsolat = kapcsolodas("sqlite:./zene.sqlite");

$albumok = lekerdezes($kapcsolat, 
  "SELECT * FROM `albumok` WHERE `egyuttes` LIKE :egyuttes",
  [ ":egyuttes" => "%${szuro}%" ]
);
?>
<h1>Albumok</h1>
<form action="">
  <input name="szuro">
  <button type="submit">Szűr</button>
</form>
<ul>
  <?php foreach ($albumok as $album) : ?>
    <li>
      <?= $album["egyuttes"] ?>: 
      <?= $album["cim"] ?>
      (<?= $album["ev"] ?>)
    </li> 
  <?php endforeach ?>
</ul>

Bemenet kezelése

Bemeneti adatok elérése és használata

Mint minden számítógépes programnak, úgy a szerveroldali alkalmazásoknak is szüksége van valamilyen bemenetre, ahhoz, hogy érdemben működni tudjanak. Szerveralkalmazások esetén ez a bemenet a klienstől, vagy valamilyen adattárból (fájl, adatbázis) érkezik valamilyen formában.

A felhasználói bemenet leggyakoribb formája az, amikor a szerverhez érkező HTTP kérés tartalmazza a működéshez szükséges bemenetet. Ez a HTTP protokoll megfelelő metódusai révén jut el a szerverhez. A PHP programhoz ezek az klienstől érkező adatok már előre részben feldolgozott formában érkeznek.

Habár van lehetőség a böngészőben, a címsoron keresztül is adatokat küldeni a szervernek, mégis a leggyakoribb módja a bemenetnek az űrlapok kitöltése és szervernek való elküldése. Ehhez a HTML űrlapot el kell látni a megfelelő paraméterekkel:

<form method="post" action="feldolgoz.php">
  Név: 
  <input type="text" name="nev">
  Életkor: 
  <input type="number" name="eletkor">
  Nem: 
  <select name="nem">
    <option>Nő</option>
    <option>Férfi</option>
  </select>
  <input type="submit" value="Küldés">
</form>

A címsoron keresztül adatokat a webcímet követő kérdőjel után, & jelekkel elválasztva tudunk küldeni get metódussal:

http://www.pelda.hu/oldal.php?nev=Anna&eletkor=19

A beérkező adatokat feldolgozó PHP oldalon úgynevezett szuperglobális változókon keresztül érhetjük el. Ha az adatok get metódussal (a címsoron keresztül, a HTTP kérés fejlécében) érkeztek, akkor a $_GET, ha post metódussal (a HTTP kérés törzsében), akkor a $_POST változóban találhatóak az értékek.

A szuperglobális változók (mint a $_GET és a $_POST) olyan változók, melyek a PHP program bármelyik pontján elérhetőek.

A get metódus használata a sok esetben nem javasolt, mivel az így elküldött adatok a felhasználó számára szabad szemmel is láthatóak a címsorban. Az ilyen módon küldött jelszavak szövegesen láthatóak a böngészőprogramban, így a felhasználó mögött álló személy, vagy bárki, akinek hozzáférése van a böngésző előzményeihez nagyon könnyen hozzáférhet ezekhez.

A $_GET és a $_POST esetében is egy olyan asszociatív tömböt (kulcs-érték párok sorozata) kapunk, melyben a kulcsok a HTML űrlap mezők name attribútumai, a hozzájuk tartozó értékek pedig az adott űrlap mezők kitöltött tartalma.

A fenti példában bemutatott HTML oldalon elküldött adatokat az alábbi módon érhetjük el PHP-ból:

// az elküldött név:
$_POST["nev"];
// az elkdüldött életkor:
$_POST["eletkor"];
// az elküldött nem:
$_POST["nem"];

Bemeneti adatok ellenőrzése

Minden olyan esetben, amikor egy számítógépes program felhasználói bemenettel dolgozik, felmerül a kérdés, hogy a felhasználó által megadott érték helyes-e. A webes világban a bemeneti adatok helyességének ellenőrzése kifejezetten fontos, mivel azon túl, hogy a helytelen bemenet a program hibás működését okozhatja, komoly biztonsági kockázatot is jelenthet.

A bemenet ellenőrzésénél felmerülhet ötletként, hogy az ellenőrzést végezzük kliens oldalon, a böngészőben, a HTML elemekre történő mintaillesztéssel. Ez a megoldás az elgépeléses hibák ellen ugyan védelmet nyújt, de a rosszindulatú támadásokkal szemben nem, mivel a HTTP kérések akár egy egyszerű böngészőprogrammal is tetszőlegesen módosíthatók. Ez az oka annak, hogy egy biztonságos rendszer létrehozásához mindenképpen szükség van az adatok helyességének szerveroldali ellenőrzésére.

A bemenet ellenőrzésénél kétféle tulajdonságát vizsgáljuk a kapott adatnak: megfelelő-e a típusa és megfelel-e a programunk előfeltételének (például egy adott szám valamilyen intervallumba esik-e, vagy a szöveg hossza nem halad-e meg valamilyen előre definiált értéket). Egy jellemző vizsgálat például az, amikor egy szövegről azt akarjuk eldönteni, hogy üres-e. Ezeket az ellenőrzéseket legegyszerűbben egyszerű elágazásokkal tudjuk elvégezni. Ügyeljünk arra, hogy csak akkor próbáljuk vizsgálni a bementi paraméterek értékét, ha volt egyáltalán valamilyen bemenet. Ezt például úgy tudjuk megvizsgálni, hogy a $_GET vagy a $_POST tömbök elemszámát (count) vizsgáljuk, hogy nagyobb-e mint nulla.

if (count($_POST) > 0) {
  /* bemenet ellenőrzése */
}

Egy változó típusának ellenőrzésére a PHP nyelv tartalmaz beépített segédfüggvényeket. Ezek a segédfüggvények mind in_* formátumúak, ahol a csillag egy adott típust jelöl (pl. is_float). Mivel az űrlapokon keresztül érkező adatok mindig szöveges formában érkeznek, ezért a valódi típus helyett érdemesebb azt vizsgálni, hogy a szöveg átalakítható-e a kívánt típussá. Számok esetében erre az is_numeric függvényt tudjuk használni. Ha a bemenet nem felel meg valamelyik feltétlenek, akkor az adott hibához generálunk egy hibaüzenetet, amit egy tömbben tárolunk. Az ellenőrzés végén, ha nem volt hiba, akkor a tömb üres maradt, ha pedig volt, akkor a tömbben lévő hibaüzenetet megjelenítjük a

A korábbiakban bemutatott példa űrlap bemenetének egy lehetséges vizsgálata:

// itt fogjuk tárolni a hibaüzeneteket
$hibak = [];

if (count($_POST) > 0) {
  // a név legyen szöveg, legalább három karakter hosszú, és van benne legalább egy szóköz
  if (!is_string($_POST["nev"]) || 
      !str_len($_POST["nev"]) >= 3 || 
      !substr_count($_POST["nev"], " ") >= 1) {
    $hibak[] = "A név formátuma nem megfelelő";
  }
  if (!is_numeric($_POST["eletkor"]) ||
    $hibak[] = "Az életkor csak szám lehet!";
  } else {
    if ($_POST["eletkor"] < 13) {
      $hibak[] = "Legalább 13 évesnek kell lenned!";
    }
  }
  if (!isset($_POST["nem"]) ||
      !in_array($_POST["nem"], ["férfi", "nő"])) {
    $hibak[] = "Nem választottad ki a nemedet!";
  }
}

Az ellenőrzést követően a HTML sablonban meg tudjuk jeleníteni a hibaüzeneteket:

<ul class="hibauzenetek">
  <?php foreach($hibak as $hiba) : ?>
    <li><?= $hiba ?></li>
  <?php endforeach; ?>
</ul>

Ha nem csak hibaüzeneteket szeretnénk tárolni, hanem például egy művelet sikerességét jelző üzenetet is, akkor arra érdemes külön tömböt használni. Ezzel a módszerrel a sablonban külön helyen és különböző formátummal tudjuk megjeleníteni a hiba- és egyéb üzeneteket.

$hibak = [];
$uzenetek = [];

if (count($_POST) > 0) {
  /* 
    bemenet ellenőrzése
  */
  if (count($hibak) == 0) {
    $uzenetek[] = "Sikeres feldolgozás!";
  }
}
<ul class="hibauzenetek">
  <?php foreach($hibak as $hiba) : ?>
    <li><?= $hiba ?></li>
  <?php endforeach; ?>
</ul>
<ul class="uzenetek">
  <?php foreach($uzenetek as $uzenet) : ?>
    <li><?= $uzenet ?></li>
  <?php endforeach; ?>
</ul>

Feladatok

  1. Készíts egy egyszerű űrlapot, ami két darab szöveg beviteli mezőt tartalmaz, valamint egy legördülő listát, amiből a négy alap matematikai műveletet (+, -, ×, ÷) lehet kiválasztani. Az űrlap küldje el magának az adatokat post metódussal.

    Hibák esetén írj ki hibaüzenetet. Ha minden rendben van, akkor számítsd ki az eredményt és jelenítsd meg.

    A beérkezett adatokat az allábiak szerint ellenőrizd:
    • Mindkét szám valóban szám legyen, osztás esetén a második szám ne legyen 0,
    • a műveleti jel az előre meghatározottak valamelyike.
  2. Készíts egy űrlapot, amin egy bevásárlólistába lehet új elemet felvenni! Lehessen megadni, hogy miből mennyit kell venni, illetve külön mértékegységet is. Egy legördülő listából lehessen kiválasztani (előre beégetett nevek közül), hogy kinek a feladata a beszerzés.

    A bemenetet értelemszerű feltételekkel ellenőrizd. Ha minden rendben van, akkor mentsd el az adatokat adatbázisba és értesítsd a felhasználót, hogy sikeres volt a felvétel. Ha hiba volt, azt is jelezd.

    Az oldal listázza ki az összes eddig felvett bevásárlólista-tételt.

Kódszervezés

Nagyobb szerver oldali alkalmazások készítésekor felmerül a kérdés, hogy hogyan érdemes a programkódunkat részekre osztani, hogy jobban átlátható legyen a program. Erre azért van szükség, mert ahogy nő az alkalmazásunk mérete, egyre több funkció, nézet (különböző elrendezésű oldal) jelenik meg, ezek ha nincsenek megfelelően szervezve, akkor átláthatatlanná teszik a kódot. Ehhez érdemes meghatározni az alkalmazás funkcióban jól elkülönülő részeit.

Ez a hármas felosztás az úgynevezett MVC architektúra hármas felosztásán alapul. Ez az alkalmazás-architektúra széles körben elterjedt mind a weben, mind pedig más platformokon.

A kódszervezés szükségességének legszembetűnőbb formája az, amikor egy szerveroldali webes alkalmazás több oldalból áll. Ezek az oldalak részben független tartalommal rendelkeznek, melyhez külön bemenet-feldolgozás tartozhat. Amellett, hogy más formában jelenik meg a tartalom, a legtöbb esetben vannak a HTML sablonnak a különböző oldalak közötti közös részei, melyek minden oldalon ismétlődnek (általában ilyen rész a fejléc és a lábléc). Ezeket az ismétlődő részeket minden oldal sablonja tartalmazza, és ha egy helyen módosítani kell valamit, akkor azt a módosítást az összes többi oldalon is meg kell tenni.

Erre a problémára megoldást jelent az, hogy ha az ilyen ismétlődő részeket egy külön fájlban helyezzük el, és azok tartalmát a megfelelő helyen beillesztjük az oldalunkba az include utasítás segítségével. Az include tulajdonképpen nem csinál mást, mint egy másik fájl tartalmát beilleszti a megfelelő helyen a forráskódunkba. Ebből az is következik, hogy az include utasítás használatának pillanatában létező összes változó elérhető a külső fájlban is. Az include használható mind statikus HTML részek, dinamikus sablonok, vagy tisztán PHP kódok betöltésére is.

<!-- az oldal fejléce -->
<?php include "fejlec.php"; ?>

<!-- az oldal egyedi része, tartalmak megjelenítése -->

<!-- az oldal lábléce -->
<?php include "lablec.php"; ?>

Az include utasításnak létezik három további változata is. Ezek működését az alábbi táblázat foglalja össze:

Utasítás Betölti többször ugyanazt? Ha nem található a külső fájl?
include igen warning szintű hiba, a program tovább fut
include_once nem warning szintű hiba, a program tovább fut
require igen fatal szintű hiba, a program leáll
require_once nem fatal szintű hiba, a program leáll

Az include utasítás segítségével tisztán PHP kódot tartalmazó fájlokat is be tudunk tölteni. Ez olyan kódrészletek esetében hasznos, melyeket több oldal is használ. Elsősorban függvények és osztályok definícióit érdemes ilyen módon kivinni külső fájlba, például az adatbáziskezelés fejezetben bemutatott kapcsolodas, lekerdezes, vegrehajtas segédfüggvények. Így bármelyik oldalon, ahol szeretnénk használni az adatbázist csak annyit kell tennünk, hogy betöltjük a külső fájlt, ami a segédfüggvényeket tartalmazza.

include `adatbazis.php`;

/* adatbázis-függvények használata */

Kódszervezésre természetesen nem csak fájlok szintjén, hanem fájlokon belül is lehetőség van. Az egyes oldalak kódjainak jobb átláthatósága érdekében érdemes egy előre meghatározott szisztéma szerint felépíteni a kódunkat. A PHP oldalakon belül a javasolt felépítés:

  1. Tisztán PHP kódot tartalmazó külső függőségek betöltése (include-ok) – ezeket az oldal további részei használják.
  2. Adatbázis csatlakozás létrehozása, ha szükséges – az adatbázis-kapcsolatra szintén szüksége lehet a többi részben.
  3. Ha szükséges, akkor a bemenet feldolgozása, adatbázis módosítása, ha a szükséges – azért kerül ide, hogy az itt végzett módosítások az oldalon már megjelenjenek.
  4. Az oldalon szükséges adatok lekérdezése az adatbázisból – a sablon enélkül nem jeleníthető meg.
  5. A sablon megjelenítése a szükséges adatokkal és üzenetekkel, az ismétlődő részek betöltése külső fájlokból – itt már minden szükséges módosítást elvégeztünk és minden adat rendelkezésünkre áll.
<?php
// 1. külső állományok betöltése
include "adatbazis.php";

// 2. adatbázis-kapcsolat létrehozása
$kapcsolat = kapcsolodas("sqlite:./adatbazis.sqlite");

// 3. bemenet feldolgozása
$hibak = [];
$uzenetek = [];
if (count($_POST) > 0) {
  // Bemenet ellenőrzése
  if (count($hibak) == 0) {
    // Bemenet feldolgozása (`vegrehajtas`)
  }
}

// 4. adatok lekérdezése
$adatok = lekerdezes($kapcsolat, /* "SELECT ..." */);
?>
<!-- 5. sablon megjelenítése -->
<?php include "fejlec.html"; ?>

<main>
  <!-- adatok, űrlapok megjelenítése -->
</main>

<?php include "lablec.html"; ?>

A példában is látszik, hogy ezzel a felosztással a teljes oldal két nagy egységre tagolódik - egy tisztán PHP kódból álló részből, illetve egy dinamikus PHP részeket tartalmazó HTML sablonból. Komolyabb MVC architektúrára épülő keretrendszerek ezt a két egységet is szétszedik külön fájlokra, ezek alkotják az úgynevezett vezérlő (controller) és nézet (view) egységeket.

Az általunk bemutatott alkalmazásokban és példákban az oldalak közötti navigációt linkek segítségével oldjuk meg, a fájlokat a webszerver (pl. Apache, nginix) szolgálja ki. Komolyabb rendszerek esetén a nagyobb biztonság és a nagyobb kontroll érdekében ezt az alapértelmezést felül szokták írni és az egyes fájlok kiszolgálását is PHP kód végzi include-ok segítségével. Ezt a megoldást nevezzük “routing”-nak.

Feladatok

  1. Készítsd el a családi bevásárlás alkalmazáshoz a szükséges fejléc és lábléc fájlokat, szervezd ki az adatbázis kapcsolatot külön fájlba, és alakítsd át az oldaladat a bemutatott strukúrának megfelelően.

Munkamenet-kezelés

A központi adattárolás nagy előnye, hogy az adatok bármikor bármelyik kliens által hozzáférhetőek az alkalmazáson keresztül. Ezek az adatok viszont közösek minden kliensre nézve. Sokszor azonban szükség van arra, hogy a klienseket megkülönböztessük és bizonyos adatokat kliensenként tároljunk. A kliensek megkülönböztetését és a kliensenkénti adattárolást munkamenet-kezelésnek hívjuk.

Egy webáruház esetén jó dolog az, hogy a bolt árukészletét mint központi adatot az egyes vásárlók elérik, böngészhetik. Mindenki ugyanazt látja. A kosár tartalma azonban vásárlónként, pontosabban kliensenként eltérő. Nem egy nagy közös bevásárló kosár van, hanem mindenkinek külön-külön van egy.

Jó pár további példa van a mindennapi életben arra, hogy az adatokat felhasználónként szükséges megkülönböztetni:

A munkamenet-kezelés működése

A klienshez tartozó adat mind a böngészőben, mind szerveroldalon tárolható. Ez a tananyag a szerveroldali megoldást tárgyalja. Ennek több előnye van:

A munkamenet kezelése, azaz a kliensek megkülönböztetése és kliensenkénti adattárolás a következőképpen történik.

  1. A megkülönböztetés végett minden kliens első kérésekor kap egy egyedi azonosító kulcsot a szervertől. Ez tipikusan egy jó hosszú véletlenszerűen előállított szöveg.
  2. A kliens ezt a kulcsot a szerver által meghatározott név alatt (pl. PHPSESSID) sütiként tárolja el. A süti a böngészőbeli adattárolás egyik lehetséges formája, ahol a böngésző minden kéréshez az adott szerverhez tartozó süti-adatokat automatikusan elküldi.
  3. Amikor a böngésző a szerver felé kérést intéz (hivatkozás, űrlap), akkor a böngésző a szerverhez tartozó kulcsot is elküldi.
  4. A szerver a kérés feldolgozásakor megnézi, érkezett-e adott nevű süti. Ha igen, akkor a szerveroldali program rendelkezésére bocsátja az adott kulcshoz tartozó adatokat. Ha nem, akkor egy új kulcsot generál, és leküldi a kliensnek.
  5. A munkamenet mindaddig él, amíg a kliens küldi a munkamenet-azonosító kulcsot. Ha az ezt tároló süti lejár, törlődik, akkor a munkamenet is véget ér.

A munkamenetkulcs és a hozzá tartozó adatot úgy is elképzelhetjük, mint egy banki széfszolgáltatást. Bérléskor kapunk egy kulcsot, és csak ezzel a kulcssal férhetünk hozzá a széf tartalmához. Ha elveszítjük a kulcsot, akkor az abban tárolt adathoz már nem férünk hozzá. Ha valaki ellopja a kulcsunkat, akkor az adatainkhoz a mi nevünkben fér hozzá. Ezért nagyon fontos a süti adatokra vigyázni (ld. a Weblabor ezzel foglalkozó cikkét)!

A munkamenet által tárolt kulcsot a böngésző fejlesztői eszköztárával tudjuk megfigyelni. Chrome böngészőben az Application fül alatt a Cookies kategóriában tudjuk a megfelelő szerver alatt kikeresni és akár kézzel módosítani, törölni.

Munkamenet-kezelés PHP-ban

PHP-ban a session_start utasítással lehet a munkamenet-kezelést aktiválni. Ez a függvény nézi meg, érkezik-e munkamenetkulcsot tartalmazó süti, ha igen, akkor elérhetővé teszi az adatokat, ha nem, akkor új kulcsot generál és gondoskodik sütiként való leküldésében. Mivel az adatokra igen hamar szükség lehet a kódban, így a session_start az első utasítások egyike a PHP kód elején. Minden fájlban meg kell jelennie, ahol munkamenetet szeretnénk kezelni.

<?php
session_start();
// többi utasítás

Az adatok a $_SESSION szuperglobális asszociatív tömbben érhetők el. A munkamenetből olvasni egyet jelent a tömb megfelelő elemére való hivatkozással, munkamenetbe írni pedig úgy kell, hogy a tömbben egy új kulcs alatt helyezzük el a tárolandó adatot. Egy munkamenetbeli adat törlését a megfelelő kulcs törlésével végezzük el. A $_SESSION tömb a session_start hívás után töltődik fel adatokkal, ezért csak azután használjuk!

session_start();

// olvasás
$valami = $_SESSION["valami"];

// írás (új létrehozás, módosítás)
$_SESSION["valami"] = 12;

// törlés
unset($_SESSION["valami"]);

A munkamenet megszüntetését a $_SESSION tömb ürítésével és a session_destroy utasítással végezhetjük el. Ezeket is meglévő munkameneten lehet értelmezni, azaz a session_start után helyezzük el.

// munkamenet indítása
session_start();
  
// munkamenetadatok törlése
$_SESSION = array();
  
// munkamenet törlése
session_destroy();

Sütik

A süti (angolul cookie) leírását a HTTP protokollban találjuk. Eredetileg arra találták ki, hogy a szerver kezdeményezésére adatot lehessen tárolni a böngészőben. A böngésző pedig az adott szerverhez tartozó sütiket automatikusan visszaküldte. Adattárolásra volt tehát lehetőség a kliens-szerver viszonylatában.

Mivel kliensoldalon más adattárolási lehetőség kezdetben nem volt, így a megfelelő JavaScript függvények segítségével a sütiket használták a kliensoldali alkalmazások adatainak tárolására olyan esetekben is, ahol az adatnak a szerverhez semmi köze nem volt. Ráadásul a sütiket JavaScript segítségével szabadon lehetett manipulálni, ami komoly biztonsági kockázatokat vetett fel (süti és így azonosítólopásokhoz vezetett). Mivel a sütiket eredendően nem erre találták ki, megjelentek tipikusan a kliensoldali tárolásra alkalmas JavaScript interfészek (Web Storage, IndexedDB). Ezekkel együtt a sütik alkalmazása is visszatért eredeti funkciójához, a szervertől kapott adatok tárolásához.

Hitelesítés és jogosultságkezelés

Egy webes alkalmazás esetén sokszor van szükségünk arra az információra, hogy ki is használja az alkalmazást. Ennek eldöntését nevezzük hitelesítésnek. A hitelesítés eredménye alapján megkülönböztetünk azonosítatlan (vendég) és azonosított felhasználót. Annak eldöntése, hogy a hitelesítés eredményeképpen kapott felhasználó mihez fér hozzá, jogosultságkezelésnek hívjuk. Előfordulhat, hogy egész oldalakat csak belépett, azaz azonosított felhasználóknak teszünk hozzáférhetővé; de az is elképzelhető, hogy csak az oldal egy része változik ennek az információnak megfelelően (ld. pl. a belép/kilép gomb váltakozását).

Hitelesítés munkamenettel

A hitelesítés általában egy egyszeri lépés: a felhasználónevet és a jelszót csak egyszer szükséges helyesen begépelnünk. Jó lenne, ha a többi kérésünknél a szerveroldali kód már látná, hogy ezen az egyszeri lépésen sikeresen túl vagyunk. A hitelesítés kliensenként változik: ki bejelentkezett már, ki nem. Ezt az információt tehát érdemes kliensenként tárolni, erre egyik alkalmas eszköz a munkamenet használata.

Munkamenettel a hitelesítés folyamata a következő:

  1. A felhasználó beírja felhasználónevét és jelszavát.
  2. Ha ez helyes, akkor a szerver a felhasználó munkamenetében egy egyszerű adat tárolásával jelzi, hogy a felhasználó azonosítása sikeresen megtörtént.
  3. További kérésekkor – ha szükséges – meg kell vizsgálni, hogy az adott bejegyzés jelen van-e a munkamenetben. Ha igen, akkor a felhasználó továbbra is azonosított. Ha nem, akkor vendég felhasználóval van dolgunk.
  4. A felhasználó kijelentkeztetése egyszerűen az adott bejegyzés munkamenetből való törlésével egyenértékű.

A továbbiakban nézzük végig egy hitelesítési folyamat lépéseit!

Adatbázis

A hitelesítés adatainkat egy adatbázis (mi esetünkben SQLite) felhasznalok nevű táblájában fogjuk tárolni:

CREATE TABLE `felhasznalok` (
  `id`                INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  `felhasznalonev`    TEXT NOT NULL UNIQUE,
  `jelszo`            TEXT NOT NULL
);

Regisztráció

A regisztráció a hitelesítés előszobája, amely során a rendszerbe felvisszük azonosító adatainkat (pl. felhasználónév és jelszó). A folyamat a következő lépésekből áll:

  1. Egy erre szolgáló űrlapon megadjuk a regisztrálandó felhasználónév-jelszó párost (esetleg egyéb adatokat is).
  2. Az adatokat leellenőrizzük (kitöltöttség, helyes formátum, nem létező felhasználónév).
  3. Helyes megadás esetén elmentjük az adatbázisba.

A következőkben az adatbázis műveleteket adottnak tekintjük. Két függvényt hozunk létre a felhasználók kezelésére: a letezik függvény megvizsgálja, hogy adott nevű felhasználó létezik-e már az adatbázisban; a regisztral függvény pedig elmenti a megadott felhasználónév és kódolt jelszó párost az adatbázisba.

<?php
function letezik($kapcsolat, $felhasznalonev) {
  $felhasznalok = lekerdezes($kapcsolat,
    "SELECT * FROM `felhasznalok` WHERE `felhasznalonev` = :felhasznalonev",
    [ ":felhasznalonev" => $felhasznalonev ]
  );
  return count($felhasznalok) === 1;
}
function regisztral($kapcsolat, $felhasznalonev, $jelszo) {
  $db = vegrehajtas($kapcsolat,
    "INSERT INTO `felhasznalok` (`felhasznalonev`, `jelszo`) 
      values (:felhasznalonev, :jelszo)",
    [
      ":felhasznalonev"   => $felhasznalonev,
      ":jelszo"           => password_hash($jelszo, PASSWORD_DEFAULT),
    ]
  );
  return $db === 1;
}

$hibak = [];
if (count($_POST) > 0) {
  $felhasznalonev = $_POST["felhasznalonev"];
  $jelszo = $_POST["jelszo"];

  $kapcsolat = kapcsolodas("sqlite:./zene.sqlite");

  if (letezik($kapcsolat, $felhasznalonev)) {
    $hibak[] = "Már létező felhasználónév!";
  }

  if (count($hibak) === 0) {
    regisztral($kapcsolat, $felhasznalonev, $jelszo);
    header("Location: bejelentkezik.php");
    exit();
  }
}
?>
<?php var_dump($hibak); ?>
<form action="" method="post">
  Felhasználónév:
  <input type="text" name="felhasznalonev"> <br>
  Jelszó:
  <input type="password" name="jelszo"> <br>
  <button type="submit">Regisztrál</button>
</form>

Soha ne tároljuk a jelszót egy az egyben, mert az komoly biztonsági kockázatot jelent. Mindig kódoljuk le egyirányú kódoló algoritmusokkal, és későbbiekben mindig a kódolt jelszóval dolgozzunk! PHP-ban jelszókódolásra külön függvény, a password_hash szolgál.

Bejelentkezés

A hitelesítési folyamat legfontosabb része a bejelentkeztetés. Ennek során győződünk meg arról, hogy a megadott felhasználónév és jelszó páros helyes, és ennek sikeres tényét a munkamenetbe eltároljuk.

<?php
function ellenoriz($kapcsolat, $felhasznalonev, $jelszo) {
  $felhasznalok = lekerdezes($kapcsolat,
    "SELECT * FROM `felhasznalok` WHERE `felhasznalonev` = :felhasznalonev",
    [ ":felhasznalonev" => $felhasznalonev ] 
  );
  if (count($felhasznalok) === 1) {
    $felhasznalo = $felhasznalok[0];
    return password_verify($jelszo, $felhasznalo["jelszo"]) 
      ? $felhasznalo 
      : false;
  }
  return false;
}

function beleptet($felhasznalo) {
  $_SESSION["felhasznalo"] = $felhasznalo;
}

session_start();

$hibak = [];
if (count($_POST) > 0) {
  $felhasznalonev = $_POST["felhasznalonev"];
  $jelszo = $_POST["jelszo"];

  $kapcsolat = kapcsolodas("sqlite:./zene.sqlite");
  $felhasznalo = ellenoriz($kapcsolat, $felhasznalonev, $jelszo);

  if ($felhasznalo === false) {
    $hibak[] = "Hibás adatok!";
  }

  if (count($hibak) === 0) {
    beleptet($felhasznalo);
    header("Location: fooldal.php");
    exit();
  }
}
?>
<?php var_dump($hibak); ?>
<form action="" method="post">
  Felhasználónév:
  <input type="text" name="felhasznalonev"> <br>
  Jelszó:
  <input type="password" name="jelszo"> <br>
  <button type="submit">Bejelentkezik</button>
</form>

Hitelesítés ellenőrzése

A további hitelesítéshez elegendő a munkamenet megfelelő kulcsának meglétét ellenőrizni.

function azonositott_e() {
  return isset($_SESSION["felhasznalo"]);
}

Kijelentkezés

Kijelentkeztetéshez elegendő a megfelelő kulcs törlése a munkamenetből.

function kijelentkeztet() {
  unset($_SESSION["felhasznalo"]);
}

Jogosultságvizsgálat

Jogosultságvizsgálat során azt nézzük meg, hogy az oldalt használónak van-e joga egy erőforrást elérni. Egyszerűbb esetekben elegendő annak megkülönböztetése, hogy vendég vagy azonosított felhasználó használja-e az oldalt. Ehhez kiváló eszköz az azonositott_e függvény. A következő példában egy teljes oldal levédéséhez használjuk:

session_start();
if (!azonositott_e()) {
  header("Location: bejelentkezik.php");
  exit();
}

Bonyolultabb esetekben a felhasználókat általában szerepkörökbe csoportosítják, és az egyes szerepkörök jogosultságait állítják be, illetve vizsgálják az adott erőforrás elérése során.

Feladatok

  1. Egy webáruház főoldalán kategóriák szerint lehet termékeket megjeleníteni, és a választott terméket kosárba helyezni.

    1. Tegyél fel egy legördülő mezőt, amiben a kategóriákat tartalmazza. Egy mellette lévő gombot megnyomva, az oldalon jelenjenek meg az adott kategóriába eső termékek.

    2. Minden termék mellett legyen egy “Kosárba” feliratú gomb. Erre kattintva a termék a kosárba kerül, aminek tartalmát ki is íratjuk a terméklista alatt.

    3. A kosárbeli termékek mellett legyen egy “Eltávolít” feliratú gomb, ezzel a kosárból lehet törölni az elemeket.

    Az alábbi SQL utasítással lehet az adatbázist gyorsan létrehozni.

    CREATE TABLE `kategoriak` (
      `id`  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
      `nev` TEXT NOT NULL UNIQUE
    );
    INSERT INTO `kategoriak` (id,nev) VALUES (1,'laptop');
    INSERT INTO `kategoriak` (id,nev) VALUES (2,'asztali gép');
    INSERT INTO `kategoriak` (id,nev) VALUES (3,'okostelefon');
    INSERT INTO `kategoriak` (id,nev) VALUES (4,'tablet');
    
    CREATE TABLE `termekek` (
      `id`  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
      `nev` TEXT NOT NULL,
      `ar`  INTEGER NOT NULL,
      `kategoria_id`  INTEGER NOT NULL,
      FOREIGN KEY(`kategoria_id`) REFERENCES `kategoriak`(`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
    );
    INSERT INTO `termekek` (id,nev,ar,kategoria_id) VALUES (1,'Lenovo Thinkpad 12345',200000,1);
    INSERT INTO `termekek` (id,nev,ar,kategoria_id) VALUES (2,'Asus Zenbook P12345',250000,1);
    INSERT INTO `termekek` (id,nev,ar,kategoria_id) VALUES (3,'Lenovo Desktop Dream 2',230000,2);
    INSERT INTO `termekek` (id,nev,ar,kategoria_id) VALUES (4,'HP EliteWork A6235',280000,2);
    INSERT INTO `termekek` (id,nev,ar,kategoria_id) VALUES (5,'Xiaomi Redmi 4X',45000,3);
    INSERT INTO `termekek` (id,nev,ar,kategoria_id) VALUES (6,'Samsung Galaxy S8',145000,3);
    INSERT INTO `termekek` (id,nev,ar,kategoria_id) VALUES (7,'Samsung A123',65000,4);
    INSERT INTO `termekek` (id,nev,ar,kategoria_id) VALUES (8,'Apple iPad5',265000,4);
  2. Készíts egy teendőket kezelő alkalmazást!

    1. Az adatbázisban tárold a felhasználókat és a teendőket. Minden felhasználónak több teendője is lehet, de egy teendő csak egy felhasználóhoz tartozhat (egy-sok kapcsolat).

      CREATE TABLE `felhasznalok` (
        `id`                INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
        `felhasznalonev`    TEXT NOT NULL UNIQUE,
        `jelszo`            TEXT NOT NULL
      );
      CREATE TABLE `teendok` (
        `id`  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
        `nev` TEXT NOT NULL,
        `kesz`  INTEGER NOT NULL DEFAULT 0,
        `felhasznalo_id`  INTEGER NOT NULL,
        FOREIGN KEY(`felhasznalo_id`) REFERENCES `felhasznalok`(`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
      );
    2. A teendőlistát csak bejelentkezett felhasználó érheti el. Védd le a főoldalt. Bejelentkezés nélkül irányítson át a bejelentkező oldalra!

    3. Legyen lehetőség regisztrálásra is! Regisztrálás után a bejelentkező oldalra kerüljünk!

    4. Sikeres bejelentkezés után a főoldalra kerüljünk!

    5. A főoldalon üdvözöljük a felhasználót és tegyük lehetővé a kijelentkezést.

    6. A főoldalon listázzuk ki a felhasználóhoz tartozó teendőket! Ha kész van a teendő, akkor áthúzva jelenítsük meg!

    7. Ugyanitt a teendőlista fölött legyen lehetőség új teendőt megadni.

    8. A teendőlistában minden teendő mellett jelenjen meg egy gomb. Amíg nincs kész, addig “Megold” felirat jelenjen meg, ha kész van, akkor az “Újra” felirat. Erre kattintva a kész állapotot tudjuk váltogatni.

Fájlkezelés

A szerveroldali webes alkalmazások képesek fájlok, fájlrendszerek kezelésére is. Ez egyrészről azt jelenti, hogy képesek (megfelelő jogosultságokkal) a szerver fájlrendszerén lévő fájlokat elérni, másrészről pedig képesek arra, hogy a klienstől fájlokat fogadjanak, azokat feldolgozzák vagy akár el is tárolják. Számos módon lehet képes egy szerveroldali program fájlokkal dolgozni. Ezekből mutatunk be néhányat.

Fájlrendszerek elérése

A PHP programok [teljes eszköztárral (fájl- és mappakezelő műveletek) rendelkeznek, amivel elérhetik, olvashaták vagy módosíthatják a szerver fájlrendszerét. Fáljok írására és olvasására a fopen, fgets, fwrite és feof függvényeket használhatjuk.

$fajl1 = fopen("szoveg1.txt", "r"); // a szoveg1.txt fájl megnyitása olvasásra
$fajl2 = fopen("szoveg2.txt", "a"); // a szoveg2.txt fájl megnyitása hozzáírásra

// amíg végére nem érünk a fájlnak, addig soronként olvasunk
while (!feof($fajl1)) {
  $adat = fgets($fajl1);
  fwrite($fajl2, $adat);
}

// fájlok lezárása
fclose($fajl1);
fclose($fajl2);

Fájlok olvasásán és módosításán kívül lehetőségünk van fájlok és mappák adatait is lekérdezni, illetve vizsgálhatjuk, hogy egy adott elérési út létezik-e, illetve, hogy fájlt vagy mappát tartalmaz.

$utvonal = "/home/user/mappa";
// ha a megadott útvonal mappa
if (is_dir($utvonal)) {
  // akkor gyűjtsük ki a benne található fájlokat
  $fajlok = scandir($utvonal);
} else {
  // különben írjunk ki egy hibaüzenetet
  echo "A megadott útvonal nem mappát jelöl.";
}

A legtöbb parancssori fájl- és mappakezelő műveletnek létezik PHP utasítás megfelelője. Minden művelet, amit a PHP program végrehajt, az a webszervert futtató felhasználó (pl. www_data) kerül végrehajtásra. Ebből adódik, hogy fájlok, mappák, amiket a PHP program hoz létre ennek a felhasználónak a tulajdonában lesznek.

Feladatok

  1. Készíts egy egyszerű programot, ami egy szöveges beviteli mezőben elkér egy útvonalat, és ha az mappa, akkor kilistázza a mappában található elemeket úgy, hogy minden elemnél jelzi, hogy az adott elem mappa-e vagy fájl, illetve ha fájl, akkor annak mérete.

Fájlok feltöltése

Mivel a PHP hozzáfér a fájlrendszerhez, így lehetőségünk van arra is, hogy a felhasználótól érkező feltöltött fájlokat kezeljük, és ha szükséges, akkor el is tároljuk. Ezáltal lehetőségünk van nem csak egyszerű szöveges tartalmak, de akár fájlok központi kiszolgálására is a felhasználók számára.

Ahhoz, hogy fájlokat tudjunk feltölteni, ki kell alakítanunk egy erre alkalmas űrlapot a felhasználó felületen. Ehhez két dologra van szükségünk: beállítani, hogy a megfelelő kódolással (multipart/form-data) küldje el az űrlap az adatokat, illetve szükség van egy fájl típusú űrlapmezőre (type="file"). Fontos, hogy fájlokat csak post metódussal van lehetőségünk elküldeni.

<form method="post" action="fajlfeltoltes.php" enctype="multipart/form-data">
  <label for="fajl">Válaszd ki a feltöltendő fájlt: </label>
  <input type="file" name="fajl" id="fajl">
  <input type="submit" value="Feltölt!">
</form>

Mikor ilyen módon elküldünk egy fájlt a webszervernek, akkor az először átmegy egy kezdezi ellenőrzésen. Ekkor a szerver vizsgálja például azt, hogy ne haladja meg a megengedett legnagyobb fájlméretet a fájl (ez a beállítás a php.ini konfigurációs fájlban van benne). Ha minden feltételnek megfelel a fájl, akkor azt a szerver egy ideiglenes mappában eltárolja (pl. /tmp), és a feltöltést kezelő PHP szkript megkapja a feltöltött fájllal kapcsolatos információkat a $_FILES szuperglobális asszociatív tömbben. A $_FILES a $_POST-hoz hasonlóan a name attribútumnak megfelelő mezőben tárolja a beérkezett adatokat.

// így vizsgáljuk, hogy érkezett-e feltöltött fájl
if (count($_FILES) > 0) {}

A feltöltött fájlról számos információ rendelkezésünkre áll a tömbben, például a fájl típusa ("type"), eredeti neve ("name"), mérete ("size"), illetve, hogy milyen ideiglenes néven mentette el a szerver ("tmp_name"). Ezen információk alapján már könnyedén tudjuk kezelni a feltöltött fájlt, például szűrni a feltölthető fájlokat típus, vagy méret alapján. Ha szeretnénk eltárolni a fájlt, akkor mindenképp szükséges, hogy azt áthelyezzük, mivel az ideiglenes mappa a legtöbb operációs rendszeren rendszeresen törlésre kerül.

Áthelyezésre a move_uploaded_file függvény szolgál.

Képfájl feltöltése és mentése a szerveren.

$hibak = [];
$uzenetek = [];
if (count($_FILES) > 0) {
  // ha hiba volt a feltöltés közben
  if ($_FILES["fajl"]["error"] != UPLOAD_ERR_OK) {
    $hibak[] = "Hiba a fájl feltöltésekor!";
  } else {
    // ha nem kép, vagyis a típusában szerepel az, hogy "image"
    if (!substr_count($_FILES["fajl"]["type"], "image") > 0) {
      $hibak[] = "A feltöltött fájl nem kép!";
    } else {
      // ha minden rendben, akkor eltároljuk a fájlt a `fajlok` mappába
      $cel_utvonal = "fajlok/" . $_FILES["fajl"]["name"];
      // a `move_uploaded_file` függvény eredményéből tudjuk meg, hogy sikeres volt-e a másolás
      $siker = move_uploaded_file($FILES["fajl"]["tmp_name"], $cel_utvonal);
      if ($siker) {
        $uzenetek[] = "Sikeres fájlfeltöltés!";
      } else {
        $hibak[] = "Sikertelen fájlfeltöltés!";
      }
    }
  }
}

A HTML szabvány lehetőséget biztosít olyan fájlfeltöltő űrlapmezőre is, ami egyszerre több fájlt képes küldeni. Ebben az esetben a $_FILES megfelelő elemének minden tulajdonságot leíró mezője egy sorszámozott tömb lesz, mely sorban tartalmazza az egyes feltöltött fájlok adatait.

Feladatok

  1. Készíts egy képgaléria alkalmazást, melyben lehetőség van új képek feltöltésére! A feltöltésnél ellenőrizd, hogy a feltöltött fájl mindenképp kép legyen, illetve azt is, hogy ne legyen a mérete több, mint 2 MB! A feltöltött képek jelenjenek meg egy galéria formájában!

  2. Alakítsd át úgy a családi bevásárló-lista alkalmazást, hogy egy külön oldalon lehetőség legyen a rendszerbe a fontosabb számlák szkennelt változatát képként feltölteni, majd keresni közöttük dátum szerint! Ily módon sosem fognak elveszni a számlák!

A webszerver csak bizonyos fájlok kiszolgálására képes a szerver számítógépen. Hogy melyek azok a mappák, amiket kiszolgálhat, az a webszerver konfigurációjától függ. Ezeket a mappákat hívjuk “webroot” mappáknak. Ezzel szemben a PHP a szerver számítógép összes fájlját eléri (legfeljebb nincs jogosultsága az íráshoz-olvasáshoz). Ez az alapértelmezett beállítás módosítható a PHP beállításai között az open_basedir paraméter átállításával. Amennyiben az open_basedir adott, a PHP csak az ott megadott mappán belüli fájlokat látja.