Just for fun——Slim借力Swoole

Slim

Slim的话,是一个遵循PSR (PSR-7)规范微型的框架,作者这样说:

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs. At its core, Slim is a dispatcher that receives an HTTP request, invokes an appropriate callback routine, and returns an HTTP response. That’s it.

大致意思:slim的核心工作:分发了Http请求,然后调用回调函数,返回一个Http response对象。

Slim其实就帮我们做了两件事

  1. 路由的分发
  2. 依赖的注入

框架很小,所以别的部分(如db操作、模板引擎)可能需要自己实现,但slim通过依赖注入,让你可以很轻松的组装其他功能到slim中。

快速入门:

<?php
// Create and configure Slim app
$config = ['settings' => [
    'addContentLengthHeader' => false,
]];
$app = new \Slim\App($config);

// Define app routes
$app->get('/hello/{name}', function ($request, $response, $args) {
    return $response->write("Hello " . $args['name']);
});

// Run app
$app->run();

request代表了当前请求对象,response代表了当前响应对象,$args是占位符的键值对数组。
访问/hello/salamander就会输出Hello salamander

添加依赖

DB是我自己封装的一个PDO的操作类。

$config = ['settings' => [
    'addContentLengthHeader' => false,
]];
$app = new \Slim\App($config);
$container = $app->getContainer();

$container['db'] = function($c) {
    $dbHost = 'localhost';
    $dbName = 'test';

    $dbConf = [
        'dsn' => "mysql:dbname={$dbName};host={$dbHost}",
        'username' => "root",
        'password' => "******",
        'charset' => 'utf8'
    ];
    $db = new \App\Library\DB();
    $db->__setup($dbConf);
    return $db;
};

// Define app routes
$app->get('/user/{uid}', 'App\Controller\IndexController:index');
IndexController类
namespace App\Controller;

class IndexController 
{
    protected $container;
    
    public function __construct(ContainerInterface $container) {
        $this->container = $container;
    }
    
    public function index($request, $response, $args) {
        $info = $this->container['db']->fetch('SELECT name FROM user WHERE uid = :uid', [
            'uid' => $args['uid']
        ]);
        echo "user name is " . $info['name'];
    }
}

IndexController类的是通过composer自动载入的(代码中没写):

"autoload": {
    "psr-4": {
        "App\\": "app/"
    }
},

代码中可以发现,依赖容器的注入是在类实例化的时候发生的。执行IndexController的index方法时,我们从$container中取出的db依赖,这时候,注册的回调函数被调用,返回实例。因为是用到时才实例化,这个叫做延迟实例化

结合Swoole

Swoole让PHP可以常驻内存,而且它提供了Http Server的功能,所以Slim和Swoole没什么冲突。

思考

Slim是通过当前路由(譬如/user/2,不带查询字符串)和http方法来找到正确的回调函数的。这些量Slim是从哪里取的呢?肯定是$_SERVER。查看slim源码:
run()方法:

public function run($silent = false)
{
    $response = $this->container->get('response');

    try {
        ob_start();
        $response = $this->process($this->container->get('request'), $response);
    } catch (InvalidMethodException $e) {
        $response = $this->processInvalidMethod($e->getRequest(), $response);
    } finally {
        $output = ob_get_clean();
    }

    if (!empty($output) && $response->getBody()->isWritable()) {
        $outputBuffering = $this->container->get('settings')['outputBuffering'];
        if ($outputBuffering === 'prepend') {
            // prepend output buffer content
            $body = new Http\Body(fopen('php://temp', 'r+'));
            $body->write($output . $response->getBody());
            $response = $response->withBody($body);
        } elseif ($outputBuffering === 'append') {
            // append output buffer content
            $response->getBody()->write($output);
        }
    }

    $response = $this->finalize($response);

    if (!$silent) {
        $this->respond($response);
    }

    return $response;
}

发现$request对象是从容器取出来的,那$request是怎么注册的呢??,那就看App类的构造函数了,最后发现Container类的构造函数中有registerDefaultServices()方法:

private function registerDefaultServices($userSettings)
{
    $defaultSettings = $this->defaultSettings;

    /**
     * This service MUST return an array or an
     * instance of \ArrayAccess.
     *
     * @return array|\ArrayAccess
     */
    $this['settings'] = function () use ($userSettings, $defaultSettings) {
        return new Collection(array_merge($defaultSettings, $userSettings));
    };

    $defaultProvider = new DefaultServicesProvider();
    $defaultProvider->register($this);
}

查看$defaultProvider->register()方法:

public function register($container)
{
    if (!isset($container['environment'])) {
        /**
         * This service MUST return a shared instance
         * of \Slim\Interfaces\Http\EnvironmentInterface.
         *
         * @return EnvironmentInterface
         */
        $container['environment'] = function () {
            return new Environment($_SERVER);
        };
    }

    if (!isset($container['request'])) {
        /**
         * PSR-7 Request object
         *
         * @param Container $container
         *
         * @return ServerRequestInterface
         */
        $container['request'] = function ($container) {
            return Request::createFromEnvironment($container->get('environment'));
        };
    }
    
    //...

可以看到$request对象是通过Request::createFromEnvironment方法构造的,它需要从容器中取出environment依赖,而environment依赖是通过构造一个Environment对象得来的,它正好放入了$_SERVER
查看Environment类源码,可以发现它继承了Collection类,Collection的构造函数如下:

public function __construct(array $items = [])
{
    $this->replace($items);
}

从上面我们可以得出,我们主要注册一个自定义的environment依赖就行,原来$_SERVER的信息可以从swoole的$request->server中取。

简单实现

server.php

<?php
use Slim\Http\Environment;

// 定义常量
define("ROOT", getcwd() . '/..');
define('APP', ROOT . '/app');

require ROOT . '/vendor/autoload.php';
// load functions
require APP . '/functions.php';


$http = new swoole_http_server("0.0.0.0", 8888);

$http->on("start", function ($server) {
    echo "Swoole http server is started at http://0.0.0.0:8888\n";
});

$http->on("request", function ($request, $response) {
    // Instantiate the app
    $config = [
        'settings' => [
            'addContentLengthHeader' => false,
        ]
    ];
    $config['environment'] = function () use($request) {
        $server = [];
        foreach ($request->server as $key => $value) {
            $server[strtoupper($key)] = $value;
        }
        return new Environment($server);
    };
    $app = new \Slim\App($config);
    // Register routes
    require APP . '/routes.php';

    // Run app
    $slimResponse = $app->run(true);
    $headers = $slimResponse->getHeaders();
    foreach ($headers as $name => $values) {
        $response->header($name, implode(", ", $values));
    }
    $response->header("X-Powered-By", "Salamander");
    $response->end($slimResponse->getBody());
});

$http->start();

注意$request->server中key都是小写的,所以这里转化了一下。

routes.php(在App目录中)

<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;


$app->get('/', function (Request $request, Response $response) {
    $response->getBody()->write('Hello Salamander');
    return $response;
});


$app->get('/user/{uid}', function (Request $request, Response $response, $args) {
    $response->getBody()->write('Hello User:' . $args['uid']);
    return $response;
});

测试

访问/

clipboard.png

访问/user/45

clipboard.png

打包下载测试

百度云盘
tip:环境基于docker的,运行docker-compose up即可

Github上的讨论

Issue


Salamander
上帝在我很小的时候送给我了两个苹果,一个红苹果,一个蓝苹果。红苹果代表疯狂,蓝苹果代表思考
6.7k 声望
407 粉丝
0 条评论
推荐阅读
Java AtomicInteger类使用
这个问题发生的原因是++counter不是一个原子性操作。当要对一个变量进行计算的时候,CPU需要先从内存中将该变量的值读取到高速缓存中,再去计算,计算完毕后再将变量同步到主内存中。这在多线程环境中就会遇到问...

pigLoveRabbit2阅读 2.3k

怎样用 PHP 来实现枚举?
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,...

唯一丶25阅读 6.3k评论 4

Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go5阅读 1.8k评论 2

封面图
微信公众号开发:自动回复文本/图片/图文消息/关键词回复/上传素材/自定义菜单
对接流程1、申请微信公众号测试账号URL:[链接]2、登录,配置开发者服务器URL和Token开发者服务器配置代码:config.php {代码...} URL是config.php在你服务器的URLToken是上面代码自己设置的Token搞定之后,就能完...

TANKING2阅读 10k

Hyperf 3.0 发布,PHP 新时代
在过去的一年半时间里,Hyperf 2.2 共发布了 35 个小版本,使 Hyperf 达到了一个前所未有的高度,这里也获得了一些不错的数据反馈。

huangzhhui3阅读 1k

封面图
多个著名 Go 开源项目被放弃,做大开源不能用爱发电,更不能只靠自己!
大家好,我是煎鱼。相信关注我的许多同学都有接触 Go 语言的开发,甚至在企业中多有实践。那么你在日常开发中,势必会接触到 gorilla 组织下的各个 Go 开源项目。如下图:gorilla/mux:Star:17.9k。a powerful r...

煎鱼1阅读 2.3k

Go for 循环有时候真的很坑。。。
大家好,我是煎鱼。不知道有多少 Go 的面试题和泄露,都和 for 循环有关。今天我在周末认真一看,发现了 redefining for loop variable semantics 。著名的硬核大佬 Russ Cox 表示他一直在研究这个问题,并表示十...

煎鱼阅读 3.4k

6.7k 声望
407 粉丝
宣传栏