### **1.通過`composer`進行安裝**
`composer require slim/slim "^3.0"`
### **2.URL重寫**
#### 1. Apache 中
```
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]
```
#### 2. Nginx 中
詳見[鏈接](https://www.bookstack.cn/read/slim3-doc/c4b8302be1a9f031.md)
### **3. 編寫 Route**
在根目錄新建`index.php`,以此為項目路由
下面是`demo`
```
<?php
/**
*
* @Description: Route 路由
* @Author: edison
* @Date: 2021/6/8
* @Time: 21:55
*
*/
require 'vendor/autoload.php';
$app = new \Slim\App();
$app->get('/',function () {
echo 'Home';
});
$app->get('/users',function () {
echo 'Users';
});
$app->run();
```
### **4.容器**
下面是一個`demo`
```
<?php
/**
*
* @Description: Route 路由
* @Author: edison
* @Date: 2021/6/8
* @Time: 21:55
*
*/
require 'vendor/autoload.php';
$app = new \Slim\App();
// 實例化一個容器
$container = $app->getContainer();
// 向容器中注入
$container['greeting'] = function () {
return 'Hello from the container';
};
$app->get('/',function () {
// 加載容器
echo $this->greeting;
});
$app->run();
```
### **5. 打開顯示錯誤信息**
```
<?php
/**
*
* @Description: Route 路由
* @Author: edison
* @Date: 2021/6/8
* @Time: 21:55
*
*/
require 'vendor/autoload.php';
$app = new \Slim\App([
'settings' => [
'displayErrorDetails' => true
]
]);
$app->get('/error',function () {
echo $this->nothing;
});
$app->run();
```
### **6. 安裝 twig(用來創建模板)**
因為`demo`中使用的是`slim3.0`,所以這里安裝`twig 2.1`
`composer require slim/twig-view:^2.1
`
#### 1. 在`Slim`容器中將次組件注冊為服務
```
// Register component on container
$container['view'] = function ($container) {
$view = new \Slim\Views\Twig(__DIR__.'/resources/views', [
'cache' => false
]);
// Instantiate and add Slim specific extension
$router = $container->get('router');
$uri = \Slim\Http\Uri::createFromEnvironment(new \Slim\Http\Environment($_SERVER));
$view->addExtension(new \Slim\Views\TwigExtension($router, $uri));
return $view;
};
```
#### 2. 使用此組件,指向對應的頁面模板
```
$app->get('/',function ($request,$response) {
return $this->view->render($response,'home.twig');
});
```
#### 3. 新建模板
1. 在根目錄新建`resources/view`,用于存儲模板文件
2. 在`views`中新建`layouts`,用于存儲公共頁面
3. 新建`users.twig`
```
{% extends 'layouts/app.twig' %}
{% block title %}
Users View
{% endblock %}
{% block content %}
Users View
{% endblock %}
```
4. 新建`layouts/app.twig`
```
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
```
5. 新建`home.twig`
```
{% extends 'layouts/app.twig' %}
{% block title %}
Home View
{% endblock %}
{% block content %}
Home View
{% endblock %}
```
6. 將數據渲染到模板
```
$app->get('/users',function ($request,$response) {
// 假設這里是從數據庫得到的users數據
$users = [
['username' => 'alex'],
['username' => 'tom'],
['username' => 'lina']
];
return $this->view->render($response,'users.twig',[
'users' => $users,
]);
});
```
```
{% extends 'layouts/app.twig' %}
{% block title %}
Profile for {{ user.username }}
{% endblock %}
{% block content %}
<ul>
{% for user in users %}
<li>{{ user.username }}</li>
{% endfor %}
</ul>
{% endblock %}
```
7. 模板之間的跳轉與提交數據
* 模板之間的跳轉
1. 在路由中設置別名`setName`
```
$app->get('/',function ($request,$response) {
return $this->view->render($response,'home.twig');
})->setName('home');
$app->get('/users',function ($request,$response) {
// 假設這里是從數據庫得到的users數據
$users = [
['username' => 'alex'],
['username' => 'tom'],
['username' => 'lina']
];
return $this->view->render($response,'users.twig',[
'users' => $users,
]);
})->setName('users.index');
```
```
<a href="{{ path_for('users.index') }}">Users Page</a>
```
* 模板提交數據
```
<form action="{{ path_for('contact') }}"></form>
```
### **7.提交路由**
* 前端
```
<form action="{{ path_for('contact') }}" method="post">
<label for="email">Email</label>
<input type="text" name="email" id="email">
<button type="submit">提交</button>
</form>
```
* 后端
```
$app->get('/contact',function ($request,$response){
return $this->view->render($response,'contact.twig');
});
$app->post('/contact',function ($request,$response){
echo $request->getParam('email');
// 重定向
return $response->withRedirect('....');
})->setName('contact');
```
### **8.路由分組**
`Slim`路由可將相同前綴的請求進行分組
```
$app->group('/topics',function () {
$this->get('',function () {
echo 'Topics';
});
$this->get('/{id}',function ($request,$response,$args) {
echo 'Topic'.$args['id'];
});
});
```
### **9.在`slim`中使用`pdo`**
1. 利用`PDO`連接遠程數據庫(這里使用容器進行依賴注入)
```
$container = $app->getContainer();
/**
* 使用pdo訪問數據庫
*/
$container['db'] = function () {
$dsn='mysql:host=81.70.105.47;dbname=xike_myxs_site';
$user='xike_myxs_site';
$password='F8T8ECdsij8L4NiG';
$status=1;
return new PDO($dsn,$user,$password);
};
```
2. 查詢數據庫取得用戶信息
```
$app->get('/',function () {
$user = $this->db->query("SELECT * FROM xike_user")->fetchAll(PDO::FETCH_OBJ);
var_dump($user);
});
```
### **10. 利用PDO查詢小例子**
```
$app->get('/users/{mobile}',function ($request,$response,$args) {
// 先使用占位符進行查詢語句整合
$user = $this->db->prepare("SELECT * FROM xike_user WHERE mobile = :mobile");
// 將占位符替換為真實數據進行查詢
$user->execute([
'mobile' => trim($args['mobile'])
]);
var_dump($user->fetch(PDO::FETCH_OBJ));
});
```
**注意:query用于簡單查詢。(好處簡單)
prepare+execute用于預處理,使sql語句靈活了很多,也可用防止SQL注入等問題。
總的來說是prepare強大安全,query簡單。**
### **11. psr 4自動加載**
1. 在`composer.json`中添加`psr4自動加載`
```
"autoload-dev": {
"psr-4": {
"App\\": "app"
}
}
```
2. 執行`composer`將自動加載掛載到類
`composer dump-autoload -o`
### **12.`Slim`中的控制器**
1. 路由指向控制器
在使用自動加載后,路由可指向控制器
```
$app->get('/topics',\App\controllers\TopicController::class.':index');
```
2. 在控制器中使用容器
因為要多個控制器使用容器,所以直接摘離,放到公共控制器中,此處為`BaseController.php`
```
abstract class BaseController
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
}
```
以后所有的控制器繼承此控制器即可
### **13.重定向**
1. 路由中
```
$app->get('/topics','\App\controllers\TopicController:index')->setName('topic.show');
```
2. 控制器中
```
public function store($request,$response)
{
return $response->withRedirect($this->container->router->pathFor('topic.show'),['id' => 5]);
}
```
### **14.設置狀態碼**
**關鍵代碼**
```
$response->withStatus(404)
```
1. `BaseController.php`中
```
public function render404($response)
{
return $this->container->view->render($response->withStatus(404),'error/404.twig');
}
```
2. 子類中
```
public function show($request,$response,$args)
{
$topic = $this->container->db->prepare("SELECT * FROM topics WHERE ID = :id");
$topic->execute([
'id' => $args['id']
]);
$topic = $topic->fetch(\PDO::FETCH_OBJ);
if ($topic === false) {
return $this->render404($response);
}
return $topic;
}
```
### **15.響應結果JSON格式化**
```
public function show($request,$response,$args)
{
$topic = $this->container->db->prepare("SELECT * FROM topics WHERE ID = :id");
$topic->execute([
'id' => $args['id']
]);
$topic = $topic->fetch(\PDO::FETCH_OBJ);
if ($topic === false) {
return $response->withJson([
'error' => '未找到對應的主題'
],404);
}
return $response->withJson($topic,200);
}
```
### **16.中間件**
#### 1. 路由中間件
中間件編寫
```
$middleware = function ($request,$response,$next) {
// $response->getBody()->write('Before');
if (!isset($_SESSION['user_id'])) {
$response = $response->withRedirect($container->router->pathFor('login'));
}
return $next($request,$response);
};
```
```
// 偽造token
$token = function ($request,$response,$next) {
$request = $request->withAttribute('token','abc123');
return $next($request,$response);
};
```
使用
```
$app->get('/topics','\App\controllers\TopicController:index')->add($middleware);
```
#### 2. 控制器中間件
新建`RedirectIfUnauthenticated.php`
```
class RedirectIfUnauthenticated
{
public function __invoke($request,$response,$next)
{
if (!isset($_SESSION['user_id'])) {
$response = $response->withRedirect("login地址");
}
return $next($request,$response);
}
}
```
在路由中使用
```
$app->get('/topics','\App\controllers\TopicController:index')->add(new \App\controllers\RedirectIfUnauthenticated());
```
#### 3.在控制器中間件中使用容器
路由中
```
$app->get('/topics','\App\controllers\TopicController:index')->add(new \App\controllers\RedirectIfUnauthenticated($container));
```
控制器中
```
class RedirectIfUnauthenticated
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function __invoke($request,$response,$next)
{
if (!isset($_SESSION['user_id'])) {
$response = $response->withRedirect($this->container->router->pathFor('login'));
}
return $next($request,$response);
}
}
```
#### 4.IP過濾器小例子
路由器中全局啟用中間件
`$app->add(new \App\controllers\InFilter());`
中間件編寫
```
class InFilter
{
protected $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function __invoke($request,$response,$next)
{
$ips = $this->db->query("SELECT * FROM blocked")->fetchAll(PDO::FETCH_COLUMN,0);
if (in_array($_SERVER['REMOTE_ADDR'],$ips)) {
return $response->withStatus(401)->write('Denied');
}
return $next($request,$response);
}
}
```
### **17.異常處理**
路由中
```
$container['notFoundHandler'] = function ($container) {
return new \App\controllers\NotFoundHandler($container);
};
```
控制器中
```
<?php
/**
*
* @Description:
* @Author: edison
* @Date: 2021/6/14
* @Time: 16:03
*
*/
namespace App\controllers;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Handlers\AbstractHandler;
class NotFoundHandler extends AbstractHandler
{
protected $view;
public function __construct(Twig $view)
{
$this->view = $view;
}
public function __invoke(ServerRequestInterface $request,ResponseInterface $response)
{
$contentType = $this->determineContentType($request);
switch ($contentType) {
case 'application/json':
$output = $this->renderNotFoundJson($response);
break;
case 'text/html':
$output = $this->renderNotFoundHtml($response);
break;
}
return $output->withStatus(404);
}
protected function renderNotFoundJson($response)
{
return $response->withJson([
'error' => 'Not Found'
]);
}
protected function renderNotFoundHtml($response)
{
return $response->view->render($response,'errors/404.twigt');
}
}
```