Yii is a high-performance, component-based PHP framework for rapid development of modern Web applications.

Today, with the idea of experiencing Yii2, I plan to use Yii2 to build a Todo List from 0 to 1 and complete the following functions:

  1. Can be based onkeycreateTodo ItemAnd then according tokeyQuery the correspondingTodo Item.
  2. Can top, complete, delete a singleTodo Item, placed at the top ofTodo ItemIt’s going to be at the front, doneTodo ItemIt’s going to be at the back.

Initialize the YII repository

Use the following command to initialize a YII repository.

composer create-project --prefer-dist yiisoft/yii2-app-basic basic
Copy the code

However, my MAC always fails to connect to the network using this method and some dependencies fail to be installed, so I choose the second method here. (below)

Download the archive at Yiiframework and unzip it to the project directory you want to place it in.

After downloading and decompressing, you need to first modify the config/web.php file and add a key to the cookieValidationKey configuration item (just enter a random value) so that the project can start normally.

After the project is initialized, let’s run the project using the following command.

php yii serve --port=8888
Copy the code

Then we open http://localhost:8888 and see that our page has successfully launched! (As shown below)

Initialize the data model

Next, we initialize our data model.

The fields we need to create are as follows:

  • Id: auto-increment primary key;
  • Key: Todo key;
  • Title: Todo’s title;
  • Is_completed: Whether Todo completed;
  • Is_top: whether Todo is the top;
  • Is_deleted: Indicates whether Todo was deleted.

In the above fields, the most common scenario is to retrieve the relevant Todo Item by key, so we should create a normal index for key.

To sum up, our SQL statement should look like this:

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;
Copy the code

Execute this SQL in the database to create the corresponding data table.

Then, we can also view the index we created with the following statement.

SHOW INDEX FROM `todos`;
Copy the code

Process Todo business logic

With the data table successfully created, we are ready to start writing the toDO-related business logic.

Before we do that, we need to do some configuration initialization for Yii.

Example Initialize Yii configuration

First of all, our PHP service needs to connect to the database, so you need to configure your database connection, i.e. Config /db.php:

  • Database Configuration

      

return [
    'class'= >'yii\db\Connection'.'dsn'= >'mysql:host=[mysql server address]; Port = [mysql port]; Dbname =[database name]'.'username'= >'[database user name]'.'password'= >'database password'.'charset'= >'utf8'.'attributes'= > [// Return the int type as the original type
        PDO::ATTR_STRINGIFY_FETCHES => false,
        PDO::ATTR_EMULATE_PREPARES => false]].Copy the code
  • URL beautification configuration

Next, let’s configure URL beautification so that it can be accessed in a standard restful style by adjusting the urlManager in config/web.php. (below)

'urlManager'= > ['enablePrettyUrl'= >true.'enableStrictParsing'= >false.'showScriptName'= >false.'rules'= > []],Copy the code
  • JSON input parameter configuration

Then, we need to modify the request configuration to accept application/ JSON inputs.

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

After modifying the configuration, restart your project.

createTodoModel + TodoRepository + TodoService + TodoController

Let’s start by creating Todo’s data entity class, TodoModel, which will continue through the Todo List lifecycle.


      
namespace 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;
}
Copy the code

Next, we create TodoRepository for data persistence. — SQL is written here.


      

namespace 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) {}}Copy the code

Next, let’s create the TodoService to handle the business logic. All business logic is placed here.


      

namespace 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) {}}Copy the code

Finally, we create a TodoController to control the business process and handle interface requests. The logic for interacting with the client is placed here.


      

namespace 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();
    }

    // Convert the response data to JSON
    public function behaviors()
    {
        return[['class' => \yii\filters\ContentNegotiator::className(),
                'only'= > ['index'.'view'].'formats'= > ['application/json' => \yii\web\Response::FORMAT_JSON,
                ],
            ],
        ];
    }

    public function actionGetTodoList() {}}Copy the code

With the basic TodoModel + TodoRepository + TodoService + TodoController (MVC) model in place, we are ready to add real, valid business logic.

Query the correspondingkeyTodo List

We are now ready to query the corresponding toDo list by key.

Let’s start by editing the selectAll of TodoRepository to write the corresponding SQL query logic.

class TodoRepository {
  / * * *@throws \yii\db\Exception
   */
  public static function selectAll(TodoModel $todo) {
    $db = Yii::$app->db;
    // Assemble the SQL statement to query the data corresponding to the key and not deleted
    // Query data in ascending order of 'done' and descending order of 'top'
    $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();
  }
  / /...
}
Copy the code

After the TodoRepository SQL statement has been edited, we can try it out in the database. (As shown below)

As you can see from the figure above, the SQL works as expected — using key as the index, retrieving only four pieces of data (the database has 10 pieces of data at this point).

This SQL also involves Using filesort, I have not thought of a better optimization scheme, you can try to optimize this SQL.

Let’s edit the actionGetTodoList method of TodoController (TodoService doesn’t need to be modified).

public function actionGetTodoList() {
    $model = new TodoModel();
    $params = Yii::$app->request->get();
    // Select key from query
    $model->key = $params['key'];
    return $this->todoService->getAllTodo($model);
}
Copy the code

In the logical complement, open the page http://localhost:8888/todo/get-todo-list? Key =test to verify this effect. (As shown below)

As you can see from the figure above, the data comes back filtered and sorted as we expected!

Complete remaining business logic – add, delete and modify

The next step is to add the logic of add, delete, and change in turn, which should be the simplest and most classic CRUD. (below)

  • TodoModel.php

      

namespace 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']]. }}Copy the code
  • TodoRepository.php

      

namespace app\repositories;

use Yii;
use app\models\TodoModel;

class TodoRepository
{
    / * * *@throws \yii\db\Exception
     */
    public static function selectAll(TodoModel $todo)
    {
        $db = Yii::$app->db;
        // Assemble the SQL statement to query the data corresponding to the key and not deleted
        // Query data in ascending order of 'done' and descending order of 'top'
        $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(); }}Copy the code
  • TodoService.php

      

namespace 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); }}Copy the code
  • TodoController.php

      

namespace 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();
    }

    // Convert the response data to 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();
        // Select key from query
        $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]; }}Copy the code

So our Todo List system is almost complete, it has done the following:

  1. Can be based onkeycreateTodo ItemAnd then according tokeyQuery the correspondingTodo Item.
  2. Can top, complete, delete a singleTodo Item, placed at the top ofTodo ItemIt’s going to be at the front, doneTodo ItemIt’s going to be at the back.

Of course, we also need to consider parameter validation, optimization of big data queries, simpler parameter binding, etc., which will not be expanded here, but will probably be covered in a new article.

The deployment of application

Now let’s deploy our Todo List system online.

Start the Docker container

Yii2 is easy to deploy because Yii has the Docker-compose configuration file built in.

Docker-compose up -d: docker-compose up -d: docker-compose up -d: docker-compose up -d: docker-compose up (As shown below)

Now let’s change the port mapping of docker-comemage. yml to a special port, 9999.

ports:
   - '9999:80'
Copy the code

Then, in our server (my server is Ali Cloud ECS), pull down the corresponding warehouse code and run docker-compose up -d to start the container.

Configure Nginx

After the service is started, we need to configure nginx to forward the request hacker.jt-gmall.com to port 9999.

Then, a cross-domain header is added to Nginx to allow the front end to make cross-domain requests (the last few lines).

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_ciphersALL:! 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" "*"; The global variable gets the origin of the current request
      add_header "Access-Control-Allow-Methods" "*"; Allow request methods
      add_header "Access-Control-Allow-Headers" "*"; Allow requested headers
      If OPTIONS is requested, 204 is returned
      if ($request_method = 'OPTIONS') {
        return 204; }}}Copy the code

Install dependencies

After the service is started and nginx is configured, the following error may occur.

This is because in Git version management, the vendor directory of Yii will be ignored. We just need to use composer to install dependencies again. Run the following command.

composer update
composer install
Copy the code

Since config/db.php contains database connection information, I didn’t put it in Git repository either.

If you are using my demo, please complete this file as well.

Then, we open a browser and type hacker.jt-gmall.com/todo/get-to… Look at the effect! (As shown below)

And you’re done!

summary

In this article, I wrote about my experience with building a basic Todo List service using Yii.

In practice, it is relatively easy to build a server-side business site using Yii, and the classic MVC pattern is relatively easy to understand.

In a future article, I will probably summarize the further uses of Yii.

Finally, the Demo address of this experience is attached.

One last thing

If you’ve already seen it, please give it a thumbs up

Your likes are the greatest encouragement to the author, and can also let more people see this article!

If you find this article helpful, please help to light up the star on Github.