V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
qiujin
V2EX  ›  PHP

推一下自己断断续续写了快一年的框架,名字还没想好就先叫 my_php_framework 吧

  •  
  •   qiujin · 2017-10-04 22:30:36 +08:00 · 4552 次点击
    这是一个创建于 2387 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目地址 https://github.com/qiu-jin/my_php_framework

    这个框架断断续续写了快一年,有空余时间就撸下,一直放在 github 上(反正也没星当私有库用),国庆花了几天整理下文档(文档还不算全,后续会补充),基本算能拿出来见人了。

    至于框架特点,先丢个 README 吧( v2 的 md 处理表格好像有问题,最好还是在 github 上看)


    应用

    框架目前支持 Standard Rest Inline Jsonrpc Micro Grpc 等多种应用模式

    用户也可以实现自己的应用模式和不使用应用模式,以适应不同需求的应用开发( Rest Jsonrpc Grpc 用于接口应用,Standard Inline Micro 即可用于接口应用也可用于视图应用,View 只能用于视图应用)

    另外为了实现不同模式应用之间的相互调用,框架在 rpc driver 中实现了一套 rpc client driver 来远程调用服务。

    默认推荐的标准模式

    RESTful 风格模式

    引用控制器文件代码

    jsonrpc 协议模式

    微框架模式

    grpc 协议模式(实验性)

    • View

    视图驱动模式(未完成)

    • Cli

    命令行模式(未开始)

    • 自定义模式

    用户可以自己实现和使用一个继承framework\App基类,并实现dispatch call error response等方法的应用模式类。

    • 无应用模式

    不使用任何应用模式,只需调用framework\App::boot()初始化环境,就可以编写代码。

    核心

    驱动

    驱动实例统一由容器类管理,有 2 种调用方式。

    1 使用辅助函数 db() cache() storage() rpc() email() sms() driver()

    // 辅助函数参数为空,会默认取驱动配置的第一个实例
    db()->table->get($id);
    // 参数指定使用 email 驱动配置的 smtp 实例
    email('smtp')->send($mail, $subject, $content);
    // geoip 等驱动没有同名的辅助函数,但可以使用 driver 函数调用。
    driver('geoip', 'ipip')->locate($ip);
    

    2 使用trait Getter,继承其魔术方法__get

    class Demo
    {
        use \Getter;
        
        // 配置 getter providers,这里使用了别名配置
        protected $providers = [
            'smtp' => 'email.smtp',
            'ipip' => 'geoip.ipip',
        ];
    
        public function test()
        {
            // 使用驱动类的同名的关键字,会默认取驱动配置的第一个实例
            $this->db->table->get($id);
            // 指定别名 smtp 到 email 驱动配置的 smtp 实例
            $this->smtp->send($mail, $subject, $content);
            // 指定别名 ipip 到 geoip 驱动配置的 ipip 实例
            $this->ipip->locate($ip);
        }
    }
    
    // 执行一条 SQL 并返回结果
    $db->exec("SELECT * FROM user WHERE id = ?", [1]);
    
    // 执行一条 SQL 并获返回 query 给后续方法处理
    $db->fetch($db->query("SELECT * FROM user"));
    
    // 简单查询
    $db->user->get(1);
    // SELECT * FROM `user` WHERE `id` = '1' LIMIT 1
    
    // 组合查询
    $db->good->select('id', 'name')->where('id', '>', 2)->limit(2)->order('id')->find();
    // SELECT `id`, `name` FROM `good` WHERE `id` > '2' ORDER BY `id` LIMIT 2
    
    // 聚合查询,查询用户 1 的最大订单金额
    $db->orders->where('user_id', 1)->max('amount');
    // SELECT max(`amount`) AS `max_amount` FROM `orders` WHERE `user_id` = '1'
    
    // join 连表查询,查询用户 1 的信息和订单
    $db->user->join('orders')->get(1);
    // desc `orders`
    // SELECT `user`.*,`orders`.`id` AS `orders_id`,`orders`.`user_id` AS `orders_user_id`,`orders`.`good_id` AS `orders_good_id`,`orders`.`quantity` AS `orders_quantity`,`orders`.`amount` AS `orders_amount`,`orders`.`time` AS `orders_time` FROM `user` LEFT JOIN `orders` ON `user`.`id` = `orders`.`user_id` WHERE `user`.`id` = '1'
    
    // join 连表查询,在 join 从表指定了 select 字段时不再需要使用 desc 语句获取表字段
    $db->user->select('name')->join('orders')->select('good_id')->get(1);
    // SELECT `user`.`name`,`orders`.`good_id` AS `orders_good_id` FROM `user` LEFT JOIN `orders` ON `user`.`id` = `orders`.`user_id` WHERE `user`.`id` = '1'
    
    // with 逻辑连表查询,先查询 user,再根据结果查询 orders
    $this->db->user->with('orders')->find();
    // SELECT * FROM `user`
    // SELECT * FROM `orders` WHERE `user_id` IN ('1','2','3')
    
    // with 逻辑连表查询,使用 on 方法指定 2 个表的关联字段
    $this->db->orders->with('good')->on('good_id', 'id')->find();
    // SELECT * FROM `orders`
    // SELECT * FROM `good` WHERE `id` IN ('1','2')
    
    // relate 逻辑连表查询,此查询方式需要一个中间表关联主表和从表的信息
    $this->db->user->relate('good')->find();
    // SELECT * FROM `user`
    // SELECT `user_id`, `good_id` FROM `user_good` WHERE `user_id` IN ('1','18','19')
    // SELECT * FROM `good` WHERE `id` IN ('1','3')
    
    // relate 逻辑连表查询,使用 on 方法指定中间表名和关联字段
    $this->db->good->relate('user')->on('user_good')->find();
    // SELECT * FROM `good`
    // SELECT `good_id`, `user_id` FROM `user_good` WHERE `good_id` IN ('1','3')
    // SELECT * FROM `user` WHERE `id` IN ('1')
    
    // sub 子查询连表查询,子查询只作为主表查询的过滤条件
    $db->user->sub('orders')->where('good_id', 1)->find();
    // SELECT * FROM `user` WHERE `id` IN (SELECT `user_id` FROM `orders` WHERE `good_id` = '1') 
    
    // union 连表查询
    $db->orders->where('user_id', 1)->union('orders_2')->where('user_id', 2)->find();
    // (SELECT * FROM `orders` WHERE `user_id` = '1') UNION ALL (SELECT * FROM `orders_2` WHERE `user_id` = '2')
    

    | 驱动 | 描述
    | ----|---- |Mysqli | 基于 php mysqli 扩展,支持一些特有的 mysql 方法 |Mysql | 基于 php pdo_mysql 扩展 |Pgsql | 基于 php pdo_pgsql 扩展(粗略测试) |Sqlite | 基于 php pdo_sqlite 扩展(粗略测试) |Sqlsrv | 在 win 系统下使用 pdo_sqlsrv 扩展,类 unix 系统下使用 pdo_odbc 扩展(无环境,未测试) |Oracle | 基于 php pdo_oci 扩展(无环境,未测试) |Cluster | 基于 Mysqli,支持设置多个数据库服务器,实现读写分离主从分离,原理是根据 SQL 的 SELECT INSERT 等语句将请求分配到不同的服务器。(无环境,未测试)

    // 设置缓存值
    $cache->set($key, $value, $ttl = null);
    
    // 检查缓存是否存在
    $cache->has($key);
    
    // 获取缓存值
    $cache->get($key, $default = null);
    
    // 获取并删除缓存值
    $cache->pull($key);
    
    // 删除缓存
    $cache->delete($key);
    
    // 批量获取
    $cache->getMultiple($keys);
    
    // 批量设置
    $cache->setMultiple($values, $ttl = null);
    
    // 批量删除
    $cache->deleteMultiple($keys);
    
    // 清除所有缓存
    $cache->clear();
    
    // 自增,目前只有 apc redis memcached 支持
    $cache->increment($key, $value = 1);
    
    // 自减,目前只有 apc redis memcached 支持
    $cache->decrement($key, $value = 1);
    
    

    | 驱动 | 描述
    | ----|---- |Apc | 基于 php apcu 扩展的单机共享内存缓存 |Db | 使用关系数据库缓存数据 |File | 使用文件保存缓存数据 |Memcached | 使用 Memcached 服务缓存数据 |Opcache | 将缓存数据写入 php 文件,使用 php Opcache 来缓存数据 |Redis | 使用 Redis 服务缓存数据

    /* 
     * 读取文件(文件不存在会触发错误或异常)
     * $from 要读取的 storage 文件路径
     * $to 本地磁盘文件路径,如果为空,返回文件读取的文件内容
     *     如果不为空,方法读取的文件内容保存到$to 的本地磁盘文件路径中,返回 true 或 false
     */
    $storage->get($from, $to = null);
    
    /* 
     * 检查文件是否存在(文件不存在不会触发错误或异常)
     */
    $storage->has($from);
    
    /* 
     * 获取文件元信息
     * 返回 array 包含,size:文件大小,type:文件类型,mtime:文件更新时间 等信息
     */
    $storage->stat($from);
    
    /* 
     * 上传更新文件
     * $from 本地文件,如果 $is_buffer 为 false,$from 为本地磁盘文件路径
     *       如果 $is_buffer 为 true,$from 为要上传的 buffer 内容
     * $to 上传后储存的 storage 路径
     */
    $storage->put($from, $to, $is_buffer = false);
    
    /* 
     * 复制 storage 文件,从$from 复制到$to
     */
    $storage->copy($from, $to);
    
    /* 
     * 移动 storage 文件,从$from 移动到$to
     */
    $storage->move($from, $to);
    
    /* 
     * 删除 storage 文件
     */
    $storage->delete($from);
    
    /* 
     * 获取 storage 文件访问 url
     */
    $storage->url($path);
    
    /* 
     * 抓取远程文件并保存到 storage
     * 支持 http https 和所有 storage 配置实例
     */
    $storage->fetch($from, $to);
    
    

    | 驱动 | 描述
    | ----|---- |Local | 本地文件处理简单适配封装 |Ftp | 基于 ftp 协议,需要 php ftp 扩展 |Sftp | 基于 ssh 协议,需要 php ssh2 扩展 |S3 | 亚马逊 s3 服务 |Oss | 阿里云 oss 服务 |Qiniu | 七牛云存储 |Webdav | 基于 Webdav 协议,兼容多种网盘,如 Box OneDrive Pcloud 坚果云

    | 驱动 | 描述
    | ----|---- |Console | 日志发送到浏览器控制台,Firefox 可直接使用 Chrome 需安装 chromelogger 插件 |Email | 日志发送到邮件 |File | 日志写入文件 |Queue | 日志发送到队列(坑)

    // 知乎 rest api 调用
    $zhihu->answers->get($id);
    
    // jsonrcp 服务调用
    $jsonrpc->User->getName($id);
    
    // jsonrcp 服务批量调用
    $jsonrpc->batch()
            ->User->getName(1)
            ->User->getName(2)
            ->User->getName(3)
            ->call();
    
    // thrift 服务调用,使用 thriftpy 创建的测试服务
    $thrift->PingPong->add(1, 2);
    
    // grpc 服务调用
    $grpc->User->getName($id);
    
    

    | 驱动 | 描述
    | ----|---- |Jsonrpc | Jsonrpc 协议 rpc 客户端 |Http | rpc 调用风格的 httpClient 封装 |Rest | rpc 调用风格的 Rest httpClient 封装 |Thrift | Thrift rpc 客户端 |Grpc | Grpc rpc 客户端

    // 简单发送
    $email->send('[email protected]', '邮件标题', '邮件正文');
    // 高级发送
    $email->to('[email protected]', 'your_name')->subject('邮件标题')->template('email/register')->send();
    

    | 驱动 | 描述
    | ----|---- |Smtp | 基于 Smtp 协议发送邮件 |Sendmail | 使用 php mail 函数发送邮件(服务器需已装 postfix 等邮件服务器并已开放相应端口) |Mailgun | 使用 Mailgun 提供的邮件发送服务 |Sendcloud | 使用 Sendcloud 提供的邮件发送服务

    /* 
     * 发送短信
     * $to 接受短信手机号
     * $template 短信模版 id
     * $data 短信内容变量
     */
    $sms->send('1520000000', 'register', ['code' => rand(1000, 9999)]);
    
    

    | 驱动 | 描述
    | ----|---- |Alidayu | 阿里大于短信服务 |Aliyun | 阿里云短信服务 |Baidu | 百度云短信服务(暂无企业账户,未测试) |Qcloud | 腾讯云短信服务 |Yuntongxun | 容联云通讯短信服务

    • captcha 验证码(配置
    $captcha->verify();
    

    | 驱动 | 描述
    | ----|---- |Image | 使用 gregwar/captcha 包 |Recaptcha | google recaptcha
    |Geetest | 极验验证

    $geoip->locate('8.8.8.8');
    

    | 驱动 | 描述
    | ----|---- |Baidu | Baidu 地图 IP 定位接口,优点几乎不限请求,缺点无法定位国外 ip |Ipip | Ipip IP 定位,有在线 api 接口和离线数据库两种使用方式 |Maxmind | Maxmind IP 定位,有在线 api 接口和离线数据库两种使用方式

    //加密
    $crypt->encrypt('hello world');
    //解密
    $crypt->decrypt('ia3E14cmVxkJhhP0YWPBvA==');
    

    | 驱动 | 描述
    | ----|---- |Openssl | 基于 php openssl 扩展 |Sodium | 基于 php libsodium 扩展

    // 使用 id 获取一条数据
    $search->index->get($id);
    // 使用 elastic 原生 query 语法搜索
    $search->index->search($query);
    // 更新设置指定 id 数据
    $search->index->put($id, $data);
    // 添加索引数据
    $search->index->index($data);
    // 更新数据,使用 query 语法
    $search->index->update($query, $data);
    // 使用 query 语法删除
    $search->index->delete($query);
    

    | 驱动 | 描述
    | ----|---- |Elastic | 基于 Elastic rest 接口 (待完善)

    • data 非关系数据库(配置
    // mongodb
    // 使用 id 获取一条数据
    $mongo->db->collection->get($id);
    // 查找数据,使用 mongodb 原生 filter options 语法
    $mongo->db->collection->find($filter, $options);
    // 获取数据记录数
    $mongo->db->collection->count($filter, $options);
    // 插入数据
    $mongo->db->collection->insert($data);
    // 更新数据
    $mongo->db->collection->update($data, $filter, $options);
    // 更新指定 id 数据
    $mongo->db->collection->upsert($id, $data);
    // 删除
    $mongo->db->collection->delete($filter, $options);
    

    | 驱动 | 描述
    | ----|---- |Cassandra | 使用 datastax 扩展(坑) |Mongo | 使用 MongoDB 扩展(待完善) |Hbase | 使用 Thrift Rpc 客户端(坑)

    // 生产者推送一条信息
    $queue->producer($job)->push($message);
    // 消费者拉取一条信息
    $queue->consumer($job)->pull();
    

    | 驱动 | 描述
    | ----|---- |Redis | 使用 redis list 类型实现简单队列(坑) |Amqp | 基于 Amqp 协议 RabbitMQ 服务(坑) |Beanstalkd | pda/pheanstalk 包(坑) |Kafka | php-rdkafka 扩展(坑)

    第 1 条附言  ·  2017-10-06 15:29:40 +08:00
    换名字了就叫 phpegg https://github.com/qiu-jin/phpegg
    16 条回复    2017-10-06 15:28:35 +08:00
    carlclone
        1
    carlclone  
       2017-10-04 22:51:44 +08:00 via Android
    好多没听过的名词
    huntzhan
        2
    huntzhan  
       2017-10-04 23:19:21 +08:00
    现在应该不流行这种带有强烈个人风格的代码库了。
    qiujin
        3
    qiujin  
    OP
       2017-10-05 00:27:56 +08:00
    @huntzhan 看来还是得想一个好听的名字,不能叫 my_php_framework。
    ioREQcom
        4
    ioREQcom  
       2017-10-05 00:36:09 +08:00
    没有优势,还不如用 Python
    http://so.factj.com/s?q=Python
    ioREQcom
        5
    ioREQcom  
       2017-10-05 00:37:07 +08:00
    补充下,Django 框架,无敌
    http://so.factj.com/s?q=Django 入门
    billwsy
        6
    billwsy  
       2017-10-05 01:26:43 +08:00 via iPhone
    看到 grpc,支持一下!
    mcfog
        7
    mcfog  
       2017-10-05 08:40:39 +08:00 via Android
    grpc jsonrpc 好评,但这种 ci 式的依赖管理不推荐,建议上依赖注入,也不要这么多功能都放框架核心里,smtp geoip 之类的最多挂成 plugin/adapter 放在别的 repo,要我说数据库都不一定要在核心里(方便 a.别人可能需要用其他著名 orm b.接口项目可能有后台服务,本身可能根本不碰数据库)
    askfilm
        8
    askfilm  
       2017-10-05 09:12:22 +08:00
    以前还是挺喜欢 python 的, 但是最近几年老是看到很多 python 无脑推手, 愈发反感了。。。
    相对来说,搞 php 的倒是越来越理性进步了。。。
    Chappako
        9
    Chappako  
       2017-10-05 10:54:39 +08:00
    4 楼,5 楼这种推广方式好么?
    gouchaoer
        10
    gouchaoer  
       2017-10-05 11:22:56 +08:00 via Android
    php 框架已经很成熟了
    orderc
        11
    orderc  
       2017-10-05 11:26:19 +08:00
    cxh116
        12
    cxh116  
       2017-10-05 15:51:07 +08:00 via Android
    开源项目比文档还重要的就是单元测试。这样别人用你的框架出 bug 了,才稍微放心的修。
    qiujin
        13
    qiujin  
    OP
       2017-10-05 16:57:33 +08:00
    @cxh116 对,单元测试也很重要,但工作量也不小,后续会慢慢补上。
    qiujin
        14
    qiujin  
    OP
       2017-10-05 19:44:09 +08:00
    @mcfog 谢谢支持哈,刚开始先就放一块了,不然太分散也没人关注,以后在拆分。
    wolong
        15
    wolong  
       2017-10-05 21:27:17 +08:00
    一般都是先起名字才开始写程序。
    qiujin
        16
    qiujin  
    OP
       2017-10-06 15:28:35 +08:00
    @wolong 换了个名字就叫 phpegg 吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3468 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 11:13 · PVG 19:13 · LAX 04:13 · JFK 07:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.