惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Recorded Future
Recorded Future
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
S
SegmentFault 最新的问题
博客园 - 司徒正美
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
博客园 - 叶小钗
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
腾讯CDC
博客园 - 聂微东
D
DataBreaches.Net
博客园 - Franky
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
量子位
宝玉的分享
宝玉的分享
D
Docker
T
Tailwind CSS Blog
IT之家
IT之家
Engineering at Meta
Engineering at Meta
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Scott Helme
Scott Helme
Project Zero
Project Zero
Microsoft Azure Blog
Microsoft Azure Blog
AWS News Blog
AWS News Blog
Google DeepMind News
Google DeepMind News
H
Heimdal Security Blog
W
WeLiveSecurity
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
有赞技术团队
有赞技术团队
Simon Willison's Weblog
Simon Willison's Weblog
NISL@THU
NISL@THU
C
Cybersecurity and Infrastructure Security Agency CISA
Google DeepMind News
Google DeepMind News
T
Threatpost
TaoSecurity Blog
TaoSecurity Blog
N
News and Events Feed by Topic
aimingoo的专栏
aimingoo的专栏
Recent Commits to openclaw:main
Recent Commits to openclaw:main
www.infosecurity-magazine.com
www.infosecurity-magazine.com
SecWiki News
SecWiki News
S
Securelist

竹林里有冰的博客

Nuxt SSG 博客的尾斜杠到底怎么加? | 竹林里有冰的博客 小米 Xiaomi Book Pro 14 (Ultra X7) Linux 兼容性实测 | 竹林里有冰的博客 国内(大陆)版小米 FCM 熄屏断连:Rootless 环境下的尝试与可能的解决方案 | 竹林里有冰的博客 我没法访问 dl.google.com —— 记一次 TUN 下的网络 debug | 竹林里有冰的博客 Vercel 的缓存控制,你注意过吗? | 竹林里有冰的博客 小记 —— Caddy 在 Layer 4 上的流量代理实践 | 竹林里有冰的博客 你的域名后缀拖慢你的网站速度了嘛?——再谈 DNS 冷启动 | 竹林里有冰的博客 DNS 冷启动:小型站点的“西西弗斯之石” | 竹林里有冰的博客 HTTP/2 Server Push 已事实性“死亡”,我很怀念它 | 竹林里有冰的博客 Nuxt Content v3 中数组字段的筛选困境与性能优化 | 竹林里有冰的博客 后 OCSP 时代,浏览器如何应对证书吊销新挑战 | 竹林里有冰的博客 初试 Github Action Self-hosted Runner,想说爱你不容易 | 竹林里有冰的博客 DNS 解析延迟毁了我的图床优化 | 竹林里有冰的博客 Vue Markdown 渲染优化实战(下):告别 DOM 操作,拥抱 AST 与函数式渲染 | 竹林里有冰的博客 Vue Markdown 渲染优化实战(上):从暴力刷新、分块更新到 Morphdom 的华丽变身 | 竹林里有冰的博客 node-sass 迁移至 dart-sass 踩坑实录 | 竹林里有冰的博客 前端中的量子力学——一打开 F12 就消失的 Bug | 竹林里有冰的博客 2025 年,如何为 web 页面上展示的视频选择合适的压缩算法? | 竹林里有冰的博客 el-image 和 el-table 怎么就打架了?Stacking Context 是什么? | 竹林里有冰的博客 2025年,前端如何使用 JS 将文本复制到剪切板? | 竹林里有冰的博客 ssh 拯救世界——通过 ssh 隧道在内网服务器执行 APT 更新 | 竹林里有冰的博客 Cudy TR3000 吃鹅(daed)记 | 竹林里有冰的博客 使用 Cloudflare Workers 监控 Fedora Copr 构建状态 | 竹林里有冰的博客 基于 Cloudflare Workers 实现的在线服务状态检测告警系统 | 竹林里有冰的博客 构建部署在 Cloudflare Workers 上的 TG Bot | 竹林里有冰的博客 2024年,Firefox 是唯一还在坚持执行在线的 SSL 证书吊销状态检查的主流浏览器 | 竹林里有冰的博客 小爱课程表适配不完全指北——以 ZJUT 本科正方教务系统为例 | 竹林里有冰的博客 将博客从 waline v2 更新到 waline v3 | 竹林里有冰的博客 给家里云装上 Fedora 41 KDE 后,我是如何配置的 | 竹林里有冰的博客 为 Hexo 添加 follow 认证 | 竹林里有冰的博客 使用 GPT 对 waline 的评论进行审查 | 竹林里有冰的博客 基于 JavaScript 的 Hexo Fluid 主题 banner 随机背景图实现 | 竹林里有冰的博客 使用向日葵智能插座 C2 用电记录推算宿舍上次烧水时间 | 竹林里有冰的博客 使用 Caddy 反向代理 dockerhub 需要几步? | 竹林里有冰的博客 将 Rustdesk 中继服务从 Arch Linux 迁移至 Debian | 竹林里有冰的博客 自建图床小记五——费用 | 竹林里有冰的博客 自建图床小记四——上传脚本编写与图片迁移 | 竹林里有冰的博客 自建图床小记三—— SSL 证书的自动更新与部署 | 竹林里有冰的博客 自建图床小记二——使用 Workers 为 R2 构建 Restful API | 竹林里有冰的博客 自建图床小记一——图床架构与 DNS 解析 | 竹林里有冰的博客 在 Linux 下使用 mitmproxy 抓取安卓手机上的 HTTPS 流量 | 竹林里有冰的博客 为中柏 N100 小主机开启来电自启 | 竹林里有冰的博客 我的博客被完整地反向代理,并自动翻译成了繁体中文 | 竹林里有冰的博客 尝试体验 Fedora COPR 中的 allow SSH 功能 | 竹林里有冰的博客 在 Arch Linux 下配置使用 HP Laser 103w 打印机无线打印 | 竹林里有冰的博客 使用动态公网 ip + ddns 实现 rustdesk 的 ip 直连 | 竹林里有冰的博客 使用 Windows 虚拟机运行虚拟专用网客户端为 Linux 提供内网环境 | 竹林里有冰的博客 以 Archlinux 中 makepkg 的方式打开 rpmbuild | 竹林里有冰的博客 使用 Github Action 更新用于 rpm 打包的 spec 文件 | 竹林里有冰的博客 使用 Python 生成甘特图(Gantt Chart) | 竹林里有冰的博客 uniapp 中的图片预加载 | 竹林里有冰的博客 小记 - 尝试拼凑出 apt 仓库中的 deb 包下载地址 | 竹林里有冰的博客 在 Linux 下使用 mitmproxy 抓取 HTTPS 流量 | 竹林里有冰的博客 如何使用 docker 部署 onemanager | 竹林里有冰的博客 crontab 中简单的@语法糖 | 竹林里有冰的博客 备份 umami 数据库,并使用 TG Bot 保存 dump 文件 | 竹林里有冰的博客 在 JavaScript 中,箭头函数中的 this 指针到底指向哪里? | 竹林里有冰的博客 结合 Vue.js 与 php 完成的 web 期末大作业,讲讲前后端分离站点开发与部署中可能遇到的 CORS 跨域问题 | 竹林里有冰的博客 vuejs、php、caddy 与 docker —— web 期末大作业上云部署 | 竹林里有冰的博客 在 Hexo Fluid 主题中使用霞鹜文楷 | 竹林里有冰的博客 【翻译】GLWTPL——祝你好运开源许可证 | 竹林里有冰的博客 通过巴法云将向日葵智能插座接入米家,实现小爱同学远程控制 | 竹林里有冰的博客 使用 Root 后的安卓手机获取向日葵智能插座 C2 的开关 api | 竹林里有冰的博客 创建 b23.tv 追踪参数移除 bot | 竹林里有冰的博客 jinja2 中如何优雅地实现换行 | 竹林里有冰的博客 手动指定 python-selenium 的 driver path 以解决在中国大陆网络环境下启动卡住的问题 | 竹林里有冰的博客 从零开始的静态网页部署(到个人云服务器) | 竹林里有冰的博客 在运行OpenWRT的N1盒子上部署 QQBot | 竹林里有冰的博客 在浙工大宿舍使用路由器连接移动网络(校园网) | 竹林里有冰的博客 为红米 Redmi AC2100 路由器刷入 Padavan | 竹林里有冰的博客 Azure 教育订阅申请时遇到的麻烦 | 竹林里有冰的博客 执行 repo sync 后将 git-lfs 中的资源文件 checkout | 竹林里有冰的博客 隐式转发——骚套路建站方案 | 竹林里有冰的博客 在 vps 上配合 caddy 部署 siteproxy | 竹林里有冰的博客 onedrive(by abraunegg) —— 一个 Linux 下的开源 OneDrive 客户端(cli) | 竹林里有冰的博客 【翻译】关于2022年11月的事件的一些话[Z-Library] | 竹林里有冰的博客 【已过期】使用 vercel+supabase 免费部署 umami | 竹林里有冰的博客 我的博客部署方案 | 竹林里有冰的博客 使用 VirtScreen 将 Pad 作为副屏 | 竹林里有冰的博客 在 Archlinux 下使用 l2tp 协议连接校园网 | 竹林里有冰的博客 为 Element 添加自己喜欢的贴纸 | 竹林里有冰的博客 nodejs16:是我配不上 openssl 3 咯? | 竹林里有冰的博客 如何拯救失声的 hollywood | 竹林里有冰的博客 处理 fcitx5 的文字候选框在 tg 客户端上闪烁的问题 | 竹林里有冰的博客 使用caddy反向代理维基百科中文站点 | 竹林里有冰的博客 创建一个本地的 Fedora 镜像源 | 竹林里有冰的博客 好软推荐——FastOCR | 竹林里有冰的博客 抛弃PicGo,直接使用curl将图片上传到LskyPro | 竹林里有冰的博客 使用 Github Action 跑 rpmbuild | 竹林里有冰的博客 如何打出一个「-git」的rpm包 | 竹林里有冰的博客 雪藏在开源镜像站点中的那些常用却不为人知的软件 | 竹林里有冰的博客 在Fedora搭建jekyll环境——dnf module | 竹林里有冰的博客 pacman更新时遇到「GPGME 错误:无数据」 | 竹林里有冰的博客 PicUploader使用系列(二)——为KDE的dolphin添加右键快捷菜单 | 竹林里有冰的博客 PicUploader使用系列(一)——在Archlinux上使用Caddy部署PicUploader | 竹林里有冰的博客 使用AUR(Helper)安装软件时究竟发生了什么?对于常见的构建错误如何解决? | 竹林里有冰的博客 下载一份openharmony的源码 | 竹林里有冰的博客 在Windows与Linux双系统下共享蓝牙鼠标 | 竹林里有冰的博客 我在Archlinux上的常用软件 | 竹林里有冰的博客 安卓解包笔记 | 竹林里有冰的博客
【翻译】使用 PHP 构建简单的 REST API | 竹林里有冰的博客
竹林里有冰 · 2023-12-12 · via 竹林里有冰的博客

我这学期有一门偏向前端的 WEB 课程,期末大作业要求使用 PHP 作为后端语言实现一个简单的影评系统,应该是不允许使用框架,使用中文关键字在搜索引擎上搜了一阵子似乎没有可供参考的案例,后来就找到了这篇博客,当中的许多观点与我不谋而合,因此我将这篇博客翻译成中文,原文戳这里: https://amirkamizi.com/blog/php-simple-rest-api

介绍#

上周 @rapid_api 发了一个非常好的关于使用 nodejs 和 express 创建 REST API 的教程帖子。我想要帮助你使用 PHP 开发同样简单的 REST API。

首先,如果你不了解 REST API,请务必查看这个 Twitter 帖子

目标#

在我们开始之前,我想提一句,当我写这篇帖子的时候,我想确保:

  1. 我使用单纯的 PHP,不使用框架
  2. 我使用最简单的函数和结构体以便所有人都可以理解并跟上
  3. 我将主体部分分开

现在让我们开始吧

准备#

在我本地的机器上,我创建了一个叫 api 的文件夹于 xampp > htdocs,在里面有一个叫 index.php 的文件

如果你没有 xampp 或者你不知道如何把 php 跑起来,请务必查看这篇文章

现在,如果你尝试访问 localhost/api,你将得到一个空的响应,因为 index.php 文件是空的

优雅的 URL#

项目中,我们需要处理的第一件事是 url

REST API 的关键特性之一是每一个 url 负责一个资源和一个操作

问题#

这时候如果我创建一个 users.php,我需要访问

localhost/api/users.php

我需要为每一个 user id 创建一个新的文件

localhost/api/users/1.php
localhost/api/users/2.php

以此类推。

这种方案有两个问题

  1. 为每个用户创建一个新文件是非常无聊和耗时的
  2. 路由不优雅,每个路径后面都带有 .php

解决方案#

让我们解决这个问题。

正如我所提到的,我不想使用任何框架,并且我想使用最简单的、最让人能够理解的方案

让我们看看如何解决这个问题

在 api 文件夹下创建一个叫 .htaccess 的文件,并且将下面的文本复制进去

RewriteEngine On
RewriteBase /api
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+)$ index.php [QSA,L]

我们告诉服务器,将所有指向 /api 的请求都转发到 index.php 文件

现在,所有的 url 都指向 index.php 了,比如下面的 url 都是指向 index.php 的

api/users
api/users/10
api/users/5

现在我们同时解决了这两个问题

  1. 所有的 url 都被一个文件处理
  2. url 都很优雅,结尾处没有 .php

URI#

但如何知道用户请求的是哪个 uri 呢?

很简单,使用 $_SERVER 超全局变量

让我们来看一些例子

// url api/users
echo $_SERVER['REQUEST_URI'];
// /api/users

// url api/users/5
echo $_SERVER['REQUEST_URI'];
// /api/users/5

// url api
echo $_SERVER['REQUEST_URI'];
// /api

看见了吗?这就是我们所需要的

现在,使用一个简单的 if 或者 switch 语句,我们就可以处理不同的路径了

如果你从来没有用过这些语句,去读这篇文章

请求方法#

接下来,我们需要从请求中获取请求的方法,以查看它是GET、POST、PUT、PATCH还是DELETE。

你可以从 $_SERVER 超全局数组中获取这个信息。

$_SERVER['REQUEST_METHOD']

让我们将这两个值存储在变量中:

$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];

我们可以在一个简单的 switch 语句中使用这两个变量来处理不同的请求。

我们需要判断以下请求

  • api/users 的 GET 请求
  • api/users/{id} 的 GET 请求
  • api/users 的 POST 请求
  • api/users/{id} 的 PUT 请求
  • api/users/{id} 的 DELETE 请求

让我们编写针对上述请求的 switch 语句

switch ($method | $uri) {
   /*
   * Path: GET /api/users
   * Task: show all the users
   */
   case ($method == 'GET' && $uri == '/api/users'):
       break;
   /*
   * Path: GET /api/users/{id}
   * Task: get one user
   */
   case ($method == 'GET' && preg_match('/\/api\/users\/[1-9]/', $uri)):
       break;
   /*
   * Path: POST /api/users
   * Task: store one user
   */
   case ($method == 'POST' && $uri == '/api/users'):
       break;
   /*
   * Path: PUT /api/users/{id}
   * Task: update one user
   */
   case ($method == 'PUT' && preg_match('/\/api\/users\/[1-9]/', $uri)):
       break;
   /*
   * Path: DELETE /api/users/{id}
   * Task: delete one user
   */
   case ($method == 'DELETE' && preg_match('/\/api\/users\/[1-9]/', $uri)):
       break;
   /*
   * Path: ?
   * Task: this path doesn't match any of the defined paths
   *      throw an error
   */
   default:
       break;
}

当我们想要在 switch 语句中使用两个变量,我们可以使用 | 符号

如果你想知道 preg_match 是如何工作的,看这篇文章

数据库#

现在是说说数据。储存数据的最好方法是将数据储存在数据库中。但在这篇教程中,我不想使用数据库。因此,我们使用一个 json 文件当作数据库来保证数据的持久性。

我的 json 文件看起来长成这个样子:

{
   "1": "Pratham",
   "2": "Amir"
}

如果你想知道如何使用 json,看这篇文章

我加载 json 数据并将其转换为数组,然后在 php 使用他们。如果我想要更改数据,我将数组转换回 json 并将其重新写入文件。

要将整个文件作为一个字符串读取并存储在变量中,我使用:

file_get_contents($jsonFile);

而要将json写入文件,我使用:

file_put_contents($jsonFile, $data);

好了,现在我们的数据库处理好了,让我们开始处理所有的路径。

我使用 Postman 发送请求并查看响应。

获取所有用户#

case ($method == 'GET' && $uri == '/api/users'):
   header('Content-Type: application/json');
   echo json_encode($users, JSON_PRETTY_PRINT);
   break;

获取单个用户#

case ($method == 'GET' && preg_match('/\/api\/users\/[1-9]/', $uri)):
   header('Content-Type: application/json');
   // get the id
   $id = basename($uri);
   if (!array_key_exists($id, $users)) {
       http_response_code(404);
       echo json_encode(['error' => 'user does not exist']);
       break;
   }
   $responseData = [$id => $users[$id]];
   echo json_encode($responseData, JSON_PRETTY_PRINT);
   break;

basename($uri) 会将 uri 的最后一部分给我。比如一个 api/users/10 这样的路径,它会返回 10.

然后我使用 array_key_exists 检查是否存在一个 id 为 10 的用户

添加一个新用户#

case ($method == 'POST' && $uri == '/api/users'):
   header('Content-Type: application/json');
   $requestBody = json_decode(file_get_contents('php://input'), true);
   $name = $requestBody['name'];
   if (empty($name)) {
       http_response_code(404);
       echo json_encode(['error' => 'Please add name of the user']);
   }
   $users[] = $name;
   $data = json_encode($users, JSON_PRETTY_PRINT);
   file_put_contents($jsonFile, $data);
   echo json_encode(['message' => 'user added successfully']);
   break;

我使用 file_get_contents('php://input') 以获取请求的 body 部分。由于在这个例子中我使用的是 json,我将会解码 json 以便我可以获取到名字。

更新一个用户#

case ($method == 'PUT' && preg_match('/\/api\/users\/[1-9]/', $uri)):
   header('Content-Type: application/json');
   // get the id
   $id = basename($uri);
   if (!array_key_exists($id, $users)) {
       http_response_code(404);
       echo json_encode(['error' => 'user does not exist']);
       break;
   }
   $requestBody = json_decode(file_get_contents('php://input'), true);
   $name = $requestBody['name'];
   if (empty($name)) {
       http_response_code(404);
       echo json_encode(['error' => 'Please add name of the user']);
   }
   $users[$id] = $name;
   $data = json_encode($users, JSON_PRETTY_PRINT);
   file_put_contents($jsonFile, $data);
   echo json_encode(['message' => 'user updated successfully']);
   break;

删除一个用户#

case ($method == 'DELETE' && preg_match('/\/api\/users\/[1-9]/', $uri)):
   header('Content-Type: application/json');
   // get the id
   $id = basename($uri);
   if (empty($users[$id])) {
       http_response_code(404);
       echo json_encode(['error' => 'user does not exist']);
       break;
   }
   unset($users[$id]);
   $data = json_encode($users, JSON_PRETTY_PRINT);
   file_put_contents($jsonFile, $data);
   echo json_encode(['message' => 'user deleted successfully']);
   break;

最终文件#

现在我们的 index.php 文件看起来是这样的

在 70 行左右的代码中,我们使用 PHP 创建了一个 RESTful API,很神奇吧?

<?php
$jsonFile = 'users.json';
$data = file_get_contents($jsonFile);
$users = json_decode($data, true);
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
switch ($method | $uri) {
   case ($method == 'GET' && $uri == '/api/users'):
       header('Content-Type: application/json');
       echo json_encode($users, JSON_PRETTY_PRINT);
       break;
   case ($method == 'GET' && preg_match('/\/api\/users\/[1-9]/', $uri)):
       header('Content-Type: application/json');
       $id = basename($uri);
       if (!array_key_exists($id, $users)) {
           http_response_code(404);
           echo json_encode(['error' => 'user does not exist']);
           break;
       }
       $responseData = [$id => $users[$id]];
       echo json_encode($responseData, JSON_PRETTY_PRINT);
       break;
   case ($method == 'POST' && $uri == '/api/users'):
       header('Content-Type: application/json');
       $requestBody = json_decode(file_get_contents('php://input'), true);
       $name = $requestBody['name'];
       if (empty($name)) {
           http_response_code(404);
           echo json_encode(['error' => 'Please add name of the user']);
       }
       $users[] = $name;
       $data = json_encode($users, JSON_PRETTY_PRINT);
       file_put_contents($jsonFile, $data);
       echo json_encode(['message' => 'user added successfully']);
       break;
   case ($method == 'PUT' && preg_match('/\/api\/users\/[1-9]/', $uri)):
       header('Content-Type: application/json');
       $id = basename($uri);
       if (!array_key_exists($id, $users)) {
           http_response_code(404);
           echo json_encode(['error' => 'user does not exist']);
           break;
       }
       $requestBody = json_decode(file_get_contents('php://input'), true);
       $name = $requestBody['name'];
       if (empty($name)) {
           http_response_code(404);
           echo json_encode(['error' => 'Please add name of the user']);
       }
       $users[$id] = $name;
       $data = json_encode($users, JSON_PRETTY_PRINT);
       file_put_contents($jsonFile, $data);
       echo json_encode(['message' => 'user updated successfully']);
       break;
   case ($method == 'DELETE' && preg_match('/\/api\/users\/[1-9]/', $uri)):
       header('Content-Type: application/json');
       $id = basename($uri);
       if (empty($users[$id])) {
           http_response_code(404);
           echo json_encode(['error' => 'user does not exist']);
           break;
       }
       unset($users[$id]);
       $data = json_encode($users, JSON_PRETTY_PRINT);
       file_put_contents($jsonFile, $data);
       echo json_encode(['message' => 'user deleted successfully']);
       break;
   default:
       http_response_code(404);
       echo json_encode(['error' => "We cannot find what you're looking for."]);
       break;
}

额外内容#

在这种情况下,我不希望删除我的所有用户,所以我加了一个新的语句,如果只剩下最后一个用户,它将不会被删除,像这样

if (sizeof($users) == 1){
   http_response_code(404);
   echo json_encode(['error' => 'there is only one user left. you cannot delete it!']);
   break;
}

源码#

你可以在原作者的 github 上看到完整注释的源代码以及 post man 集合

总结#

现在你知道如何在 PHP 中创建一个简单的 RESTful API。

我推荐你打开一个 PHP 文件并复习所有的这些我们进行的步骤,并且像本文一样添加一些额外的资源

如果你有任何的建议、问题或者观点,请联系文章原作者,他期待着听到你的声音。

要点#

  • 不使用框架,用 PHP 创建一个 RESTful API
  • 在 PHP 中使用优雅的 URL
  • 处理请求的 body
  • 使用 Json 文件作为你的数据库
  • 使用多个变量作为 switch 的关键词