iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 27
0

初學PHP做網頁時,會使用類似這樣localhost:8000/board.php?no=3,透過URI來傳遞參數,但這麼做是有風險的:第一容易被攻擊,二來維護性、可讀性差。
想要使URI的使用更彈性、看起來更簡潔,我們可以透過Routing來達成,它是一種可以定義URI如何從某一處到另一處的機制,讓一個URI對應一個檔案。

建議先看完下一篇的MVC介紹再回來看這個範例

自己做一個仿造框架的router設計,讓URI可以長的像這樣:
網頁進入點
https://ithelp.ithome.com.tw/upload/images/20191012/201200241jad0xg24M.png
輸入使用者名稱後(重點在網址):
https://ithelp.ithome.com.tw/upload/images/20191012/20120024YykuIovE7K.png
About:
https://ithelp.ithome.com.tw/upload/images/20191012/201200248GAMB68ZBF.png
About Our Culture:
https://ithelp.ithome.com.tw/upload/images/20191012/20120024rfgLoR2VPK.png
Contact:
https://ithelp.ithome.com.tw/upload/images/20191012/20120024suNu8Pd6z5.png
檔案結構:
https://ithelp.ithome.com.tw/upload/images/20191012/20120024ytX2HiNRbD.png

連結資料庫所需的參數 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。

  • 網頁上執行的程式檔進入點會從相對路徑下去叫其他require的檔案,如果叫出放在進入點上層的檔案,require()裡的路徑要加"../"切回上層。

定義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中。

controllers

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

core底下的database裡的檔案所扮演角色比較接近一般MVC架構中的model的角色,負責和資料庫連接。
Router.php和 Request.php則是負責處理routing的部份。

  • databse(資料夾)
    • Connection.php
      建立data object
    <?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());
            }
        }
    }
    
    • QueryBuilder.php
      把connection中,連接資料庫所需的引數放進去,連接MySQL
    <?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

views裡放的是負責處理使用者介面的檔案
按照慣例,view的取名方式都是xxx.view.php

partilas裡放的是幾乎每個view都有的head、footer和nav(連結)
統一寫在一個檔案,以提高程式維護性,當某個view需要用到的時候直接載入即可,修改的時候也只需要更動一個檔案。

  • partials(資料夾)

    • footer.php
    </body>
    </html>
    
    
    • head.php
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
    <body>
        <?php require('nav.php'); ?>
    
    • 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


上一篇
Day 26 Laravel 5.8:安裝與簡介
下一篇
Day 28 MVC架構
系列文
後端基礎PHP+Mysql & Laravel 30日養成計畫36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言