Yii 是一个高性能,基于组件的 PHP 框架,用于疾速开发古代 Web 应用程序。

明天,我本着体验 Yii2 的想法,筹备应用 Yii2 从 0 到 1 来搭建一个 Todo List,并实现以下性能:

  1. 能够基于某个 key 创立 Todo Item,而后依据 key 查问对应的 Todo Item
  2. 能够置顶、实现、删除单条 Todo Item,置顶的 Todo Item 将排列在最后面,实现的 Todo Item 将排列在最初面。

初始化 YII 仓库

应用上面的命令即可初始化一个 YII 的仓库。

composer create-project --prefer-dist yiisoft/yii2-app-basic basic

然而,我的 mac 通过这个办法老是连不上网络,装置某些依赖失败,所以这里抉择第二种形式。(如下)

在 yiiframework 下载归档文件,而后解压到你要搁置的我的项目目录中。

在下载解压实现后,须要先批改 config/web.php 文件,给 cookieValidationKey 配置项增加一个密钥(轻易输出一个值就能够),以便我的项目可能失常启动。

在我的项目初始化实现当前,咱们应用上面这个命令运行我的项目吧。

php yii serve --port=8888

而后咱们关上 http://localhost:8888,看到咱们的页面曾经胜利启动啦!(如下图)

初始化数据模型

接下来,咱们来初始化咱们的数据模型。

咱们须要创立的字段有上面这些:

  • id:自增主键;
  • key:Todo 的 key;
  • title:Todo 的题目;
  • is_completed:Todo 是否实现;
  • is_top:Todo 是否置顶;
  • is_deleted:Todo 是否删除;

而下面这些字段中,咱们最多的场景是通过 key 来捞出相干的 Todo Item,所以应该给 key 建设一个一般索引。

综上,咱们的 sql 语句应该是这样的:

CREATE TABLE IF NOT EXISTS `todos` (    `id` int PRIMARY KEY AUTO_INCREMENT,    `key` varchar(64) NOT NULL DEFAULT '',    `title` varchar(64) NOT NULL DEFAULT '',    `is_top` tinyint(1) NOT NULL DEFAULT 0,    `is_completed` tinyint(1) NOT NULL DEFAULT 0,    `is_deleted` tinyint(1) NOT NULL DEFAULT 0,    index `key`(`key`)) engine=InnoDB CHARSET=utf8;

在数据库中执行该条 SQL,创立对应的数据表。

而后,咱们还能够通过上面这条语句查看咱们创立的索引。

SHOW INDEX FROM `todos`;

解决 Todo 业务逻辑

在数据表创立胜利后,咱们就筹备开始写 Todo 相干的业务逻辑了。

在此之前,咱们还须要做一些 Yii 的配置初始化工作。

初始化 Yii 配置

首先,咱们的 php 服务须要连贯数据库,所以你须要先配置你的数据库连贯,也就是 config/db.php

  • 数据库配置
<?phpreturn [    'class' => 'yii\db\Connection',    'dsn' => 'mysql:host=[mysql服务器地址];port=[mysql端口];dbname=[数据库名称]',    'username' => '[数据库用户名]',    'password' => '[数据库明码]',    'charset' => 'utf8',    'attributes' => [        // 查问时将 int 类型按原类型返回        PDO::ATTR_STRINGIFY_FETCHES => false,        PDO::ATTR_EMULATE_PREPARES => false    ]];
  • URL 丑化配置

而后,咱们再来配置一下 URL 丑化,这样就能够依照规范的 restful 格调进行拜访了,调整 config/web.php 中的 urlManager 即可。(如下)

'urlManager' => [  'enablePrettyUrl' => true,  'enableStrictParsing' => false,  'showScriptName' => false,  'rules' => [  ],],
  • JSON 入参配置

而后,咱们还须要批改一下 request 的配置,以便承受 application/json 的入参。

'components' => [    ...    'request' => [        'parsers' => [            'application/json' => 'yii\web\JsonParser',        ]    ],    ...]

批改完了配置后,能够重启一下你的我的项目。

创立 TodoModel + TodoRepository + TodoService + TodoController

咱们先来创立 Todo 的数据实体类 —— TodoModel,这个模型将会贯通 Todo List 的整个生命周期。

<?phpnamespace app\models;use Yii;use Yii\base\Model;class TodoModel extends Model {    public $id;    public $key;    public $title;    public $is_top;    public $is_completed;    public $is_deleted;}

而后,咱们创立 TodoRepository,用于数据长久化。 —— SQL 写在这里。

<?phpnamespace app\repositories;use app\models\TodoModel;class TodoRepository {    public static function selectAll(TodoModel $todo) {    }    public static function insertOne(TodoModel $todo) {    }    public static function deleteOne(TodoModel $todo) {    }    public static function updateOne(TodoModel $todo) {    }}

接下来,咱们来创立 TodoService,用于解决业务逻辑。 —— 所有业务逻辑放在这里。

<?phpnamespace app\services;use app\models\TodoModel;use app\repositories\TodoRepository;class TodoService {    public function getAllTodo(TodoModel $model) {        return TodoRepository::selectAll($model);    }    public function addTodo(TodoModel $model) {            }    public function topTodo(TodoModel $model) {    }    public function completeTodo(TodoModel $model) {    }    public function deleteTodo(TodoModel $model) {    }}

最初,咱们创立 TodoController,用于管制业务流程和解决接口申请。 —— 与客户端交互的逻辑放在这里。

<?phpnamespace app\controllers;use Yii;use yii\rest\ActiveController;use yii\web\Response;use app\services\TodoService;use app\models\TodoModel;class TodoController extends ActiveController{    public TodoService $todoService;    public function __construct($id, $module, $config = [])    {        parent::__construct($id, $module, $config);        this.$todoService = new TodoService();    }    // 将响应数据转成 JSON    public function behaviors()    {        return [            [                'class' => \yii\filters\ContentNegotiator::className(),                'only' => ['index', 'view'],                'formats' => [                    'application/json' => \yii\web\Response::FORMAT_JSON,                ],            ],        ];    }    public function actionGetTodoList() {          }}

将根底的 TodoModel + TodoRepository + TodoService + TodoController,也就是 MVC 模型筹备好了当前,咱们就筹备开始增加真实有效的业务逻辑了。

查问对应 keyTodo List

咱们当初筹备依据 key 来查问对应的 todo 列表。

咱们首先来编辑 TodoRepositoryselectAll,将对应的 SQL 查问逻辑写好。

class TodoRepository {  /**   * @throws \yii\db\Exception   */  public static function selectAll(TodoModel $todo) {    $db = Yii::$app->db;    // 组装 SQL 语句,查问对应 key 且未删除的数据    // 查问的数据依照 `是否实现` 升序排列,依照 `是否置顶` 降序排列    $sql = "SELECT *             FROM `todos`            WHERE `key` = :code AND `is_deleted` = 0            ORDER BY is_completed ASC, is_top DESC";    return $db->createCommand($sql)->bindValue(':code', $todo->key)->queryAll();  }  //...}

TodoRepositorySQL 语句编辑实现后,咱们能够在数据库中执行试试。(如下图)

从上图能够看出,该 SQL 按咱们料想的运行 —— 应用 key 作为索引,只检索了 4 条数据(此时数据库有 10 条数据)。

这条 SQL 还波及到了 Using filesort,我还没有想到比拟好的优化计划,大家能够尝试一下优化这条 SQL。

咱们来编辑 TodoControlleractionGetTodoList 办法即可(TodoService 不须要批改)。

public function actionGetTodoList() {    $model = new TodoModel();    $params = Yii::$app->request->get();    // 取出 query 参数中的 key 字段    $model->key = $params['key'];    return $this->todoService->getAllTodo($model);}

在逻辑补充完后,关上页面 http://localhost:8888/todo/get-todo-list?key=test 验证一下成果吧。(如下图)

从上图能够看出,数据依照咱们预期的筛选和排序返回了!

补全残余业务逻辑 —— 增删改

接下来,就是顺次将 增删改 的逻辑加上就好了,这应该是最简略也是最经典的 CRUD 了。(如下)

  • TodoModel.php
<?phpnamespace app\models;use Yii;use yii\base\Model;class TodoModel extends Model{    public $id;    public $key = '';    public $title = '';    public $is_top = 0;    public $is_completed = 0;    public $is_deleted = 0;    public function rules()    {        return [            [['id', 'key', 'title'], 'required']        ];    }}
  • TodoRepository.php
<?phpnamespace app\repositories;use Yii;use app\models\TodoModel;class TodoRepository{    /**     * @throws \yii\db\Exception     */    public static function selectAll(TodoModel $todo)    {        $db = Yii::$app->db;        // 组装 SQL 语句,查问对应 key 且未删除的数据        // 查问的数据依照 `是否实现` 升序排列,依照 `是否置顶` 降序排列        $sql = "SELECT *                 FROM `todos`                WHERE `key` = :code AND `is_deleted` = 0                ORDER BY is_completed ASC, is_top DESC";        return $db->createCommand($sql)->bindValue(':code', $todo->key)->queryAll();    }    /**     * @throws \yii\db\Exception     */    public static function insertOne(TodoModel $todo)    {        $db = Yii::$app->db;        return $db->createCommand()->insert('todos', $todo)->execute();    }    /**     * @throws \yii\db\Exception     */    public static function updateOne(array $todoData, string $id)    {        $db = Yii::$app->db;        return $db                ->createCommand()                ->update('todos', $todoData, "id = :id")                ->bindValue("id", $id)                ->execute();    }}
  • TodoService.php
<?phpnamespace app\services;use app\models\TodoModel;use app\repositories\TodoRepository;class TodoService{    public function getAllTodo(TodoModel $model)    {        return TodoRepository::selectAll($model);    }    public function addTodo(TodoModel $model)    {        return TodoRepository::insertOne($model);    }    public function topTodo(TodoModel $model)    {        return TodoRepository::updateOne([            'is_top' => 1        ], $model->id);    }    public function completeTodo(TodoModel $model)    {        return TodoRepository::updateOne([            'is_completed' => 1        ], $model->id);    }    public function deleteTodo(TodoModel $model)    {        return TodoRepository::updateOne([            'is_deleted' => 1        ], $model->id);    }}
  • TodoController.php
<?phpnamespace app\controllers;use Yii;use yii\web\Controller;use app\services\TodoService;use app\models\TodoModel;class TodoController extends Controller{    public $todoService;    public $enableCsrfValidation = false;    public function __construct($id, $module, $config = [])    {        parent::__construct($id, $module, $config);        $this->todoService = new TodoService();    }    // 将响应数据转成 JSON    public function behaviors()    {        return [            [                'class' => \yii\filters\ContentNegotiator::className(),                'formats' => [                    'application/json' => \yii\web\Response::FORMAT_JSON,                ],            ],        ];    }    public function actionGetTodoList()    {        $model = new TodoModel();        $params = Yii::$app->request->get();        // 取出 query 参数中的 key 字段        $model->key = $params['key'];        return [            'code' => 0,            'data' => $this->todoService->getAllTodo($model)        ];    }    public function actionAdd()    {        $model = new TodoModel();        $params = Yii::$app->request->post();        $model->key = $params['key'];        $model->title = $params['title'];        $this->todoService->addTodo($model);        return ['code' => 0];    }    public function actionTop()    {        $model = new TodoModel();        $params = Yii::$app->request->post();        $model->id = $params['id'];        $this->todoService->topTodo($model);        return ['code' => 0];    }    public function actionComplete()    {        $model = new TodoModel();        $params = Yii::$app->request->post();        $model->id = $params['id'];        $this->todoService->completeTodo($model);        return ['code' => 0];    }    public function actionDelete()    {        $model = new TodoModel();        $params = Yii::$app->request->post();        $model->id = $params['id'];        $this->todoService->deleteTodo($model);        return ['code' => 0];    }}

如此一来,咱们的 Todo List 零碎就根本实现了,它曾经实现了上面这些性能:

  1. 能够基于某个 key 创立 Todo Item,而后依据 key 查问对应的 Todo Item
  2. 能够置顶、实现、删除单条 Todo Item,置顶的 Todo Item 将排列在最后面,实现的 Todo Item 将排列在最初面。

当然,咱们还须要思考参数验证、大数据查问的优化问题、更简洁的参数绑定等等问题,这里就不做开展了,可能会以一期新的文章进行解说。

部署利用

当初,咱们来将咱们的 Todo List 零碎部署到线上吧。

启动 Docker 容器

Yii2 的部署非常简单,因为 Yii 内置了 docker-compose 配置文件。

所以,咱们只须要在文件夹内运行 docker-compose up -d 就能够启动一个 docker 服务了。(如下图)

当初,咱们批改一下 docker-compose.yml 的端口映射改一下,将其改成一个比拟非凡的端口 —— 9999

ports:   - '9999:80'

而后,咱们在咱们的服务器(我的服务器是阿里云 ECS)内,把对应的仓库代码拉下来,运行 docker-compose up -d 启动容器即可。

配置 Nginx

服务启动后,咱们须要配置 nginx,将咱们指定域名的申请 hacker.jt-gmall.com 转发到 9999 端口。

而后,在 nginx 上加上跨域头,容许前端跨域申请(最初几行)。

server {    listen 443;    server_name hacker.jt-gmall.com;    ssl on;    ssl_certificate /https/hacker.jt-gmall.com.pem;    ssl_certificate_key /https/hacker.jt-gmall.com.key;    ssl_session_timeout 5m;    ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;    ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2;    ssl_prefer_server_ciphers on;    location / {      index index.html index.jsp;      client_max_body_size 300m;      client_body_buffer_size 128k;      proxy_connect_timeout 600;      proxy_read_timeout 600;      proxy_send_timeout 600;      proxy_buffer_size 64k;      proxy_buffers 4 64k;      proxy_busy_buffers_size 64k;      proxy_temp_file_write_size 64k;      proxy_set_header X-Real-IP $remote_addr;      proxy_set_header Host $host;      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;      proxy_http_version 1.1;      proxy_pass http://127.0.0.1:9999;      add_header "Access-Control-Allow-Origin" "*"; # 全局变量取得以后申请origin,带cookie的申请不反对*      add_header "Access-Control-Allow-Methods" "*"; # 容许申请办法      add_header "Access-Control-Allow-Headers" "*"; # 容许申请的 header      # 如果是 OPTIONS 申请,则返回 204      if ($request_method = 'OPTIONS') {        return 204;      }    }  }

装置依赖

在服务启动并且配置好了 nginx 后进行拜访,可能会呈现下图这个谬误。

这是因为 Git 版本治理中,会疏忽 Yiivendor 目录,咱们只须要应用 composer 将依赖重新安装一遍即可,运行上面这个命令。

composer updatecomposer install

因为 config/db.php 中蕴含了数据库连贯信息,我也没有放到 Git 仓库中。

如果你在应用我的 demo,也请将这个文件补齐。

而后,咱们关上浏览器,输出 https://hacker.jt-gmall.com/todo/get-todo-list?key=test 看看成果吧!(如下图)

功败垂成啦!

小结

在本篇文章中,我针对本人应用 Yii 搭建一个根底 Todo List 服务的体验,写了一篇文章。

实际操作下来,发现应用 Yii 搭建一个服务端业务站点还是比较简单的,经典的 MVC 模式也比拟浅显易懂。

在后续的文章里,我可能会针对 Yii 的进阶应用再进行演绎总结。

最初附上本次体验的 Demo 地址。

最初一件事

如果您曾经看到这里了,心愿您还是点个赞再走吧~

您的点赞是对作者的最大激励,也能够让更多人看到本篇文章!

如果感觉本文对您有帮忙,请帮忙在 github 上点亮 star 激励一下吧!