### **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');
}
}
```