35、PHP 网站开发:从关系管理到性能优化

PHP 网站开发:从关系管理到性能优化

在网站开发过程中,实现对象化的网站架构是一个重要的目标,而其中涉及到的关系管理、性能优化以及功能拓展都是关键环节。下面将详细介绍如何在网站中进行这些操作。

编辑笑话功能

编辑笑话时,会根据笑话的作者 ID 进行权限判断。如果笑话的 ID 为空或者当前用户 ID 与笑话的作者 ID 相同,用户可以编辑笑话。以下是相应的代码:

<?php if (empty($joke->id) || $userId == $joke->authorId):?>
<form action="" method="post">
<input type="hidden" name="joke[id]" value="<?=$joke->id ?? ''?>">
<label for="joketext">Type your joke here:</label>
<textarea id="joketext" name="joke[joketext]" rows="3" cols="40"><?=$joke->joketext ?? ''?></textarea>
<input type="submit" name="submit" value="Save">
</form>
<?php else:?>
<p>You may only edit jokes that you posted.</p>
<?php endif; ?>

这个代码片段实现了一个简单的权限控制,只有笑话的作者才能对其进行编辑。

缓存优化

在 Joke 实体类的 getAuthor 方法中,存在潜在的性能问题。原始的 getAuthor 方法每次调用都会向数据库发送相同的查询请求,导致不必要的性能开销。例如:

public function getAuthor() {
    return $this->authorsTable->findById($this->authorId);
}

当多次调用该方法时,会多次查询数据库:

echo $joke->getAuthor()->name;
echo $joke->getAuthor()->email;
echo $joke->getAuthor()->password;

为了避免这个问题,可以使用透明缓存技术。具体步骤如下:
1. 在 Joke 实体类中添加一个私有属性 $author 用于存储作者信息。

class Joke {
    // …
    public $joketext;
    private $authorsTable;
    private $author;
    // …
}
  1. 修改 getAuthor 方法,在方法中添加逻辑判断,如果 $author 为空,则从数据库中获取作者信息并存储在 $author 中,否则直接返回 $author
public function getAuthor() {
    if (empty($this->author)) {
        $this->author = $this->authorsTable->findById($this->authorId);
    }
    return $this->author;
}

这样,无论调用多少次 getAuthor 方法,数据库只会被查询一次,大大提高了性能。

笑话分类功能

随着网站上笑话数量的增加,为了方便用户查看特定类型的笑话,需要添加笑话分类功能。以下是实现该功能的详细步骤:

创建分类表

使用 SQL 代码创建分类表:

CREATE TABLE `ijdb_sample`.`category` (
    `id` INT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL,
    PRIMARY KEY (`id`)
);

也可以使用 MySQL Workbench 来创建这个表,确保 id 是自增的主键, name 类型为 VARCHAR。

创建分类控制器

Ijdb/Controllers/Category.php 中创建一个新的控制器 Category ,并在构造函数中传入 DatabaseTable 实例,用于与分类表进行交互。

<?php
namespace Ijdb\Controllers;
class Category {
    private $categoriesTable;
    public function __construct(\Ninja\DatabaseTable $categoriesTable) {
        $this->categoriesTable = $categoriesTable;
    }
}
创建分类编辑模板

创建 editcategory.html.php 模板,用于显示添加或编辑分类的表单。

<form action="" method="post">
    <input type="hidden" name="category[id]" value="<?=$category->id ?? ''?>">
    <label for="categoryname">Enter category name:</label>
    <input type="text" id="categoryname" name="category[name]" value="<?=$category->name ?? ''?>" />
    <input type="submit" name="submit" value="Save">
</form>
添加编辑方法

Category 类中添加 edit 方法,用于处理分类的编辑操作。

public function edit() {
    if (isset($_GET['id'])) {
        $category = $this->categoriesTable->findById($_GET['id']);
    }
    $title = 'Edit Category';
    return [
        'template' => 'editcategory.html.php',
        'title' => $title,
        'variables' => [
            'category' => $category ?? null
        ]
    ];
}
配置路由

IjdbRoutes 中创建 DatabaseTable 实例,并配置分类编辑页面的路由。

class IjdbRoutes implements \Ninja\Routes {
    private $authorsTable;
    private $jokesTable;
    private $categoriesTable;
    private $authentication;
    public function __construct() {
        include __DIR__ . '/../../includes/DatabaseConnection.php';
        $this->jokesTable = new \Ninja\DatabaseTable($pdo, 'joke', 'id', '\Ijdb\Entity\Joke', [&$this->authorsTable]);
        $this->authorsTable = new \Ninja\DatabaseTable($pdo, 'author', 'id', '\Ijdb\Entity\Author', [&$this->jokesTable]);
        $this->categoriesTable = new \Ninja\DatabaseTable($pdo, 'category', 'id');
        // …
    }
    // …
    $categoryController = new \Ijdb\Controllers\Category($this->categoriesTable);
    $routes = [
        // …
        'category/edit' => [
            'POST' => [
                'controller' => $categoryController,
                'action' => 'saveEdit'
            ],
            'GET' => [
                'controller' => $categoryController,
                'action' => 'edit'
            ],
            'login' => true
        ],
        // …
    ];
}
添加保存方法

Category 类中添加 saveEdit 方法,用于处理表单提交并将分类信息保存到数据库。

public function saveEdit() {
    $category = $_POST['category'];
    $this->categoriesTable->save($category);
    header('location: /category/list');
}
创建分类列表页面

创建 categories.html.php 模板,用于显示分类列表,并为每个分类提供编辑和删除按钮。

<h2>Categories</h2>
<a href="/category/edit">Add a new category</a>
<?php foreach ($categories as $category): ?>
<blockquote>
    <p>
        <?=htmlspecialchars($category->name, ENT_QUOTES, 'UTF-8')?>
        <a href="/category/edit?id=<?=$category->id?>">Edit</a>
        <form action="/category/delete" method="post">
            <input type="hidden" name="id" value="<?=$category->id?>">
            <input type="submit" value="Delete">
        </form>
    </p>
</blockquote>
<?php endforeach; ?>
添加列表方法和路由

Category 类中添加 list 方法,用于获取所有分类信息并返回给模板。

public function list() {
    $categories = $this->categoriesTable->findAll();
    $title = 'Joke Categories';
    return [
        'template' => 'categories.html.php',
        'title' => $title,
        'variables' => [
            'categories' => $categories
        ]
    ];
}

IjdbRoutes 中配置分类列表页面的路由。

'category/list' => [
    'GET' => [
        'controller' => $categoryController,
        'action' => 'list'
    ],
    'login' => true
];
添加删除方法和路由

Category 类中添加 delete 方法,用于删除指定的分类。

public function delete() {
    $this->categoriesTable->delete($_POST['id']);
    header('location: /category/list');
}

IjdbRoutes 中配置分类删除的路由。

'category/delete' => [
    'POST' => [
        'controller' => $categoryController,
        'action' => 'delete'
    ],
    'login' => true
];
为笑话分配分类

为了实现笑话与分类的关联,需要创建一个关联表 joke_category ,并对相关代码进行修改。

创建关联表

使用 SQL 代码创建 joke_category 表:

CREATE TABLE `ijdb_sample`.`joke_category` (
    `jokeId` INT NOT NULL,
    `categoryId` INT NOT NULL,
    PRIMARY KEY (`jokeId`, `categoryId`)
);

这个表的两个字段 jokeId categoryId 共同作为主键,防止同一个笑话被重复添加到同一个分类中。

修改 Joke 控制器

Joke 控制器的构造函数中添加 $categoriesTable 参数,并在实例化时传入该参数。

class Joke {
    private $authorsTable;
    private $jokesTable;
    private $categoriesTable;
    private $authentication;
    public function __construct(DatabaseTable $jokesTable, DatabaseTable $authorsTable, DatabaseTable $categoriesTable, Authentication $authentication) {
        $this->jokesTable = $jokesTable;
        $this->authorsTable = $authorsTable;
        $this->categoriesTable = $categoriesTable;
        $this->authentication = $authentication;
    }
    // …
}
修改编辑方法

Joke 控制器的 edit 方法中,获取所有分类信息并传递给模板。

public function edit() {
    $author = $this->authentication->getUser();
    $categories = $this->categoriesTable->findAll();
    if (isset($_GET['id'])) {
        $joke = $this->jokesTable->findById($_GET['id']);
    }
    $title = 'Edit joke';
    return [
        'template' => 'editjoke.html.php',
        'title' => $title,
        'variables' => [
            'joke' => $joke ?? null,
            'userId' => $author->id ?? null,
            'categories' => $categories
        ]
    ];
}
修改编辑模板

editjoke.html.php 模板中,使用一系列复选框让用户选择笑话所属的分类。

<form action="" method="post">
    <input type="hidden" name="joke[id]" value="<?=$joke->id ?? ''?>">
    <label for="joketext">Type your joke here:</label>
    <textarea id="joketext" name="joke[joketext]" rows="3" cols="40"><?=$joke->joketext ?? ''?></textarea>
    <p>Select categories for this joke:</p>
    <?php foreach ($categories as $category): ?>
    <input type="checkbox" name="category[]" value="<?=$category->id?>" />
    <label><?=$category->name?></label>
    <?php endforeach; ?>
    <input type="submit" name="submit" value="Save">
</form>
修改 CSS 样式

为了让页面显示更美观,在 jokes.css 中添加以下 CSS 样式:

form p {clear: both;}
input[type="checkbox"] {float: left; clear: left; width: auto; margin-right: 10px;}
input[type="checkbox"] + label {clear: right;}
修改保存方法

Joke 控制器的 saveEdit 方法中,处理分类信息并将其保存到 joke_category 表中。

public function saveEdit() {
    $author = $this->authentication->getUser();
    $joke = $_POST['joke'];
    $joke['jokedate'] = new \DateTime();
    $jokeEntity = $author->addJoke($joke);
    foreach ($_POST['category'] as $categoryId) {
        $jokeEntity->addCategory($categoryId);
    }
    header('location: /joke/list');
}
修改 addJoke 方法

Author 类的 addJoke 方法中,返回一个 Joke 实体实例。

public function addJoke($joke) {
    $joke['authorId'] = $this->id;
    return $this->jokesTable->save($joke);
}
修改 DatabaseTable 类的 save 方法

DatabaseTable 类的 save 方法中,创建并返回一个实体实例,并将数据写入该实例。

public function save($record) {
    $entity = new $this->className(...$this->constructorArgs);
    try {
        if ($record[$this->primaryKey] == '') {
            $record[$this->primaryKey] = null;
        }
        $this->insert($record);
    } catch (\PDOException $e) {
        $this->update($record);
    }
    foreach ($record as $key => $value) {
        if (!empty($value)) {
            $entity->$key = $value;
        }
    }
    return $entity;
}

通过以上步骤,我们实现了一个功能较为完善的网站,包括笑话的编辑、缓存优化、分类管理以及笑话与分类的关联。这些操作不仅提高了网站的性能,还增强了网站的功能和用户体验。

PHP 网站开发:从关系管理到性能优化

总结与回顾

我们来总结一下前面实现的各项功能及其关键步骤,以下是一个简要的表格梳理:
| 功能模块 | 关键步骤 |
| — | — |
| 编辑笑话 | 1. 判断用户权限,仅作者可编辑
2. 提供编辑表单并处理提交 |
| 缓存优化 | 1. 在 Joke 类添加 $author 属性
2. 修改 getAuthor 方法实现缓存 |
| 笑话分类功能 | 1. 创建分类表
2. 创建分类控制器、模板和方法
3. 配置路由实现分类的增删改查 |
| 为笑话分配分类 | 1. 创建关联表 joke_category
2. 修改 Joke 控制器及相关方法
3. 处理分类信息保存到关联表 |

性能优化的重要性

在整个网站开发过程中,性能优化是一个不可忽视的环节。就像前面提到的缓存优化,数据库查询是一个相对耗时的操作,如果不进行优化,多次重复查询会严重影响网站的响应速度。透明缓存技术通过在类内部进行优化,使得外部使用时无需关心具体实现,只需调用方法即可,大大提高了性能。

例如,在没有使用缓存时,多次调用 getAuthor 方法会多次查询数据库:

echo $joke->getAuthor()->name;
echo $joke->getAuthor()->email;
echo $joke->getAuthor()->password;

而使用缓存后,数据库只会被查询一次:

echo $joke->getAuthor()->name;
echo $joke->getAuthor()->email;
echo $joke->getAuthor()->password;

这个过程可以用以下 mermaid 流程图表示:

graph TD;
    A[调用 getAuthor 方法] --> B{缓存是否存在};
    B -- 是 --> C[返回缓存中的作者信息];
    B -- 否 --> D[查询数据库获取作者信息];
    D --> E[将作者信息存入缓存];
    E --> C;
功能拓展的灵活性

我们实现的笑话分类功能展示了网站功能拓展的灵活性。随着网站的发展,用户可能有更多的需求,比如查看特定类型的笑话。通过创建分类表和关联表,我们可以轻松地为笑话添加分类,并且支持一个笑话属于多个分类。

整个笑话分类功能的实现流程可以概括为以下步骤:
1. 创建分类表 :定义分类的基本信息。
2. 创建控制器和模板 :实现分类的增删改查操作。
3. 配置路由 :将不同的操作映射到相应的控制器方法。
4. 创建关联表 :建立笑话与分类的多对多关系。
5. 修改控制器和方法 :处理分类信息的保存和关联。

这个流程可以用以下 mermaid 流程图表示:

graph LR;
    A[创建分类表] --> B[创建控制器和模板];
    B --> C[配置路由];
    C --> D[创建关联表];
    D --> E[修改控制器和方法];
代码复用与框架的优势

在开发过程中,我们使用的类和框架体现了代码复用的优势。例如, DatabaseTable 类的 save 方法经过修改后,可以在不同的实体类中复用,每次保存数据时都能返回一个实体实例,方便后续操作。

public function save($record) {
    $entity = new $this->className(...$this->constructorArgs);
    try {
        if ($record[$this->primaryKey] == '') {
            $record[$this->primaryKey] = null;
        }
        $this->insert($record);
    } catch (\PDOException $e) {
        $this->update($record);
    }
    foreach ($record as $key => $value) {
        if (!empty($value)) {
            $entity->$key = $value;
        }
    }
    return $entity;
}

通过这样的设计,我们避免了重复编写大量的数据库操作代码,提高了开发效率。而且,当需要对数据库操作进行修改时,只需要在 DatabaseTable 类中进行修改,就可以影响到所有使用该类的地方。

未来展望

虽然我们已经实现了网站的基本功能,但仍然有很多可以改进和拓展的地方。例如,可以添加用户对笑话的点赞、评论功能,进一步丰富用户体验;可以对分类进行更细致的管理,如添加分类的描述信息等。

在性能方面,可以考虑使用更高级的缓存技术,如 Redis 缓存,进一步提高网站的响应速度。同时,对数据库进行优化,如创建合适的索引,也可以提高查询效率。

总之,通过不断地优化和拓展,我们可以让网站更加完善,满足用户日益增长的需求。

通过以上的开发和优化过程,我们可以看到,一个功能完善、性能良好的网站需要在多个方面进行精心设计和实现。从权限控制到性能优化,从功能拓展到代码复用,每一个环节都对网站的质量有着重要的影响。希望这些经验和方法能对大家的网站开发有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值