Frameworköt írni nem egy nagy dolog.

Tulajdonképpen minden jó programozó tud frameworköt írni, és a legtöbbje ír is. Persze a Jó Programozó azt is tudja, más, mégjobb programozók írtak frameworköket, így ő, ha teheti, nem ír sajátot.

Mi nem tehettük meg: nincs sok PHP4 framework, és létező kódhoz kellett hozzáírnunk, természetesen egy szigorú security audit előtt (aminél jobb, ha nem a mi kódunk bukik meg.)

Mi is kell egy webes frameworkhöz?

  • Adatelérési réteg
  • “Routing” (méltánytalanul keveset beszélünk róla)
  • Validáció
  • Template-ek és logika kettéválasztása.


  • Az “Architect dolgok” második része erről szól.

    Adatréteg

    Először is: Minek kell adatréteg?

    Elsősorban azért, hogy ne kerüljön szemét az adatbázisba. Ezt nyilván egy validációs frameworkkel érdemes megoldani. Meg persze, decoupling miatt is jól jön, ha objektumokat toszogatunk, nem pedig SQL-kéréseket írunk.

    Igaziból már volt adatréteg. Ezt a mintát követte:

    class TáblaneveDAO {
     var $_adatbhandler; // handler: specialis adattipus PHP-ben
     var $_id; // mindig id
     var $_egyik_oszlop;
     var $_masik_oszlop;
    
     function TáblaneveDAO($dbhandler, $id=NULL); // setId-t hív
     function getEgyikOszlop();
     function getMasikOszlop();
     function setEgyikOszlop($adat){...} // nincs validáció
     function setMasikOszlop($adat) {...}
     function load() {...} // ez fogta az ID-t és betöltött egyet
     function loadAll() {...} // az egész tábla, tömbben...
     function insert() {...}
     function update() {...}
    }
    

    Persze ez mind leimplementálva külön, valószínűleg copy-paste -vel.

    Copy-paste-t nem szeressük.

    Amikor lenyomod a Ctrl-v billentyűkombinációt, mindig fusson le az algoritmus:

      a megelőző gomb ctrl-x volt?
          [IGEN]: Refaktorálok
          [NEM]: Szar programozó vagyok?
                 [határidő ma éjfél] IGEN, de sietek
                 [határidő több, mint 24 óra] IGEN
    

    Nyilván vannak kivételek, de az esetek meglepő többségét jól lefedi. (Hivatalos, a határozott leanyázás miatt kevésbé hatékony formája: Don’t Repeat Yourself elv.)

    Meglepően sokat tanultam programozásból, mikor ezt a kérdést ilyen határozott formában kezdtem el feltenni magamnak.

    De térjünk vissza az adatréteghez…

    Nyilván kivennénk az egészet egy PersistentDAO interfészbe, vagy Persistence, vagy valami, itt kénytelenek leszünk a persistentDAO ősosztálynál maradni.

    Viszont ebbe az ősosztályba, némi nézelődés után, kapásból implementálhatnánk is az insert, update, load és loadAll függvényeket, HA…

    …ha lenne valami iterálható leírónk arról, milyen oszlopaink vannak.

    Csináljunk!

    Első körben sima asszociatív tömbön gondolkoztunk, de kompatibilitási okok miatt maradt a YAML.

    Oké, akkor most van egy ősosztályunk, meg minden résztvevő táblához egy leírónk.

    Ebből simán megírhatóak az alapfüggvények, az előző cikk alapján az Olvasóra bízom, hogyan.

    Következő kör: getter-setter.

    Ha PHP5-ben lennénk, a __get - __set páros ismét nagy segítség lenne, de ez PHP4, szóval maradunk a -

    Generálásnál!

    Sajnos néha el kell követni az automatikus kódduplikáció ezen formáját (Igen, ez is kódduplikáció, és redundancia!). Mentségünkre legyen mondva, mindegyik visszanyúl egy getProperty(változónév) ill. setProperty(változónév, érték) függvényhez, amik biztos ami biztos a privát változókat használják, valami ilyesféleképp (egyszerűsítve:)

    function getProperty($propname){
      $variablename = "_".$propname;
      return $this->$variablename; //említettem, hogy a PHP dinamikus nyelv?
    }
    

    Halkan jegyzem meg, a szép nevekhez sokat segít a symfony Camelize metódusa.

    A későbbiekben még rájövünk, hogy az SQL-t ki kell egészíteni egy bindvariable metódussal, mert az egyik kezdő programozó plaintext rakta össze az egyik custom query-t, és ezt én közel sem tartottam olyan viccesnek, mint ő, egy még idén megejtendő audit előtt.

    (Egyébként ebből a szempontból a Typo3 tetszett anno nagyon… ott a query nyelv SQL volt, amit visszaparsolt, levalidált, majd újból összerakta… mi megelégedtünk egy $this->bindQuery($querystr, array(”valtozonev”=>”tablanev.tulajdonsag”), array(”valtozonev”=>$ertek)) metódussal, ahol a valtozonev valahogy jelölve volt a sztringben.)

    (Egyébként a programozó, jó esetben, úgy kódol, ahogy a körülötte levő forráskód kinéz… legalábbis könnyebb kiszúrni, ha egy függvény sql injectionös).

    Controllerek

    A controllerek nagy nyűgje igazából a routing, azaz hogy jutok el az URL-ből a konkrét végrehajtó kódig. Itt a routing az index.php-ben egy nagy switch-case szerkezetben volt:

    switch($_GET['action']){
    case "egyik":
       $valtozo = $_POST['valtozo'];
       $masikvaltozo = $_POST['masikvaltozo'];
       include ("views/egyik.php");
    break;
    case "masik":
       include("views/masik.php");
    ...
    default:
       echo "Error, no such action"; // nincs ilyen
    }
    

    Ez a default lett megváltoztatva picit.

    Mindenkit beraktunk egy osztályba, aminek az execute metódusa végezte a logikát, miután végrehajtódott, beinclude-oltuk alapértelmezésben az action nevével egyező view fájlt, ha létezett és az execute visszatérése típusazonosan TRUE volt (dinamikus nyelv, vissza tud térni union-nal :) ), vagy az execute által sztringként visszaadott view fájlnév. Valami ilyesmi:

    ...
    default:
     $actionname = $_GET['action'];
     $actionfilename = "controllers/".$actionname."Action.class.php";
     $actionclassname = $actionname"."Action";
     if(file_exists($actionfilename)){
        include($actionfilename);
        $actionobj = new $actionclassname; // igen, ezt PHP-ban lehet!
        $ret = $actionobj->execute();
        if ($ret === TRUE) {
            $viewname = $actionname;
       } else $viewname = $ret;
       $actionobj->getView()->render($viewname) // include ("views/".$viewnamename.".php";
     } else echo "error"; // az eredetit megtartjuk
    } // switch close;
    

    Ehhez igazából volt még egy getParameters függvény, ami összeszedte a GET/POST/COOKIE paramétereket (a controllerek 90%-a csak ezt csinálja, lássuk be), de ezt a többiek nem szerették, ők form-osztályokat akartak, ez már másik történet… :)

    A view réteg

    Helló, a PHP eredetileg erre lett kitalálva. Minek smarty-zzunk, ha nem muszáj?

    (Persze egyes moduloknál muszáj, merthogy azok smartyval lettek írva, erre - is - van a view absztrakciónk!)

    Validálás

    A validálás több helyen kell, hogy történjen:

    • A model-osztályok custom query-jeinél, amikor értéket illesztek be
    • A model osztályok settereinél
    • Esetleg közvetlen az update/insert előtt

    Ez csak az első réteg.

    Egy jó rendszer viszont minden inputját szűri.

    Hol jönnek be az inputok? a controllerekben.

    A controllereknek kell biztosítania azt, hogy a bevitt adatok szűrve érkezzenek a rendszerbe. Ehhez az kell,hogy:

    • Csak azok az adatok legyenek érvényesek,amit a controller kért (ha vannak blokkok - portletek, ezekre ugyanígy vonatkozik, de halásszák ki maguknak!)
    • Ezek lehetőleg validak legyenek, de legalábbis jó típusúak
    • És talán az egyetlen, legfontosabb szabály, miszerint:

    • Ha valamely paraméter SQL unsafe string, ezt külön kelljen, explicit módon jelezni!

    A register_globals az első szabály megsértése, a második szabályhoz pedig a legtöbb programozó lusta.

    De a harmadik szabályból ne engedjünk!

    Ezzel meg is volnánk.

    Ja, említettem, mennyi idő volt mindezt megírni?

    A validálás kivételével - plusz két-három nap, második iteráció - négy nap.