40、PHP 应用开发:Markdown 格式处理与数据排序分页技巧

PHP 应用开发:Markdown 格式处理与数据排序分页技巧

1. Markdown 格式处理

在处理笑话文本时,我们可以使用 Markdown 格式来增强文本的显示效果。首先,将显示笑话文本的代码替换为以下内容:

<?php
$markdown = new \Ninja\Markdown($joke->joketext);
echo $markdown->toHtml();
?>

这段代码将 joketext 的内容作为构造函数参数传递给 Markdown 类,并调用 toHtml 方法将文本转换为 HTML。不过,这种写法比原始方法更繁琐,我们可以使用更简洁的语法:

<?=(new \Ninja\Markdown($joke->joketext))->toHtml()?>

使用 Markdown 格式的好处在于,有很多开源代码可以帮助我们处理它。常见的 Markdown 库包括 ParseDown 和 cebe/markdown。

2. 数据排序

MySQL 支持按特定顺序检索记录。当前笑话列表页面按发布顺序显示笑话,我们希望将最新的笑话显示在前面。可以使用 SELECT 查询中的 ORDER BY 子句来指定排序的列,例如:

SELECT * FROM `joke` ORDER BY `jokedate` DESC

这个查询将选择所有笑话,并按日期降序排列,即最新的笑话在前。

为了在网站上实现这个功能,我们需要修改 DatabaseTable 类的 findAll 方法,添加一个可选的 $orderBy 参数:

public function findAll($orderBy = null) {
    $query = 'SELECT * FROM ' . $this->table;
    if ($orderBy != null) {
        $query .= ' ORDER BY ' . $orderBy;
    }
    $result = $this->query($query);
    return $result->fetchAll(\PDO::FETCH_CLASS, $this->className, $this->constructorArgs);
}

然后,修改 Joke 控制器的 list 方法,为 findAll 方法提供 $orderBy 参数:

public function list() {
    if (isset($_GET['category'])) {
        $category = $this->categoriesTable->findById($_GET['category']);
        $jokes = $category->getJokes();
    } else {
        $jokes = $this->jokesTable->findAll('jokedate DESC');
    }
    // …
}

目前,主笑话列表页面按最新优先排序,但点击分类后,笑话仍按最早优先排序。我们可以考虑在 find 方法中添加相同的可选参数:

public function find($column, $value, $orderBy = null) {
    $query = 'SELECT * FROM ' . $this->table . ' WHERE ' . $column . ' = :value';
    $parameters = [
        'value' => $value
    ];
    if ($orderBy != null) {
        $query .= ' ORDER BY ' . $orderBy;
    }
    $query = $this->query($query, $parameters);
    return $query->fetchAll(\PDO::FETCH_CLASS, $this->className, $this->constructorArgs);
}

然而,这并不能解决分类内笑话排序的问题。因为 find 方法是在代表 joke_category 表的 DatabaseTable 实例上调用的,我们无法轻松按日期排序。可以使用 PHP 的 usort 函数来解决这个问题:

public function getJokes() {
    $jokeCategories = $this->jokeCategoriesTable->find('categoryId', $this->id);
    $jokes = [];
    foreach ($jokeCategories as $jokeCategory) {
        $joke = $this->jokesTable->findById($jokeCategory->jokeId);
        if ($joke) {
            $jokes[] = $joke;
        }
    }
    usort($jokes, [$this, 'sortJokes']);
    return $jokes;
}

private function sortJokes($a, $b) {
    $aDate = new \DateTime($a->jokedate);
    $bDate = new \DateTime($b->jokedate);
    if ($aDate->getTimestamp() == $bDate->getTimestamp()) {
        return 0;
    }
    return $aDate->getTimestamp() > $bDate->getTimestamp() ? -1 : 1;
}

使用 usort 函数会有轻微的性能开销,但在处理少量记录时,这种差异可以忽略不计。

3. 分页处理

随着网站的发展,笑话数量可能会增加,导致笑话列表页面加载时间过长。为了解决这个问题,我们可以使用分页来显示合理数量的笑话,例如每页显示 10 条。

首先,我们需要在 DatabaseTable 类的 findAll find 方法中添加 $limit $offset 可选参数:

public function find($column, $value, $orderBy = null, $limit = null, $offset = null) {
    $query = 'SELECT * FROM ' . $this->table . ' WHERE ' . $column . ' = :value';
    $parameters = [
        'value' => $value
    ];
    if ($orderBy != null) {
        $query .= ' ORDER BY ' . $orderBy;
    }
    if ($limit != null) {
        $query .= ' LIMIT ' . $limit;
    }
    if ($offset != null) {
        $query .= ' OFFSET ' . $offset;
    }
    $query = $this->query($query, $parameters);
    return $query->fetchAll(\PDO::FETCH_CLASS, $this->className, $this->constructorArgs);
}

public function findAll($orderBy = null, $limit = null, $offset = null) {
    $query = 'SELECT * FROM ' . $this->table;
    if ($orderBy != null) {
        $query .= ' ORDER BY ' . $orderBy;
    }
    if ($limit != null) {
        $query .= ' LIMIT ' . $limit;
    }
    if ($offset != null) {
        $query .= ' OFFSET ' . $offset;
    }
    $result = $this->query($query);
    return $result->fetchAll(\PDO::FETCH_CLASS, $this->className, $this->constructorArgs);
}

然后,在 Joke 控制器的 list 方法中提供 $limit $offset 参数:

$page = $_GET['page'] ?? 1;
$offset = ($page - 1) * 10;
if (isset($_GET['category'])) {
    $category = $this->categoriesTable->findById($_GET['category']);
    $jokes = $category->getJokes();
} else {
    $jokes = $this->jokesTable->findAll('jokedate DESC', 10, $offset);
}

为了显示分页链接,我们需要计算总页数,并在模板中生成链接:

<?php
$numPages = ceil($totalJokes / 10);
for ($i = 1; $i <= $numPages; $i++):
    if ($i == $currentPage):
?>
    <a class="currentpage" href="/joke/list?page=<?=$i?>"><?=$i?></a>
<?php else: ?>
    <a href="/joke/list?page=<?=$i?>"><?=$i?></a>
<?php endif; ?>
<?php endfor; ?>

我们还可以通过添加 CSS 类来突出显示当前页面的链接:

.currentpage:before {
    content: "[";
}
.currentpage:after {
    content: "]";
}

以下是分页处理的流程图:

graph TD;
    A[开始] --> B[获取当前页码];
    B --> C[计算偏移量];
    C --> D{是否有分类参数};
    D -- 是 --> E[获取分类信息];
    D -- 否 --> F[按日期降序获取笑话列表];
    E --> G[获取分类下的笑话列表];
    F --> H[获取指定偏移量和数量的笑话列表];
    G --> I[显示笑话列表];
    H --> I;
    I --> J[计算总页数];
    J --> K[生成分页链接];
    K --> L[结束];
4. 分类内分页处理

目前的代码在分类内分页存在问题,点击分类时无法提供正确的偏移值。我们需要修改 Category 实体的 getJokes 方法,添加 $limit $offset 参数:

public function getJokes($limit = null, $offset = null) {
    $jokeCategories = $this->jokeCategoriesTable->find('categoryId', $this->id, null, $limit, $offset);
    $jokes = [];
    foreach ($jokeCategories as $jokeCategory) {
        $joke = $this->jokesTable->findById($jokeCategory->jokeId);
        if ($joke) {
            $jokes[] = $joke;
        }
    }
    usort($jokes, [$this, 'sortJokes']);
    return $jokes;
}

同时,在 Joke 控制器的 list 方法中提供这些参数:

if (isset($_GET['category'])) {
    $category = $this->categoriesTable->findById($_GET['category']);
    $jokes = $category->getJokes(10, $offset);
}

另外,当前的分页链接存在两个问题:一是链接不包含分类变量,二是显示的页数是基于数据库中所有笑话的数量,而不是所选分类中的笑话数量。

为了解决第一个问题,我们需要在 list 方法中传递分类信息到模板,并在模板中修改链接:

return [
    'template' => 'jokes.html.php',
    'title' => $title,
    'variables' => [
        'totalJokes' => $totalJokes,
        'jokes' => $jokes,
        'user' => $author,
        'categories' => $this->categoriesTable->findAll(),
        'currentPage' => $page,
        'category' => $_GET['category'] ?? null
    ]
];
<?php
$numPages = ceil($totalJokes / 10);
for ($i = 1; $i <= $numPages; $i++):
    if ($i == $currentPage):
?>
    <a class="currentpage" href="/joke/list?page=<?=$i?><?=!empty($categoryId) ? '&category=' . $categoryId : '' ?>"><?=$i?></a>
<?php else: ?>
    <a href="/joke/list?page=<?=$i?><?=!empty($categoryId) ? '&category=' . $categoryId : '' ?>"><?=$i?></a>
<?php endif; ?>
<?php endfor; ?>

为了解决第二个问题,我们需要修改 DatabaseTable 类的 total 方法,支持按条件统计记录数量:

public function total($field = null, $value = null) {
    $sql = 'SELECT COUNT(*) FROM `' . $this->table . '`';
    $parameters = [];
    if (!empty($field)) {
        $sql .= ' WHERE `' . $field . '` = :value';
        $parameters = ['value' => $value];
    }
    $query = $this->query($sql, $parameters);
    $row = $query->fetch();
    return $row[0];
}

然后,在 Category 实体类中添加一个新方法来返回该分类下的笑话数量:

public function getNumJokes() {
    return $this->jokeCategoriesTable->total('categoryId', $this->id);
}

最后,在 Joke 控制器的 list 方法中调用这个方法:

$totalJokes = $category->getNumJokes();

以下是分类内分页处理的步骤列表:
1. 修改 Category 实体的 getJokes 方法,添加 $limit $offset 参数。
2. 在 Joke 控制器的 list 方法中传递分类信息到模板。
3. 修改模板中的分页链接,包含分类变量。
4. 修改 DatabaseTable 类的 total 方法,支持按条件统计记录数量。
5. 在 Category 实体类中添加 getNumJokes 方法。
6. 在 Joke 控制器的 list 方法中调用 getNumJokes 方法。

通过以上步骤,我们可以实现分类内的正确分页。

PHP 应用开发:Markdown 格式处理与数据排序分页技巧

5. 总结与注意事项

在开发过程中,我们完成了 Markdown 格式处理、数据排序、分页处理以及分类内分页处理等功能。以下是一些总结和注意事项:

5.1 Markdown 格式处理
  • 优点 :使用 Markdown 可以让文本内容更具格式,且有很多开源库可以帮助处理,如 ParseDown 和 cebe/markdown。
  • 代码优化 :可以使用更简洁的语法 <?=(new \Ninja\Markdown($joke->joketext))->toHtml()?> 来替代多行代码。
5.2 数据排序
  • 数据库排序 :使用 ORDER BY 子句可以让数据库对数据进行排序,如 SELECT * FROM joke ORDER BY jokedate DESC
  • PHP 排序 :当数据库排序无法满足需求时,可以使用 PHP 的 usort 函数进行排序,但会有轻微性能开销。
5.3 分页处理
  • LIMIT 和 OFFSET :使用 LIMIT OFFSET 子句可以实现分页功能,如 SELECT * FROM joke ORDER BY jokedate LIMIT 10 OFFSET 10
  • 页面链接 :需要计算总页数并生成页面链接,同时可以通过 CSS 类突出显示当前页面链接。
5.4 分类内分页处理
  • 参数传递 :需要在相关方法中添加 $limit $offset 参数,并传递分类信息到模板。
  • 记录统计 :修改 DatabaseTable 类的 total 方法,支持按条件统计记录数量。

以下是一个总结表格:
| 功能 | 实现方式 | 注意事项 |
| ---- | ---- | ---- |
| Markdown 格式处理 | 使用 \Ninja\Markdown 类 | 可使用简洁语法优化代码 |
| 数据排序 | 数据库使用 ORDER BY ,PHP 使用 usort | usort 有轻微性能开销 |
| 分页处理 | 使用 LIMIT OFFSET 子句 | 计算总页数并生成页面链接 |
| 分类内分页处理 | 添加参数,传递分类信息,修改 total 方法 | 确保链接包含分类变量,统计分类内记录数量 |

6. 性能考虑

在处理大量数据时,性能是一个重要的考虑因素。以下是一些性能相关的建议:

6.1 排序性能
  • 数据库排序 :使用 ORDER BY 子句让数据库进行排序,性能较高,尤其是处理大量数据时。
  • PHP 排序 usort 函数会有一定的性能开销,尽量在数据量较小时使用。
6.2 分页性能
  • LIMIT 和 OFFSET :当偏移量较大时, OFFSET 会导致性能下降。可以考虑使用 WHERE 子句结合索引来优化。

以下是性能对比表格:
| 操作 | 数据库实现 | PHP 实现 | 性能对比 |
| ---- | ---- | ---- | ---- |
| 排序 | ORDER BY | usort | 数据库排序性能高 |
| 分页 | LIMIT OFFSET | 无 | 偏移量较大时性能需优化 |

7. 扩展与优化

为了让应用更加完善,我们可以考虑以下扩展和优化:

7.1 动态分页数量

目前每页显示的笑话数量是固定的,可以通过用户选择或配置文件来动态调整每页显示的数量。

7.2 搜索功能

添加搜索功能,让用户可以根据关键词搜索笑话。可以在 DatabaseTable 类中添加相应的搜索方法。

7.3 缓存机制

对于一些频繁访问的数据,可以使用缓存机制来提高性能,如使用 Redis 缓存。

以下是扩展与优化的步骤列表:
1. 实现动态分页数量:在模板中添加选择框,根据用户选择调整 $limit 参数。
2. 添加搜索功能:在 DatabaseTable 类中添加搜索方法,在控制器中处理搜索请求。
3. 引入缓存机制:安装 Redis 并在代码中使用 Redis 缓存数据。

8. 总结

通过本文的介绍,我们学习了如何在 PHP 应用中处理 Markdown 格式、对数据进行排序和分页处理,以及解决分类内分页的问题。同时,我们也考虑了性能和扩展优化的方面。希望这些内容能帮助你开发出更加高效、完善的 PHP 应用。

以下是整个开发流程的 mermaid 流程图:

graph LR;
    A[Markdown 格式处理] --> B[数据排序];
    B --> C[分页处理];
    C --> D[分类内分页处理];
    D --> E[性能考虑];
    E --> F[扩展与优化];
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值