Skip to content

Commit

Permalink
Merge pull request #17 from ipuppet/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
ipuppet committed Jan 28, 2021
2 parents ae363ca + d902caf commit cee8fda
Show file tree
Hide file tree
Showing 15 changed files with 364 additions and 283 deletions.
76 changes: 54 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
## ipuppet/jade

> A RESTful API framework.
### 安装

`composer require ipuppet/jade`

### 创建项目

使用`vendor/ipuppet/jade/src`目录下的`build`脚本创建新项目

您可以在该脚本位置运行
Expand All @@ -16,6 +19,7 @@
e.g. `php build.php -r /path/to/`

在composer.json中更新如下内容:

```json
{
"autoload": {
Expand All @@ -26,28 +30,65 @@ e.g. `php build.php -r /path/to/`
}
}
```

创建以后请运行`composer dump-autoload`以更新自动加载文件。

### 配置文件

### 基本配置
> 配置文件均在`/config`目录中
项目创建完成后,您可以在`config/response.json`文件中设置如果请求发生错误该返回什么内容。(如请求方法不被允许、未匹配到结果等)
#### `config.json`

该实现在`Component/Router/Reason/Reason.php`抽象类中,如果向构造函数传递一个`Config`对象则会尝试从其中读取数据
项目全局配置文件

若开头为符号`@`,则该值被视为路径且符号`@`将被自动替换成项目根目录(该路径是通过`AppKernel`中的`getRootDir()`方法获取的)
##### 内容说明

若为其他内容则直接以字符串形式输出。
| 名称 | 默认值 | 说明 |
| --- | --- | --- |
| `logAccessError` | `false` | `Boolean` 是否记录拒绝访问以及未匹配的路由 `true`表示记录 |
| `errorResponse` || `Object` 设置当请求发生错误时返回的内容。<br>若开头为符号`@`,则该值被视为路径且符号`@`将被自动替换成项目根目录(该路径是通过`AppKernel`中的`getRootDir()`方法获取的)若为其他内容则直接以字符串形式输出。 |
| `cors` || `Object` 设置跨域。<br>属性:(以下属性的默认值只有在您设置了cors字段后才生效)<br>`hosts`: `Array` 允许跨域请求的协议+域名,如`http://a.example.com`<br>`methods`: `Array` 允许的方法,默认为`["get", "post", "put", "delete"]`<br>`headers`: `Array` 允许的方法,默认为`["Content-Type", "Authorization"]` |

### 配置文件
配置文件示例:

项目全局配置文件为`config/config.json`
```json
{
"logAccessError": false,
"errorResponse": {
"404": "@/public/response/404.html"
},
"cors": {
"hosts": [
"https://a.example.com",
"https://b.example.com"
]
}
}
```

#### 内容说明
#### `routes.json`

| 名称 | 默认值 | 说明 |
| --- | --- | --- |
| logAccessError | false | 是否记录拒绝访问以及未匹配的路由 `true`表示记录 |
示例:

```json
[
{
"method": "get",
"name": "SayHello",
"path": "/say-hello/{like}/{name}/",
"controller": "App\\Controller\\SayHelloController::sayHelloAction"
},
{
"methods": [
"get",
"post"
],
"name": "SayHello2",
"path": "/say-hello2/{like}/{name}/",
"controller": "App\\Controller\\SayHelloController::sayHelloTwoAction"
}
]
```

### 控制器

Expand All @@ -68,9 +109,7 @@ public function sayHelloAction($like, $name)

```json
{
"methods": [
"get"
],
"method": "get",
"name": "SayHello",
"path": "/say-hello/{like}/{name}/",
"controller": "App\\Controller\\SayHelloController::sayHelloAction"
Expand All @@ -92,17 +131,10 @@ public function sayHelloAction($like, $name)
```php
public function __construct(Request $req)
{
if ($req->getMethod() === 'OPTIONS') {
$this->ignoreRequest();
}
var_dump($req);
}
```

`ignoreRequest()`方法可用来忽略一次请求,该方法在抽象类`Controller`中。

与其共同工作的还有`setDefaultResponse()`方法,该方法接受一个`Response`型的变量,用来明确默认情况下该作何反应。
若不调用将会返回一个状态为`204`的响应。

### API

由于目前分身乏术,可能日后再进行更新,目前还请直接查看源码。
87 changes: 42 additions & 45 deletions src/Component/Kernel/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Ipuppet\Jade\Component\Http\Request;
use Ipuppet\Jade\Component\Http\Response;
use Ipuppet\Jade\Component\Kernel\Config\Config;
use Ipuppet\Jade\Component\Kernel\Config\ConfigLoader;
use Ipuppet\Jade\Component\Kernel\Controller\ControllerResolver;
use Ipuppet\Jade\Component\Logger\Logger;
Expand All @@ -17,10 +18,10 @@
use Ipuppet\Jade\Foundation\Path\Exception\PathException;
use Ipuppet\Jade\Foundation\Path\Path;
use Ipuppet\Jade\Foundation\Path\PathInterface;
use ReflectionException;

abstract class Kernel
{
private bool $isLogAccessError = false;
/**
* @var ?ConfigLoader
*/
Expand All @@ -40,6 +41,19 @@ abstract class Kernel
* @var ?PathInterface
*/
protected ?PathInterface $rootPath = null;
/**
* @var Config
*/
private Config $config;

/**
* Kernel constructor.
* @throws PathException
*/
public function __construct()
{
$this->config = $this->getConfigLoader()->setName('config')->loadFromFile();
}

/**
* 获取缓存目录
Expand Down Expand Up @@ -88,58 +102,54 @@ public function getRootPath(): PathInterface
* @return Response
* @throws PathException
* @throws NoMatcherException
* @throws \ReflectionException
* @throws ReflectionException
*/
public function handle(Request $request): Response
{
$request->headers->set('X-Php-Ob-Level', ob_get_level());
$logger = new Logger();
//实例化ControllerResolver
// 实例化ControllerResolver
$logger->setName('ControllerResolver')->setOutput($this->getLogPath());
$controllerResolver = new ControllerResolver($logger);
//实例化Router对象
// 实例化Router对象
$logger->setName('Router')->setOutput($this->getLogPath());
$router = new Router();
$matcher = new MatchByRegexPath();
$router->setRequest($request)
->setLogger($logger)
->setRouteContainer($this->getRouteContainer())
->setMatcher($matcher);
//获取Config对象
$config = $this->getConfigLoader()->setName('response')->loadFromFile();
//如果加载成功则向Router中传递
if ($config !== null) {
$config->add(['root_dir' => $this->getRootPath()]);
$router->setConfig($config);
}
//开始匹配路由
// 获取Config对象
$config = new Config();
if ($this->config->has('errorResponse')) $config->add($this->config->get('errorResponse'));
$config->add(['rootPath' => $this->getRootPath()]);
$router->setConfig($config);
// 开始匹配路由
if ($router->matchAll()) {
$request = $router->getRequest();
$controller = $controllerResolver->getController($request);
//调用
$isIgnoreRequest = call_user_func([$controller[0], 'isIgnoreRequest']);
if ($isIgnoreRequest) {
$response = call_user_func([$controller[0], 'getDefaultResponse']);
// 判断配置文件内是否有跨域配置,若有则注入到控制器中
if ($this->config->has('cors') && !empty($this->config->get('cors'))) {
call_user_func([$controller[0], 'setCorsConfig'], new Config($this->config->get('cors')));
}
// 验证是否可以跨域
$isPassCorsCheck = call_user_func([$controller[0], 'checkCors']);
if ($request->getMethod() === 'OPTIONS') { // 对OPTIONS请求进行处理
return Response::create('', $isPassCorsCheck ? Response::HTTP_204 : Response::HTTP_400);
}
// 整理参数顺序,按照方法签名对齐
$parameters = $controllerResolver->sortRequestParameters($controller, $request);
// 调用控制器中对应的方法并获得Response
$response = call_user_func_array($controller, $parameters);
if ($response instanceof Response) {
return $response;
} else {
//整理参数顺序,按照方法签名对齐
$parameters = $controllerResolver->sortRequestParameters($controller, $request);
$response = call_user_func_array($controller, $parameters);
if ($response instanceof Response) {
return $response;
} else {
$logger->error('Your response not instanceof Response.');
}
$logger->error('The return value of Controller must need instance of Response.');
}
}
//响应错误信息
$reason = $router->getReason();
$response = new Response($reason->getContent(), $reason->getHttpStatus());
if ($this->isLogAccessError) {
//此时已经是Router日志
$logger->error("Access error '{$request->getPathInfo()}' {$reason->getDescription()}");
}
return $response;
// 响应错误信息
$reason = $router->getReason($this->config->get('logAccessError', false));
return new Response($reason->getContent(), $reason->getHttpStatus());
}

/**
Expand Down Expand Up @@ -167,20 +177,7 @@ public function getConfigLoader(): ConfigLoader
$path = new Path($this->getRootPath());
$path->after('/config');
$this->configLoader->setPath($path)->setParser(new JsonParser());
$this->loadDefaultConfig($this->configLoader);
}
return $this->configLoader;
}

/**
* 加载框架默认配置,默认认为所有配置项到保存在config文件中
* @param ConfigLoader $configLoader
*/
private function loadDefaultConfig(ConfigLoader $configLoader)
{
$config = $configLoader->setName('config')->loadFromFile();
if ($config->has('logAccessError')) {
$this->isLogAccessError = (bool)$config->get('logAccessError');
}
}
}
20 changes: 0 additions & 20 deletions src/Component/Router/Reason/HostNotAllow.php

This file was deleted.

8 changes: 4 additions & 4 deletions src/Component/Router/Reason/Reason.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ abstract class Reason implements ReasonInterface

/**
* Reason constructor.
* @param Config|null $config
* @param LoggerInterface|null $logger
* @param ?Config $config
* @param ?LoggerInterface $logger
* @throws Exception
*/
public function __construct(Config $config = null, LoggerInterface $logger = null)
{
if ($config !== null) {
$content = $config->get($this->getHttpStatus());
$content = $config->get('errorResponse')[$this->getHttpStatus()];
if ($content[0] === '@') {
$content = str_replace('@', $config->get('root_dir'), $content);
$content = str_replace('@', $config->get('rootPath'), $content);
if (file_exists($content)) {
$this->content = file_get_contents($content);
} else {
Expand Down

0 comments on commit cee8fda

Please sign in to comment.