1 快速开发

1.1 安装

composer一键安装

composer create-project phalapi/phalapi

手动下载安装

下载 phalapi 项目master-2x分支

composer update

Nginx配置

......
location / {
        index index.php;
    }

    # 开启URI路由匹配
    # location / {
    #       try_files $uri $uri/ /?$args;
    # }
    # if (!-e $request_filename) {
    #        rewrite ^/(.*)$ /index.php last;
    # }
......
nginx -t
nginx -s reload

1.2 运行Hello world

点击此处

1.3 如何请求接口服务

开启url匹配

扩展:如何定制接口服务的传递方式?

1.4 接口响应与在线调试

跨域

./config/di.php后面的位置添加

// 允许跨域
$response = \PhalApi\DI()->response;
$response->addHeaders('Access-Control-Allow-Origin', '*'); // *代表允许任何网址请求
// $response->addHeaders('Access-Control-Allow-Origin', 'www.phalapi.net'); // 推荐指定网站
$response->addHeaders('Access-Control-Allow-Methods', 'POST,GET,OPTIONS,DELETE'); // 允许请求的类型
$response->addHeaders('Access-Control-Allow-Credentials', 'true'); // 设置是否允许发送 cookies
$response->addHeaders('Access-Control-Allow-Headers', 'Content-Type,Content-Length,Accept-Encoding,X-Requested-with, Origin'); // 设置允许自定义请求头的字段

在线调试

  • 单次请求开启调试:默认添加请求参数&__debug__=1
  • 全部请求开启调试:把配置文件./config/sys.php文件中的配置改成'debug' => true,

自定义埋点

// 添加纪录埋点,并指定节点标识
PhalApi\DI()->tracer->mark('DO_SOMETHING');

自定义调试信息

$x = 'this is x';
$y = array('this is y');
\PhalApi\DI()->response->setDebug('XXX_x', $x); // XXX扩展
\PhalApi\DI()->response->setDebug('XXX_y', $y);

1.5 Api接口层

异常抛出

use App\Common\AppException;
......
throw new AppException('提示消息', 1000);
......

手动指定ret状态码

// 手动设置ret为1000
// ret=200时表示正常返回,ret=4xx表示额端非法请求,ret=500表示服务器内部错误,手动设置时应设置成其他整数范围,避免语义冲突
\PhalApi\DI()->response->setRet(1000);

// 手动设置提示消息
\PhalApi\DI()->response->setMsg('手动设置提示消息');

钩子函数

  • PhalApi\Api::getRules(),获取参数设置的规则,可由开发人员根据需要重载
  • PhalApi\Api::userCheck(),用户身份验证,可由开发人员根据需要重载,此通用操作一般可以使用委托或者放置在应用接口基类

1.6 DataApi通用数据接口

DataApi有哪些接口?

PhalApi\Api\DataApi目前有5个数据接口(后面会进一步扩展):

  • 创建新数据,{命名空间}.{接口类名}.CreateData
  • 批量删除,{命名空间}.{接口类名}.DeleteDataIDs
  • 获取一条数据,{命名空间}.{接口类名}.GetData
  • 获取表格列表数据,{命名空间}.{接口类名}.TableList
  • 更新数据,{命名空间}.{接口类名}.UpdateData

如何屏蔽不需要的接口?

/**
 * @ignore
 */
public function createData() {
    throw new \PhalApi\Exception\BadRequestException('此接口已关闭');
}

如何修改接口文档?

/**
 * 发布一篇新的博客文章
 * @desc 进行博客文章的发布,发布后内容进入待审状态
 * @return int id 新博客文章的ID
 */
public function createData() {
    return parent::createData();
}

1.7 Domain领域业务层与ADM模式

ADM调用关系

需要注意不能越层调用也不能逆向调用,即不能Api调用Model。而应该是上层调用下层,或者同层级调用,也就是说,我们应该:

  • Api层调用Domain层
  • Domain层调用Domain层
  • Domain层调用Model层
  • Model层调用Model层

1.8 Model数据模型层与数据库操作

一个简单的Model例子

1.9 DataModel数据模型

简单:4个CURD基本操作

$model = new App\Model\User();

// 查询
$row = $model->get(1);
$row = $model->get(1, 'id, name'); //取指定的字段
$row = $model->get(1, array('id', 'name')); //可以数组取指定要获取的字段

// 更新
$data = array('name' => 'test', 'update_time' => time());
$model->update(1, $data); //基于主键的快速更新

// 插入
$data = array('name' => 'phalapi');
$id = $model->insert($data);
//$id = $model->insert($data, 5); //如果是分表,可以通过第二个参数指定分表的参考ID

// 删除
$model->delete(1);

总数

接口:PhalApi\Model\DataModel::count($where = NULL, $countBy = '*')

最小值

接口:PhalApi\Model\DataModel::min($where, $minBy)

最大值

接口:PhalApi\Model\DataModel::max($where, $maxBy)

求和

接口:PhalApi\Model\DataModel::sum($where, $sumBy)

获取字段值

接口:PhalApi\Model\DataModel::getValueBy($field, $value, $selectFiled, $default = FALSE)

获取字段值(多个)

接口:PhalApi\Model\DataModel::getValueMoreBy($field, $value, $selectFiled, $limit = 0, $isDistinct = FALSE)

获取一条纪录

接口:PhalApi\Model\DataModel::getDataBy($field, $value, $select = '*', $default = FALSE)

获取多条纪录

接口:PhalApi\Model\DataModel::getDataMoreBy($field, $value, $limit = 0, $select = '*')

点击查看更多

执行SQL查询语句

接口:PhalApi\Model\DataModel::queryAll($sql, $parmas = array())

执行SQL变更语句

接口:PhalApi\Model\DataModel::executeSql($sql, $params = array())

第三种获取NotORM的方式

  • 全局获取方式,通过\PhalApi\DI()->notorm->表名方式获取,可以用于任何地方。
    • 局部获取方式,通过在继承PhalApi\Model\NotORMModel的子类中使用$this->getORM()获取当前Model对应的NotORM,仅限用于Model子类内部。
  • 使用PhalApi\Model\DataModel::notorm()静态方法获取。

DataModel与NotORMModel的区别

DataModel是比NotORMModel更新推出的数据基类,比NotORMModel功能更强大,并且开发使用更友好。推荐从PhalApi 2.12.0 及以上版本改用DataModel。

使用DataModel前后的继承关系对比如下

1.10 单元测试

PHPUnit

PHPUnit官网:https://phpunit.de,如需进行单元测试,请先安装PHPUnit。

拓展: phpstorm使用单元测试

composer require phpunit/phpunit
Run->......
(后续我会单独做单元测试的笔记!!!)

1.11 自动加载和PSR-4

如何增加一个顶级的命名空间?

注册顶级命名空间需要在composer.jsonautoload里注册, 如:

{
    "autoload": {
        "psr-4": {
            "App\\": "src/app",
            "Foo\\": "src/foo"
        }
    }
}
更新
composer update
(只更新命名空间的映射关系)
composer dumpautoload

添加全局函数

放置到./src/app/functions.php文件内, 如:

<?php
namespace App;

function hello() {
        return 'Hey, man~';
}

使用:

echo \App\hello();

1.12 接口文档

在线接口文档

http://dev.phalapi.net/docs.php

注释

接口服务名称通常为接口类成员函数的第一行注释

接口说明对应接口类成员函数的@desc注释,支持HTML格式

@method POST指定了当前接口,只允许POST请求

接口参数在当前接口类getRules()方法中的返回

如:

class Site extends Api {

    /**
     * 默认接口服务
     * @desc 默认接口服务,当未指定接口服务时执行此接口服务
     * @return string title 标题
     * @return string content 内容
     * @return string version 版本,格式:X.X.X
     * @return int time 当前时间戳
     */
    public function index() {
    }
}

隐藏接口

类注释添加@ignore

生成离线文档

php ./public/docs.php

1.13 初始化

框架初始化

public/init.php

2 数据库

2.1 数据库连接

MySQL (PDO)
MS SQL Server (PDO)
PostgreSQL (PDO)

数据库基本配置

./config/dbs.php

主要有两部分配置:serverstables

servers,针对数据库的配置,可以配置多个数据库
tables,针对表的配置,支持配置分表(不需要分表可不配置分表)

如何排查数据库连接错误?

?__debug__=1

如何断开数据库连接?

\PhalApi\DI()->notorm->disconnect();

在./public/index.php文件最后进行手动断开

2.2 数据库于NotORM

PhalApi的Model和NotORM整体架构

当我们需要操作数据库时,主要分为三个步骤:连接数据库、实现数据库表操作、调用。

如何指定表名?

<?php
namespace App\Model;

use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {
    protected function getTableName($id) {
        return 'my_user';  // 手动设置表名为 my_user
    }
}

4个CURD基本操作

$model = new App\Model\User();

// 查询
$row = $model->get(1);
$row = $model->get(1, 'id, name'); //取指定的字段
$row = $model->get(1, array('id', 'name')); //可以数组取指定要获取的字段

// 更新
$data = array('name' => 'test', 'update_time' => time());
$model->update(1, $data); //基于主键的快速更新

// 插入
$data = array('name' => 'phalapi');
$id = $model->insert($data);
//$id = $model->insert($data, 5); //如果是分表,可以通过第二个参数指定分表的参考ID

// 删除
$model->delete(1);

附录:PhalApi对NotORM的优化

如果了解NotORM的使用,自然而然对PhalApi中的数据库操作也就一目了然了。但为了更符合接口类项目的开发,PhalApi对NotORM的底层进行优化和调整。以下改动点包括但不限于:

  • 将原来返回的结果全部从对象类型改成数组类型,便于数据流通
  • 添加查询多条纪录的接口:NotORM_Result::fetchAll()NotORM_Result::fetchRows()
  • 添加支持原生SQL语句查询的接口:NotORM_Result::queryAll()NotORM_Result::queryRows()
  • limit 操作的调整,取消原来OFFSET关键字的使用
  • 当数据库操作失败时,抛出PDOException异常
  • 将结果集中以主键作为下标改为以顺序索引作为下标
  • 禁止全表删除,防止误删
  • 调整调试模式

2.3 数据库使用和查询

事务

// Step 1: 开启事务
\PhalApi\DI()->notorm->beginTransaction('db_master');

// Step 2: 数据库操作
\PhalApi\DI()->notorm->user->insert(array('name' => 'test1'));
\PhalApi\DI()->notorm->user->insert(array('name' => 'test2'));

// Step 3: 提交事务/回滚
\PhalApi\DI()->notorm->commit('db_master');
//\PhalApi\DI()->notorm->rollback('db_master');

在Model子类内,可以:

  • 开启事务:$this->getORM()->transaction('BEGIN');
  • 提交事务:$this->getORM()->transaction('COMMIT');
  • 回滚事务:$this->getORM()->transaction('ROLLBACK');

可以使用更底层更通用的接口,即:\NotORM_Result::query($query, $parameters)

如,删除一张表

// DROP TABLE tbl_user
return $this->getORM()->query('DROP TABLE tbl_user', array());

2.4 数据库分库分表策略

2.5 连接多个数据库

2.6 打印和保存SQL语句

  • sys.debug:是否开启接口调试模式,开启后在客户端可以直接看到更多调试信息
  • sys.notorm_debug,是否开启NotORM调试模式,开启后仅针对NotORM服务开启调试模式
  • sys.enable_sql_log,是否纪录SQL到日志,需要同时开启notorm_debug方可写入日志

3 高级专题

3.1 接口参数

参数规则格式

参数规则是针对各个接口服务而配置的多维规则数组,由接口类的getRules()方法返回。其中,

  • 一维下标是接口类的方法名,对应接口服务的Action
  • 二维下标是类属性名称,对应在服务端获取通过验证和转换化的最终客户端参数;
  • 三维下标name是接口参数名称,对应外部客户端请求时需要提供的参数名称。

三级参数规则配置

参数规则主要有三种,分别是:系统参数规则、应用参数规则、接口参数规则。

多个参数规则优先级

简而言之,多个参数规则的优先级从高到下,分别是(正如你想到的那样):

  • 1、指定接口参数规则
  • 2、通用接口参数规则
  • 3、应用参数规则
  • 4、系统参数规则

对于重叠的接口参数,若指定接口不需要某个接口参数,可以通过将参数规则配置置为NULL或FALSE,从而取消此参数。例如取消sign全局参数;

'sign' => NULL,

参数规则配置详细说明

主要的类型有:字符串、整数、浮点数、布尔值、时间戳/日期、数组、枚举类型、文件上传和回调函数

3.2 配置

配置文件简单读取

// 配置
$di->config = new FileConfig(API_ROOT . '/config');
// app.php里面的全部配置
\PhalApi\DI()->config->get('app');//返回:array( ... ... )
\PhalApi\DI()->config->get('app.not_found', 404); //返回:404

当前环境配置文件

// 运行模式,可以是:dev, test, prod// 运行模式,可以是:dev, test, prod
defined('API_MODE') || define('API_MODE', 'prod');

API_MODE有三个值,分别是:

  • dev表示开发模式,此时如果./config/sys_dev.php、./config/app_dev.php、./config/dbs_dev.php配置文件若存在,则会优先加载*_dev.php系列配置文件。
  • test表示测试模式,此时如果./config/sys_test.php、./config/app_test.php、./config/dbs_test.php配置文件若存在,则会优先加载*_test.php系列配置文件。
  • prod表示生产模式,则加载./config/sys.php、./config/app.php、./config/dbs.php配置文件。

3.3 日志

7种级别:

const EMERGENCY = 'energency';
const ALERT		= 'alert';
const CRITICAL	= 'critical';
const ERROR 	= 'error';
const WARNING	= 'warning';
const NOTICE	= 'notice';
const INFO		= 'info';
const DEBUG		= 'debug';

简化版日记接口

只有三种

error: 系统异常类日记
info: 业务纪录类日记
debug: 开发调试类日记

error

// 只有描述
\PhalApi\DI()->logger->error('fail to insert DB');

// 描述 + 简单的信息
\PhalApi\DI()->logger->error('fail to insert DB', 'try to register user dogstar');

// 描述 + 当时的上下文数据
$data = array('name' => 'dogstar', 'password' => '123456');
\PhalApi\DI()->logger->error('fail to insert DB', $data);

info

// 假设:10 + 2 = 12
\PhalApi\DI()->logger->info('add user exp', array('name' => 'dogstar', 'before' => 10, 'addExp' => 2, 'after' => 12, 'reason' => 'help one more phper'));

debug

// 只有描述
\PhalApi\DI()->logger->debug('just for test');

// 描述 + 简单的信息
\PhalApi\DI()->logger->debug('just for test', '一些其他的描述 ...');

// 描述 + 当时的上下文数据
\PhalApi\DI()->logger->debug('just for test', array('name' => 'dogstar', 'password' => '******'));

更灵活的记录:

\PhalApi\DI()->logger->log('demo', 'add user exp', array('name' => 'dogstar', 'after' => 12));
\PhalApi\DI()->logger->log('test', 'add user exp', array('name' => 'dogstar', 'after' => 12));

扩展:定制你的日志

<?php
namespace App\Common\Logger;

use PhalApi\Logger;

class DBLogger extends Logger {

    public function log($type, $msg, $data) {
        // TODO 数据库的日记写入 ...
    } 
}

3.4 缓存

本地缓存

这里所指的简单缓存,主要是存储在单台服务器上的缓存,例如使用系统文件的文件缓存,PHP语言提供的APCU缓存。因为实现简单,且部署方便。但其缺点也是明显的,如文件I/O读写导致性能低,不能支持分布式。所以在没有集群服务器下是适用的。

文件缓存

例如,当需要使用文件缓存时,先在DI容器中注册对文件缓存到\PhalApi\DI()->cache

$di->cache = new PhalApi\Cache\FileCache(array('path' => API_ROOT . '/runtime', 'prefix' => 'demo'));

初始化文件缓存时,需要传入配置数组,其中path为缓存数据的目录,可选的前缀prefix,用于区别不同的项目。

// 使用方法
// 设置
PhalApi\DI()->cache->set('thisYear', 2015, 600);

// 获取,输出:2015
echo PhalApi\DI()->cache->get('thisYear');

// 删除
PhalApi\DI()->cache->delete('thisYear');

APCU缓存

安装好APCU扩展和设置相关配置并重启PHP后,便可开始使用APCU缓存。APCU缓存的初始化比较简单,只需要简单创建实例即可,不需要任何配置。

$di->cache = new PhalApi\Cache\APCUCache();

Memcache/Memcached缓存

若需要使用Memcache/Memcached缓存,则需要安装相应的PHP扩展。PHP 7中已经逐渐不支持Memcache,因此建议尽量使用Memcached扩展。

如使用Memcached:

$di->cache = new PhalApi\Cache\MemcachedCache(array('host' => '127.0.0.1', 'port' => 11211, 'prefix' => 'demo_'));

初始化Memcached时,需要传递一个配置数组,其中host为缓存服务器,port为缓存端口,prefix为可选的前缀,用于区别不同的项目。配置前缀,可以防止同一台MC服务器同一端口下key名冲突。对于缓存的配置,更好的建议是使用配置文件来统一管理配置。例如调整成:

$di->cache = new PhalApi\Cache\MemcachedCache(DI()->config->get('sys.mc'));

相应的配置,则在./config/sys.php中的mc选项中统一维护。

Redis缓存

当需要使用Redis缓存时,需要先安装对应的Redis扩展。

简单的Redis缓存的初始化如下:

$config = array('host' => '127.0.0.1', 'port' => 6379);
$di->cache = new PhalApi\Cache\RedisCache($config);

3.5 过滤器(签名)

默认可用的MD5签名

白名单配置

实现过滤器接口

微信验签

注册过滤器服务

随后,我们只需要再简单地注册一下过滤器服务即可,在./config/di.php文件最后追加:

// 签名验证服务
$di->filter = new App\Common\SignFilter();

如同其他的服务一样,我们在使用前需要对COOKIE进行注册。COOKIE服务注册在\PhalApi\DI()->cookie中,可以使用PhalApi\Cookie实例进行初始化,如:

$config = array('domain' => '.phalapi.net');
\PhalApi\DI()->cookie = new PhalApi\Cookie($config);
// 设置COOKIE
// Set-Cookie:"name=phalapi; expires=Sun, 07-May-2017 03:26:45 GMT; domain=.phalapi.net"
\PhalApi\DI()->cookie->set('name', 'phalapi', $_SERVER['REQUEST_TIME'] + 600);

// 获取COOKIE,输出:phalapi
echo \PhalApi\DI()->cookie->get('name');

// 删除COOKIE
\PhalApi\DI()->cookie->delete('name');

扩展:定制专属的COOKIE

当项目中需要定制专属的COOKIE服务时,可以继承PhalApi\Cookie基类,并按需要重写对应的接口。主要的接口有三个:

  • 设置COOKIE:PhalApi\Cookie::set($name, $value, $expire = NULL)
  • 获取COOKIE:PhalApi\Cookie::get($name = NULL)
  • 删除COOKIE:PhalApi\Cookie::delete($name)

3.7 加密

PHP的mcrypt加密扩展

3.8 国际化

语言设定

在初始化文件./public/init.php中,通过快速函数\PhalApi\SL($language)可以设定当前所使用的语言。例如设置语言为简体中文,可以:

// 翻译语言包设定
\PhalApi\SL('zh_cn');

翻译包

翻译包的文件路径为:./language/语言/common.php,例如简体中文zh_cn对应的翻译包文件为:./Language/zh_cn/common.php。此翻译包文件返回的是一个数组,其中键为待翻译的内容,值为翻译后的内容.

3.9 CURL请求

当需要进行curl请求时,可使用PhalApi封装的CURL请求类PhalApi\CUrl,从而实现快捷方便的请求。

3.10 工具和杂项

$ip = \PhalApi\Tool::getClientIp();

// 指定使用字符集,如6位数字验证码
$len = 6;
$str = \PhalApi\Tool::createRandStr($len, '0123456789');

$arr = array('name' => 'PhalApi');
$xml = \PhalApi\Tool::arrayToXml($arr);

$xml = '<xml><name>PhalApi</name></xml>';
$arr = \PhalApi\Tool::xmlToArray($xml);

......

3.11 DI服务汇总

基本注册

$di = \PhalApi\DI();

// 配置
$di->config = new FileConfig(API_ROOT . '/config');

// 调试模式,$_GET['__debug__']可自行改名
$di->debug = !empty($_GET['__debug__']) ? true : $di->config->get('sys.debug');

// 日记纪录
$di->logger = new FileLogger(API_ROOT . '/runtime', Logger::LOG_LEVEL_DEBUG | Logger::LOG_LEVEL_INFO | Logger::LOG_LEVEL_ERROR);

// 数据操作 - 基于NotORM
$di->notorm = new NotORMDatabase($di->config->get('dbs'), $di->debug);

定制注册

// 签名验证服务
// $di->filter = new \PhalApi\Filter\SimpleMD5Filter();

// 缓存 - Memcache/Memcached
// $di->cache = function () {
//     return new \PhalApi\Cache\MemcacheCache(DI()->config->get('sys.mc'));
// };

// 支持JsonP的返回
// if (!empty($_GET['callback'])) {
//     $di->response = new \PhalApi\Response\JsonpResponse($_GET['callback']);
// }

DI服务资源一览表

正确判断的写法:先获取,再判断

正确的用法应该是:

// 先获取,再判断
$XXX = $di->XXX;
var_dump(isset($XXX));
var_dump(!empty($XXX));

3.12 扩展类库

扩展类库列表

3.13 SDK包的使用

SDK包列表

3.14 脚本命令

phalapi-buildtest命令

phalapi-buildsqls命令

phalapi-cli命令

3.15 MQ队列

Gearman整合

RabbitMQ整合

NSQ整合

3.16 错误处理

PhalApi的错误处理

在./config/di.php文件注册:

$di->error = new \PhalApi\Error\ApiError();

在这背后,会:

  • 1、通过set_error_handler()注册用户错误处理函数
  • 2、通过register_shutdown_function()处理PHP致命错误

主要处理方式,是将相关的警告、错误、和提醒信息纪录到文件日志。

4 运营平台

运营平台

6 视频教程

PhalApi 2.x 接口开发 - 2020视频教程开讲啦!

后记: 20200315