https://github.com/mevdschee/php-crud-api

将 REST API 添加到 MySQL/MariaDB、PostgreSQL、SQL Server 或 SQLite 数据库的单个文件 PHP 脚本。

注意:这是 PHP 中的TreeQL参考实现。

相关项目:

  • JS-CRUD-API : PHP-CRUD-API API 的 JavaScript 客户端库
  • PHP-API-AUTH:单个文件 PHP 脚本,它是 PHP-CRUD-API 的身份验证提供程序
  • PHP-CRUD-UI:将 UI 添加到 PHP-CRUD-API 项目的单个文件 PHP 脚本。
  • PHP-CRUD-ADMIN:将数据库管理界面添加到 PHP-CRUD-API 项目的单个文件 PHP 脚本。
  • PHP-SP-API:将 REST API 添加到 SQL 数据库的单个文件 PHP 脚本。
  • VUE-CRUD-UI:将 UI 添加到 PHP-CRUD-API 项目的单个文件 Vue.js 脚本。

此脚本还有以下端口:

此脚本还有一些概念验证端口,仅支持基本的 REST CRUD 功能: PHP、 Java、 Go、 C# .net core、 Node.js和 Python

要求

  • PHP 7.0 或更高版本,为以下数据库系统之一启用了 PDO 驱动程序:
    • MySQL 5.6 / MariaDB 10.0 或更高版本,用于 MySQL 中的空间功能
    • PostgreSQL 9.1 或更高版本,带有 PostGIS 2.0 或更高版本的空间特征
    • SQL Server 2012 或更高版本(2017 支持 Linux)
    • SQLite 3.16 或更高版本(不支持空间功能)

安装

这是一个单文件应用程序!在某处上传“ api.php”并享受!

对于本地开发,您可以运行 PHP 的内置 Web 服务器:

php -S localhost:8080

通过打开以下 URL 来测试脚本:

http://localhost:8080/api.php/records/posts/1

不要忘记修改文件底部的配置。

或者,您可以将此项目集成到您选择的 Web 框架中,请参阅:

在这些集成中, Composer用于将此项目作为依赖项加载。

对于不使用composer的人,api.include.php提供了文件“”。该文件包含“”中的所有内容,但“ api.php”中的配置除外,src/index.php并且可以被 PHP 的“include”函数使用。

配置

编辑文件“”底部的以下行api.php

$config = new Config([
    'username' => 'xxx',
    'password' => 'xxx',
    'database' => 'xxx',
]);

这些是括号中的所有配置选项及其默认值:

  • "司机": mysqlpgsql,sqlsrvsqlitemysql)
  • “地址”:数据库服务器的主机名(或文件名)(localhost
  • “端口”:数据库服务器的 TCP 端口(默认为驱动程序默认值)
  • “username”:连接数据库的用户的用户名(无默认)
  • “password”:连接数据库的用户密码(无默认)
  • “数据库”:连接的数据库(无默认值)
  • “tables”:要发布的表的逗号分隔列表(默认为“全部”)
  • "middlewares": 要加载的中间件列表 ( cors)
  • “控制器”:要加载的控制器列表(records,geojson,openapi,status
  • “customControllers”:要加载的用户自定义控制器列表(无默认值)
  • "openApiBase": OpenAPI 信息 ( {"info":{"title":"PHP-CRUD-API","version":"1.0.0"}})
  • "cacheType": TempFileRedisMemcache,MemcachedNoCacheTempFile)
  • “cachePath”:缓存的路径/地址(默认为系统的临时目录)
  • “cacheTime”:缓存有效的秒数(10
  • “调试”:在“X-Exception”标头中显示错误 ( false)
  • “basePath”:API 的 URI 基本路径(默认使用 PATH_INFO 确定)

所有配置选项也可用作环境变量。用大写字母,“PHP_CRUD_API_”前缀和下划线来编写配置选项,例如:

  • PHP_CRUD_API_DRIVER=mysql
  • PHP_CRUD_API_ADDRESS=本地主机
  • PHP_CRUD_API_PORT=3306
  • PHP_CRUD_API_DATABASE=php-crud-api
  • PHP_CRUD_API_USERNAME=php-crud-api
  • PHP_CRUD_API_PASSWORD=php-crud-api
  • PHP_CRUD_API_DEBUG=1

环境变量优先于 PHP 配置。

限制

这些限制和约束适用:

  • 主键应该是自动增量(从 1 到 2^53)或 UUID
  • 不支持复合主键和复合外键
  • 不支持复杂的写入(事务)
  • 不支持调用函数(如“concat”或“sum”)的复杂查询
  • 数据库必须支持和定义外键约束
  • SQLite 不能有 bigint 类型的自动递增主键
  • SQLite 不支持更改表列(结构)

特征

支持以下功能:

  • Composer 安装或单个 PHP 文件,易于部署。
  • 代码很少,易于适应和维护
  • 支持 POST 变量作为输入(x-www-form-urlencoded)
  • 支持 JSON 对象作为输入
  • 支持 JSON 数组作为输入(批量插入)
  • 使用类型规则和回调清理和验证输入
  • 数据库、表、列和记录的权限系统
  • 支持多租户单数据库和多数据库布局
  • 跨域请求的多域 CORS 支持
  • 支持从多个表中读取连接结果
  • 对多个条件的搜索支持
  • 分页、排序、top N 列表和列选择
  • 具有嵌套结果的关系检测(belongsTo、hasMany 和 HABTM)
  • 通过 PATCH 支持原子增量(用于计数器)
  • base64 编码支持的二进制字段
  • WKT 和 GeoJSON 支持的空间/GIS 字段和过滤器
  • 使用 OpenAPI 工具生成 API 文档
  • 通过 API 密钥、JWT 令牌或用户名/密码进行身份验证
  • 数据库连接参数可能取决于身份验证
  • 支持以 JSON 格式读取数据库结构
  • 支持使用 REST 端点修改数据库结构
  • 包括安全增强中间件
  • 符合标准:PSR-4、PSR-7、PSR-12、PSR-15 和 PSR-17

汇编

您可以使用以下命令安装此项目的所有依赖项:

php install.php

您可以使用以下命令将所有文件编译成一个“ api.php”文件:

php build.php

注意:安装脚本将修补供应商目录中的依赖项以兼容 PHP 7.0。

发展

您可以通过 URL 访问未编译的代码:

http://localhost:8080/src/records/posts/1

未编译的代码位于“ src”和“ vendor”目录中。“ vendor”目录包含依赖项。

更新依赖项

您可以使用以下命令更新此项目的所有依赖项:

php update.php

此脚本将安装并运行Composer以更新依赖项。

注意:更新脚本将修补供应商目录中的依赖项以兼容 PHP 7.0。

TreeQL,一个实用的 GraphQL

TreeQL允许您基于 SQL 数据库结构(关系)和查询创建 JSON 对象的“树”。

它松散地基于 REST 标准,也受到 json:api 的启发。

CRUD + 列表

示例帖子表只有几个字段:

posts  
=======
id     
title  
content
created

下面的 CRUD + List 操作作用于这个表。

创建

如果要创建记录,可以将请求以 URL 格式编写为:

POST /records/posts

您必须发送包含以下内容的正文:

{
    "title": "Black is the new red",
    "content": "This is the second post.",
    "created": "2018-03-06T21:34:01Z"
}

它将返回新创建记录的主键值:

2

要从此表中读取记录,可以将请求以 URL 格式编写为:

GET /records/posts/1

其中“1”是您要读取的记录的主键值。它将返回:

{
    "id": 1
    "title": "Hello world!",
    "content": "Welcome to the first post.",
    "created": "2018-03-05T20:12:56Z"
}

在读取操作中,您可以应用连接。

更新

要更新此表中的记录,可以将请求以 URL 格式编写为:

PUT /records/posts/1

其中“1”是您要更新的记录的主键值。作为正文发送:

{
    "title": "Adjusted title!"
}

这会调整帖子的标题。返回值是设置的行数:

1

删除

如果要从此表中删除记录,则可以将请求以 URL 格式编写为:

DELETE /records/posts/1

它将返回删除的行数:

1

列表

要列出该表中的记录,可以将请求以 URL 格式编写为:

GET /records/posts

它将返回:

{
    "records":[
        {
            "id": 1,
            "title": "Hello world!",
            "content": "Welcome to the first post.",
            "created": "2018-03-05T20:12:56Z"
        }
    ]
}

在列表操作中,您可以应用过滤器和连接。

过滤器

过滤器使用“过滤器”参数在列表调用上提供搜索功能。您需要指定列名、逗号、匹配类型、另一个逗号以及要过滤的值。这些是受支持的匹配类型:

  • “cs”:包含字符串(字符串包含值)
  • “sw”:以(字符串以值开头)开头
  • “ew”:以(以值结尾的字符串)结尾
  • “eq”:相等(字符串或数字完全匹配)
  • “lt”:低于(数字低于值)
  • “le”:小于等于(数字小于等于值)
  • “ge”:大于等于(数大于等于值)
  • “gt”:大于(数字大于值)
  • “bt”:介于(数字在两个逗号分隔值之间)
  • “in”:in(数字或字符串在逗号分隔的值列表中)
  • “is”:为空(字段包含“NULL”值)

您可以通过在前面加上一个“n”字符来否定所有过滤器,这样“eq”就变成了“neq”。过滤器使用示例如下:

GET /records/categories?filter=name,eq,Internet
GET /records/categories?filter=name,sw,Inter
GET /records/categories?filter=id,le,1
GET /records/categories?filter=id,ngt,1
GET /records/categories?filter=id,bt,0,1
GET /records/categories?filter=id,in,0,1

输出:

{
    "records":[
        {
            "id": 1
            "name": "Internet"
        }
    ]
}

在下一节中,我们将深入探讨如何在单个列表调用中应用多个过滤器。

多个过滤器

可以通过在 URL 中重复“过滤器”参数来应用过滤器。例如以下网址:

GET /records/categories?filter=id,gt,1&filter=id,lt,3

将请求所有类别“其中 id > 1 和 id < 3”。如果你想要“其中 id = 2 或 id = 4”,你应该写:

GET /records/categories?filter1=id,eq,2&filter2=id,eq,4

如您所见,我们在“filter”参数中添加了一个数字,以指示应该应用“OR”而不是“AND”。请注意,您也可以重复“filter1”并在“OR”中创建“AND”。由于您还可以通过添加一个字母 (af) 来更深一层,因此您几乎可以创建任何相当复杂的条件树。

注意:您只能过滤请求的表(而不是包含的表),过滤器仅适用于列表调用。

列选择

默认情况下,所有列都被选中。使用“include”参数,您可以选择特定的列。您可以使用点将表名与列名分开。多列应以逗号分隔。星号(“*”)可以用作通配符来表示“所有列”。与“包含”类似,您可以使用“排除”参数删除某些列:

GET /records/categories/1?include=name
GET /records/categories/1?include=categories.name
GET /records/categories/1?exclude=categories.id

输出:

    {
        "name": "Internet"
    }

注意:用于包含相关实体的列是自动添加的,不能被排除在输出之外。

订购

使用“order”参数可以进行排序。默认情况下,排序是按升序排列的,但是通过指定“desc”,这可以反转:

GET /records/categories?order=name,desc
GET /records/categories?order=id,desc&order=name

输出:

    {
        "records":[
            {
                "id": 3
                "name": "Web development"
            },
            {
                "id": 1
                "name": "Internet"
            }
        ]
    }

注意:您可以使用多个“订单”参数对多个字段进行排序。您不能在“加入”列上订购。

限制大小

“size”参数限制返回记录的数量。这可以与“order”参数一起用于前 N 个列表(使用降序)。

GET /records/categories?order=id,desc&size=1

输出:

    {
        "records":[
            {
                "id": 3
                "name": "Web development"
            }
        ]
    }

注意:如果您还想知道记录总数,您可能需要使用“page”参数。

分页

“page”参数保存请求的页面。默认页面大小为 20,但可以调整(例如调整为 50)。

GET /records/categories?order=id&page=1
GET /records/categories?order=id&page=1,50

输出:

    {
        "records":[
            {
                "id": 1
                "name": "Internet"
            },
            {
                "id": 3
                "name": "Web development"
            }
        ],
        "results": 2
    }

元素“results”保存表中的记录总数,如果不使用分页,则将返回。

注意:由于没有排序的页面不能分页,页面将按主键排序。

加入

假设您有一个包含评论(由用户发表)的帖子表,并且帖子可以有标签。

posts    comments  users     post_tags  tags
=======  ========  =======   =========  ======= 
id       id        id        id         id
title    post_id   username  post_id    name
content  user_id   phone     tag_id
created  message

当您想列出带有评论用户和标签的帖子时,您可以要求两个“树”路径:

posts -> comments  -> users
posts -> post_tags -> tags

这些路径具有相同的根,并且此请求可以用 URL 格式编写为:

GET /records/posts?join=comments,users&join=tags

在这里,您可以省略将帖子绑定到标签的中间表。在此示例中,您会看到所有三种表关系类型(hasMany、belongsTo 和 hasAndBelongsToMany)都有效:

  • “帖子”有很多“评论”
  • “评论”属于“用户”
  • “post”拥有并属于许多“标签”

这可能会导致以下 JSON 数据:

{
    "records":[
        {
            "id": 1,
            "title": "Hello world!",
            "content": "Welcome to the first post.",
            "created": "2018-03-05T20:12:56Z",
            "comments": [
                {
                    id: 1,
                    post_id: 1,
                    user_id: {
                        id: 1,
                        username: "mevdschee",
                        phone: null,
                    },
                    message: "Hi!"
                },
                {
                    id: 2,
                    post_id: 1,
                    user_id: {
                        id: 1,
                        username: "mevdschee",
                        phone: null,
                    },
                    message: "Hi again!"
                }
            ],
            "tags": []
        },
        {
            "id": 2,
            "title": "Black is the new red",
            "content": "This is the second post.",
            "created": "2018-03-06T21:34:01Z",
            "comments": [],
            "tags": [
                {
                    id: 1,
                    message: "Funny"
                },
                {
                    id: 2,
                    message: "Informational"
                }
            ]
        }
    ]
}

您会看到检测到“belongsTo”关系,并且外键值被引用的对象替换。在“hasMany”和“hasAndBelongsToMany”的情况下,表名用于对象的新属性。

批量操作

当您想要创建、读取、更新或删除时,您可以在 URL 中指定多个主键值。您还需要在请求正文中发送数组而不是对象以进行创建和更新。

要从此表中读取记录,可以将请求以 URL 格式编写为:

GET /records/posts/1,2

结果可能是:

[
        {
            "id": 1,
            "title": "Hello world!",
            "content": "Welcome to the first post.",
            "created": "2018-03-05T20:12:56Z"
        },
        {
            "id": 2,
            "title": "Black is the new red",
            "content": "This is the second post.",
            "created": "2018-03-06T21:34:01Z"
        }
]

同样,当您要进行批量更新时,URL 格式的请求写为:

PUT /records/posts/1,2

其中“1”和“2”是要更新的记录的主键值。正文应包含与 URL 中的主键相同数量的对象:

[   
    {
        "title": "Adjusted title for ID 1"
    },
    {
        "title": "Adjusted title for ID 2"
    }        
]

这会调整帖子的标题。返回值是设置的行数:

[1,1]

这意味着有两个更新操作,每个更新操作都设置了一行。批处理操作使用数据库事务,因此它们要么全部成功,要么全部失败(成功的会被退回)。如果它们失败,正文将包含错误文档列表。在以下响应中,第一个操作成功,而批处理的第二个操作由于完整性违规而失败:

[   
    {
        "code": 0,
        "message": "Success"
    },
    {
        "code": 1010,
        "message": "Data integrity violation"
    }
]

如果其中一个批处理操作失败,响应状态代码将始终为 424(失败的依赖项)。

空间支持

对于空间支持,有一组额外的过滤器可以应用于几何列并且以“s”开头:

  • “sco”:空间包含(几何包含另一个)
  • “scr”:空间交叉(几何交叉另一个)
  • “sdi”:空间不相交(几何与另一个不相交)
  • “seq”:空间相等(几何等于另一个)
  • “sin”:空间相交(几何与另一个相交)
  • “sov”:空间重叠(几何重叠另一个)
  • “sto”:空间接触(几何接触另一个)
  • “swi”:空间内(几何在另一个内)
  • “原文如此”:空间是封闭的(几何是封闭的和简单的)
  • “sis”:空间很简单(几何很简单)
  • “siv”:空间有效(几何有效)

这些过滤器基于 OGC 标准,表示几何列的 WKT 规范也是如此。

地理JSON

GeoJSON 支持是 GeoJSON 格式的表和记录的只读视图。支持这些请求:

method path                  - operation - description
----------------------------------------------------------------------------------------
GET    /geojson/{table}      - list      - lists records as a GeoJSON FeatureCollection
GET    /geojson/{table}/{id} - read      - reads a record by primary key as a GeoJSON Feature

“ /geojson”端点在内部使用“ /records”端点并继承所有功能,例如连接和过滤器。它还支持“几何”参数来指示几何列的名称,以防表格有多个。对于地图视图,它支持“bbox”参数,您可以在其中指定左上角和右下角坐标(逗号分隔)。GeoJSON 实现支持以下几何类型:

  • 观点
  • 多点
  • 线串
  • 多行字符串
  • 多边形
  • 多多边形

GeoJSON 功能默认启用,但可以使用“控制器”配置禁用。

中间件

您可以使用“middlewares”配置参数启用以下中间件:

  • “防火墙”:限制对特定 IP 地址的访问
  • “sslRedirect”:强制通过 HTTPS 而不是 HTTP 进行连接
  • “cors”:支持 CORS 请求(默认启用)
  • “xsrf”:使用“双重提交 Cookie”方法阻止 XSRF 攻击
  • “ajaxOnly”:限制非 AJAX 请求以防止 XSRF 攻击
  • “apiKeyAuth”:支持“API 密钥认证”
  • “apiKeyDbAuth”:支持“API 密钥数据库身份验证”
  • “dbAuth”:支持“数据库身份验证”
  • “jwtAuth”:支持“JWT 身份验证”
  • “basicAuth”:支持“基本身份验证”
  • "reconnect":用不同的参数重新连接数据库
  • “授权”:限制对某些表或列的访问
  • “validation”:返回自定义规则和默认类型规则的输入验证错误
  • “ipAddress”:用创建时的 IP 地址填充受保护的字段
  • “卫生”:在创建和更新时应用输入卫生
  • “multiTenancy”:在多租户场景中限制租户访问
  • “pageLimits”:限制列表操作以防止数据库抓取
  • “joinLimits”:限制连接参数以防止数据库抓取
  • “customization”:为请求和响应定制提供处理程序
  • “json”:支持 JSON 字符串作为 JSON 对象/数组的读/写
  • “xml”:将所有输入和输出从 JSON 转换为 XML

“中间件”配置参数是一个逗号分隔的已启用中间件列表。您可以使用中间件特定的配置参数调整中间件行为:

  • “firewall.reverseProxy”:使用反向代理时设置为“true”(“”)
  • “firewall.allowedIpAddresses”:允许连接的 IP 地址列表(“”)
  • “cors.allowedOrigins”:CORS 标头中允许的来源(“*”)
  • “cors.allowHeaders”:CORS 请求中允许的标头(“Content-Type, X-XSRF-TOKEN, X-Authorization”)
  • “cors.allowMethods”:CORS 请求中允许的方法(“OPTIONS、GET、PUT、POST、DELETE、PATCH”)
  • “cors.allowCredentials”:允许 CORS 请求中的凭据(“true”)
  • “cors.exposeHeaders”:允许浏览器访问的白名单标头(“”)
  • “cors.maxAge”:CORS 授权的有效时间,以秒为单位(“1728000”)
  • “xsrf.excludeMethods”:不需要XSRF保护的方法(“OPTIONS,GET”)
  • “xsrf.cookieName”:XSRF 保护 cookie 的名称(“XSRF-TOKEN”)
  • “xsrf.headerName”:XSRF 保护头的名称(“X-XSRF-TOKEN”)
  • “ajaxOnly.excludeMethods”:不需要AJAX的方法(“OPTIONS,GET”)
  • “ajaxOnly.headerName”:所需标头的名称(“X-Requested-With”)
  • “ajaxOnly.headerValue”:所需标头的值(“XMLHttpRequest”)
  • “apiKeyAuth.mode”:如果要允许匿名访问,请设置为“可选”(“必需”)
  • “apiKeyAuth.header”:API 密钥头的名称(“X-API-Key”)
  • "apiKeyAuth.keys":有效的 API 密钥列表 ("")
  • “apiKeyDbAuth.mode”:如果要允许匿名访问,请设置为“可选”(“必需”)
  • “apiKeyDbAuth.header”:API 密钥头的名称(“X-API-Key”)
  • “apiKeyDbAuth.usersTable”:用于存储用户的表(“users”)
  • “apiKeyDbAuth.apiKeyColumn”:保存 API 密钥(“api_key”)的用户表列
  • “dbAuth.mode”:如果要允许匿名访问,请设置为“可选”(“必需”)
  • “dbAuth.usersTable”:用于存储用户的表(“users”)
  • “dbAuth.usernameColumn”:保存用户名的用户表列(“用户名”)
  • “dbAuth.passwordColumn”:保存密码的用户表列(“password”)
  • “dbAuth.returnedColumns”:登录成功返回的列,为空表示“全部”(“”)
  • “dbAuth.usernameFormField”:保存用户名的表单字段的名称(“用户名”)
  • “dbAuth.passwordFormField”:保存密码的表单域的名称(“password”)
  • “dbAuth.newPasswordFormField”:保存新密码的表单域的名称(“newPassword”)
  • “dbAuth.registerUser”:JSON 用户数据(或“1”),以防您希望启用 /register 端点(“”)
  • “dbAuth.passwordLength”:密码必须具有的最小长度(“12”)
  • “dbAuth.sessionName”:启动的 PHP 会话的名称(“”)
  • “jwtAuth.mode”:如果要允许匿名访问,请设置为“可选”(“必需”)
  • “jwtAuth.header”:包含 JWT 令牌的标头名称(“X-Authorization”)
  • “jwtAuth.leeway”:可接受的时钟偏差秒数(“5”)
  • “jwtAuth.ttl”:令牌有效的秒数(“30”)
  • “jwtAuth.secrets”:用于签署 JWT 令牌的共享密钥(“”)
  • “jwtAuth.algorithms”:允许的算法,空表示“全部”(“”)
  • “jwtAuth.audiences”:允许的观众,为空表示“全部”(“”)
  • "jwtAuth.issuers": 允许的颁发者,空的意思是'all' ("")
  • “jwtAuth.sessionName”:启动的 PHP 会话的名称(“”)
  • “basicAuth.mode”:如果要允许匿名访问,请设置为“可选”(“必需”)
  • “basicAuth.realm”:显示登录时提示的文本(“需要用户名和密码”)
  • “basicAuth.passwordFile”:要读取的用户名/密码组合文件(“.htpasswd”)
  • “basicAuth.sessionName”:启动的 PHP 会话的名称(“”)
  • “reconnect.driverHandler”:实现检索数据库驱动程序(“”)的处理程序
  • “reconnect.addressHandler”:实现检索数据库地址(“”)的处理程序
  • “reconnect.portHandler”:实现检索数据库端口(“”)的处理程序
  • “reconnect.databaseHandler”:实现检索数据库名称(“”)的处理程序
  • “reconnect.tablesHandler”:实现表名(“”)检索的处理程序
  • “reconnect.usernameHandler”:实现检索数据库用户名(“”)的处理程序
  • “reconnect.passwordHandler”:实现检索数据库密码(“”)的处理程序
  • “authorization.tableHandler”:实现表授权规则的处理程序(“”)
  • “authorization.columnHandler”:实现列授权规则的处理程序(“”)
  • “authorization.pathHandler”:实现路径授权规则的处理程序(“”)
  • “authorization.recordHandler”:实现记录授权过滤规则的处理程序(“”)
  • “validation.handler”:实现输入值验证规则的处理程序(“”)
  • “validation.types”:启用类型验证的类型,空表示“无”(“全部”)
  • “validation.tables”:启用类型验证的表,空表示“无”(“全部”)
  • “ipAddress.tables”:用于搜索要使用 IP 地址(“”)覆盖的列的表
  • “ipAddress.columns”:在创建时使用 IP 地址保护和覆盖的列(“”)
  • “sanitation.handler”:处理输入值的卫生规则(“”)
  • “sanitation.types”:启用类型清理的类型,空表示“无”(“全部”)
  • “sanitation.tables”:启用类型清理的表,空表示“无”(“全部”)
  • “multiTenancy.handler”:实现简单多租户规则的处理程序(“”)
  • “pageLimits.pages”:列表操作允许的最大页码(“100”)
  • “pageLimits.records”:列表操作返回的最大记录数(“1000”)
  • “joinLimits.depth”:连接路径中允许的最大深度(长度)(“3”)
  • “joinLimits.tables”:允许加入的最大表数(“10”)
  • “joinLimits.records”:为连接实体返回的最大记录数(“1000”)
  • “customization.beforeHandler”:实现请求定制的处理程序(“”)
  • “customization.afterHandler”:实现响应定制的处理程序(“”)
  • “json.controllers”:处理 JSON 字符串的控制器(“records,geojson”)
  • “json.tables”:为(“all”)处理 JSON 字符串的表
  • “json.columns”:为(“all”)处理 JSON 字符串的列
  • “xml.types”:应添加到 XML 类型属性中的 JSON 类型(“null,array”)

如果您未在配置中指定这些参数,则使用默认值(括号之间)。

在下面的部分中,您可以找到有关内置中间件的更多信息。

验证

当前支持五种类型的身份验证。它们都将经过身份验证的用户存储在$_SESSION超级全局中。这个变量可以在授权处理程序中使用来决定sombeody是否应该对某些表、列或记录具有读或写访问权限。以下概述显示了您可以启用的身份验证中间件的种类。

姓名中间件通过身份验证用户存储在会话变量
API 密钥apiKeyAuth“X-API-Key”标头配置$_SESSION['apiKey']
API 密钥数据库apiKeyDbAuth“X-API-Key”标头数据库表$_SESSION['apiUser']
数据库数据库认证'/login' 端点数据库表$_SESSION['user']
基本的基本认证“授权”标头'.htpasswd' 文件$_SESSION['username']
智威汤逊jwtAuth“授权”标头身份提供者$_SESSION['claims']

您可以在下面找到有关每种身份验证类型的更多信息。

API 密钥认证

API 密钥身份验证通过在请求标头中发送 API 密钥来工作。标头名称默认为“X-API-Key”,可以使用“apiKeyAuth.header”配置参数进行配置。必须使用“apiKeyAuth.keys”配置参数(逗号分隔列表)配置有效的 API 密钥。

X-API-Key: 02c042aa-c3c2-4d11-9dae-1a6e230ea95e

经过身份验证的 API 密钥将存储在$_SESSION['apiKey']变量中。

请注意,API 密钥身份验证不需要或使用会话 cookie。

API密钥数据库认证

API 密钥数据库身份验证通过在请求标头“X-API-Key”(名称可配置)中发送 API 密钥来工作。从“users”表的“api_key”列从数据库中读取有效的 API 密钥(两个名称都是可配置的)。

X-API-Key: 02c042aa-c3c2-4d11-9dae-1a6e230ea95e

经过身份验证的用户(及其所有属性)将存储在$_SESSION['apiUser']变量中。

请注意,API 密钥数据库身份验证不需要或使用会话 cookie。

数据库认证

数据库身份验证中间件定义了五个新路由:

method path       - parameters                      - description
---------------------------------------------------------------------------------------------------
GET    /me        -                                 - returns the user that is currently logged in
POST   /register  - username, password              - adds a user with given username and password
POST   /login     - username, password              - logs a user in by username and password
POST   /password  - username, password, newPassword - updates the password of the logged in user
POST   /logout    -                                 - logs out the currently logged in user

用户可以通过将用户名和密码发送到登录端点(JSON 格式)来登录。经过身份验证的用户(及其所有属性)将存储在$_SESSION['user']变量中。可以通过向注销端点发送带有空正文的 POST 请求来注销用户。密码作为哈希值存储在用户表的密码列中。您可以使用注册端点注册新用户,但必须使用“dbAuth.registerUser”配置参数打开此功能。

重要的是使用“授权”中间件限制对用户表的访问,否则所有用户都可以自由添加、修改或删除任何帐户!最低配置如下所示:

'middlewares' => 'dbAuth,authorization',
'authorization.tableHandler' => function ($operation, $tableName) {
    return $tableName != 'users';
},

请注意,此中间件使用会话 cookie 并将登录状态存储在服务器上。

基本认证

Basic 类型支持一个文件(默认为 '.htpasswd'),该文件保存用户及其(散列)密码,由冒号 (':') 分隔。当密码以纯文本形式输入时,它们将被自动散列。经过身份验证的用户名将存储在$_SESSION['username']变量中。您需要在“基本”一词之后发送一个“授权”标头,其中包含冒号分隔的用户名和密码的 base64 url​​ 编码版本。

Authorization: Basic dXNlcm5hbWUxOnBhc3N3b3JkMQ

此示例发送字符串“username1:password1”。

智威汤逊认证

JWT 类型需要另一个 (SSO/Identity) 服务器来签署包含声明的令牌。两个服务器共享一个秘密,以便它们可以签名或验证签名是否有效。声明存储在$_SESSION['claims']变量中。您需要在“Bearer”一词之后发送一个“X-Authorization”标头,其中包含 base64 url​​ 编码和点分隔的令牌标头、正文和签名(在此处阅读有关 JWT的更多信息)。该标准说您需要使用“授权”标头,但这在 Apache 和 PHP 中是有问题的。

X-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6IjE1MzgyMDc2MDUiLCJleHAiOjE1MzgyMDc2MzV9.Z5px_GT15TRKhJCTHhDt5Z6K6LRDSFnLj8U5ok9l7gw

此示例发送已签名的声明:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": "1538207605",
  "exp": 1538207635
}

注意:JWT 实现仅支持基于 RSA 和 HMAC 的算法。

使用 Auth0 配置和测试 JWT 身份验证

首先,您需要在Auth0上创建一个帐户。登录后,您必须创建一个应用程序(其类型无关紧要)。收集Domain 并Client ID保留它们以备后用。然后,创建一个 API:给它一个名称并 identifier使用您的 API 端点的 URL 填充该字段。

然后你必须jwtAuth.secrets在你的api.php文件中配置配置。不要填写secret您将在 Auth0 应用程序设置中找到的内容,而是使用公共证书。要找到它,请转到您的应用程序的设置,然后在“额外设置”中。您现在将找到一个“证书”选项卡,您可以在其中找到您在签名证书字段中的公钥。

要测试您的集成,您可以复制auth0/vanilla.html 文件。请务必填写以下三个变量:

  • authUrl使用您的 Auth0 域
  • clientId使用您的客户 ID
  • audience使用您在 Auth0 中创建的 API URL

⚠️如果您不填写受众参数,它将无法正常工作,因为您将无法获得有效的 JWT。

您还可以更改url用于通过身份验证测试 API 的变量。

更多信息

使用 Firebase 配置和测试 JWT 身份验证

首先,您需要在Firebase 控制台上创建一个 Firebase 项目。向该项目添加一个 Web 应用程序并获取代码片段以供以后使用。

然后你必须jwtAuth.secrets在你的api.php文件中配置配置。这可以按如下方式完成:

一种。将用户登录到基于 Firebase 的应用程序,获取该用户的身份验证令牌 b。转到JSON Web Tokens - jwt.io并将令牌粘贴到解码字段 c。从令牌中读取解码后的标头信息,它将为您提供正确的kid d. 通过此URL获取公钥,该URL对应于您kid在上一步 e 中的操作。现在,只需jwtAuth.secretsapi.php

以下是它在配置中应该是什么样子的示例:

...,
'middlewares' => 'cors, jwtAuth, authorization',
        'jwtAuth.secrets' => "ce5ced6e40dcd1eff407048867b1ed1e706686a0:-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIExun9bJSK1wwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTkx\nMjIyMjEyMTA3WhcNMjAwMTA4MDkzNjA3WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKsvVDUwXeYQtySNvyI1/tZAk0sj7Zx4/1+YLUomwlK6vmEd\nyl2IXOYOj3VR7FBA24A9//nnrp+mV8YOYEOdaWX7PQo0PIPFPqdA0r7CqBUWHPfQ\n1WVHVRQY3G0c7upM97UfMes9xOrMqyvecMRk1e5S6eT12Zh2og7yiVs8gP83M1EB\nGqseUaltaadjyT35w5B0Ny0/7NdLYiv2G6Z0S821SxvSo1/wfmilnBBKYYluP0PA\n9NPznWFP6uXnX7gKxyJT9//cYVxTO6+b1TT13Yvrpm1a4EuCOhLrZH6ErHQTccAM\nhAx8mdNtbROsp0dlPKrSfqO82uFz45RXZYmSeP0CAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBACNsJ5m00gdTvD6j6ahURsGrNZ0VJ0YREVQ5U2Jtubr8\nn2fuhMxkB8147ISzfi6wZR+yNwPGjlr8JkAHAC0i+Nam9SqRyfZLqsm+tHdgFT8h\npa+R/FoGrrLzxJNRiv0Trip8hZjgz3PClz6KxBQzqL+rfGV2MbwTXuBoEvLU1mYA\no3/UboJT7cNGjZ8nHXeoKMsec1/H55lUdconbTm5iMU1sTDf+3StGYzTwC+H6yc2\nY3zIq3/cQUCrETkALrqzyCnLjRrLYZu36ITOaKUbtmZhwrP99i2f+H4Ab2i8jeMu\nk61HD29mROYjl95Mko2BxL+76To7+pmn73U9auT+xfA=\n-----END CERTIFICATE-----\n",
        'cors.allowedOrigins' => '*',
        'cors.allowHeaders' => 'X-Authorization'

笔记:

  • kid:key对被格式化为字符串
  • 不要在“:”之前或之后包含空格
  • 在字符串文本周围使用双引号 (")
  • 字符串必须包含换行符 (\n)

要测试您的集成,您可以复制firebase/vanilla.html 文件和firebase /vanilla-success.html文件,用作“成功”页面并显示 API 结果。

在两个文件中替换 Firebase 配置(firebaseConfig对象)。

您还可以更改url用于通过身份验证测试 API 的变量。

更多信息

授权操作

授权模型作用于“操作”。这里列出了最重要的一些:

method path                  - operation - description
----------------------------------------------------------------------------------------
GET    /records/{table}      - list      - lists records
POST   /records/{table}      - create    - creates records
GET    /records/{table}/{id} - read      - reads a record by primary key
PUT    /records/{table}/{id} - update    - updates columns of a record by primary key
DELETE /records/{table}/{id} - delete    - deletes a record by primary key
PATCH  /records/{table}/{id} - increment - increments columns of a record by primary key

/openapi" 端点只会显示会话中允许的内容。它还有一个特殊的“文档”操作,允许您从文档中隐藏表格和列。

对于以“”开头的端点,/columns有“反射”和“重塑”操作。这些操作可以显示或更改数据库、表或列的定义。默认情况下禁用此功能,并且有充分的理由(小心!)。在配置中添加“列”控制器以启用此功能。

授权表、列和记录

默认情况下,所有表、列和路径都是可访问的。如果您想限制对某些表的访问,您可以添加“授权”中间件并定义一个“authorization.tableHandler”函数,该函数为这些表返回“false”。

'authorization.tableHandler' => function ($operation, $tableName) {
    return $tableName != 'license_keys';
},

上面的示例将限制所有操作对表“license_keys”的访问。

'authorization.columnHandler' => function ($operation, $tableName, $columnName) {
    return !($tableName == 'users' && $columnName == 'password');
},

上面的示例将限制对所有操作的“用户”表的“密码”字段的访问。

'authorization.recordHandler' => function ($operation, $tableName) {
    return ($tableName == 'users') ? 'filter=username,neq,admin' : '';
},

上面的示例将禁止访问用户名为“admin”的用户记录。此构造为每个执行的查询添加一个过滤器。

'authorization.pathHandler' => function ($path) {
    return $path === 'openapi' ? false : true;
},

上面的示例将禁用/openapi路由。

注意:您需要使用验证(或卫生)处理程序来处理无效记录的创建。

SQL GRANT 授权

您也可以使用数据库权限(SQL GRANT 语句)来定义授权模型。在这种情况下,您不应该使用“授权”中间件,但确实需要使用“重新连接”中间件。“重新连接”中间件的处理程序允许您指定正确的用户名和密码,如下所示:

'reconnect.usernameHandler' => function () {
    return 'mevdschee';
},
'reconnect.passwordHandler' => function () {
    return 'secret123';
},

这将使 API 连接到指定“mevdschee”作为用户名和“secret123”作为密码的数据库。当您使用数据库权限时,OpenAPI 规范对允许和不允许的操作不太具体,因为在反射步骤中不会读取权限。

注意:您可能希望从会话中检索用户名和密码(“$_SESSION”变量)。

消毒输入

默认情况下,所有输入都被接受并发送到数据库。如果您想在存储之前去除(某些)HTML 标签,您可以添加“sanitation”中间件并定义一个“sanitation.handler”函数来返回调整后的值。

'sanitation.handler' => function ($operation, $tableName, $column, $value) {
    return is_string($value) ? strip_tags($value) : $value;
},

上面的示例将从输入中的字符串中删除所有 HTML 标记。

类型卫生

如果您启用“卫生”中间件,那么您(自动)也启用类型卫生。启用此功能后,您可以:

  • 在非字符字段中发送前导和尾随空格(它将被忽略)。
  • 将浮点数发送到整数或 bigint 字段(将被舍入)。
  • 发送一个 base64url 编码的字符串(它将被转换为常规的 base64 编码)。
  • 以任何strtotime 接受的格式发送时间/日期/时间戳(它将被转换)。

您可以使用配置设置 " sanitation.types" 和 " sanitation.tables"' 来定义要在哪些类型和表中应用类型清理(默认为“全部”)。例子:

'sanitation.types' => 'date,timestamp',
'sanitation.tables' => 'posts,comments',

在这里,我们为帖子和评论表中的日期和时间戳字段启用类型清理。

验证输入

默认情况下,所有输入都被接受并发送到数据库。如果您想以自定义方式验证输入,您可以添加“validation”中间件并定义一个“validation.handler”函数,该函数返回一个布尔值,指示该值是否有效。

'validation.handler' => function ($operation, $tableName, $column, $value, $context) {
    return ($column['name'] == 'post_id' && !is_numeric($value)) ? 'must be numeric' : true;
},

当您使用 id 4 编辑评论时:

PUT /records/comments/4

你作为一个身体发送:

{"post_id":"two"}

然后服务器会返回一个 '422' HTTP 状态码和漂亮的错误信息:

{
    "code": 1013,
    "message": "Input validation failed for 'comments'",
    "details": {
        "post_id":"must be numeric"
    }
}

您可以解析此输出以使表单字段显示为红色边框及其相应的错误消息。

类型验证

如果您启用“验证”中间件,那么您(自动)也启用类型验证。这包括以下错误消息:

错误信息原因适用于类型
不能为空意外的空值(任何不可为空的列)
非法空格前导/尾随空格整数 bigint 十进制 float double 布尔值
无效整数非法字符整数大整数
字符串太长字符太多varchar varbinary
无效小数非法字符十进制
小数太大点之前的数字太多十进制
小数太精确点后数字太多十进制
无效的浮动非法字符浮动双
无效的布尔值使用 1、0、真或假布尔值
失效日期使用 yyyy-mm-dd日期
无效时间使用 hh:mm:ss时间
无效的时间戳使用 yyyy-mm-dd hh:mm:ss时间戳
无效的 base64非法字符varbinary,斑点

您可以使用配置设置“ validation.types”和“ validation.tables”'来定义要应用类型验证的类型和表(默认为“全部”)。例子:

'validation.types' => 'date,timestamp',
'validation.tables' => 'posts,comments',

在这里,我们为帖子和评论表中的日期和时间戳字段启用类型验证。

注意:当列不可为空时,将检查启用的类型是否存在空值。

多租户支持

支持两种形式的多租户:

  • 单一数据库,其中每个表都有一个租户列(使用“multiTenancy”中间件)。
  • 多数据库,每个租户都有自己的数据库(使用“重新连接”中间件)。

下面是相应中间件的解释。

多租户中间件

当您有一个多租户数据库时,您可以使用“multiTenancy”中间件。如果您的租户由“customer_id”列标识,那么您可以使用以下处理程序:

'multiTenancy.handler' => function ($operation, $tableName) {
    return ['customer_id' => 12];
},

此构造为每个操作添加了一个过滤器,要求“customer_id”为“12”(“create”除外)。它还将“create”上的“customer_id”列设置为“12”,并从任何其他写入操作中删除该列。

注意:您可能希望从会话中检索客户 ID(“$_SESSION”变量)。

重新连接中间件

当每个租户都有单独的数据库时,您可以使用“重新连接”中间件。如果租户有自己的名为“customer_12”的数据库,那么您可以使用以下处理程序:

'reconnect.databaseHandler' => function () {
    return 'customer_12';
},

这将使 API 重新连接到指定“customer_12”作为数据库名称的数据库。如果您不想使用相同的凭据,那么您还应该实现“usernameHandler”和“passwordHandler”。

注意:您可能希望从会话中检索数据库名称(“$_SESSION”变量)。

防止数据库抓取

您可以使用“joinLimits”和“pageLimits”中间件来防止数据库抓取。“joinLimits”中间件限制表深度、表数和连接操作中返回的记录数。如果要允许 5 个直接直接连接,每个连接最多 25 条记录,您可以指定:

'joinLimits.depth' => 1,
'joinLimits.tables' => 5,
'joinLimits.records' => 25,

“pageLimits”中间件限制页码和列表操作返回的记录数。如果您希望允许不超过 10 个页面,每个页面最多 25 条记录,您可以指定:

'pageLimits.pages' => 10,
'pageLimits.records' => 25,

注意:当请求中没有指定页码时,也会应用最大记录数。

自定义处理程序

您可以使用“自定义”中间件来修改请求和响应并实现任何其他功能。

'customization.beforeHandler' => function ($operation, $tableName, $request, $environment) {
    $environment->start = microtime(true);
},
'customization.afterHandler' => function ($operation, $tableName, $response, $environment) {
    return $response->withHeader('X-Time-Taken', microtime(true) - $environment->start);
},

上面的示例将添加一个标头“X-Time-Taken”,其中包含 API 调用所用的秒数。

JSON 中间件

您可以使用“json”中间件来读取/写入 JSON 字符串作为 JSON 对象和数组。

启用“json”中间件时会自动检测 JSON 字符串。

您可以通过指定特定的表和/或字段名称来限制扫描:

'json.tables' => 'products',
'json.columns' => 'properties',

这将改变以下输出:

GET /records/products/1

如果没有“json”中间件,输出将是:

{
    "id": 1,
    "name": "Calculator",
    "price": "23.01",
    "properties": "{\"depth\":false,\"model\":\"TRX-120\",\"width\":100,\"height\":null}",
}

使用“json”中间件,输出将是:

{
    "id": 1,
    "name": "Calculator",
    "price": "23.01",
    "properties": {
        "depth": false,
        "model": "TRX-120",
        "width": 100,
        "height": null
    },
}

这也适用于创建或修改 JSON 字符串字段(也适用于使用批处理操作)。

请注意,JSON 字符串字段不能部分更新,并且默认情况下禁用此中间件。您可以使用“中间件”配置设置启用“json”中间件。

XML 中间件

您可以使用“xml”中间件将输入和输出从 JSON 转换为 XML。这个请求:

GET /records/posts/1

输出(当“漂亮打印”时):

{
    "id": 1,
    "user_id": 1,
    "category_id": 1,
    "content": "blog started"
}

而(注意“格式”查询参数):

GET /records/posts/1?format=xml

输出:

<root>
    <id>1</id>
    <user_id>1</user_id>
    <category_id>1</category_id>
    <content>blog started</content>
</root>

此功能默认禁用,必须使用“中间件”配置设置启用。

文件上传

通过FileReader API支持文件上传,请查看示例

OpenAPI 规范

在“/openapi”端点上提供 OpenAPI 3.0(以前称为“Swagger”)规范。它是您的 API 的机器可读即时文档。要了解更多信息,请查看以下链接:

缓存

有 4 个缓存引擎可以通过“cacheType”配置参数进行配置:

  • 临时文件(默认)
  • 雷迪斯
  • 内存缓存
  • 内存缓存

您可以通过运行以下命令安装最后三个引擎的依赖项:

sudo apt install php-redis redis
sudo apt install php-memcache memcached
sudo apt install php-memcached memcached

默认引擎没有依赖关系,将使用系统“temp”路径中的临时文件。

您可以使用“cachePath”配置参数来指定临时文件的文件系统路径,或者如果您使用非默认的“cacheType”缓存服务器的主机名(可选地带有端口)。

类型

这些是受支持的类型及其长度、类别、JSON 类型和格式:

类型长度类别JSON 类型格式
varchar255特点细绳
特点细绳
布尔值布尔值布尔值
整数整数数字
大整数整数数字
漂浮漂浮数字
双倍的漂浮数字
十进制19,4十进制细绳
日期约会时间细绳yyyy-mm-dd
时间约会时间细绳hh:mm:ss
时间戳约会时间细绳yyyy-mm-dd hh:mm:ss
变量二进制255二进制细绳base64 编码
斑点二进制细绳base64 编码
几何学其他细绳众所周知的文字

请注意,几何是非 jdbc 类型,因此支持有限。

JavaScript 中的数据类型

Javascript 和 Javascript 对象表示法 (JSON) 不太适合读取数据库记录。十进制、日期/时间、二进制和几何类型必须表示为 JSON 中的字符串(二进制是 base64 编码,几何是 WKT 格式)。下面描述了两个更严重的问题。

64 位整数

JavaScript 不支持 64 位整数。所有数字都存储为 64 位浮点值。64 位浮点数的尾数只有 53 位,这就是为什么所有大于 53 位的整数都可能导致 JavaScript 出现问题。

Inf 和 NaN 浮点数

有效的浮点值“无限”(用“1/0”计算)和“非数字”(用“0/0”计算)不能用 JSON 表示,因为它们不受JSON 规范的支持。当这些值存储在数据库中时,您无法读取它们,因为此脚本将数据库记录输出为 JSON。

错误

可能会报告以下错误:

错误HTTP 响应代码信息
1000404 未找到找不到路线
1001404 未找到找不到表
1002422 无法处理的实体参数计数不匹配
1003404 未找到记录不存在
1004403 禁止禁止出处
1005404 未找到未找到列
1006409 冲突表已存在
1007409 冲突列已存在
1008422 无法处理的实体无法读取 HTTP 消息
1009409 冲突重复键异常
1010409 冲突数据完整性违规
1011401未经授权需要身份验证
1012403 禁止身份验证失败
1013422 无法处理的实体输入验证失败
1014403 禁止禁止操作
1015405 方法不允许不支持操作
1016403 禁止暂时或永久封锁
1017403 禁止错误或缺少 XSRF 令牌
1018403 禁止只允许 AJAX 请求
1019403 禁止禁止分页
9999500内部服务器错误未知错误

使用以下 JSON 结构:

{
    "code":1002,
    "message":"Argument count mismatch in '1'"
}

注意:任何非错误响应都会有状态:200 OK

状态

要连接到您的监控,有一个“ping”端点:

GET /status/ping

这应该返回状态 200 并作为数据:

{
    "db": 42,
    "cache": 9
}

这些可用于测量连接和从数据库和缓存读取数据的时间(以微秒为单位)。

自定义控制器

您可以通过编写自己的自定义控制器类来添加自己的自定义 REST API 端点。该类必须提供一个接受五个参数的构造函数。使用这些参数,您可以将自己的端点注册到现有路由器。该端点可以使用数据库和/或数据库的反射类。

这是一个自定义控制器类的示例:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Tqdev\PhpCrudApi\Cache\Cache;
use Tqdev\PhpCrudApi\Column\ReflectionService;
use Tqdev\PhpCrudApi\Controller\Responder;
use Tqdev\PhpCrudApi\Database\GenericDB;
use Tqdev\PhpCrudApi\Middleware\Router\Router;

class MyHelloController {

    private $responder;

    public function __construct(Router $router, Responder $responder, GenericDB $db, ReflectionService $reflection, Cache $cache)
    {
        $router->register('GET', '/hello', array($this, 'getHello'));
        $this->responder = $responder;
    }

    public function getHello(ServerRequestInterface $request): ResponseInterface
    {
        return $this->responder->success(['message' => "Hello World!"]);
    }
}

然后你可以像这样在配置对象中注册你的自定义控制器类:

$config = new Config([
    ...
    'customControllers' => 'MyHelloController',
    ...
]);

customControllers配置支持以逗号分隔的自定义控制器类列表。

测试

我主要在 Ubuntu 上进行测试,我有以下测试设置:

  • (Docker) Ubuntu 16.04 和 PHP 7.0、MariaDB 10.0、PostgreSQL 9.5 (PostGIS 2.2) 和 SQL Server 2017
  • (Docker) Debian 9 与 PHP 7.0、MariaDB 10.1、PostgreSQL 9.6 (PostGIS 2.3) 和 SQLite 3.16
  • (Docker) Ubuntu 18.04 和 PHP 7.2、MySQL 5.7、PostgreSQL 10.4 (PostGIS 2.4) 和 SQLite 3.22
  • (Docker) Debian 10 与 PHP 7.3、MariaDB 10.3、PostgreSQL 11.4 (PostGIS 2.5) 和 SQLite 3.27
  • (Docker) Ubuntu 20.04 和 PHP 7.4、MySQL 8.0、PostgreSQL 12.2 (PostGIS 3.0) 和 SQLite 3.31
  • (Docker) CentOS 8 与 PHP 8.1、MariaDB 10.6、PostgreSQL 12.8 (PostGIS 3.0) 和 SQLite 3.26
  • (Docker) Debian 11 与 PHP 7.4、MariaDB 10.5、PostgreSQL 13.4 (PostGIS 3.1) 和 SQLite 3.34

这尚未涵盖所有环境,因此请通知我测试失败并报告您的环境。我将尝试在项目的“docker”文件夹中介绍最相关的设置。

跑步

要在本地运行功能测试,您可以运行以下命令:

php build.php
php test.php

这会从“tests”目录运行功能测试。它使用相应子目录中的数据库转储(fixtures)和数据库配置(config)。

Nginx 配置示例

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;
    index index.php index.html index.htm index.nginx-debian.html;
    server_name server_domain_or_IP;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        try_files $fastcgi_script_name =404;
        set $path_info $fastcgi_path_info;
        fastcgi_param PATH_INFO $path_info;
        fastcgi_index index.php;
        include fastcgi.conf;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    }

    location ~ /\.ht {
        deny all;
    }
}

Docker 测试

使用以下命令安装 docker,然后注销并登录以使更改生效:

sudo apt install docker.io
sudo usermod -aG docker ${USER}

要运行 docker 测试,请从 docker 目录运行“build_all.sh”和“run_all.sh”。输出应该是:

================================================
CentOS 8 (PHP 8.1)
================================================
[1/4] Starting MariaDB 10.6 ..... done
[2/4] Starting PostgreSQL 12.8 .. done
[3/4] Starting SQLServer 2017 ... skipped
[4/4] Cloning PHP-CRUD-API v2 ... skipped
------------------------------------------------
mysql: 117 tests ran in 1336 ms, 1 skipped, 0 failed
pgsql: 117 tests ran in 1316 ms, 1 skipped, 0 failed
sqlsrv: skipped, driver not loaded
sqlite: 117 tests ran in 958 ms, 13 skipped, 0 failed
================================================
Debian 10 (PHP 7.3)
================================================
[1/4] Starting MariaDB 10.3 ..... done
[2/4] Starting PostgreSQL 11.4 .. done
[3/4] Starting SQLServer 2017 ... skipped
[4/4] Cloning PHP-CRUD-API v2 ... skipped
------------------------------------------------
mysql: 117 tests ran in 1276 ms, 1 skipped, 0 failed
pgsql: 117 tests ran in 1364 ms, 1 skipped, 0 failed
sqlsrv: skipped, driver not loaded
sqlite: 117 tests ran in 948 ms, 13 skipped, 0 failed
================================================
Debian 11 (PHP 7.4)
================================================
[1/4] Starting MariaDB 10.5 ..... done
[2/4] Starting PostgreSQL 13.4 .. done
[3/4] Starting SQLServer 2017 ... skipped
[4/4] Cloning PHP-CRUD-API v2 ... skipped
------------------------------------------------
mysql: 117 tests ran in 1255 ms, 1 skipped, 0 failed
pgsql: 117 tests ran in 1329 ms, 1 skipped, 0 failed
sqlsrv: skipped, driver not loaded
sqlite: 117 tests ran in 972 ms, 13 skipped, 0 failed
================================================
Debian 9 (PHP 7.0)
================================================
[1/4] Starting MariaDB 10.1 ..... done
[2/4] Starting PostgreSQL 9.6 ... done
[3/4] Starting SQLServer 2017 ... skipped
[4/4] Cloning PHP-CRUD-API v2 ... skipped
------------------------------------------------
mysql: 117 tests ran in 1475 ms, 1 skipped, 0 failed
pgsql: 117 tests ran in 1394 ms, 1 skipped, 0 failed
sqlsrv: skipped, driver not loaded
sqlite: 117 tests ran in 1065 ms, 13 skipped, 0 failed
================================================
Ubuntu 16.04 (PHP 7.0)
================================================
[1/4] Starting MariaDB 10.0 ..... done
[2/4] Starting PostgreSQL 9.5 ... done
[3/4] Starting SQLServer 2017 ... done
[4/4] Cloning PHP-CRUD-API v2 ... skipped
------------------------------------------------
mysql: 117 tests ran in 1434 ms, 1 skipped, 0 failed
pgsql: 117 tests ran in 1432 ms, 1 skipped, 0 failed
sqlsrv: 117 tests ran in 8634 ms, 1 skipped, 0 failed
sqlite: skipped, driver not loaded
================================================
Ubuntu 18.04 (PHP 7.2)
================================================
[1/4] Starting MySQL 5.7 ........ done
[2/4] Starting PostgreSQL 10.4 .. done
[3/4] Starting SQLServer 2017 ... skipped
[4/4] Cloning PHP-CRUD-API v2 ... skipped
------------------------------------------------
mysql: 117 tests ran in 1687 ms, 1 skipped, 0 failed
pgsql: 117 tests ran in 1393 ms, 1 skipped, 0 failed
sqlsrv: skipped, driver not loaded
sqlite: 117 tests ran in 1158 ms, 13 skipped, 0 failed
================================================
Ubuntu 20.04 (PHP 7.4)
================================================
[1/4] Starting MySQL 8.0 ........ done
[2/4] Starting PostgreSQL 12.2 .. done
[3/4] Starting SQLServer 2019 ... done
[4/4] Cloning PHP-CRUD-API v2 ... skipped
------------------------------------------------
mysql: 117 tests ran in 2096 ms, 1 skipped, 0 failed
pgsql: 117 tests ran in 1368 ms, 1 skipped, 0 failed
sqlsrv: 117 tests ran in 8410 ms, 1 skipped, 0 failed
sqlite: 117 tests ran in 1053 ms, 13 skipped, 0 failed

在我的慢速笔记本电脑上,上述测试运行(包括启动数据库)用时不到 5 分钟。

$ ./run.sh
1) centos8
2) debian10
3) debian11
4) debian9
5) ubuntu16
6) ubuntu18
7) ubuntu20
> 6
================================================
Ubuntu 18.04 (PHP 7.2)
================================================
[1/4] Starting MySQL 5.7 ........ done
[2/4] Starting PostgreSQL 10.4 .. done
[3/4] Starting SQLServer 2017 ... skipped
[4/4] Cloning PHP-CRUD-API v2 ... skipped
------------------------------------------------
mysql: 117 tests ran in 1687 ms, 1 skipped, 0 failed
pgsql: 117 tests ran in 1393 ms, 1 skipped, 0 failed
sqlsrv: skipped, driver not loaded
sqlite: 117 tests ran in 1158 ms, 13 skipped, 0 failed
root@b7ab9472e08f:/php-crud-api# 

如您所见,“run.sh”脚本使您可以在选定的 docker 环境中访问提示。在这个环境中,本地文件被挂载。这允许在不同的环境中轻松调试。完成后,您可以键入“exit”。

Docker 镜像

存储库中有一个Dockerfile用于在以下位置构建图像:

Docker Hub

它将在每个版本上自动构建。“最新”标签指向最后一个版本。

docker 镜像接受配置中的环境变量参数。

码头工人撰写

此存储库还包含一个docker-compose.yml文件,您可以使用以下命令安装/构建/运行:

sudo apt install docker-compose
docker-compose build
docker-compose up

这将设置一个数据库 (MySQL) 和一个网络服务器 (Apache),并使用测试中使用的博客示例数据运行应用程序。

通过打开以下 URL 测试脚本(在容器中运行):

http://localhost:8080/records/posts/1

享受!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐