没有不值得去解决的问题,也没有不值得去学习的技术!

Create a GitHub Repository for yiisoft/yii2-app-advanced and Build a RESTful API With Layered Models

Create a GitHub Repository for yiisoft/yii2-app-advanced and Build a RESTful API With Layered Models

1. Definitions and conventions:
Definitions:
(1) Data layer: models is used to define data-related automatic validation, automatic completion, and data access interfaces;
(2) Logic layer: logics is used to define data-related business logic;

Conventions:
(1) Model class files in the /common/models directory are only allowed to be generated by the Gii tool, serving as the common model data layer;
(2) Model class files in the /common/logics directory are business logic related, inheriting from the /common/models data layer, serving as the common model logic layer;
(3) When referencing model class files in the /common directory, only reference the model class files in /common/logics, for example:

PHP
public $modelClass = 'common\logics\User';

(4) Model class files in the /api/models, /backend/models, and /frontend/models directories are business logic related (only related to their respective applications), inheriting from the /common/logics common logic layer;
(5) When referencing model class files in the API, frontend, and backend application directories, only reference the corresponding model class files in their own directories, for example:

PHP
use api\models\User;

(6) Model class files in the /api/modules/v1/models directory are business logic related (only related to the module), inheriting from the /api/models application logic layer;
(7) When referencing model class files in the module directory, only reference the corresponding model class files in their own module, for example:

PHP
public $modelClass = 'api\modules\v1\models\User';

2. Create a logics directory in the common directory to serve as the directory for the MySQL model logic layer, as shown in Figure 1

Create a logics directory in the common directory to serve as the directory for the MySQL model logic layer
Figure 1

3. Copy \common\models\LoginForm.php and \common\models\User.php to \common\logics, as shown in Figure 2

Copy \common\models\LoginForm.php and \common\models\User.php to \common\logics
Figure 2

4. MySQL model files in the common/models directory should only be generated by the Gii tool; delete \common\models\LoginForm.php, as shown in Figure 3

MySQL model files in the common/models directory should only be generated by the Gii tool; delete \common\models\LoginForm.php
Figure 3

5. Configure routing to use pretty URLs; edit \frontend\config\main.php

PHP
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
            ],
        ],

6. For internationalization support, create directories \common\messages\en-US\model and \common\messages\zh-CN\model for the model data layer’s US English and Simplified Chinese, respectively; create \common\messages\{language}\app.php, \common\messages\{language}\error.php, and \common\messages\{language}\success.php for application global messages, application error messages, and application success messages, respectively, as shown in Figure 4

For internationalization support, create directories \common\messages\en-US\model and \common\messages\zh-CN\model for the model data layer's US English and Simplified Chinese, respectively; create \common\messages\{language}\app.php, \common\messages\{language}\error.php, and \common\messages\{language}\success.php for application global messages, application error messages, and application success messages, respectively
Figure 4

app.php

PHP
<?php
return [
];

error.php

PHP
<?php
return [
    20000 => 'error',
];

success.php

PHP
<?php
return [
    10000 => 'success',
];

7. Configure the application language. The source language is US English and the target language is Simplified Chinese. When the source and target languages are the same, whether to force message translation defaults to false; set it to true. Edit \common\config\main.php

PHP
    'sourceLanguage' => 'en-US',
    'language' => 'zh-CN',
    'components' => [
        'i18n' => [
            'translations' => [
                '*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'forceTranslation' => true,
                    'basePath'=>'@common/messages',
                    'fileMap' => [
                        'app' => 'app.php',
                        'error' => 'error.php',
                        'success' => 'success.php',
                    ],
                ],
            ],
        ],
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
    ],

8. Open the URL: http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model . In the options, set the namespace to common\models. At this point, internationalization support is required; overwrite \common\models\User.php, as shown in Figure 5

Open the URL: http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model . In the options, set the namespace to common\models. At this point, internationalization support is required; overwrite \common\models\User.php
Figure 5
PHP
<?php
 
namespace common\models;
 
use Yii;
 
/**
 * This is the model class for table "{{%user}}".
 *
 * @property int $id
 * @property string $username
 * @property string $auth_key
 * @property string $password_hash
 * @property string $password_reset_token
 * @property string $email
 * @property int $status
 * @property int $created_at
 * @property int $updated_at
 */
class User extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%user}}';
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['username', 'auth_key', 'password_hash', 'email', 'created_at', 'updated_at'], 'required'],
            [['status', 'created_at', 'updated_at'], 'integer'],
            [['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255],
            [['auth_key'], 'string', 'max' => 32],
            [['username'], 'unique'],
            [['email'], 'unique'],
            [['password_reset_token'], 'unique'],
        ];
    }
 
    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('model/user', 'ID'),
            'username' => Yii::t('model/user', 'Username'),
            'auth_key' => Yii::t('model/user', 'Auth Key'),
            'password_hash' => Yii::t('model/user', 'Password Hash'),
            'password_reset_token' => Yii::t('model/user', 'Password Reset Token'),
            'email' => Yii::t('model/user', 'Email'),
            'status' => Yii::t('model/user', 'Status'),
            'created_at' => Yii::t('model/user', 'Created At'),
            'updated_at' => Yii::t('model/user', 'Updated At'),
        ];
    }
}
 
 

9. MySQL model files in the common/logics directory are business logic related and inherit from the \common\models data layer. Open the URL: http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model , options, as shown in Figure 6

MySQL model files in the common/logics directory are business logic related and inherit from the \common\models data layer. Open the URL: http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model , options
Figure 6

10. Based on the diff, edit \common\logics\User.php, as shown in Figure 7

11. MySQL model files in the common/logics directory are business logic related and inherit from the \common\models\User data layer

PHP
<?php
 
namespace common\logics;
 
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\web\IdentityInterface;
 
/**
 * This is the model class for table "{{%user}}".
 *
 * @property int $id
 * @property string $username
 * @property string $auth_key
 * @property string $password_hash
 * @property string $password_reset_token
 * @property string $email
 * @property int $status
 * @property int $created_at
 * @property int $updated_at
 * @property string $password write-only password
 */
class User extends \common\models\User implements IdentityInterface
{
    const STATUS_DELETED = 0;
    const STATUS_ACTIVE = 10;
 
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            TimestampBehavior::className(),
        ];
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['username', 'auth_key', 'password_hash', 'email'], 'required'],
            [['status', 'created_at', 'updated_at'], 'integer'],
            [['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255],
            [['auth_key'], 'string', 'max' => 32],
            [['username'], 'unique'],
            [['email'], 'unique'],
            [['password_reset_token'], 'unique'],
            ['status', 'default', 'value' => self::STATUS_ACTIVE],
            ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
        ];
    }
 
    /**
     * @inheritdoc
     */
    public static function findIdentity($id)
    {
        return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
    }
 
    /**
     * @inheritdoc
     */
    public static function findIdentityByAccessToken($token, $type = null)
    {
        throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
    }
 
    /**
     * Finds user by username
     *
     * @param string $username
     * @return static|null
     */
    public static function findByUsername($username)
    {
        return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
    }
 
    /**
     * Finds user by password reset token
     *
     * @param string $token password reset token
     * @return static|null
     */
    public static function findByPasswordResetToken($token)
    {
        if (!static::isPasswordResetTokenValid($token)) {
            return null;
        }
 
        return static::findOne([
            'password_reset_token' => $token,
            'status' => self::STATUS_ACTIVE,
        ]);
    }
 
    /**
     * Finds out if password reset token is valid
     *
     * @param string $token password reset token
     * @return bool
     */
    public static function isPasswordResetTokenValid($token)
    {
        if (empty($token)) {
            return false;
        }
 
        $timestamp = (int) substr($token, strrpos($token, '_') + 1);
        $expire = Yii::$app->params['user.passwordResetTokenExpire'];
        return $timestamp + $expire >= time();
    }
 
    /**
     * @inheritdoc
     */
    public function getId()
    {
        return $this->getPrimaryKey();
    }
 
    /**
     * @inheritdoc
     */
    public function getAuthKey()
    {
        return $this->auth_key;
    }
 
    /**
     * @inheritdoc
     */
    public function validateAuthKey($authKey)
    {
        return $this->getAuthKey() === $authKey;
    }
 
    /**
     * Validates password
     *
     * @param string $password password to validate
     * @return bool if password provided is valid for current user
     */
    public function validatePassword($password)
    {
        return Yii::$app->security->validatePassword($password, $this->password_hash);
    }
 
    /**
     * Generates password hash from password and sets it to the model
     *
     * @param string $password
     */
    public function setPassword($password)
    {
        $this->password_hash = Yii::$app->security->generatePasswordHash($password);
    }
 
    /**
     * Generates "remember me" authentication key
     */
    public function generateAuthKey()
    {
        $this->auth_key = Yii::$app->security->generateRandomString();
    }
 
    /**
     * Generates new password reset token
     */
    public function generatePasswordResetToken()
    {
        $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
    }
 
    /**
     * Removes password reset token
     */
    public function removePasswordResetToken()
    {
        $this->password_reset_token = null;
    }
}
 

12. Create \common\messages\en-US\model\user.php to support message translation when the target language is US English

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:34
 */
 
return [
    'ID' => 'ID',
    'Username' => 'Username',
    'Auth Key' => 'Auth Key',
    'Password Hash' => 'Password Hash',
    'Password Reset Token' => 'Password Reset Token',
    'Email' => 'Email',
    'Status' => 'Status',
    'Created At' => 'Created At',
    'Updated At' => 'Updated At',
];

13. Create \common\messages\zh-CN\model\user.php to support message translation when the target language is Simplified Chinese

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:39
 */
 
return [
    'ID' => 'ID',
    'Username' => '用户名',
    'Auth Key' => '认证密钥',
    'Password Hash' => '密码哈希',
    'Password Reset Token' => '密码重置令牌',
    'Email' => '邮箱',
    'Status' => '状态',
    'Created At' => '创建时间',
    'Updated At' => '更新时间',
];

14. Create \api\models\User.php. MySQL model files in the api/models directory are business logic related (only related to the API) and inherit from the \common\logics\User logic layer

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:44
 */
 
namespace api\models;
 
class User extends \common\logics\User
{
 
}

15. Copy \api\models\User.php to \frontend\models\User.php and \backend\models\User.php, and adjust their respective namespaces
\frontend\models\User.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:44
 */
 
namespace frontend\models;
 
class User extends \common\logics\User
{
 
}

\backend\models\User.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:44
 */
 
namespace backend\models;
 
class User extends \common\logics\User
{
 
}

16. In the API application, search for use common\models\User; and replace it with: use api\models\User;. Apply similar processing in the frontend and backend applications, as shown in Figure 8

In the API application, search for use common\models\User; and replace it with: use api\models\User;. Apply similar processing in the frontend and backend applications
Figure 8

17. In the API application, search for common\models\User and replace it with: api\models\User. Apply similar processing in the frontend and backend applications, as shown in Figure 9

In the API application, search for common\models\User and replace it with: api\models\User. Apply similar processing in the frontend and backend applications
Figure 9

18. Edit \common\logics\LoginForm.php to adjust the namespace

PHP
<?php
namespace common\logics;
 
use Yii;
use yii\base\Model;
 
/**
 * Login form
 */
class LoginForm extends Model
{
    public $username;
    public $password;
    public $rememberMe = true;
 
    private $_user;
 
 
    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            // username and password are both required
            [['username', 'password'], 'required'],
            // rememberMe must be a boolean value
            ['rememberMe', 'boolean'],
            // password is validated by validatePassword()
            ['password', 'validatePassword'],
        ];
    }
 
    /**
     * Validates the password.
     * This method serves as the inline validation for password.
     *
     * @param string $attribute the attribute currently being validated
     * @param array $params the additional name-value pairs given in the rule
     */
    public function validatePassword($attribute, $params)
    {
        if (!$this->hasErrors()) {
            $user = $this->getUser();
            if (!$user || !$user->validatePassword($this->password)) {
                $this->addError($attribute, 'Incorrect username or password.');
            }
        }
    }
 
    /**
     * Logs in a user using the provided username and password.
     *
     * @return bool whether the user is logged in successfully
     */
    public function login()
    {
        if ($this->validate()) {
            return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
        }
         
        return false;
    }
 
    /**
     * Finds user by [[username]]
     *
     * @return User|null
     */
    protected function getUser()
    {
        if ($this->_user === null) {
            $this->_user = User::findByUsername($this->username);
        }
 
        return $this->_user;
    }
}
 
 

19. Create \api\models\LoginForm.php. MySQL model files in the api/models directory are business logic related (only related to the API) and inherit from the \common\logics\LoginForm logic layer

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 11:10
 */
 
namespace api\models;
 
 
class LoginForm extends \common\logics\LoginForm
{
 
}

20. Copy \api\models\LoginForm.php to \frontend\models\LoginForm.php and \backend\models\LoginForm.php, and adjust their respective namespaces
\frontend\models\LoginForm.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 11:10
 */
 
namespace frontend\models;
 
 
class LoginForm extends \common\logics\LoginForm
{
 
}

\backend\models\LoginForm.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 11:10
 */
 
namespace backend\models;
 
 
class LoginForm extends \common\logics\LoginForm
{
 
}

21. In the API application, search for use common\models\LoginForm; and replace it with: use api\models\LoginForm;. Apply similar processing in the frontend and backend applications, as shown in Figure 10

In the API application, search for use common\models\LoginForm; and replace it with: use api\models\LoginForm;. Apply similar processing in the frontend and backend applications
Figure 10

22. In the API application, search for common\models\LoginForm and replace it with: api\models\LoginForm. Apply similar processing in the frontend and backend applications, as shown in Figure 11

In the API application, search for common\models\LoginForm and replace it with: api\models\LoginForm. Apply similar processing in the frontend and backend applications
Figure 11

23. In the common directory, search for common\models\User and replace it with: common\logics\User, with the exception of \common\logics\User.php, as shown in Figure 12

In the common directory, search for common\models\User and replace it with: common\logics\User, with the exception of \common\logics\User.php
Figure 12

24. In the common directory, search for common\models\LoginForm and replace it with: common\logics\LoginForm, as shown in Figure 13

In the common directory, search for common\models\LoginForm and replace it with: common\logics\LoginForm
Figure 13

25. Test the registration feature by opening the URL: http://www.github-shuijingwan-yii2-app-advanced.localhost/site/signup . It works as expected, as shown in Figure 14

Test the registration feature by opening the URL: http://www.github-shuijingwan-yii2-app-advanced.localhost/site/signup . It works as expected
Figure 14

26. View the database, as shown in Figure 15

View the database
Figure 15
系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系