← 返回
未分类

phalapi

Use when developing APIs with PhalApi framework in PHP, including creating API interfaces, configuring Domain/Model layers, setting up database connections, debugging PhalApi projects, or working with PhalApi ADM architecture, NotORM, caching, logging, and filters.
Use when developing APIs with PhalApi framework in PHP, including creating API interfaces, configuring Domain/Model layers, setting up database connections, debugging PhalApi projects, or working with PhalApi ADM architecture, NotORM, caching, logging, and filters.
leqq00
未分类 community v1.0.0 1 版本 100000 Key: 无需
★ 0
Stars
📥 84
下载
💾 0
安装
1
版本
#latest

概述

PhalApi 开发指南

概述

PhalApi(π框架)是一个 PHP 轻量级开源接口框架,专注于接口服务开发。支持 HTTP/SOAP/RPC 协议,采用 ADM(Api-Domain-Model)分层架构,自动生成在线接口文档。

核心架构:ADM 模式

三层职责

层级职责不应该做
----------------------
Api层接收请求、参数验证、调度 Domain 层、返回结果直接操作数据库、业务规则处理
Domain层业务规则处理、数据逻辑、调用 Model 层直接数据库操作、实现缓存
Model层数据库操作、缓存实现业务规则处理

调用规则

Api层 → Domain层 → Model层

严格禁止:Api 直接调用 Model、Domain 调用 Api、Model 调用 Domain

快速开始

安装

composer create-project phalapi/phalapi

目录结构

./phalapi
├── public/              # 网站根目录
│   ├── index.php       # 入口
│   ├── init.php        # 初始化
│   └── docs.php        # 离线文档生成
├── src/app/            # 源代码
│   ├── Api/            # 接口层
│   ├── Domain/         # 领域层
│   ├── Model/          # 模型层
│   └── Common/         # 公共类
├── config/             # 配置
│   ├── app.php         # 应用配置
│   ├── dbs.php         # 数据库配置
│   ├── di.php          # DI服务
│   └── sys.php         # 系统配置
└── runtime/            # 运行目录(需写权限)

请求示例

http://dev.phalapi.net/?s=App.Site.Index

Api 层开发

创建接口

文件:./src/app/Api/User.php

<?php
namespace App\Api;
use PhalApi\Api;

class User extends Api {

    public function getRules() {
        return array(
            'login' => array(
                'username' => array('name' => 'username', 'require' => true),
                'password' => array('name' => 'password', 'require' => true, 'min' => 6),
            ),
        );
    }

    public function login() {
        $name = $this->username;
        $pass = $this->password;
        return array('username' => $name);
    }
}

接口返回格式

{
    "ret": 200,
    "data": { "key": "value" },
    "msg": ""
}

异常返回

throw new \PhalApi\Exception\BadRequestException('签名失败', 401);
{
    "ret": 401,
    "data": {},
    "msg": "Bad Request: 签名失败"
}

自定义业务异常

// ./src/app/Common/AppException.php
namespace App\Common;
use PhalApi\Exception;

class AppException extends Exception {}

// 抛出
throw new AppException('提示消息', 1000);

JSONP/XML 支持

// config/di.php
$di->response = new \PhalApi\Response\JsonpResponse($_GET['callback']);
$di->response = new \PhalApi\Response\XmlResponse();

参数规则

三级参数规则

  1. 系统参数:service/s 参数(框架保留)
  2. 应用参数:全部接口通用(在 config/app.phpapiCommonRules 配置)
  3. 接口参数:特定接口专用

参数类型

类型配置说明
------------------
stringtype => 'string'字符串,默认
inttype => 'int'整数
floattype => 'float'浮点数
booleantype => 'boolean'布尔值
datetype => 'date', 'format' => 'timestamp'日期/时间戳
arraytype => 'array', 'format' => 'explode'数组
enumtype => 'enum', 'range' => ['a', 'b']枚举
filetype => 'file'文件上传
callabletype => 'callable', 'callback' => 'Class::method'回调验证

完整参数配置

array(
    'name' => 'username',           // 参数名
    'type' => 'string',            // 类型
    'require' => true,            // 是否必须
    'default' => 'PhalApi',       // 默认值
    'min' => 1,                    // 最小值/长度
    'max' => 50,                   // 最大值/长度
    'regex' => '/^[a-z]+$/',      // 正则验证
    'format' => 'utf8',           // 格式(utf8/gbk)
    'source' => 'post',            // 数据来源
    'desc' => '用户名',            // 描述
    'message' => '自定义错误提示',  // 错误提示
)

Domain 层开发

文件:./src/app/Domain/User.php

<?php
namespace App\Domain;
use PhalApi\Domain_Base;

class User extends Domain_Base {

    public function login($name, $pass) {
        $model = new \App\Model\User();
        return $model->getByName($name);
    }
}

Domain 调用规则

  • Domain 可调用 Domain(同级)
  • Domain 可调用 Model
  • Domain 不可调用 Api

Model 层开发

基本 Model

文件:./src/app/Model/User.php

<?php
namespace App\Model;
use PhalApi\Model\NotORMModel as NotORM;

class User extends NotORM {

    protected function getTableName($id) {
        return 'user';  // 自动映射为 tbl_user
    }

    public function getByName($name) {
        return $this->getORM()->where('name', $name)->fetchOne();
    }
}

NotORM 数据库操作

查询

// 获取单条
$this->getORM()->where('id', 1)->fetchOne();

// 获取多条
$this->getORM()->where('id', array(1, 2, 3))->fetchAll();

// 条件
$this->getORM()->where('name LIKE ?', '%test%')->fetchAll();
$this->getORM()->where('age > ?', 18)->order('age DESC')->fetchAll();

// 分页
$this->getORM()->page(1, 20)->fetchAll();  // 第1页,每页20条

// 原生SQL
$this->getORM()->queryAll('SELECT * FROM user WHERE id > ?', [$id]);

插入

$data = array('name' => 'test', 'age' => 20);
$id = $this->getORM()->insert($data);

// 批量插入
$this->getORM()->insert_multi($rows);

// 插入或更新
$this->getORM()->insert_update($unique, $insert, $update);

更新

$this->getORM()->where('id', 1)->update($data);

// 计数器
$this->getORM()->where('id', 1)->updateCounter('age', 1);  // age + 1

删除

$this->getORM()->where('id', 1)->delete();
// 禁止全表删除!

分表

protected function getTableName($id) {
    $tableName = 'log';
    if ($id !== null) {
        $tableName .= '_' . ($id % 100);  // log_0, log_1, ...
    }
    return $tableName;
}

数据库配置

config/dbs.php

return array(
    'servers' => array(
        'db_master' => array(
            'type'     => 'mysql',
            'host'     => '127.0.0.1',
            'name'     => 'phalapi',
            'user'     => 'root',
            'password' => '',
            'port'     => 3306,
            'charset'  => 'UTF8',
        ),
    ),
    'tables' => array(
        '__default__' => array(
            'prefix' => 'tbl_',
            'key' => 'id',
            'map' => array(array('db' => 'db_master')),
        ),
    ),
);

全局获取 NotORM

$user = \PhalApi\DI()->notorm->user->where('id', 1)->fetchOne();

缓存

文件缓存

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

\PhalApi\DI()->cache->set('key', $value, 600);
\PhalApi\DI()->cache->get('key');
\PhalApi\DI()->cache->delete('key');

Redis 缓存

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

Memcache 缓存

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

日志

三种日志级别

\PhalApi\DI()->logger->error('错误描述', $context);
\PhalApi\DI()->logger->info('业务记录', $context);
\PhalApi\DI()->logger->debug('调试信息', $context);

文件日志配置

$di->logger = new \PhalApi\Logger\FileLogger(
    API_ROOT . '/runtime',
    \PhalApi\Logger::LOG_LEVEL_DEBUG | \PhalApi\Logger::LOG_LEVEL_INFO | \PhalApi\Logger::LOG_LEVEL_ERROR
);

过滤器(签名验证)

启用 MD5 签名

// config/di.php
$di->filter = new \PhalApi\Filter\SimpleMD5Filter();

白名单配置

// config/app.php
'service_whitelist' => array(
    'Site.Index',    // 精确匹配
    '*.List',        // 通配符匹配
),

自定义过滤器

// ./src/app/Common/SignFilter.php
namespace App\Common;
use PhalApi\Filter;
use PhalApi\Exception\BadRequestException;

class SignFilter implements Filter {
    public function check() {
        $signature = \PhalApi\DI()->request->get('signature');
        // 验签逻辑
        if ($signature !== $expected) {
            throw new BadRequestException('wrong sign', 402);
        }
    }
}

// config/di.php
$di->filter = new \App\Common\SignFilter();

DI 服务

config/di.php

$di = \PhalApi\DI();

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

// 数据库
$di->notorm = new \PhalApi\Database\NotORMDatabase($di->config->get('dbs'));

// 缓存
$di->cache = new \PhalApi\Cache\RedisCache([...]);

// 日志
$di->logger = new \PhalApi\Logger\FileLogger([...]);

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

常用 DI 服务

服务说明
------------
$di->config配置读取
$di->request请求参数
$di->response响应输出
$di->notorm数据库
$di->cache缓存
$di->logger日志
$di->filter过滤器

配置读取

\PhalApi\DI()->config->get('app');           // 全部配置
\PhalApi\DI()->config->get('app.version');    // 单个配置
\PhalApi\DI()->config->get('app.not_found', 'default');  // 默认值

环境配置

defined('API_MODE') || define('API_MODE', 'prod');
// dev: 加载 *_dev.php
// test: 加载 *_test.php
// prod: 加载 *.php

初始化

public/init.php

defined('API_ROOT') || define('API_ROOT', dirname(__FILE__) . '/..');
defined('API_MODE') || define('API_MODE', 'prod');
require_once API_ROOT . '/vendor/autoload.php';
date_default_timezone_set('Asia/Shanghai');
include API_ROOT . '/config/di.php';
\PhalApi\SL('zh_cn');

自动加载(PSR-4)

命名空间对应

命名空间文件路径
--------------------
App\Api\Usersrc/app/Api/User.php
App\Domain\Usersrc/app/Domain/User.php
App\Model\Usersrc/app/Model/User.php

使用类

use App\Domain\User as DomainUser;
$domain = new DomainUser();

// 或完整路径
$domain = new \App\Domain\User();

添加全局函数

// src/app/functions.php
namespace App;
function hello() { return 'world'; }

// 使用
\App\hello();

在线接口文档

访问地址

  • 列表:http://dev.phalapi.net/docs.php
  • 详情:http://dev.phalapi.net/docs.php?service=App.Site.Index&detail=1

注释规范

/**
 * 用户登录
 * @desc 用于用户登录验证
 * @return int user_id 用户ID
 * @return string token 登录令牌
 * @exception 400 参数错误
 * @exception 401 签名错误
 */
public function login() { }

// 隐藏接口
/**
 * @ignore
 */
public function hidden() { }

生成离线文档

php ./public/docs.php expand  # 展开版
php ./public/docs.php fold    # 折叠版

单元测试

自动生成测试

php ./bin/phalapi-buildtest ./src/app/Api/User.php App\\Api\\User > ./tests/app/Api/User_Test.php

测试示例

class PhpUnderControl_AppApiUser_Test extends \PHPUnit_Framework_TestCase
{
    public function testLogin() {
        // 构造
        $url = 's=User.Login';
        $params = array('username' => 'test', 'password' => '123456');

        // 操作
        $rs = \PhalApi\Helper\TestRunner::go($url, $params);

        // 检验
        $this->assertEquals(0, $rs['code']);
    }
}

CURL 请求

$curl = new \PhalApi\CUrl();

// GET
$rs = $curl->get('http://api.example.com/?id=1', 3000);

// POST
$rs = $curl->post('http://api.example.com/', array('name' => 'test'), 3000);

扩展类库

常用扩展(通过 composer 安装):

{
    "require": {
        "phalapi/redis": "2.*",
        "phalapi/jwt": "2.*",
        "phalapi/pay": "2.*"
    }
}

请求数据来源

// 全局指定
$di->request = new \PhalApi\Request($_POST);

// 参数级别
'source' => 'get'   // $_GET
'source' => 'post'  // $_POST
'source' => 'header' // $_SERVER['HTTP_X']

事务操作

// 方式1:全局
\PhalApi\DI()->notorm->beginTransaction('db_master');
\PhalApi\DI()->notorm->user->insert($data);
\PhalApi\DI()->notorm->commit('db_master');

// 方式2:局部
$this->getORM()->transaction('BEGIN');
$this->getORM()->transaction('COMMIT');
$this->getORM()->transaction('ROLLBACK');

NotORM 数据库操作详解

SELECT 查询

// 基础查询
$this->getORM()->select('id, name')->fetchAll();
$this->getORM()->select('DISTINCT name')->fetchAll();
$this->getORM()->select('id, name, MAX(age) AS max_age')->fetchAll();

// 指定字段
$this->getORM()->select('id, name, email')->where('status', 1)->fetchAll();

WHERE 条件

// 等值条件
$this->getORM()->where('id', 1)->fetchOne();
$this->getORM()->where('id = ?', 1)->fetchOne();

// 多条件 AND(链式调用)
$this->getORM()->where('status', 1)->where('age > ?', 18)->fetchAll();

// OR 条件
$this->getORM()->where('status = ? OR type = ?', 1, 2)->fetchAll();
$this->getORM()->where('status', 1)->or('type', 2)->fetchAll();

// 嵌套条件
$this->getORM()->where('(name = ? OR id = ?)', 'test', 1)->fetchAll();

// IN 查询
$this->getORM()->where('id', array(1, 2, 3))->fetchAll();

// NOT IN
$this->getORM()->where('NOT id', array(1, 2, 3))->fetchAll();

// 模糊匹配
$this->getORM()->where('name LIKE ?', '%test%')->fetchAll();

// NULL 判断
$this->getORM()->where('name IS NULL')->fetchAll();
$this->getORM()->where('name IS NOT NULL')->fetchAll();

ORDER 排序

// 单字段排序
$this->getORM()->order('age DESC')->fetchAll();

// 多字段排序
$this->getORM()->order('id, age DESC')->fetchAll();

LIMIT 和分页

// 限制条数
$this->getORM()->limit(10)->fetchAll();

// 偏移量(跳过5条,取10条)
$this->getORM()->limit(10, 5)->fetchAll();

// 分页(2.8.0+),第2页每页10条
$this->getORM()->page(2, 10)->fetchAll();

GROUP BY 和 HAVING

// 分组统计
$this->getORM()->select('note, COUNT(*) AS count')->group('note')->fetchAll();

// 分组 + HAVING
$this->getORM()->select('note, COUNT(*) AS count')->group('note', 'age > 10')->fetchAll();

获取结果的不同方式

// fetch() - 循环获取单条
$row = $this->getORM()->where('status', 1)->fetch();

// fetchOne() / fetchRow() - 获取单条(推荐)
$row = $this->getORM()->where('id', 1)->fetchOne();

// fetchAll() / fetchRows() - 获取全部
$rows = $this->getORM()->where('status', 1)->fetchAll();

// fetchPairs() - 获取键值对
$pairs = $this->getORM()->fetchPairs('id', 'name');
// 结果:array(1 => '张三', 2 => '李四')

// queryAll() / queryRows() - 原生SQL查询
$rows = $this->getORM()->queryAll('SELECT * FROM user WHERE id > ?', array($id));

聚合函数

// 计数
$count = $this->getORM()->where('status', 1)->count();

// 最小值
$min = $this->getORM()->min('age');

// 最大值
$max = $this->getORM()->max('age');

// 求和
$sum = $this->getORM()->sum('score');

// 综合使用
$this->getORM()->select('COUNT(*) AS total, MAX(age) AS max_age, SUM(score) AS total_score')
    ->where('status', 1)->fetchOne();

插入数据

// 单条插入
$id = $this->getORM()->insert(array(
    'name' => '张三',
    'email' => 'zhangsan@example.com',
    'created_at' => time(),
));

// 获取自增ID
$id = $this->getORM()->insert_id();

// 批量插入
$rows = array(
    array('name' => '张三', 'email' => 'zhangsan@example.com'),
    array('name' => '李四', 'email' => 'lisi@example.com'),
);
$this->getORM()->insert_multi($rows);

// 插入或更新(有则更新,无则插入)
$this->getORM()->insert_update(
    array('name' => '张三'),                    // 唯一键条件
    array('name' => '张三', 'age' => 25),       // 插入数据
    array('age' => 26)                          // 更新数据(已存在时)
);

更新数据

// 条件更新
$affected = $this->getORM()->where('id', 1)->update(array(
    'name' => '新名字',
    'updated_at' => time(),
));

// 返回值说明:
// int(1) - 更新成功
// int(0) - 数据无变化
// false  - 更新失败

// 计数器更新(2.6.0+)- age + 1
$this->getORM()->where('id', 1)->updateCounter('age', 1);

// 计数器递减 - age - 1
$this->getORM()->where('id', 1)->updateCounter('age', -1);

// 多计数器同时更新(2.6.0+)
$this->getORM()->where('id', 1)->updateMultiCounters(array(
    'age' => 1,
    'points' => 10,
));

删除数据

// 条件删除
$affected = $this->getORM()->where('id', 1)->delete();

// 禁止全表删除!以下写法会报错
// $this->getORM()->delete();

// 批量删除
$this->getORM()->where('id', array(1, 2, 3))->delete();

// 条件删除
$this->getORM()->where('status = ? AND created_at < ?', 0, time() - 86400)->delete();

原生 SQL

// queryAll - 查询类SQL,返回结果集
$rows = $this->getORM()->queryAll('SELECT * FROM user WHERE id > ?', array($id));

// executeSql - 写入类SQL(2.6.0+),返回影响行数
$affected = $this->getORM()->executeSql('UPDATE user SET status = ? WHERE id = ?', array(0, 1));

// query - 最底层的查询方法
$result = $this->getORM()->query('SELECT * FROM user WHERE id = ?', array($id));

事务操作详解

方式一:全局事务

\PhalApi\DI()->notorm->beginTransaction('db_master');

try {
    \PhalApi\DI()->notorm->user->insert(array('name' => 'test'));
    \PhalApi\DI()->notorm->order->insert(array('user_id' => 1));
    \PhalApi\DI()->notorm->commit('db_master');
} catch (\Exception $e) {
    \PhalApi\DI()->notorm->rollback('db_master');
    throw $e;
}

方式二:局部事务

$this->getORM()->transaction('BEGIN');

try {
    $this->getORM()->insert(array('name' => 'test'));
    $this->getORM()->where('id', 1)->update(array('status' => 1));
    $this->getORM()->transaction('COMMIT');
} catch (\Exception $e) {
    $this->getORM()->transaction('ROLLBACK');
    throw $e;
}

事务注意事项

  • NotORM 实例带状态,事务中每次操作前需获取新实例
  • 局部事务使用 $this->getORM() 获取新实例
  • 全局事务指定数据库名称(如 'db_master'

关联查询

左连接查询(2.12.0+ 推荐)

$rows = $this->getORM()
    ->alias('u')                                    // 主表别名
    ->select('u.id, u.name, p.avatar')              // 选择字段
    ->leftJoin('profile', 'p', 'u.id = p.user_id')  // 左连接
    ->where('u.status', 1)
    ->order('u.id DESC')
    ->fetchAll();

NotORM 自动关联(点号表示法)

// 前提:表间有外键关联
$this->getORM()->select('user.username, comment.content')->fetchAll();

分库分表

分表配置

config/dbs.phptables 中配置分表策略:

'tables' => array(
    '__default__' => array(
        'prefix' => 'tbl_',
        'key' => 'id',
        'map' => array(
            array('db' => 'db_master'),
        ),
    ),
    // 分表示例:log 表分成3个表
    'log' => array(
        'prefix' => 'tbl_',
        'key' => 'id',
        'map' => array(
            array('db' => 'db_master', 'start' => 0, 'end' => 2),
            // 对应 tbl_log_0, tbl_log_1, tbl_log_2
        ),
    ),
),

Model 中实现分表

class Log extends NotORM {

    protected function getTableName($id) {
        $tableName = 'log';
        if ($id !== null) {
            $tableName .= '_' . ($id % 100);  // log_0, log_1, ..., log_99
        }
        return $tableName;
    }

    // 使用时传入 $id 获取分表实例
    public function getByLogId($id) {
        return $this->getORM($id)->where('id', $id)->fetchOne();
    }
}

分库配置

'servers' => array(
    'db_master' => array(         // 主数据库
        'type' => 'mysql',
        'host' => '192.168.1.100',
        'name' => 'phalapi_main',
        'user' => 'root',
        'password' => '',
        'port' => 3306,
        'charset' => 'UTF8',
    ),
    'db_ext' => array(            // 扩展数据库
        'type' => 'mysql',
        'host' => '192.168.1.200',
        'name' => 'phalapi_ext',
        'user' => 'root',
        'password' => '',
        'port' => 3306,
        'charset' => 'UTF8',
    ),
),

'tables' => array(
    '__default__' => array(
        'prefix' => 'tbl_',
        'key' => 'id',
        'map' => array(
            array('db' => 'db_master'),
        ),
    ),
    'order' => array(            // order 表使用扩展库
        'prefix' => 'tbl_',
        'key' => 'id',
        'map' => array(
            array('db' => 'db_ext'),
        ),
    ),
),

自动建表

php ./bin/phalapi-buildsqls

keep_suffix_if_no_map(2.12.0+)

当表名保留下划线+数字后缀时:

'tables' => array(
    'log' => array(
        'prefix' => 'tbl_',
        'key' => 'id',
        'keep_suffix_if_no_map' => true,  // 保留后缀
        'map' => array(
            array('db' => 'db_master'),
        ),
    ),
),

多数据库连接

简单方案:同一配置文件

config/dbs.php 中配置多个 servers,通过 tables 的 map 路由到不同数据库。

复杂方案:独立配置和 NotORM 实例

步骤 1:创建配置文件 config/dbs_ms.php

return array(
    'servers' => array(
        'db_ms' => array(
            'type'     => 'sqlserver',  // dblib_sqlserver
            'host'     => '192.168.1.100',
            'name'     => 'phalapi_ms',
            'user'     => 'sa',
            'password' => '',
            'port'     => 1433,
            'charset'  => 'UTF8',
        ),
    ),
    'tables' => array(
        '__default__' => array(
            'prefix' => '',
            'key' => 'id',
            'map' => array(
                array('db' => 'db_ms'),
            ),
        ),
    ),
);

步骤 2:创建 Model 基类

// ./src/app/Common/MSModelBase.php
namespace App\Common;
use PhalApi\Model\NotORMModel as NotORM;

class MSModelBase extends NotORM {

    protected function getNotORM() {
        return \PhalApi\DI()->notorm_ms;
    }
}

步骤 3:在 config/di.php 中注册

// 注册 SQL Server 的 NotORM 实例
$di->notorm_ms = new \PhalApi\Database\NotORMDatabase(
    $di->config->get('dbs_ms')
);

步骤 4:Model 继承 MSModelBase

namespace App\Model;
use App\Common\MSModelBase;

class MSUser extends MSModelBase {

    protected function getTableName($id) {
        return 'ms_user';
    }
}

支持的数据库类型

类型type 配置说明
----------------------
MySQLmysql默认,最常用
SQL Serversqlserver / dblib_sqlserver需安装 dblib 扩展
PostgreSQLpgsql需安装 pgsql 扩展

2.8.0+ 切换数据库实例

class ExtModel extends NotORM {

    protected function getNotORM() {
        // 重写此方法切换到其他数据库实例
        return \PhalApi\DI()->notorm_ext;
    }
}

SQL 调试

三个调试开关

配置项位置说明
--------------------
sys.debugconfig/sys.php接口调试模式
sys.notorm_debugconfig/sys.phpNotORM 调试模式
sys.enable_sql_logconfig/sys.phpSQL 日志记录
// config/sys.php
return array(
    'debug' => true,            // 开启接口调试
    'notorm_debug' => true,     // 开启 NotORM 调试
    'enable_sql_log' => true,   // 开启 SQL 日志
);

查看执行的 SQL

开启 sys.debug 后,返回结果中会包含 debug.sqls 字段,列出所有执行的 SQL 语句。

日志记录 SQL

开启 sys.enable_sql_log 后,SQL 语句会写入日志文件:

# 实时查看 SQL 日志
tail -f ./runtime/log/20240101.log

自定义 SQL 追踪器

// ./src/app/Common/SqlTracer.php
namespace App\Common;
use PhalApi\Helper\Tracer;

class SqlTracer extends Tracer {

    public function sql($sql, $params = array()) {
        // 自定义追踪逻辑
        \PhalApi\DI()->logger->info('SQL: ' . $sql, $params);
    }
}

// 注册
$di->tracer = new \App\Common\SqlTracer();

缓存详解

FileCache 文件缓存

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

// 使用
\PhalApi\DI()->cache->set('key', $value, 600);    // 设置缓存(600秒=10分钟)
$value = \PhalApi\DI()->cache->get('key');          // 获取缓存
\PhalApi\DI()->cache->delete('key');                // 删除缓存

APCUCache APCu 缓存

// config/di.php
$di->cache = new \PhalApi\Cache\APCUCache();

// 使用(接口同上)
\PhalApi\DI()->cache->set('key', $value, 600);
$value = \PhalApi\DI()->cache->get('key');
\PhalApi\DI()->cache->delete('key');

MemcachedCache 缓存

// config/di.php
$di->cache = new \PhalApi\Cache\MemcachedCache(array(
    'host' => '127.0.0.1',
    'port' => 11211,
));

// 多实例配置(用逗号分隔)
$di->cache = new \PhalApi\Cache\MemcachedCache(array(
    'host' => '192.168.1.1,192.168.1.2',  // 多台Memcached
    'port' => '11211,11211',
));

RedisCache 缓存

// config/di.php
$di->cache = new \PhalApi\Cache\RedisCache(array(
    'type'    => 'redis',
    'host'    => '127.0.0.1',
    'port'    => 6379,
    'timeout' => 300,
    'prefix'  => 'phalapi_',
    'auth'    => 'your_password',
    'db'      => 0,
));

// Socket 方式连接
$di->cache = new \PhalApi\Cache\RedisCache(array(
    'type'   => 'unix',
    'socket' => '/var/run/redis/redis.sock',
));

缓存架构对比

驱动适用场景优点缺点
----------------------------
FileCache开发环境、小数据量无需额外服务性能低
APCUCache单机、高速缓存极快不跨进程共享
MemcachedCache多进程共享、中等数据量分布式、成熟无持久化
RedisCache高性能、大数据量、分布式持久化、丰富数据结构需维护 Redis

缓存使用模式

// Domain 层中的缓存使用模式
class User {

    public function getUserInfo($userId) {
        $key = 'user_info_' . $userId;

        // 先查缓存
        $data = \PhalApi\DI()->cache->get($key);
        if ($data !== null) {
            return $data;
        }

        // 缓存未命中,查询数据库
        $model = new \App\Model\User();
        $data = $model->get($userId);

        if (!empty($data)) {
            // 写入缓存,1小时过期
            \PhalApi\DI()->cache->set($key, $data, 3600);
        }

        return $data;
    }
}

日志详解

三种日志级别

方法用途示例场景
----------------------
error()系统异常数据库连接失败、外部服务异常
info()业务记录用户登录、订单创建
debug()开发调试变量值、执行流程
\PhalApi\DI()->logger->error('数据库连接失败', array('host' => $host));
\PhalApi\DI()->logger->info('用户登录', array('user_id' => $userId, 'ip' => $ip));
\PhalApi\DI()->logger->debug('查询参数', array('where' => $where, 'bind' => $bind));

自定义日志类型

// 使用 log() 方法记录自定义类型
\PhalApi\DI()->logger->log('access', '接口访问', array('url' => $url, 'time' => time()));

FileLogger 配置

// 方式一:直接创建
$di->logger = new \PhalApi\Logger\FileLogger(
    API_ROOT . '/runtime',
    \PhalApi\Logger::LOG_LEVEL_DEBUG | \PhalApi\Logger::LOG_LEVEL_INFO | \PhalApi\Logger::LOG_LEVEL_ERROR
);

// 方式二:使用 create 工厂方法(2.7.0+)
$di->logger = \PhalApi\Logger\FileLogger::create(array(
    'log_path' => API_ROOT . '/runtime',
    'log_level' => \PhalApi\Logger::LOG_LEVEL_DEBUG | \PhalApi\Logger::LOG_LEVEL_INFO | \PhalApi\Logger::LOG_LEVEL_ERROR,
));

// 方式三:配置文件 + 工厂方法
// config/sys.php
return array(
    'log_path' => API_ROOT . '/runtime',
    'log_level' => \PhalApi\Logger::LOG_LEVEL_DEBUG | \PhalApi\Logger::LOG_LEVEL_INFO | \PhalApi\Logger::LOG_LEVEL_ERROR,
);

// config/di.php
$di->logger = \PhalApi\Logger\FileLogger::create($di->config->get('sys'));

多日志服务

当不同业务模块需要独立的日志文件时:

// config/sys.php
return array(
    'log_path' => API_ROOT . '/runtime',
    'log_level' => \PhalApi\Logger::LOG_LEVEL_ALL,
    'file_logger_app' => array(
        'log_path' => API_ROOT . '/runtime/app',
        'log_level' => \PhalApi\Logger::LOG_LEVEL_INFO,
    ),
    'file_logger_pay' => array(
        'log_path' => API_ROOT . '/runtime/pay',
        'log_level' => \PhalApi\Logger::LOG_LEVEL_ALL,
    ),
);

// config/di.php
$di->logger_app = \PhalApi\Logger\FileLogger::create($di->config->get('sys.file_logger_app'));
$di->logger_pay = \PhalApi\Logger\FileLogger::create($di->config->get('sys.file_logger_pay'));

// 使用
\PhalApi\DI()->logger_app->info('应用日志', $data);
\PhalApi\DI()->logger_pay->error('支付错误', $errorData);

日志最佳实践

  1. 敏感信息脱敏:日志中避免记录密码、Token 等敏感信息
  2. 合理选择级别:调试信息用 DEBUG,生产环境用 INFO
  3. 结构化日志:使用数组记录上下文信息,便于分析
  4. 日志切割:FileLogger 自动按天分割,避免单个文件过大
  5. 异常必记:所有 catch 块中必须记录异常信息

过滤器(签名验证)

SimpleMD5Filter 签名机制

签名计算规则:

  1. 排除 sign 参数
  2. 将剩余参数按字典序排列
  3. 拼接为 key1=value1&key2=value2&... 格式
  4. 末尾追加密钥
  5. 计算 MD5 值
// 启用 MD5 签名验证
// config/di.php
$di->filter = new \PhalApi\Filter\SimpleMD5Filter();

白名单配置

免签名验证的接口列表:

// config/app.php
'service_whitelist' => array(
    'Site.Index',         // 精确匹配
    'User.GetBaseInfo',   // 精确匹配
    '*.List',             // 通配符匹配(所有 List 结尾的接口)
    'Site.*',             // 通配符匹配(Site 下所有接口)
),

自定义过滤器

// ./src/app/Common/SignFilter.php
namespace App\Common;
use PhalApi\Filter;
use PhalApi\Exception\BadRequestException;

class SignFilter implements Filter {

    public function check() {
        $signature = \PhalApi\DI()->request->get('signature');
        $timestamp = \PhalApi\DI()->request->get('timestamp');

        // 验证时间戳(5分钟内有效)
        if (abs(time() - $timestamp) > 300) {
            throw new BadRequestException('请求过期', 401);
        }

        // 验证签名
        $params = \PhalApi\DI()->request->getAll();
        unset($params['signature']);
        ksort($params);
        $signStr = http_build_query($params) . '&key=your_secret_key';
        $expectedSign = md5($signStr);

        if ($signature !== $expectedSign) {
            throw new BadRequestException('签名验证失败', 402);
        }
    }
}

// config/di.php
$di->filter = new \App\Common\SignFilter();

DataApi 通用数据接口(2.13.0+)

DataApi 提供5个通用数据操作接口,无需为每张表编写单独的接口。

5个内置接口

接口名功能说明
--------------------
CreateData创建数据新增记录
DeleteDataIDs删除数据根据ID批量删除
GetData获取数据单条/多条查询
TableList表列表分页查询表数据
UpdateData更新数据根据ID更新

使用步骤

步骤 1:创建 Api 类继承 DataApi

// ./src/app/Api/UserData.php
namespace App\Api;
use PhalApi\DataApi;

class UserData extends DataApi {

    protected function userCheck() {
        // 权限验证(必须重写)
        // 返回 true 或抛出异常
        $token = \PhalApi\DI()->request->get('token');
        if (empty($token)) {
            throw new \PhalApi\Exception\BadRequestException('缺少token');
        }
        return true;
    }

    protected function getDataModel() {
        // 返回对应的 Model 实例(必须重写)
        return new \App\Model\UserDataModel();
    }
}

步骤 2:创建 Model 继承 DataModel

// ./src/app/Model/UserDataModel.php
namespace App\Model;
use PhalApi\Model\DataModel;

class UserDataModel extends DataModel {

    protected function getTableName($id) {
        return 'user';
    }
}

DataApi 定制选项

class UserData extends DataApi {

    // 定制表列表查询
    protected function getTableListSelect() { return 'id, name, email'; }
    protected function getTableListOrder() { return 'id DESC'; }
    protected function getTableListWhere() { return array('status' => 1); }

    // 定制创建数据
    protected function createDataRequireKeys() { return array('name', 'email'); }
    protected function createDataExcludeKeys() { return array('id', 'created_at'); }

    // 定制更新数据
    protected function updateDataExcludeKeys() { return array('created_at'); }
}

DataApi 请求示例

# 获取表列表(分页)
GET /?s=App.UserData.TableList&page=1&per_page=20

# 获取单条数据
GET /?s=App.UserData.GetData&id=1

# 创建数据
POST /?s=App.UserData.CreateData
name=张三&email=zhangsan@example.com

# 更新数据
POST /?s=App.UserData.UpdateData&id=1&name=新名字

# 删除数据
POST /?s=App.UserData.DeleteDataIDs&ids=1,2,3

Cookie 操作

基本使用

// 设置 Cookie
\PhalApi\Cookie\FCookie::set('key', 'value', 3600);

// 获取 Cookie
$value = \PhalApi\Cookie\FCookie::get('key');

// 删除 Cookie
\PhalApi\Cookie\FCookie::delete('key');

MultiCookie 加密版

// config/di.php
$di->cookie = new \PhalApi\Cookie\MultiCookie(array(
    'crypt' => new \PhalApi\Crypt\McryptCrypt('your_secret_key'),
    'path' => '/',
    'domain' => '.example.com',
));

// 使用
\PhalApi\DI()->cookie->set('user_token', $token, 86400);
$token = \PhalApi\DI()->cookie->get('user_token');
\PhalApi\DI()->cookie->delete('user_token');

加密和解密

McryptCrypt(对称加密)

use PhalApi\Crypt\McryptCrypt;

$crypt = new McryptCrypt('your_secret_key');

// 加密
$encrypted = $crypt->encrypt('敏感数据', 'secret_key');

// 解密
$decrypted = $crypt->decrypt($encrypted, 'secret_key');

MultiMcryptCrypt(序列化+base64+mcrypt)

use PhalApi\Crypt\MultiMcryptCrypt;

$crypt = new MultiMcryptCrypt('your_secret_key');

// 加密(自动序列化+base64)
$encrypted = $crypt->encrypt(array('user_id' => 1, 'role' => 'admin'), 'secret_key');

// 解密(自动反序列化)
$data = $crypt->decrypt($encrypted, 'secret_key');

RSA 非对称加密

use PhalApi\Crypt\Rsa\MultiPri2PubCrypt;

// 私钥加密,公钥解密
$rsa = new MultiPri2PubCrypt();

// 加密(使用私钥)
$encrypted = $rsa->encrypt($data, $privateKey);

// 解密(使用公钥)
$decrypted = $rsa->decrypt($encrypted, $publicKey);

Crypt 接口

所有加密类实现 PhalApi\Crypt 接口:

interface Crypt {
    public function encrypt($data, $key);
    public function decrypt($data, $key);
}

HTTP 请求(CUrl)

$curl = new \PhalApi\CUrl();

// GET 请求
$rs = $curl->get('http://api.example.com/?id=1', 3000);  // 3秒超时

// POST 请求
$rs = $curl->post('http://api.example.com/', array('name' => 'test'), 3000);

// 构造参数为重试次数
$curl = new \PhalApi\CUrl(3);  // 失败后最多重试3次
$rs = $curl->get('http://api.example.com/?id=1', 3000);

Tool 工具类

use PhalApi\Tool;

// 获取客户端IP
$ip = Tool::getClientIp();

// 生成随机字符串
$str = Tool::createRandStr(16);              // 默认字母数字
$str = Tool::createRandStr(6, '0123456789'); // 纯数字验证码

// 数组转XML
$xml = Tool::arrayToXml($data);

// XML转数组
$data = Tool::xmlToArray($xml);

// 排除数组中的指定键
$result = Tool::arrayExcludeKeys($data, 'password,token');

扩展类库

安装扩展

// composer.json
{
    "require": {
        "phalapi/redis": "2.*",
        "phalapi/jwt": "2.*",
        "phalapi/pay": "2.*",
        "phalapi/qrcode": "2.*",
        "phalapi/wechat": "2.*",
        "phalapi/sms": "2.*",
        "phalapi/upload": "2.*"
    }
}
composer update

扩展配置和使用

每个扩展需要三步:安装 → 配置 → 注册

// 步骤1:安装(composer require)

// 步骤2:配置(config/app.php 中添加扩展配置)
return array(
    'redis' => array(
        'host' => '127.0.0.1',
        'port' => 6379,
        'auth' => '',
    ),
);

// 步骤3:注册(config/di.php)
$di->redis = new \PhalApi\Redis\Redis($di->config->get('app.redis'));

开发自定义扩展

扩展目录结构:

phalapi-custom-ext/
├── composer.json        # 包信息
├── src/
│   └── Ext.php         # 扩展类
└── README.md
// composer.json
{
    "name": "yourname/phalapi-custom-ext",
    "type": "phalapi-extension",
    "require": {
        "phalapi/kernal": "2.*"
    },
    "autoload": {
        "psr-4": {
            "YourName\\CustomExt\\": "src/"
        }
    }
}

消息队列(Gearman)

Api 层写入队列

// ./src/app/Api/Task.php
namespace App\Api;
use PhalApi\Api;

class Task extends Api {

    public function createTask() {
        $client = new \GearmanClient();
        $client->addServer('127.0.0.1', 4730);

        $data = array(
            'task_id' => $this->taskId,
            'params' => $this->taskParams,
        );

        $client->doBackground('process_task', json_encode($data));

        return array('task_id' => $this->taskId);
    }
}

消费脚本

// ./bin/worker.php
$worker = new \GearmanWorker();
$worker->addServer('127.0.0.1', 4730);

$worker->addFunction('process_task', function($job) {
    $data = json_decode($job->workload(), true);
    // 处理任务逻辑
    $domain = new \App\Domain\Task();
    $domain->process($data);
});

while ($worker->work()) {}

守护进程运行

# 启动 worker
nohup php ./bin/worker.php > /dev/null 2>&1 &

# 查看运行状态
ps aux | grep worker

# 停止
kill -9 $(pgrep -f worker.php)

配置详解

配置读取

// 获取完整配置
$config = \PhalApi\DI()->config->get('app');

// 获取单个配置(多级用点号分隔)
$version = \PhalApi\DI()->config->get('app.version');

// 获取配置(带默认值)
$name = \PhalApi\DI()->config->get('app.name', '默认名称');

环境配置

根据 API_MODE 自动加载不同环境配置:

API_MODE加载规则示例
--------------------------
prodapp.php生产配置
devapp_dev.php(优先),app.php(回退)开发配置
testapp_test.php(优先),app.php(回退)测试配置
// 定义环境模式
defined('API_MODE') || define('API_MODE', 'dev');

Yaconf 扩展支持

// 使用 Yaconf 读取配置(高性能)
$di->config = new \PhalApi\Config\YaconfConfig('phalapi.');

DI 服务详解

服务注册

$di = \PhalApi\DI();

// 配置(必须手动注册)
$di->config = new \PhalApi\Config\FileConfig(API_ROOT . '/config');

// 数据库(推荐注册)
$di->notorm = new \PhalApi\Database\NotORMDatabase($di->config->get('dbs'));

// 日志(必须手动注册)
$di->logger = new \PhalApi\Logger\FileLogger(
    API_ROOT . '/runtime',
    \PhalApi\Logger::LOG_LEVEL_DEBUG | \PhalApi\Logger::LOG_LEVEL_INFO | \PhalApi\Logger::LOG_LEVEL_ERROR
);

// 缓存(推荐注册)
$di->cache = new \PhalApi\Cache\RedisCache(array('host' => '127.0.0.1'));

// 过滤器(推荐注册)
$di->filter = new \PhalApi\Filter\SimpleMD5Filter();

自动注册的服务

服务说明
------------
$di->request请求对象(自动注册)
$di->response响应对象(自动注册)

判断服务是否注册

// 错误方式(未注册时报错)
if (isset($di->cache)) { ... }

// 正确方式(先获取再判断)
$cache = $di->cache;
if (isset($cache)) {
    $cache->get('key');
}

常用 DI 服务一览

服务是否必须注册说明
-------------------------
$di->config必须配置读取
$di->logger必须日志记录
$di->notorm推荐数据库操作
$di->cache推荐缓存
$di->filter推荐签名验证
$di->request自动请求参数
$di->response自动响应输出

在线接口文档

访问地址

  • 列表:http://dev.phalapi.net/docs.php
  • 详情:http://dev.phalapi.net/docs.php?service=App.Site.Index&detail=1

注释规范

/**
 * 用户登录
 *
 * @desc 用于用户登录验证,返回用户信息和Token
 * @param string username 用户名(必填)
 * @param string password 密码(必填,最少6位)
 * @return int user_id 用户ID
 * @return string token 登录令牌
 * @return string username 用户名
 * @exception 400 参数错误
 * @exception 401 签名错误
 * @exception 403 权限不足
 */
public function login() { }

/**
 * 内部接口(不在文档中显示)
 *
 * @ignore
 */
public function internalMethod() { }

生成离线文档

# 展开版(所有接口详情展开显示)
php ./public/docs.php expand

# 折叠版(接口列表折叠显示)
php ./public/docs.php fold

自动加载(PSR-4)

命名空间对应

命名空间文件路径
--------------------
App\Api\Usersrc/app/Api/User.php
App\Domain\Usersrc/app/Domain/User.php
App\Model\Usersrc/app/Model/User.php
App\Common\Utilssrc/app/Common/Utils.php

多层子命名空间

// App\Api\Weixin\User → src/app/Api/Weixin/User.php
namespace App\Api\Weixin;

class User {
    // 微信用户相关接口
}

use + 别名

use App\Domain\User as DomainUser;
use App\Model\User as ModelUser;

$domain = new DomainUser();
$model = new ModelUser();

// 或使用完整类名
$domain = new \App\Domain\User();

composer.json 配置

{
    "autoload": {
        "psr-4": {
            "App\\": "src/app/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "App\\Test\\": "tests/"
        }
    }
}

全局函数

// src/app/functions.php
namespace App;

function hello() {
    return 'world';
}

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

// 使用
$result = \App\hello();
$time = \App\now();

单元测试

自动生成测试

php ./bin/phalapi-buildtest ./src/app/Api/User.php App\\Api\\User > ./tests/app/Api/User_Test.php

BUILD-OPERATE-CHECK 模式

class PhpUnderControl_AppApiUser_Test extends \PHPUnit_Framework_TestCase
{
    public function testLogin() {
        // BUILD - 构造
        $url = 's=User.Login';
        $params = array('username' => 'test', 'password' => '123456');

        // OPERATE - 操作
        $rs = \PhalApi\Helper\TestRunner::go($url, $params);

        // CHECK - 检验
        $this->assertNotEmpty($rs);
        $this->assertEquals(200, $rs['ret']);
        $this->assertArrayHasKey('user_id', $rs['data']);
    }

    public function testGetUserInfo() {
        $url = 's=User.GetUserInfo';
        $params = array('id' => 1);

        $rs = \PhalApi\Helper\TestRunner::go($url, $params);

        $this->assertEquals(200, $rs['ret']);
        $this->assertEquals(1, $rs['data']['id']);
    }
}

TestRunner 模拟请求

// 模拟 GET 请求
$rs = \PhalApi\Helper\TestRunner::go('s=User.GetUserInfo&id=1');

// 模拟 POST 请求
$rs = \PhalApi\Helper\TestRunner::go('s=User.Login', array(
    'username' => 'test',
    'password' => '123456',
));

// 带自定义配置
$rs = \PhalApi\Helper\TestRunner::go('s=User.GetUserInfo', array('id' => 1), array(
    'HTTP_X_TOKEN' => 'test_token',
));

常见问题

问题解决方案
----------------
中文返回为空检查 json_encode 编码,确保数据库 UTF8
跨层调用报错确认调用关系:Api→Domain→Model
缓存不生效检查 runtime 目录权限
NotORM 状态问题每次查询获取新实例 $this->getORM()
全表删除禁止必须带 WHERE 条件
签名验证失败确认密钥一致、参数排序规则、编码格式
数据库连接失败检查 config/dbs.php、MySQL服务、防火墙
参数校验失败查看 code 错误码、检查参数类型和范围
日志文件过大FileLogger 自动按天分割,也可手动清理
分表查询不到数据确认 $id 正确传入 getORM($id)

快速参考表

功能配置/使用位置关键类/方法
--------------------------------
数据库配置config/dbs.phpNotORMDatabase
缓存配置config/di.phpFileCache/RedisCache/MemcachedCache
日志配置config/di.phpFileLogger
签名配置config/di.phpSimpleMD5Filter
白名单config/app.phpservice_whitelist
系统配置config/sys.phpdebug/notorm_debug/enable_sql_log
应用配置config/app.phpapiCommonRules
DI服务注册config/di.php\PhalApi\DI()
接口层src/app/Api/extends Api
领域层src/app/Domain/业务逻辑
数据层src/app/Model/extends NotORMModel
通用数据DataApiextends DataApi
参数规则getRules()9种类型
在线文档/docs.php@desc/@return/@exception
单元测试./bin/phalapi-buildtestTestRunner::go()
扩展安装composer.jsonphalapi/*
环境模式API_MODEdev/test/prod
加密PhalApi\CryptMcryptCrypt/RSA
HTTP请求PhalApi\CUrlget()/post()
CookiePhalApi\Cookieset()/get()/delete()
工具PhalApi\ToolgetClientIp()/createRandStr()

资源链接

  • 官方文档:http://docs.phalapi.net/#/v2.0/
  • 在线示例:http://demo.phalapi.net/docs.php
  • GitHub:https://github.com/phalapi/phalapi
  • 扩展市场:https://www.phalapi.net/

版本历史

共 1 个版本

  • v1.0.0 Initial release 当前
    2026-04-28 11:26 安全 安全

安全检测

腾讯云安全 (Keen)

安全,无风险
查看报告

腾讯云安全 (Sanbu)

安全,无风险
查看报告

🔗 相关推荐

dev-programming

Github

steipete
使用 `gh` CLI 与 GitHub 交互,通过 `gh issue`、`gh pr`、`gh run` 和 `gh api` 管理议题、PR、CI 运行及高级查询。
★ 681 📥 329,993
dev-programming

CodeConductor.ai

larsonreever
AI驱动平台,提供快速全栈开发、智能体、工作流自动化及低代码AI集成的可扩展产品创建。
★ 78 📥 182,645
knowledge-management

coder-llm-wiki

user_ef796dd2
Use when maintaining developer-facing wiki pages for a code project, especially after code changes, when creating or ref
★ 0 📥 123