
php阅读器使用教程:新手完成第一张作品的方法
“PHP 阅读器”不是 PHP 官方提供的一款固定软件,也没有统一的作品格式或操作界面。它通常指用 PHP 编写的网页阅读器,或者用 PHP 阅读、展示文本内容的程序。本文按后者来讲:从零搭建一个简单的网页阅读器,把一部作品及其章节显示在浏览器里。
这里说的“第一张作品”,按常见的内容管理习惯理解为“第一部作品”。完成后,你会得到一个可以本地运行的 PHP 页面:页面显示作品名、章节目录和正文,并能通过链接切换章节。示例不依赖框架、数据库或第三方扩展,适合先理解基本流程,再按实际需求扩展。
一、适用场景与功能边界
这个小程序适合用来学习 PHP、展示少量公开文本、制作个人作品样例,或验证“作品—章节—正文”的数据结构。它把数据写在 PHP 数组里,适合演示和小规模试用。
它不是完整的电子书管理系统,也不是 PDF、EPUB 阅读器。示例不包含用户注册、后台编辑、文件上传、搜索、分页、版权管理或数据持久化。作品内容要直接写入代码;修改内容后,通常需要编辑文件并重新部署。若需要管理大量作品,建议使用数据库和后台管理功能;若要阅读 PDF 或 EPUB,则需要相应格式的解析或阅读组件,不能把下面的纯文本方案当作格式转换器。
本文中的“阅读器”是由 PHP 在服务器端生成 HTML 页面,再交给浏览器显示。PHP 不会自动把一段文字变成可翻页的电子书,也不会自动保存作品。把这几个边界弄清楚,后续选择方案会容易得多。
二、准备 PHP 运行环境
需要一台已安装 PHP 的电脑或服务器。示例使用 PHP 8.0 及以上版本,采用的语法在常见 PHP 8.x 环境中可运行。它没有用到数据库、Composer 或额外扩展。可在终端检查版本:
php -v
如果系统提示找不到 php 命令,说明 PHP 尚未安装,或 PHP 可执行文件没有加入系统的 PATH。请按所用操作系统安装 PHP CLI;使用集成开发环境时,也要确认其 PHP 版本与终端使用的版本一致。
新建一个目录,例如 php-reader,在里面创建 index.php。用终端进入该目录后,执行 PHP 自带的开发服务器:
php -S 127.0.0.1:8000
浏览器访问 http://127.0.0.1:8000。这个内置服务器适合本机开发和测试,不应直接当作面向公众的生产服务器使用。上线时应根据部署环境配置正式 Web 服务器、PHP 运行方式、HTTPS 和访问控制。
三、创建第一部作品
下面先用 PHP 数组表示作品数据。每部作品有标题、作者和章节列表;每个章节有标题和正文。为便于入门,示例只放一部作品和两章内容。新手可以先原样复制,确认页面运行后再改成自己的文字。
<?php
declare(strict_types=1);
$work = [
'title' => '雨后的车站',
'author' => '林间',
'chapters' => [
[
'title' => '第一章:晚点的列车',
'content' => <<<'TEXT'
傍晚下起了雨。林舟走进车站时,广播刚好响起,提醒旅客列车晚点。
他把湿透的伞靠在墙边,抬头看向站台上方的时钟。
TEXT
],
[
'title' => '第二章:站台尽头',
'content' => <<<'TEXT'
雨声渐渐小了。站台尽头亮起一盏灯,林舟在那里看见一张熟悉的车票。
他想起很多年前,也曾有人在这里等他。
TEXT
],
],
];
function escapeHtml(string $value): string
{
return htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
}
$chapters = $work['chapters'];
$chapterCount = count($chapters);
if ($chapterCount === 0) {
http_response_code(500);
exit('作品还没有章节。');
}
$requestedChapter = filter_input(
INPUT_GET,
'chapter',
FILTER_VALIDATE_INT
);
$chapterIndex = ($requestedChapter === false || $requestedChapter === null)
? 0
: $requestedChapter;
if ($chapterIndex < 0 || $chapterIndex >= $chapterCount) {
http_response_code(404);
exit('章节不存在。');
}
$currentChapter = $chapters[$chapterIndex];
$previousIndex = $chapterIndex - 1;
$nextIndex = $chapterIndex + 1;
?>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= escapeHtml($work['title']) ?></title>
<style>
body {
max-width: 760px;
margin: 2rem auto;
padding: 0 1rem;
color: #222;
font: 18px/1.9 sans-serif;
}
nav a { margin-right: 1rem; }
.chapter-list { padding-left: 1.5rem; }
.content { white-space: pre-wrap; }
.muted { color: #666; font-size: 0.9em; }
</style>
</head>
<body>
<header>
<h1><?= escapeHtml($work['title']) ?></h1>
<p class="muted">作者:<?= escapeHtml($work['author']) ?></p>
</header>
<nav aria-label="章节目录">
<h2>章节目录</h2>
<ol class="chapter-list">
<?php foreach ($chapters as $index => $chapter): ?>
<li>
<a href="?chapter=<?= $index ?>">
<?= escapeHtml($chapter['title']) ?>
</a>
</li>
<?php endforeach; ?>
</ol>
</nav>
<main>
<h2><?= escapeHtml($currentChapter['title']) ?></h2>
<div class="content"><?= escapeHtml($currentChapter['content']) ?></div>
</main>
<nav aria-label="章节翻页">
<?php if ($previousIndex >= 0): ?>
<a href="?chapter=<?= $previousIndex ?>">上一章</a>
<?php endif; ?>
<?php if ($nextIndex < $chapterCount): ?>
<a href="?chapter=<?= $nextIndex ?>">下一章</a>
<?php endif; ?>
</nav>
</body>
</html>
这里使用 PHP 的 nowdoc 写正文。nowdoc 以 <<<'TEXT' 开始,直到单独一行的 TEXT 结束,正文里的引号不会被当作 PHP 字符串语法处理。结束标记应单独占一行,且不要在标记后再写其他字符;若 PHP 报语法错误,先检查这一点,以及复制代码时有没有漏掉分号、括号或引号。
四、运行后页面做了什么
首次访问首页时,程序读取 chapter 参数。没有这个参数,就显示索引为 0 的第一章。点击目录中的章节链接后,地址会变为类似 http://127.0.0.1:8000/?chapter=1,程序据此显示第二章。
示例的章节索引从 0 开始:第一章是 0,第二章是 1。链接中输出的索引来自程序内部循环,不是读者提交的任意路径。用户输入的参数仍会经过整数验证和范围检查:无效格式或超出章节数量的值不会被用于访问数组。这能避免无效索引导致的错误,但它不是用户身份认证,也不能代替完整的访问控制。
正文容器使用 CSS 的 white-space: pre-wrap 保留换行。PHP 输出前调用 htmlspecialchars,所以正文中的尖括号会显示为文本,而不会被浏览器当成 HTML 标签执行。若你的作品正文里本来就需要呈现加粗、链接等 HTML,这个示例会把标签转义显示;不要为了“让格式生效”就删除转义。应改用可信任的内容格式和经过严格净化的输出流程。
五、把示例改成自己的作品
修改作品名和作者,直接改数组中的 title、author 值;添加章节,则在 chapters 数组里增加一组包含 title 与 content 的数据。每组之间用逗号分隔,字符串使用 UTF-8 保存。
'chapters' => [
[
'title' => '第一章:开端',
'content' => <<<'TEXT'
在这里写第一章正文。
可以直接换行。
TEXT
],
[
'title' => '第二章:新的线索',
'content' => <<<'TEXT'
在这里写第二章正文。
TEXT
],
],
添加章节后不需要另外设置总章节数,程序会用 count($chapters) 计算数量。目录链接、上一章和下一章按钮也会根据数组位置自动生成。若章节顺序不对,检查数组中的排列顺序;若页面报错,检查数组方括号、键名引号和逗号是否配对。
数组适合少量演示数据,但不适合频繁编辑或大量内容。如果每次更新都要修改 PHP 文件,不便于多人维护,也难以提供草稿、权限、搜索等功能。扩展到这类需求时,通常会把作品与章节存进数据库,并使用参数化查询读取数据。数据库解决的是数据保存和查询问题,并不会自动解决登录授权、内容审核或输出安全问题。
六、参数限制与安全用途说明
本示例只接受可验证为整数的 chapter 参数,并要求它落在 0 到章节总数减一的范围内。它没有对外开放文件路径,也没有把用户输入拼接进 include、文件读取路径或 SQL 语句。不要为了从文件读取章节,就直接写成 file_get_contents($_GET['file']);这类写法会把用户输入变成文件访问目标,可能引发目录穿越或读取不应公开的文件。
HTML 转义的作用也有边界:它降低了把普通文本输出到 HTML 页面时发生脚本注入的风险,但前提是使用在正确的输出位置。HTML 文本节点、HTML 属性、JavaScript、CSS 和 URL 的编码规则并不相同,不能认为一次 htmlspecialchars 就适用于所有上下文。示例把动态标题和正文放在 HTML 文本节点中,因此采用 HTML 转义;没有把作品内容拼进脚本或样式。
示例中的 filter_input 只做整数格式验证,范围检查由后续条件完成。它不是防爬虫、防拒绝服务或防滥用机制;公开部署时仍要根据服务需求设置请求限制、日志、备份和内容访问策略。本文也没有加入身份验证,因此页面中的作品默认对任何能访问网站的人可见。不要把未公开、敏感或受访问权限限制的内容直接放入公开页面。
作品文字的版权和发布许可需要由内容提供者自行确认。代码只负责展示,并不判断文字是否有权发布,也不提供自动授权或版权保护。若作品面向公众发布,还需按实际情况处理署名、授权、隐私和平台规则。
七、常见错误排查
- 浏览器显示 PHP 源码。说明文件没有经过 PHP 解释器处理。确认文件名是
index.php,并通过 PHP 开发服务器或配置好的 Web 服务器访问;不要双击文件用浏览器直接打开。 - 出现“命令找不到”。检查 PHP 是否安装,以及终端是否能找到 PHP 可执行文件。
php -v若无法运行,先修复环境,页面代码还没有机会执行。 - 提示章节不存在。确认地址参数是有效整数,并且小于章节数。示例从
0开始编号;把第二章写成?chapter=2,在只有两章时会超出范围。 - 中文乱码。确认 PHP 文件保存为 UTF-8,页面包含
meta charset="utf-8",并在转义函数中指定 UTF-8。不要在编辑器里把文件保存成与页面编码不一致的格式。 - 正文换行消失。确认正文在 HTML 中放在带有
white-space: pre-wrap的容器内。若替换了样式或容器,浏览器可能按普通 HTML 空白规则合并换行。 - 改完代码出现解析错误。检查 nowdoc 结束标记、方括号、逗号和分号。PHP 语法错误通常会在服务器端显示行号;开发环境可以据此检查附近代码,上线环境则不应向访客公开详细错误信息。
- 标题里输入尖括号后显示成实体。这是转义的预期结果,页面会把输入当作文字,而不是执行它。不要为了让标签生效而直接输出未经处理的用户内容。
八、完成第一部作品后的下一步
先确认这三个动作都能工作:打开首页看到作品信息;点击目录能切换章节;最后一章没有“下一章”,第一章没有“上一章”。然后再逐步增加章节、调整字体和版面。一次只改一个部分,出错时更容易判断原因。
如果只是展示固定文本,这个单文件示例就足以说明 PHP 阅读页的基本结构:准备数据、验证请求参数、选择章节、转义动态内容,再输出 HTML。若要让非开发者编辑作品、管理多个用户或保存阅读进度,就已经超出这个入门示例的范围,需要进一步设计数据存储、权限和部署方案。明确这些边界,再决定是否扩展,比把所有功能塞进一个页面更稳妥。








