一个极简的PHP框架,支持在fpm或swoole协程模式下运行

2.3.1 2022-12-13 03:10 UTC

README

英文 | 中文

One - 一个支持[swoole | php-fpm]环境的极简高性能PHP框架

  • 快速 - 即使在php-fpm环境下也能在 1ms 内响应用户请求
  • 简单 - 让您专注于使用one做什么,而不是如何使用one
  • 灵活 - 每个组件松散耦合,可以灵活匹配和使用,使用方法一致
    • 原生SQL可以与模型关系 with 一起使用,关系可以跨数据库类型
    • session可以在http、websocket或甚至tcp、udp和cli下使用
    • ...
  • 高效 - 运行性能、开发效率和易于维护
  • 轻量级 - 无其他依赖,路由和orm所有组件的总代码量不超过500k。如果没有复杂调用关系,可以快速掌握设计原则
  • 子数据库和子表 - 支持无线子数据库和表,上层使用方法保持不变

hello world

安装

composer create-project lizhichao/one-app app
cd app
php App/swoole.php 

# stop : `php App/swoole.php -o stop`  
# reload : `php App/swoole.php -o reload`  
# Daemon start : `php App/swoole.php -o start`  

curl http://127.0.0.1:8081/

性能

参考:

主要组件

  • 路由器
    • 支持贪婪匹配和优先级
    • 支持ws/tcp/http……任何协议
    • 高性能,添加数万条路由不会降低解析性能
    • 路由分组、中间件...应有尽有
  • ORM
    • 支持数据库:mysql、clickHouse
    • 关系处理:一对一、一对多、多对一、多态...有多种关系,可以跨数据库类型关联
    • 缓存:自动刷新数据,支持配置各种缓存粒度
    • 事件:所有操作都可以捕获,包括您使用原生SQL操作数据库
    • 数据库连接:支持同步、异步、阻塞、断开和重新连接
    • SQL模板:自动生成模板ID,您可以了解项目中有哪些类型的SQL,调用比例,并为后续数据优化提供数据支持。
    • 语句重用:提供SQL执行性能
    • 模型支持动态子数据库子表和大量数据
  • RPC
    • 可以自动生成远程方法映射,支持IDE提示
    • 直接调用映射方法 == 调用远程方法,支持链式调用
    • 支持 rpc middleware,认证、加密和解密、缓存...
  • 日志
    • 完整信息:记录完整的文件名 + 行号,以便快速定位代码位置
    • requestId:您可以轻松查看整个请求日志信息和服务关系

路由器

Router::get('/', \App\Controllers\IndexController::class . '@index');

// router with params
Router::get('/user/{id}', \App\Controllers\IndexController::class . '@user');

// router with group
Router::group(['namespace'=>'App\\Test\\WebSocket'],function (){
	// websocket router
    Router::set('ws','/a','TestController@abc'); 
    Router::set('ws','/b','TestController@bbb'); 
});

// Middleware
Router::group([
    'middle' => [
        \App\Test\MixPro\TestMiddle::class . '@checkSession'
    ]
], function () {
    Router::get('/mix/ws', HttpController::class . '@ws');
    Router::get('/user/{id}', \App\Controllers\IndexController::class . '@user');
    Router::post('/mix/http/send', HttpController::class . '@httpSend');
});

ORM

定义模型

namespace App\Model;

use One\Database\Mysql\Model;

// There is no need to specify the primary key in the model, the framework will cache the database structure
// Automatically match the primary key, automatically filter the fields in the non-table structure
class User extends Model
{
	// Define the table name corresponding to the model
    CONST TABLE = 'users';

	// define relationship
    public function articles()
    {
        return $this->hasMany('id',Article::class,'user_id');
    }
    
    // define event
    // Whether to enable automatic caching
    // ……
}

使用模型

  • 数据库连接在 fpm 下是单列
  • 所有数据库操作在 swoole 模式下自动切换到连接池
// Query a record
$user = User::find(1);

// Related query
$user_list = User::whereIn('id',[1,2,3])->with('articles')->findAll()->toArray();

// update
$r = $user->update(['name' => 'aaa']);
// or
$r = user::where('id',1)->update(['name' => 'aaa']);
// $r To influence the number of records

缓存

// Set cache without expiration time
Cache::set('ccc',1);

// Set the cache to expire in 1 minute
Cache::set('ccc',1,60);


Cache::get('ccc');

// or cache ccc expires 10s under tag1
Cache::get('ccc',function (){
    return 'info';
},10,['tag1']);

// Refresh all caches under tag1
Cache::flush('tag1');

HTTP/TCP/WEBSOCKET/UDP

启动websocket服务器,添加http服务监控,添加tcp服务监控

[
	 // Main server
    'server' => [
        'server_type' => \One\Swoole\OneServer::SWOOLE_WEBSOCKET_SERVER,
        'port' => 8082,
        // Event callback
        'action' => \One\Swoole\Server\WsServer::class,
        'mode' => SWOOLE_PROCESS,
        'sock_type' => SWOOLE_SOCK_TCP,
        'ip' => '0.0.0.0',
        // swoole Server setting parameters
        'set' => [
            'worker_num' => 5
        ]
    ],
    // Add listener
    'add_listener' => [
        [
            'port' => 8081,
            // Event callback
            'action' => \App\Server\AppHttpPort::class,
            'type' => SWOOLE_SOCK_TCP,
            'ip' => '0.0.0.0',
            // Set parameters for monitoring
            'set' => [
                'open_http_protocol' => true,
                'open_websocket_protocol' => false
            ]
        ],
        [
            'port' => 8083,
            // Unpacking protocol
            'pack_protocol' => \One\Protocol\Text::class,
            // Event callback
            'action' => \App\Test\MixPro\TcpPort::class,
            'type' => SWOOLE_SOCK_TCP,
            'ip' => '0.0.0.0',
            // Set parameters for monitoring
            'set' => [
                'open_http_protocol' => false,
                'open_websocket_protocol' => false
            ]
        ]
    ]
];

RPC

像调用本项目中的方法一样调用远程服务器的方法。跨语言,跨机器。

服务

启动rpc服务。框架为每种协议内置了rpc服务,只需在上面的配置文件中将它们添加到 action 中即可。例如:支持 http 调用,支持 tcp 调用。

// http Protocol rpc service
[
    'port'   => 8082,
    'action' => \App\Server\RpcHttpPort::class,
    'type'   => SWOOLE_SOCK_TCP,
    'ip'     => '0.0.0.0',
    'set'    => [
        'open_http_protocol'      => true,
        'open_websocket_protocol' => false
    ]
],
// tcp Protocol rpc service
[
    'port'          => 8083,
    'action'        => \App\Server\RpcTcpPort::class,
    'type'          => SWOOLE_SOCK_TCP,
    'pack_protocol' => \One\Protocol\Frame::class, // tcp packing protocol
    'ip'            => '0.0.0.0',
    'set'           => [
        'open_http_protocol'      => false,
        'open_websocket_protocol' => false,
        'open_length_check'       => 1,
        'package_length_func'     => '\One\Protocol\Frame::length',
        'package_body_offset'     => \One\Protocol\Frame::HEAD_LEN,
    ]
]

将特定服务添加到rpc中,例如,有一个类Abc

class Abc
{
    private $a;

    public function __construct($a = 0)
    {
        $this->a = $a;
    }

    public function add($a, $b)
    {
        return $this->a + $a + $b;
    }

    public function time()
    {
        return date('Y-m-d H:i:s');
    }

    public function setA($a)
    {
        $this->a = $a;
        return $this;
    }
}

Abc添加到rpc服务

// Add Abc to rpc service
RpcServer::add(Abc::class);

// If you don't want to add all the methods under Abc to the rpc service, you can also specify the addition.
// Unspecified methods cannot be called by the client.
// RpcServer::add(Abc::class,'add');

// Add in groups
//RpcServer::group([
//    // The middleware can do permission verification, data encryption and decryption, etc.
//    'middle' => [
//        TestMiddle::class . '@aa'
//    ],
//    // Cache If set, when called with the same parameters, the cache information will be returned and will not be called. Unit: seconds
//    'cache'  => 10
//], function () {
//    RpcServer::add(Abc::class);
//    RpcServer::add(User::class);
//});

客户端调用

为了便于调用,我们创建了一个映射类(一个框架可以自动生成)

class ClientAbc extends RpcClientHttp {

    // rpc server address
    protected $_rpc_server = 'http://127.0.0.1:8082/';

    // The remote class is not set and the default is the current class name
    protected $_remote_class_name = 'Abc';
}

调用rpc服务的远程方法与调用本项目的方法相同。你可以想象这个方法就在你的项目中。

$abc = new ClientAbc(5);

// $res === 10
$res = $abc->add(2,3);

// Chain call $res === 105
$res = $abc->setA(100)->add(2,3);

// If the User of the above model is added to rpc
// RpcServer::add(User::class);
// The following operation results are the same as above
// $user_list = User::whereIn('id',[1,2,3])->with('articles')->findAll()->toArray();

以上是通过http协议调用的。你也可以通过其他协议调用。例如,Tcp协议

class ClientAbc extends RpcClientTcp {

    // rpc server address
    protected $_rpc_server = 'tcp://127.0.0.1:8083/';

    // The remote class is not set and the default is the current class name
    protected $_remote_class_name = 'Abc';
}

框架中有RpcClientHttpRpcClientTcp类。你也可以将其复制到其他任何地方使用。

更多

文档

待办事项

我的其他开源项目