初學PHP做網頁時,會使用類似這樣localhost:8000/board.php?no=3
,透過URI來傳遞參數,但這麼做是有風險的:第一容易被攻擊,二來維護性、可讀性差。
想要使URI的使用更彈性、看起來更簡潔,我們可以透過Routing來達成,它是一種可以定義URI如何從某一處到另一處的機制,讓一個URI對應一個檔案。
建議先看完下一篇的MVC介紹再回來看這個範例
自己做一個仿造框架的router設計,讓URI可以長的像這樣:
網頁進入點
輸入使用者名稱後(重點在網址):
About:
About Our Culture:
Contact:
檔案結構:
連結資料庫所需的參數 config.php
<?php
return [
'database' => [
'name' => '資料庫名稱',
'username' => '你的mysql使用者名稱',
'password' => '你的mysql密碼',
'connection' => 'mysql:host=127.0.0.1',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING,
],
],
];
程式進入點 index.php
<?php
require 'core/bootstrap.php';
require Router::load('routes.php')
->direct(Request::uri(), Request::method());
第一行載入bootstrap.php,會將所有其他需要的檔案載入,
第二行則是將網址修剪、獲得request方法後,將這兩者送到direct()檢查是否有在routes中,然後依照routes對該URI的定義進入所對應到的controller並載入view。
定義URI和controller之間的對應關係
講白話一點就是URI的斜槓後輸入什麼,則下一個要到哪個檔案
routes.php
<?php
$router->get('', 'controllers/index.php');
$router->get('about', 'controllers/about.php');
$router->get('about/culture', 'controllers/about-culture.php');
$router->get('contact', 'controllers/contact.php');
$router->post('names', 'controllers/add-name.php');
router 中有get和post兩種method,依照RESTful風格的規範,要依照不同的請求方式將URI放到對應的method中。
controller在MVC架構中是負責處理程式邏輯的,也可將它視為在畫面和資料處理之間作為溝通的橋樑。
此範例中放在controllers下的檔案基本上是專門負責跟views溝通的,多寫這些檔案好像就只為了把views中的檔案叫出來,看起來似乎有點多餘。不過一般來說,當專案越寫越大,controller所扮演的角色或越來越重要,下回的mvc會再細談。
about.php
<?php
require 'views/about.view.php';
about-culture.php
<?php
$name = 'myCompany';
require 'views/about-culture.view.php';
add-name.php
<?php
var_dump('You typed ' . $_POST['name']);
contact.php
<?php
require 'views/contact.view.php';
index.php
selectAll()中放的是table名稱
<?php
$tasks = $app['database']->selectAll('todos');
require 'views/index.view.php';
core底下的database裡的檔案所扮演角色比較接近一般MVC架構中的model的角色,負責和資料庫連接。
Router.php和 Request.php則是負責處理routing的部份。
<?php
class Connection {
public static function make($config) {
try {
return new PDO($config['connection'] . ';dbname=' . $config['name'], $config['username'], $config['password'], $config['option']);
} catch (PDOException $e) {
die($e->getMessage());
}
}
}
<?php
class QueryBuilder {
protected $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function selectAll($table) {
$statement = $this->pdo->prepare("select * from $table");
$statement->execute();
return $statement->fetchAll(PDO::FETCH_CLASS); //抓取class Task中所的有資源
}
}
bootstrap.php
這個檔案專門負責載入其他所需要的檔案
<?php
$app = [];
$app['config'] = require 'config.php';
require 'core/Router.php';
require 'core/Request.php';
require 'core/database/Connection.php';
require 'core/database/QueryBuilder.php';
$app['database'] = new QueryBuilder(
Connection::make($app['config']['database'])
);
修剪URI Request、獲得訪問頁面時的請求方法 Request.php
原本完整的URI長這樣:'http://localhost:8888/about/culture'
urI()會把多餘的部份修剪掉,回傳留下的'about/culture'。
<?php
class Request {
public static function uri() {
return trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
}
public static function method() {
return $_SERVER['REQUEST_METHOD'];
}
}
Router.php
這個檔案有點像router中的controller,主要功能是將相對應的URI和controller放進routes[]中,並檢查傳過來的URI是否存在於routes[]。
<?php
class Router {
protected $routes = [
'GET' => [],
'POST' => [],
];
public static function load($file) {
$router = new static;
require $file;
return $router;
}
public function get($uri, $controller) {
$this->routes['GET'][$uri] = $controller;
}
public function post($uri, $controller) {
$this->routes['POST'][$uri] = $controller;
}
public function direct($uri, $requestType) {
if (array_key_exists($uri, $this->routes[$requestType])) {
return $this->routes[$requestType][$uri];
}
throw new Exception('No route defined for this URI.');
}
}
direct()是檢查輸入的URI有沒有被定義在routes中,有的話就回傳他所對應到的controller;沒有則回報'No route defined for this URI.'的錯誤訊息。
順帶一提,在沒有使用到繼承的情況下,
new self 等同於new static又等同於new Router
不過如果有使用到繼承,static和self就有差別了。
static : method在哪邊被呼叫
self : 在哪邊被new出來
views裡放的是負責處理使用者介面的檔案
按照慣例,view的取名方式都是xxx.view.php
partilas裡放的是幾乎每個view都有的head、footer和nav(連結)
統一寫在一個檔案,以提高程式維護性,當某個view需要用到的時候直接載入即可,修改的時候也只需要更動一個檔案。
partials(資料夾)
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<?php require('nav.php'); ?>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/about/culture">About Our Culture</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
about.view.php
<?php require('partials/head.php'); ?>
<h1>About Us</h1>
<?php require('partials/footer.php'); ?>
about-culture.view.php
<?php require 'partials/head.php';?>
<h1>Our Culture at <?=$name;?></h1>
<?php require 'partials/footer.php';?>
contact.view.php
<?php require('partials/head.php'); ?>
<h1>Contact Us</h1>
<?php require('partials/footer.php'); ?>
index.view.php
<?php require 'partials/head.php';?>
<h1>My Tasks</h1>
<?php foreach ($tasks as $task) : ?>
<li>
<?php if ($task->completed) : ?>
<strike><?= $task->description; ?></strike>
<?php else : ?>
<?= $task->description; ?>
<?php endif; ?>
</li>
<?php endforeach; ?>
<h1>Submit Your Name</h1>
<form method="POST" action="/names">
<input name="name"></input>
</form>
<?php require 'partials/footer.php';?>
todos欄位
create table todos( id integer auto_increment primary key, description text(50), complited tinyint(1) );
參考資料:https://laracasts.com/series/php-for-beginners/episodes/16