webes alkalmazÁsfejlesztÉs 1.webprogramozas.inf.elte.hu/weaf1/ea/weaf1_11.pdf · webes...
TRANSCRIPT
WEBES
ALKALMAZÁSFEJLESZTÉS 1.
Horváth Győző
Egyetemi adjunktus
1117 Budapest,
Pázmány Péter sétány 1/C, 2.420
Tel: (1) 372-2500/1816
Tartalom2
MVC ismétlés
Mini MVC – saját MVC keretrendszer
MVC részeinek finomítása
Modell finomítása
Nézet finomítása
Vezérlő finomítása
Egyéb funkciók beillesztése az MVC mintába
Ismétlés7
Kiindulási pont: egyszerű listázó alkalmazás
vegyes HTML és PHP kód
vegyes funkcionalitások
Kód funkcionális (és ezzel nyelvi) szétválasztása
Nézet (view)
Modell (model)
Vezérlő (controller)
Vezérlő (controller)8
<?php
include('albums_model.php');
$nev = '';$leiras = '';if (isset($_POST['nev'])) {
$nev = $_POST['nev'];}if (isset($_POST['leiras'])) {
$leiras = $_POST['leiras'];}
$albums = get_albums($nev, $leiras); //modell
include('list.php'); //view
Modell (model)9
<?phpfunction get_albums($nev = '', $leiras = '') {
$mysqli = new mysqli('localhost', 'wabp3', 'ab2009', 'wabp3');if ($mysqli->connect_error) {
die('Kapcsolodasi hiba' . $mysqli->connect_errno .$mysqli->connect_error);
}$mysqli->query('set names utf8');
$stmt = $mysqli->prepare("select * from photo_albumswhere nev like ? and
leiras like ?");$stmt->bind_param('ss', $nev, $leiras);$nev = "%{$nev}%";$leiras = "%{$leiras}%";$stmt->execute();
Modell (model)10
$meta = $stmt->result_metadata();$sor = array();foreach ($meta->fetch_fields() as $field) {
$params[] = &$sor[$field->name];}call_user_func_array(array($stmt, 'bind_result'), $params);
$albums = array();while ($stmt->fetch()) {
$albums[] = array('id' => $sor['id'],'nev' => $sor['nev'],'leiras' => $sor['leiras'],
);}$stmt->free_result();$mysqli->close();
return $albums;}
Nézet (view)11
<html><head>
<title></title><meta http-equiv="Content-Type"
content="text/html; charset=UTF-8"></head><body>
<h1>Albumok listazasa</h1><form action="index2.php" method="post">
Nev: <input type="text" name="nev"value="<?php echo $nev; ?>" ><br>
Leiras: <input type="text" name="leiras"value="<?php echo $leiras; ?>" ><br>
<input type="submit" value="Szur"></form>
Nézet (view)12
<table><tr>
<th>Azonosító</th><th>Név</th><th>Leírás</th>
</tr><!--Dinamikus resz kezdete --><?php foreach ($albums as $sor) : ?><tr>
<td><?php echo $sor['id']; ?></td><td><?php echo $sor['nev']; ?></td><td><?php echo $sor['leiras']; ?></td>
</tr><?php endforeach; ?><!--Dinamikus resz vege -->
</table><a href="insert.php">Új album...</a>
</body></html>
Ismétlés13
Ez a megvalósítás az MVC architekturális tervezési
mintát követi, valósítja meg
Vezérlő
NézetModell
Ismétlés14
MVC minta általánosságban
MVC keretrendszerek
CodeIgniter (professzionális MVC keretrendszer)
Saját MVC keretrendszer16
Elvárások
Egy belépési pontja legyen az alkalmazásnak
Az MVC részei külön könyvtárba kerüljenek
Elnevezési konvenciók
Sablon nyelv a PHP (echo, if, foreach)
Oldalsablon támogatás legyen
OOP
Front Controller17
Az alkalmazás belépési pontja
Vezérlők közös részeit tartalmazza
munkamenet-kezelés, konfiguráció beolvasása,
authentikáció, előszűrések, stb.
Többi fájl elérhetetlensége
.htaccess
token definiálása a FC-ben és ellenőrzése a vezérlőkben
index.php
Kért oldal jelzése
GET paraméterrel
index.php?oldal=main
Front Controller 1.18
switch ($oldal) {case "index":
index();break;
case "page1":page1();break;
case "page2":page2();break;
case "page3":page3();break;
default:index();
}
function index() {
}
function page1() {
}
function page2() {
}
function page3() {
}
Front Controller 2.19
if (function_exists($oldal)) {call_user_func($oldal);
}
function index() {
}
function page1() {
}
function page2() {
}
function page3() {
}
Front Controller 3.20
switch ($oldal) {case "index":
include "v3_index.php";break;
case "page1":include "v3_page1.php";break;
case "page2":include "v3_page2.php";break;
case "page3":include "v3_page3.php";break;
default:include "v3_index.php";
}
Front Controller 4.21
if (file_exists("v3_" . $oldal . ".php")) {include_once("v3_" . $oldal . ".php");
}
Front Controller (index.php)22
index.php?class=products&method=list
if (file_exists("controllers/" . $class . ".php")) {include_once("controllers/" . $class . ".php");if (class_exists($class . "_Controller")) {
$class .= "_Controller";$ctrl = new $class();if (method_exists($ctrl, $method)) {
//call_user_func(array($class, $method));$ctrl->$method();
}else {
include_once("error.html");}
}else {
include_once("error.html");}
}else {
include_once("error.html");}
define( "TOKEN", "mini_mvc" );session_start();
function __autoload($class_name) {require_once
strtolower($class_name) . '.php';}
require "models/db.php";//Konfiguracio betoltese//Jogosultsagok ellenorzese
$class = 'main';if (isset($_GET['class'])) {
$class = $_GET['class'];}$method = 'index';if (isset($_GET['method'])) {
$method = $_GET['method'];}
Front Controller vs bootstrap23
A fenti megoldásban a Front Controller
procedurális, nem OOP-s
Ha a Front Controllert osztálypéldányként
(singleton) szeretnénk használni, akkor az az
állomány, amely előkészíti és példányosítja a
bootstrap állomány (index.php)
boots
trap
Front
Contr
olle
r
Contr
olle
r
Vezérlő25
<?php if (!defined('TOKEN')) exit('Ejnye-bejnye');class Main_Controller {
function index() {include('models/albums.php');$nev = '';$leiras = '';if (isset($_POST['nev'])) {
$nev = $_POST['nev'];}if (isset($_POST['leiras'])) {
$leiras = $_POST['leiras'];}
$model = new Albums_Model();$albums = $model->get_albums($nev, $leiras); //modell
$view = new View();$view
->set('nev', $nev)->set('leiras', $leiras)->set('albums', $albums);
$content = $view->get_include_contents('views/list.php');include('views/template.php');
}}
View osztály26
class View {private $vars = array();public function get_include_contents($filename) {
extract($this->vars);if (is_file($filename)) {
ob_start();include $filename;$contents = ob_get_contents();ob_end_clean();return $contents;
}return false;
}public function set($name, $value) {
$this->vars[$name] = $value;return $this;
}}
Modell27
<?php if (!defined('TOKEN')) exit('Ejnye-bejnye');
class Albums_Model {function get_albums($nev = '', $leiras = '') {
...return $albums;
}}
Nézet28
<table><tr>
<th>Azonosító</th><th>Név</th><th>Leírás</th><th>Funkciók</th>
</tr><!--Dinamikus resz kezdete --><?php foreach ($albums as $sor) : ?><tr>
<td><?php echo $sor['id']; ?></td><td><a href="index.php?class=images&method=index&album_id=<?php echo
$sor['id']; ?>"><?php echo $sor['nev']; ?></a></td><td><?php echo $sor['leiras']; ?></td><td><a href="index.php?class=main&method=delete&id=<?php echo
$sor['id']; ?>">Torol</a></td></tr><?php endforeach; ?><!--Dinamikus resz vege -->
</table><a href="index.php?class=main&method=insert">Új album...</a>
Oldalsablon29
<div id="wrap"><div id="header">
<h1 id="logo-text"><a href="index.html">Sablon</a>
</h1></div>
<!-- content-wrap starts here --><div id="content-wrap">
<div id="main"><?php echo $content; ?>
</div></div>
</div>
Modell finomítása31
Az alkalmazás adatait és az ezek feldolgozásához
szükséges üzleti logikát tartalmazza
További rétegekre bontható
üzleti logikai réteg (üzleti logikához illeszkedő
adatszerkezetek, objektumok)
adatbázis-absztrakciós réteg (adatbázis- és
táblaszerkezetnek megfelelő objektumok hierarchiája)
adatbázis-elérési absztrakciós réteg
adatbázis (táblák, tárolt eljárások, nézetek)
Modell minták32
Domain model: olyan réteg, amely a valós világ
adatainak megfelelő absztrakt objektumokat,
logikákat tartalmazza
Simple Domain Model: egy-egy kapcsolat az üzleti
objektumok és táblák között
Active Record minta (rekord szintű)
Table Data Gateway minta (tábla szintű)
Data Mapper minta (táblaadatok üzleti objektum)
Rich Domain Model: üzleti objektumok bonyolultabb
kapcsolatrendszere (tipikusan nagyvállalati projektek)
Adatbázis-elérési absztrakció33
A modellünk jelenleg egy bizonyos adatbázis
driverhez kötődik.
Ha megváltozik az alkalmazásunk mögötti
adatbáziskezelő, vagy egyszerűen olyan szerverre
költözünk, ahol egy bizonyos driver nincsen
feltelepítve, az átállás nagyon nehéz lehet.
Megoldás: adatbázis-elérési absztrakciós réteg
kialakítása, mely egy magasabb szintű absztrakt
nyelvvel elfedi a konkrét adatbázis függvényeit.
Adatbázis-elérési absztrakció34
Adatbázis-elérési absztrakciós lehetőségek
Saját magunk készítünk ilyen réteget
Előre megírt függvénykönyvtárat alkalmazunk
PHP PDO
Doctrine Database Abstraction Layer
http://www.doctrine-project.org/projects/dbal
Saját absztrakciós réteg35
db.php: konfiguráció
mysql.php: absztrakt műveletek Mysqli interfésszel
kifejtve
oracle.php: absztrakt műveletek OCI8 interfésszel
kifejtve
albums.php: absztrakt interfésszel megvalósított
modell
PHP PDO
PHP „natív” absztrakciós rétege
Többféle interfészt támogat
CUBRID (PDO)
MS SQL Server (PDO)
Firebird/Interbase (PDO)
IBM (PDO)
Informix (PDO)
MySQL (PDO)
Oracle (PDO)
ODBC and DB2 (PDO)
PostgreSQL (PDO)
SQLite (PDO)
4D (PDO)
Műveletkategóriák
Kapcsolatkezelés
Tranzakciókezelés
Paraméterezett SQL
utasítások és tárolt eljárások
kezelése
Hibakezelés
LOB-ok kezelése
http://www.php.net/manual
/en/book.pdo.php
36
PHP PDO
PDO
exec
query
prepare
lastInsertId
beginTransaction
inTransaction
commit
rollBack
errorCode
errorInfo
PDOStatement
bindColumn
bindParam
bindValue
execute
rowCount
fetch
fetch…
closeCursor
errorCode
errorInfo
37
PHP PDO példa38
$sth = $dbh->prepare('SELECT name, colour, caloriesFROM fruitWHERE calories < :calories AND colour = :colour');
$sth->bindParam(':calories', $calories, PDO::PARAM_INT);$sth->bindParam(':colour', $colour, PDO::PARAM_STR, 12);$calories = 150;$colour = 'red';$sth->execute();
$sth = $dbh->prepare('SELECT name, colour, caloriesFROM fruitWHERE calories < ? AND colour = ?');
$calories = 150;$colour = 'red';$sth->execute(array($calories, $colour));
Adatbázis-absztrakciós megoldások40
Általában egy adatbázis-elérési absztrakciós rétegen ülnek
Tipikusan egy-egy kapcsolatot teremtenek az adatbázisbeli táblák és az üzleti objektumok adattagjai között
Az adatbázist az üzleti objektum magas szintű absztrakt metódusain keresztül manipuláljuk
get, getAll, save (insert vagy update)
Általában ORM megoldások: Object RelationalMapping
Adatbázis-absztrakciók41
Nem jelennek meg benne direkt SQL utasítások
OOP felületen adjuk meg a logikát, mit szeretnénk,
s az absztrakciós réteg generálja le a neki
megfelelő SQL utasítást
A megoldások egy részében saját lekérdező
nyelven keresztül
Hibernate HQL
Doctrine DQL
Object Relational Mapping42
Az ORM egy tábla-egy objektum kapcsolatot létesít
Lehetőség van kapcsolatok megadására (1-N, N-N
viszonyok) és ezeknek OOP-s felületen történő
kezelésére
pl. $user->subjects
PHP ORM megoldások
Független projektek
ActiveRecord
Doctrine
Eloquent
Propel
Keretrendszerbe
ágyazva
CodeIgniter
Laravel
Symfony
CakePHP
Kohana PHP
Yii
43
PHP ORM példa (ActiveRecord)44
class User extends ActiveRecord\Model { }
// create Tito$user = User::create(array('name' => 'Tito', 'state' => 'VA'));
// read Tito$user = User::find_by_name('Tito');
// update Tito$user->name = 'Tito Jr';$user->save();
// delete Tito$user->delete();
CodeIgniter specifikumok47
CodeIgniter a Database Classon keresztül biztosít
adatelérési-absztrakciós réteget
adatbázis-absztrakciós réteget (ActiveRecord minta
alapján, de nem teljes ORM funkcionalitás)
Nézet finomítása49
Alkalmazott tervezési minták
Template view
Transform view
Layout view (oldalsablon megadása)
Template view50
Általában ezt használjuk webalkalmazásokban
A nézet ebben az esetben egy sablon, amiben
speciális jelölőket cserélünk ki a modellből kinyert
adatokkal
Előző előadásban is ezt mutattuk be
Részei
sablon (PHP, Smarty, stb.)
nézethez tartozó logika, melynek során a jelölőkhöz
adatokat rendelünk, valamint a feldolgozó rész
Template view példa (CodeIgniter)51
class Products extends CI_Controller {public function index() {
$data = array('alma' => 'piros','korte' => 'kukacos',
);$this->load->view('proba', $data);
}}
<html><head>
<title>My Blog</title></head><body>
<p>Az alma <?php echo $alma; ?>.</p><p>A körte <?php echo $korte; ?>.</p>
</body></html>
Transform view52
A transform view adatokat nyer ki a modellből, és
azokat a kimenetnek megfelelően átalakítja.
A template view a kimenet vázlatával kezdi, és
abba illeszti be az adatokat
A transform view-nál az adatok az elsők, és abból
építi fel a kimenetet
Tipikusan technológia: XSLT
Layout view53
A nézet további részekre bontását végzi el
Egy weboldalon általában vannak viszonylag
állandó részek: fejléc, az oldalelrendezés, lábléc,
globális navigáció
És vannak az oldalról oldalra változó tartalmi
részek
A nézetet ennek megfelelően szokták szétválasztani
oldal elrendezésére (layout)
tartalmi sablonra (template)
CodeIgniter specifikumok54
Template view támogatott a View classon keresztül
$this->load->view('sablon.php');
Transform view-t PHP támogatja az xml és xslt
függvényein keresztül
Layout view ld. később
Controller finomítása56
Példánkban a vezérlő nem csinált túl sok mindent
Általában a vezérlőknek elég sok mindent kell
elintéznie, mielőtt a konkrét művelet
végrehajtásába kezd
Ezek között sok olyan dolog van, ami minden
vezérlő számára közös
A közös dolgokat külön választják, ez lesz a Front
Controller, az egyedi dolgok pedig az egyes
műveletekben kerülnek megvalósításra
További finomítás57
Alap esetben a Front Controller biztosítja az
alkalmazás belépési pontját (ez előny)
De ekkor szükségképpen tartalmaz procedurális
kódot, így a Front Controller csak félig OOP-s
A megfelelő művelet kiválasztása is külön vehető
Ezért többfelé választják
Bootstrap
Front Controller
Application Controller
Bootstrap58
index.php
alkalmazás belépési pontja
procedurális
feladata
Front Controller példányosításának előkészítése
konfiguráció, alapváltozók
a példányosítás (singleton)
a példány kezdőmetódusának meghívása (run)
Front Controller59
Objektum-orientált megközelítés
tipikusan singleton
Közös feladatok centralizált elvégzése
kérés feldolgozása
routing
caching
biztonság
konfiguráció
Intercepting filter helye
Intercepting filter60
Globális kódok és funkcionalitás a Front
Controllerben az Intercepting filter minta
felhasználásával kerülhetnek
Kétféle megközelítés
szűrők szekvenciális végrehajtása az
alkalmazásvezérlőig
elő- és utószűrők megadása
Intercepting filter példa61
class FrontController {var $_filter_chain = array();
function registerFilter(&$filter) {$this->_filter_chain[] =& $filter;
}function run() {
foreach(array_keys($this->_filter_chain) as $filter) {$this->_filter_chain[$filter]->preFilter();
}$this->_process();foreach(array_reverse(array_keys($this->_filter_chain)) as $filter) {
$this->_filter_chain[$filter]->postFilter();}
}function _process() {
// FrontController tennivalói }
}
Intercepting filter példa62
Szűrő példa
class HtmlCommentFilter {function preFilter() {
ob_start();}function postFilter() {
$page = ob_get_clean();echo preg_replace(
‘~<!—.*—>~ims’,’’,$page);
}}
Application Controller63
Ez az MVC vezérlő központi része: ő dönti el, hogy
a bejövő kérés alapján az alkalmazás hogyan
válaszoljon
Tipikusan
egy nagy elágazás (if, switch)
vagy felparaméterezett tömb
vagy elnevezési konvenciók (kérés, file és osztálynév)
Application Controller példa64
Paraméterezett asszociatív tömb
$action_map = array('del' => 'DeleteBookmark','upd' => 'UpdateBookmark','add' => 'InsertBookmark'
);$action_class = (array_key_exists($_POST['action'], $action_map))
? $action_map[$_POST['action']]: 'DisplayBookmark';
if (!class_defined($action)) {require_once 'actions/'.$action_class.'.php';
}$action =& new $action_class;$action->run();
Vitatott kérdések66
Az MVC minta a vezérlő, a nézet és az adatok
helyéről nyilatkozik általánosságban
Egy webalkalmazásban azonban számos további
funkció is helyet kap
munkamenet-kezelés
authentikáció
authorizáció
oldalsablon használata
Munkamenet-kezelés67
Többféle elképzelés van, de ezek fontossága
azonban elvi jelentőségű
$_SESSION
modell: adatok vannak benne
vezérlő: az alkalmazás input oldalán jelennek meg
nézet: ha sütikkel oldjuk meg, akkor viszont HTTP
specifikus
Authentikáció68
Az authentikációs logika külön osztályban (library)
megvalósítható
Hol épüljön az alkalmazásba?
Lehetőségek
őscontroller
hook
Authorizáció69
Ez is külön osztályban megvalósítható
Sok helyen ACL (Access Control List) néven hivatkoznak rá
Sokféle megvalósítása van
Népszerű a Zend ACL használata
Helye
őscontroller
hook
controller
még alsóbb szinteken (feladatfüggő, pl. mezőszintű elérési szabályok)
Layout használata70
Helye
őscontrollerben megírni
set_template_data
display_template
hook-ot készíteni rá
http://hasin.wordpress.com/2007/03/05/adding-yield-
codeigniter/
library-t készíteni rá
http://codeigniter.com/wiki/layout_library/
Layout ősvezérlőben71
class MY_Controller extends CI_Controller {protected $template = 'template';protected $template_data = array();
protected function set_template_part($part, $view, $data = array()) {$this->template_data[$part] = $this->load->view($view, $data, true);
}protected function set_template_data($part, $data) {
$this->template_data[$part] = $data;}protected function display_template($view = null, $data = array()) {
if (!is_null($view)) {$this->set_template_part('content', $view, $data);
}$this->load->view($this->template, $this->template_data);
}}
Layout használata72
public function index(){
$albums = $this->Albums_Model->get_albums();$this->display_template('list', array(
'nev' => $nev,'leiras' => $leiras,'albums' => $albums,
));}
Input Class74
Az Input Class biztonságos adatelérést tesz
lehetővé
megszűri a POST és COOKIE adatokat
igény szerint XSS szűrést hajt végre
Nem kell külön betölteni (automatikusan történik)
Metódusok
$this->input->post('vmi' [, true])
$this->input->cookie()
$this->input->server()
Speciális útvonalak75
Alapból az URI eldönti, melyik vezérlő melyik
metódusát hívjuk
Ez utóbbi felülbírálható a _remap függvénnyel
Ha van, akkor mindig meghívódik
1. paraméter: metódus
2. paraméter: opcionális paraméterlista
Pl. speciális előtag használata (pl. AJAX hívásnál)
Speciális útvonal76
public function _remap($method, $params = array()){
if ($this->request->is_ajax()) {$method = '_ajax_'.$method;
}if (method_exists($this, $method)){
return call_user_func_array(array($this, $method), $params);}show_404();
}
Speciális útvonalak77
Átirányítási szabályok (config/routes.php)
//Egy-egy leképezés$route['journals'] = "blogs";$route['blog/joe'] = "blogs/users/34";
//Joker karakterek$route['product/:num'] = "catalog/product_lookup";$route['product/(:any)'] = "catalog/product_lookup";$route['product/(:num)'] = "catalog/product_lookup_by_id/$1";
//Reguláris kifejezések$route['products/([a-z]+)/(\d+)'] = "$1/id_$2";// products/shirts/123 --> shirts/id_123