如何正确使用 Slim 框架

同步发布于 SXYBlog

本文将讲述如何将 Slim 弄成 MVC 架构,以及在其中使用 Eloquent ORM 和 Blade。

这是我习惯的目录结构,供诸位参考。

V4rSxJ.png

第一步:基本配置

按照 Slim 官方的教程,先创建一个项目框架。

$ composer create-project slim/slim-skeleton [my-app-name]

然后安装需要用到的扩展。

$ composer require illuminate/database rubellum/slim-blade-view

再将 public/index.php 改成这样。

<?php
// index.php

use Carbon\Carbon;
use Illuminate\Database\Capsule\Manager;
use Slim\App;

if (PHP_SAPI == 'cli-server') {
    // To help the built-in PHP dev server, check if the request was actually for
    // something which should probably be served as a static file
    $url = parse_url($_SERVER['REQUEST_URI']);
    $file = __DIR__ . $url['path'];
    if (is_file($file)) {
        return false;
    }
}

require __DIR__ . '/../vendor/autoload.php';

session_start();

// 创建 App 实例
$settings = require __DIR__ . '/../bootstrap/settings.php';
$app = new App($settings);
$container = $app->getContainer();

// 设置 Carbon 的语言以及默认时区
Carbon::setLocale('zh');
date_default_timezone_set('Asia/Shanghai');

// 启动 Eloquent ORM
$capsule = new Manager;
$capsule->addConnection($container->get('settings')['database']);
$capsule->setAsGlobal();
$capsule->bootEloquent();

// 加载依赖
$dependencies = require __DIR__ . '/../bootstrap/dependencies.php';
$dependencies($app);

// Utils.php 是我自己写的函数库,你可以去掉
require_once __DIR__ . '/../bootstrap/utils.php';

// 注册全局中间件
$middleware = require __DIR__ . '/../bootstrap/middleware.php';
$middleware($app);

// 注册路由
require __DIR__ . '/../routes/web.php';

// 运行 App
$app->run();
<?php
// dependencies.php

use Slim\App;
use Slim\Container;
use Slim\Views\Blade;

return function (App $app) {
    // 获取容器实例
    $container = $app->getContainer();

    // 配置 Blade 作为模板引擎
    $container['renderer'] = function (Container $c) {
        $settings = $c->get('settings')['renderer'];
        return new Blade($settings['template_path'], $settings['cache_path']);
    };

    // 配置 Eloquent ORM
    $container['db'] = function (Container $c) {
        global $capsule;

        return $capsule;
    };
};
<?php
// middleware.php

use Slim\App;

return function (App $app) {
    // 这里需要返回一个中间件,可以是函数或者类。
    // 如果传入类,该类需要有 __invoke 方法。
    // 具体可参阅官方文档: http://www.slimframework.com/docs/v3/concepts/middleware.html
    // e.g: $app->add(new \Slim\Csrf\Guard);
};
<?php
// settings.php

return [
    'settings' => [
        'displayErrorDetails' => true, // 是否显示错误详情。务必在线上设为 false !!!
        'addContentLengthHeader' => false, // 设为 true 以在每次响应中添加 Content-Length Header

        // Blade,或者说模板引擎的配置
        'renderer' => [
            'template_path' => __DIR__ . '/../resources/view/',
            'cache_path' => __DIR__ . '/../storage/cache',
        ],

        // 数据库配置
        'database' => [
            'driver' => 'mysql',
            'host' => 'localhost',
            'database' => 'lw',
            'username' => 'root',
            'password' => '',
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
        ],
    ],
];
<?php
// utils.php

use App\Http\Models\Model;
use App\Http\Models\User;
use Overtrue\Validation\Translator;
use Overtrue\Validation\Validator;
use Slim\Http\Response;

// 跟 Laravel 的 view 用法基本一致
function view($path, array $args = [])
{
    global $container;

    return $container->get('renderer')->render($container->get('response'), $path, $args);
}

// 获取 Response 实例
function response(): Response
{
    global $container;

    return $container->get('response');
}

// 跟 Laravel 用途一致
function asset($path)
{
    $schema = $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'];
    $path = trim($path, '/');
    $url = "{$schema}://{$host}/{$path}";

    return $url;
}

// 跟 Laravel 用途一致
function route($name, $args = [])
{
    global $container;

    if (is_object($args) && $args instanceof Model) {
        $args = [$args->getRouteKeyName() => $args->getRouteKey()];
    }

    return $container->get('router')->pathFor($name, $args);
}

// 在 Blade 模板中使用 `@auth` 会调用此函数。
function auth()
{
    // 见下方 Auth 类
    return new Auth();
}

// 在 Blade 模板中使用 `@method` 会调用此函数。
function method_field($method)
{
    return "<input type='hidden' name='_METHOD' value='{$method}'>";
}

// 一个(伪)认证类
class Auth
{
    public function guard()
    {
        return $this;
    }

    public function check()
    {
        if (isset($_SESSION['uid']) && is_numeric($_SESSION['uid'])) {
            return true;
        }

        return false;
    }

    public function user()
    {
        $user = User::find($_SESSION['uid']);

        return $user;
    }
}

第二步:控制器

我建议大家建立一个 Controller 类,封装一些常用方法,再由每个控制器继承该类。

<?php
// Controller.php

namespace App\Http\Controllers;

use Illuminate\Database\Capsule\Manager;

class Controller
{
    protected $db;

    public function __construct()
    {
        global $capsule;

        // 可在控制器中通过 `$this->db` 获取数据库实例,类似 Laravel 的 DB Facade。
        $this->db = $capsule;
    }
}

下面是一个用户控制器的例子。

<?php
// UserController.php

namespace App\Http\Controllers;

use App\Http\Models\User;
use Slim\Http\Request;

// 继承了 Controller 类
class UserController extends Controller
{
    public function profile(Request $request)
    {
        // 通过 User 模型获取
        // $request->getAttribute($name) 用于获取路由参数
        $user = User::query()->find($request->getAttribute('id'));

        // 返回视图
        return view('users.profile', compact('user'));
    }

    public function edit(Request $request)
    {
        $user = User::query()->find($request->getAttribute('id'));

        return view('users.edit', compact('user'));
    }

    public function secure(Request $request)
    {
        $user = User::query()->find($request->getAttribute('id'));
        // 使用 `$this->db` 操作数据库
        $auths = $this->db->getConnection()->table('local_auths')->where('user_id', $user->id)->get();

        return view('users.secure', compact('user', 'auths'));
    }

    public function update(Request $request)
    {
        $user = User::query()->find($request->getAttribute('id'));

        // 判断请求方法
        if ($request->isMethod('PUT')) {
            // $request->getParams(array $only = null) 用于获取请求参数
            $user->update($request->getParams(['nickname']));

            // 重定向
            return response()->withRedirect(route('users.profile', $user));
        } else {
            $data = $request->getParams();
            // 这是我自己封装的数据验证
            $v = validate($data, [
                'current_password' => 'required',
                'new_password' => 'required|confirmed|between:6,18',
            ]);
            if ($v->fails()) {
                return response()->write($v->errors()->first());
            }
            $auth = $this->db->getConnection()->table('local_auths')->where('user_id', $user->id)->where('username', $data['username']);
            if (password_verify($data['current_password'], $auth->first()->password)) {
                $auth->update([
                    'password' => password_hash($data['new_password'], PASSWORD_DEFAULT)
                ]);

                unset($_SESSION['uid']);
                return response()->withRedirect(route('login'));
            } else {
                // 输出文本
                return response()->write('当前密码错误');
            }
        }
    }
}
<?php
// User.php

namespace App\Http\Models;

class User extends Model
{
    // Eloquent ORM 默认会自动维护这两字段
    const UPDATED_AT = null;
    const CREATED_AT = 'registered_at';

    protected $guarded = [];

    protected $dates = ['registered_at'];

    public function getAvatarAttribute($value)
    {
        return $value ?: 'https://api.adorable.io/avatars/300/' . ($this->email ?: 'mail@example.com');
    }
}

第三步:路由

<?php
// routes/web.php

use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\PageController;
use App\Http\Controllers\UserController;
use App\Http\Middleware\VerifyUserPermission;

$app->get('/', PageController::class . ':index')->setName('index');

$app->map(['get', 'post'], '/login', LoginController::class . ':login')->setName('login');
$app->map(['get', 'post'], '/register', RegisterController::class . ':register')->setName('register');
$app->post('/logout', LoginController::class . ':logout')->setName('logout');

$app->get('/about', PageController::class . ':about')->setName('about');

$app->get('/users/{id}', UserController::class . ':profile')->setName('users.profile');

$app->group('/users', function () use ($app) {
    $app->get('/{id}/edit', UserController::class . ':edit')->setName('users.edit');
    $app->get('/{id}/secure', UserController::class . ':secure')->setName('users.secure');
    $app->put('/{id}', UserController::class . ':update')->setName('users.update');
    $app->patch('/{id}', UserController::class . ':update')->setName('users.update');
    // TODO: 完善用户注销功能
    $app->delete('/{id}', UserController::class . ':delete')->setName('users.delete');
})->add(new VerifyUserPermission());

大功告成

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
sunxyw
讨论数量: 2

这不就是laravel的目录结构吗

4年前 评论
sunxyw

@ice_sun 是的,因为我比较习惯使用 Laravel 的目录结构。

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!