前言
ThinkPHP 即将迎来最新版本 6.0,针对目前越来越流行 Swoole,thinkphp 也推出了最新的扩展 think-swoole 3.0
沙盒
本文主要介绍在 ThinkPHP-swoole 3.0 当中所用到的沙盒技术。
沙盒 – 顾名思义,所有程序都运行在一个封闭容器当中,得益于更完善的容器技术,在 3.0 扩展当中沙盒得以大展身手。
首先,查看沙盒是如何使用的,查看扩展当中 Swoole.php,其中的 OnRequest 函数
public function onRequest($req, $res)
{$this->app->event->trigger('swoole.request');
$this->resetOnRequest();
/** @var Sandbox $sandbox */
$sandbox = $this->app->make(Sandbox::class);
$request = $this->prepareRequest($req);
try {$sandbox->setRequest($request);
$sandbox->init();
$response = $sandbox->run($request);
$this->sendResponse($sandbox, $response, $res);
} catch (Throwable $e) {
try {
$exceptionResponse = $this->app
->make(Handle::class)
->render($request, $e);
$this->sendResponse($sandbox, $exceptionResponse, $res);
} catch (Throwable $e) {$this->logServerError($e);
}
} finally {$sandbox->clear();
}
}
代码中,从容器中取出沙盒,然后将请求注入到沙盒,并在沙盒中计算并返回结果。最终对沙盒进行清除,那么 Sandbox 是如何起到沙盒的作用的呢?
//$sandbox->setRequest($request);
public function setRequest(Request $request)
{Context::setData('_request', $request);
return $this;
}
上述代码将请求注入到了沙盒内,这里又多出一个 Context,那么这个类又是做什么的呢?为何不在沙盒内整个属性来存储呢?这个我们文末在做介绍,我介绍沙盒。
//$sandbox->init();
public function init()
{if (!$this->config instanceof Config) {throw new RuntimeException('Please initialize after setting base app.');
}
$this->setInstance($app = $this->getApplication());
$this->resetApp($app);
}
最主要的环节也就是这里了,看到这里就明白沙盒为何称之为沙盒了。由于 tp6 是基于容器创建和销毁资源的,那么各个容器之间是相对隔离的。下面接着看代码
//$this->setInstance($app = $this->getApplication());
public function getApplication()
{$snapshot = $this->getSnapshot();
if ($snapshot instanceof Container) {return $snapshot;}
$snapshot = clone $this->getBaseApp();
$this->setSnapshot($snapshot);
return $snapshot;
}
看到什么了吗?clone,复制。这里将容器对象进行了复制,也就是原容器有的对象,这个新容器也有。也就是说每次请求都会创建一个新的环境用于执行和解析,由于容器的隔离性,每个请求都不会和其他请求进行干扰。
至于下面这段代码,看到这里,我觉得您也已经明白了。
$this->resetApp($app);
最后,$sandbox->clear(),清空 Context 类中保存的当前协程的数据,并将当前沙盒的容器初始化
public function clear()
{Context::clear();
$this->setInstance($this->getBaseApp());
}
番外篇
沙盒当中为何会需要 Context 类呢?看到这个类以后就会明白了,static::getCoroutineId() 是获取当前的协程 ID,每一个协程都会有一个独一无二的 ID,这样通过 Context 来存一些特殊数据或者对象就不会造成数据混乱。因为只有当前协程才可以读取到该数据。
<?php
namespace think\swoole\coroutine;
use Swoole\Coroutine;
use think\Container;
class Context
{
/**
* The app containers in different coroutine environment.
*
* @var array
*/
protected static $apps = [];
/**
* The data in different coroutine environment.
*
* @var array
*/
protected static $data = [];
/**
* Get app container by current coroutine id.
*/
public static function getApp()
{return static::$apps[static::getCoroutineId()] ?? null;
}
/**
* Set app container by current coroutine id.
*
* @param Container $app
*/
public static function setApp(Container $app)
{static::$apps[static::getCoroutineId()] = $app;
}
/**
* Get data by current coroutine id.
*
* @param string $key
*
* @return mixed|null
*/
public static function getData(string $key)
{return static::$data[static::getCoroutineId()][$key] ?? null;
}
/**
* Set data by current coroutine id.
*
* @param string $key
* @param $value
*/
public static function setData(string $key, $value)
{static::$data[static::getCoroutineId()][$key] = $value;
}
/**
* Remove data by current coroutine id.
*
* @param string $key
*/
public static function removeData(string $key)
{unset(static::$data[static::getCoroutineId()][$key]);
}
/**
* Get data keys by current coroutine id.
*/
public static function getDataKeys()
{return array_keys(static::$data[static::getCoroutineId()] ?? []);
}
/**
* Clear data by current coroutine id.
*/
public static function clear()
{unset(static::$apps[static::getCoroutineId()]);
unset(static::$data[static::getCoroutineId()]);
}
/**
* Get current coroutine id.
*/
public static function getCoroutineId()
{return Coroutine::getuid();
}
}