🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### **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'); } } ```