如何正確使用 Slim 框架

sunxyw發表於2019-06-14

同步釋出於 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 協議》,轉載必須註明作者和本文連結

相關文章