# YanPHP
**Repository Path**: JIANGWL/YanPHP
## Basic Information
- **Project Name**: YanPHP
- **Description**: YanPHP——一个为API开发而设计的高性能轻量级框架
- **Primary Language**: PHP
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 53
- **Forks**: 10
- **Created**: 2017-10-06
- **Last Updated**: 2022-03-09
## Categories & Tags
**Categories**: webframework
**Tags**: None
## README
# YanPHP V0.2
[Release Note](https://github.com/kong36088/YanPHP/tree/master/doc/Release.md)
# Guide
- [Documentation](#documentation)
- [安装](#安装)
- [环境要求](#环境要求)
- [安装步骤](#安装步骤)
- [hello world](#hello-world)
- [路由](#路由)
- [默认路由规则](#默认路由规则)
- [自定义路由规则](#自定义路由规则)
- [配置](#配置)
- [系统配置相关](#系统配置相关)
- [日志配置相关](#日志配置相关)
- [database](#database)
- [YAssert](#yassert)
- [入参和Input](#入参和input)
- [用法介绍](#用法介绍)
- [相关入参规则](#相关入参规则)
- [获取输入参数](#获取输入参数)
- [Database](#database)
- [使用介绍](#使用介绍)
- [DB多连接管理](#db多连接管理)
- [Session](#session)
- [定制化](#定制化)
- [定制Result格式](#定制result格式)
- [定制ReturnCode](#定制returncode)
- [YanPHP命名规范准则](#yanphp命名规范准则)
- [控制器](#控制器)
- [Model](#model)
- [入参配置文件(`Param/*.ini`)](#入参配置文件(`param/*.ini`))
- [安全](#安全)
- [重新生成SessionID](#重新生成sessionid)
- [CSRF防御措施](#csrf防御措施)
- [Nginx](#nginx)
- [Apache](#apache)
- [Tests](#tests)
- [License](#license)
# Documentation
这是一个为API开发而设计的高性能轻量级框架。
框架为你集成了一些常用的类库,让你开发更加便捷。
另外引入了Composer,可以让你更加轻松地管理依赖以及处理命名空间问题。
让语法更加贴近原生PHP,并且在此基础上让你实现自己的定制化功能。
因为封装程度不高,所以框架的性能十分之优秀。
## 目录结构
```
├── Application --你的代码目录
│ ├── Cgi --分层应用根目录(这里是Cgi代码)
│ │ ├── Cache --缓存
│ │ ├── Compo --自定义组件
│ │ ├── Config --配置
│ │ ├── Controller --控制器,用于编写业务逻辑
│ │ ├── Logs --日志存放
│ │ ├── Model --模型层
│ │ ├── Param --入参定义,以及参数校验
│ │ └── Util --工具类库
│ └── Server --分层应用根目录(这里是Server代码)
│ │ ├── Cache --缓存
│ │ ├── Compo --自定义组件
│ │ ├── Config --配置
│ │ ├── Controller --控制器,用于编写业务逻辑
│ │ ├── Logs --日志存放
│ │ ├── Model --模型层
│ │ ├── Param --入参定义,以及参数校验
│ │ └── Util --工具类库
├── System --框架目录
│ └── Yan
│ ├── Common
│ └── Core
│ ├── Compo
│ └── Exception
```
## 安装
### 环境要求
Require:
`PHP >= 7.0`
`Composer`
Composer安装:https://getcomposer.org/download/
### 安装步骤
先把`YanPHP`从github拷贝到本地
```bash
git clone https://github.com/kong36088/YanPHP.git
```
然后进行框架依赖组件的安装,这里需要用到`composer`。请确保您的机器中已经安装了`composer`
```bash
composer install
```
完成!
## hello world
首先安装YanPHP的依赖项:
```php
composer install
```
来编写我们的第一个hello world
首先我们需要先新增一个控制器
新建一个控制器文件 `Application/Cgi/Controller/HelloController.php`
``` php
succ('hello world');
}
}
```
紧接着到`Param`目录下创建我们的控制器入参规则,
`Application/Cgi/Param/HelloController.ini`内容如下:
``` ini
[index]
```
用命令行重新加载composer中注册的命名空间:
``` bash
composer dump-autoload
```
最后,用浏览器访问我们刚才编写的hello world:`http://localhost/interface.php/hello/index`
返回结果为以下内容
``` json
{
"code": 0,
"message": "hello world",
"data": []
}
```
若有新增类库文件,**记得一定要运行一次 `composer dump-autoload` 以刷新我们的autoloader**
## 路由
### 默认路由规则
默认路由寻路路径是:/interface.php/controller/method
controller代表您的控制器,method是指向的方法
举例:http://localhost/interface.php/user/getUser
这个路径映射到`UserController`的`getUser`方法
### 自定义路由规则
当然,您也可以自定义自己的路由规则。
路由规则存放在 `Application/YourLevel/Config/router.php`
``` php
$config['default_method'] = 'index'; //默认方法
$config['route'] = [
'/' => [ //被映射的路径
'request_method' => ['GET','POST'], //支持的http动作,支持GET和POST
'controller' => 'App\\Cgi\\Controller\\UserController', //所映射到的控制器,需要包含命名空间,映射到Application/Cgi/Controller/UserController
'method' => 'index' //所映射到的方法,映射到UserController的index方法
],
'/user' => [
'request_method' => ['GET'], //支持的http动作,支持GET
'controller' => 'App\\Cgi\\Controller\\UserController', //所映射到的控制器,需要包含命名空间,映射到Application/Cgi/Controller/UserController
'method' => 'getUser' //映射到UserController的index方法getUser方法
],
];
```
## 配置
配置文件统一存放在 `Application/YourLevel/Config` 目录下
`Application`下的各个文件夹对应着您应用的各个分层,每一层都采用自己独立的Config配置
#### 系统配置相关
``` php
$config['namespace'] = 'App\\Cgi';
```
这里用于配置你的应用层采用的命名空间,在新添加应用层后请勿忘记修改这里的配置哦。
``` php
$config['session_path'] = BASE_PATH.'/Cache/session';
$config['session_name'] = 'YAN_SESSION';
```
`session_path` 用于配置session存放的僦
`session_name` 用于配置session名称,即用于标识用户session id的key值
#### 日志配置相关
几种日志等级。比如日志等级配置为`INFO`,则INFO及INFO以上的(NOTICE、WARING、ERROR)等等级的日志将会被记录。
```
/**
'DEBUG'
'INFO'
'NOTICE'
'WARNING'
'ERROR'
'CRITICAL'
'ALERT'
'EMERGENCY'
*/
$config['log_level'] = 'DEBUG';
```
日志存放路径
```
/**
* The log path
*/
$config['log_path'] = BASE_PATH . '/logs/cgi.log';
```
```
/**
* 最大存放的日志文件数量
*/
$config['log_max_file'] = 0;
/**
* 配置日志记录的格式
* "[%datetime%] %channel%.%level_name%: %message% %context%\n";
*/
$config['log_format'] = "[%datetime%]-%extra.process_id% %channel%.%level_name%: %message% %context%\n";
```
#### database
`database.php`
|config|options|description|
|:-----------:|:-----------:|:-----------:|
|`db_host`||DB host|
|`db_user`||用户名|
|`db_password`||密码|
|`db_port`| 3306/(others)|端口号|
|`db_database`||库|
|`db_charset`|utf8/(others)||
|`db_driver`| mysql/postgres/sqlite/sqlsrv |目前支持四种数据库类型|
可以配置多个数据库连接,默认使用`default`进行连接
例如下面的例子:
```php
$config['db'] = [
'default' => [
/** host */
'db_host' => 'mysql',
/** 数据库用户名 */
'db_user' => 'root',
/** 数据库密码 */
'db_password' => 'root',
/** 端口 */
'db_port' => 3306,
/** 数据库 */
'db_database' => 'yan',
/** 表名前缀 */
'db_prefix' => '',
/**
* mysql/postgres/sqlite/sqlsrv
*/
'db_driver' => 'mysql',
'db_charset' => 'utf8',
'db_collation' => 'utf8_unicode_ci'
],
'mysql1'=>[
'db_host' => '',
'db_user' => '',
'db_password' => '',
'db_port' => 3306,
'db_database' => '',
'db_prefix' => '',
'db_driver' => 'mysql',
'db_charset' => '',
'db_collation' => ''
]
];
```
这里我们可以对连接进行管理,其中上面的`default`以及`mysql1`是我们的连接名称,我们可以根据名称进行数据库连接的切换。
具体可以看[Database/DB多连接管理](#db多连接管理)
## YAssert
YanPHP内嵌的断言支持。感谢[beberlei/assert](https://github.com/beberlei/assert)提供类库支持
详细的使用方法在这里:[YassertDocument](https://github.com/kong36088/YanPHP/tree/master/doc/YAssert.md)
## 入参和Input
### 用法介绍
所有入参都需要定义在应用目录路径下的Param目录,并且可以对其进行相关的参数校验操作。
下面我们会举例对该功能进行讲解。
例如我们需要请求`UserController`的`index`方法,那么我们需要创建一个`入参配置文件` `Param/UserController.ini`
文件内容如下:
```ini
[index]
user_id="starts_with[1]|required|numeric|between[1,123]"
page="numeric"
domain="string|numeric"
arr="array"
[getUser]
```
“=”号左边的是需要的入参,右边的是需要验证的规则。规则都是`Validator`内置好的,基于[Respect/Validation](https://github.com/Respect/Validation)开发
并且只有被定义在`入参配置文件`中的参数才会被Input类所识别,其余参数一律丢弃。
若参数不符合规则要求,则会直接返回错误信息。
如若你需要为参数配置多个验证规则,可以用 `|` 进行规则分割,例子:`domain="string|length[1,20]"`。
在这个例子中,我们要求domain必须是字符串类型,并且长度在1-20个字符之间。
### 相关入参规则
|规则|参数|使用说明|例子|
|:---:|:---:|:---:|:---:|
|required|否|参数必填||
|optional|否|参数可空||
|integer|否|整型||
|numeric|否|所有字符都是数字(不区分变量类型)||
|float|否|浮点型||
|string|否|字符型||
|array|否|数组型||
|ip|否|验证是否为一个有效的ip||
|json|否|验证是否为合法json格式||
|email|否|验证是否为合法邮箱||
|domain|否|验证是否为合法域名||
|regex|是|正则匹配|`regex(/[0-9]+/)`|
|starts_with|是|是否以规定的字符开头|`starts_with(ab)`|
|ends_with|是|是否以规定的字符结束|`ends_with(ab)`|
|between|是|数值在定义的范围之间|`between(1,100)`|
|min|是|定义最小不小于|`min(1)`|
|max|是|定义最大不大于|`max(100)`|
|length|是|定义字符串长度在定义范围内|`length(1,100)`|
|equal|是|入参的值必须等于定义的值|`equal(123)`|
|contain|是|入参是否包含给出的值|`contain([ab])`|
### 获取输入参数
```php
Input::get('user_id'); //获取参数user_id
Input::set('user_id',1); //设置参数
```
## Database
DB方面YanPHP采用了[illuminate/Database](https://github.com/illuminate/database)。
编码设计风格与其保持总体一致。
### 使用介绍
> `composer require "illuminate/events"` required when you need to use observers with Eloquent.
Once the Capsule instance has been registered. You may use it like so:
**Using The Query Builder**
```PHP
$users = Capsule::table('users')->where('votes', '>', 100)->get();
```
Other core methods may be accessed directly from the Capsule in the same manner as from the DB facade:
```PHP
$results = Capsule::select('select * from users where id = ?', array(1));
```
**Using The Schema Builder**
```PHP
Capsule::schema()->create('users', function ($table) {
$table->increments('id');
$table->string('email')->unique();
$table->timestamps();
});
```
**Using The Eloquent ORM**
```PHP
class User extends Illuminate\Database\Eloquent\Model {}
$users = User::where('votes', '>', 1)->get();
```
或者使用YanPHP提供的风格
```PHP
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
class User extends Model
{
protected $table = 'user';
protected $primaryKey = 'uid';
protected $keyType = 'int';
public function getById($id): Collection
{
return $this->where([$this->primaryKey => $id])->get();
}
public function getByCond($cond): Collection
{
return $this->where($cond)->get();
}
public function updateByCond($cond, $update): bool
{
return $this->where($cond)->update($update);
}
public function deleteById($id)
{
return $this->where($id)->delete();
}
}
$UserModel = new User();
$UserModel->getById(1); // 获取user表中uid为1的用户数据信息
```
For further documentation on using the various database facilities this library provides, consult the [Laravel database documentation](https://docs.golaravel.com/docs/5.4/database/).
### DB多连接管理
在配置文件`database.php`配置我们的连接后,可以实现多个db连接实例。
下面我们将介绍如何进行连接的切换。
`Model/User.php`
```php
setConnection('default');
return $this->where([$this->primaryKey => $id])->get();
}
}
```
我们可以使用Model当中的`$connection`配置默认的连接。
另外一种方法是使用自带的`$this->setConnection($name)`方法进行连接的设置
## Session
用法示例
``` php
use Yan\Core\Session;
Session::set('a','b'); //设置session值
$sessionVaue = Session::get('a'); //获取session中的值
Session::destroy(); //销毁所有session
```
Session类中有以下方法
``` php
/**
* @method static mixed get($key, $alt = null)
* @method static mixed set($key, $val)
* @method static null clear()
* @method static mixed getFlash($key, $alt = null)
* @method static null setFlash($key, $val)
* @method static null clearFlash()
* @method static mixed getFlashNext($key, $alt = null)
* @method static null setFlashNow($key, $val)
* @method static null clearFlashNow()
* @method static null keepFlash()
* @method boo null destroy()
*/
```
## 定制化
### 定制Result格式
可以到你的应用目录下的`Compo/Result.php`定制化你的Result格式
下面是Result类的示例:
``` php
namespace App\Cgi\Compo;
use Yan\Core\Compo\ResultInterface;
class Result implements ResultInterface
{
protected $code;
protected $message;
protected $data;
public function __construct(int $code, string $message, array $data = [])
{
$this->code = $code;
$this->message = $message;
$this->data = $data;
}
function getCode(): int
{
return $this->code;
}
function getMessage(): string
{
return $this->message;
}
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by json_encode,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
function jsonSerialize()
{
return ['code' => $this->code, 'message' => $this->message, 'data' => $this->data];
}
}
```
`jsonSerialize()`返回一个数组,作为结果输出
### 定制ReturnCode
YanPHP为你定义了一个全局的返回码,返回码的修改可以到`System/Yan/Core/ReturnCode.php`修改
## YanPHP命名规范准则
YanPHP的所有的类文件文件名都 **必须** 与类名保持一致
框架的命名方式应该遵循[驼峰命名法](https://zh.wikipedia.org/zh-cn/%E9%A7%9D%E5%B3%B0%E5%BC%8F%E5%A4%A7%E5%B0%8F%E5%AF%AB)的命名规范。相关介绍可以看[这里](https://zh.wikipedia.org/zh-cn/%E9%A7%9D%E5%B3%B0%E5%BC%8F%E5%A4%A7%E5%B0%8F%E5%AF%AB)
### 控制器
控制器类名需要以`驼峰命名法`进行命名,并且以Controller作为后缀结束。
例如:`UserController`、`TotalStatisticsController`、`InfoListController`
### Model
采用驼峰法,名字可以根据你自己的喜好进行命名。我们会推荐你根据数据库表名或相关的业务用途对model进行命名。
例如:`User`、`Product`、`Price`
### 入参配置文件(`Param/*.ini`)
文件名 **必须** 与你的控制器名称保持一致。每个控制器单独对应一个入参配置文件。
例如:`UserController.ini`、`TotalStatisticsController.ini`、`InfoListController.ini`
## 安全
### 重新生成SessionID
Any time a user has a change in privilege (that is, gaining or losing access
rights within a system) be sure to regenerate the session ID:
```php
```
> N.b.: The `regenerateId()` method also regenerates the CSRF token value.
### Cross-Site Request Forgery
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。
尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。
#### CSRF防御措施
为了防范Csrf攻击,server端的处理逻辑应该是这样的:
1. 为每一个已经登陆了的用户的每个表单请求放置一个验证token;
2. 确保所有通过 POST/PUT/DELETE (i.e., "unsafe") 的请求都包含上一步骤提到的token值。
> N.b.: 如果我们的应用是通过GET请求方法去修改应用资源 (这
> 是一个错误的做法), 我们同样应该对GET方法的请求
> 进行CSRF token的验证,以确保用户资源的安全。
例如,我们想要生成一个CSRF token,可以参考以下做法
```php
getValue();
```
当server端接收到一个请求时,我们应该对CSRF token进行以下的处理:
```php
auth->isValid()) {
$csrf_value = Input::get('__csrf_value');
$csrf_token = Session::getCsrfToken();
if (! $csrf_token->isValid($csrf_value)) {
echo "This looks like a cross-site request forgery.";
} else {
echo "This looks like a valid request.";
}
} else {
echo "CSRF attacks only affect unsafe requests by authenticated users.";
}
?>
```
## Nginx
我们需要把每一层应用层作为你的根目录
``` bash
# 这里是你的Cgi层
server
{
listen 80;
server_name cgi.example.com;
index index.shtml index.html index.htm interface.php;
root root /path/to/root/YanPHP/Application/Cgi/;
location / {
try_files $uri $uri/ =404;
if (!-e $request_filename)
{
rewrite (.*) /interface.php;
}
}
location ~ .*\.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
access_log /var/log/YanPHP/access.log;
error_log /var/log/YanPHP/error.log;
}
# 这里是你的Server层
server
{
listen 80;
server_name server.example.com;
index index.shtml index.html index.htm interface.php;
root root /path/to/root/YanPHP/Application/Server/;
location / {
try_files $uri $uri/ =404;
if (!-e $request_filename)
{
rewrite (.*) /interface.php;
}
}
location ~ .*\.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
access_log /var/log/YanPHP/access.log;
error_log /var/log/YanPHP/error.log;
}
```
## Apache
如果你需要进行url重写,那么你需要开启 `rewrite module`。
YanPHP已经为你编写好了 `.htaccess` 文件,并且存放在每一个应用层的目录下。
``` apacheconfig
DocumentRoot "/path/to/root/YanPHP/Application/Cgi/;"
ServerName cgi.example.com
AddType application/x-httpd-php .php
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
DirectoryIndex interface.php
DocumentRoot "/path/to/root/YanPHP/Application/Server/;"
ServerName server.example.com
AddType application/x-httpd-php .php
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
DirectoryIndex interface.php
```
## Tests
YanPHP编写了相关的测试用例,可以通过运行一下命令进行单元测试,同时也欢迎提交pull request进行测试用例的补充。
``` bash
phpunit --configuration phpunit.xml
```
## License
MIT