基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,新建接口应用(实现 RESTful 风格的 Web Service 服务的 API),实现 ActiveRecord 的软删除,生成 ActiveQuery,自定义查询类 (六) (1)
1、基于 yii2tech\ar\softdelete\SoftDeleteBehavior,实现 ActiveRecord 的软删除,打开网址:https://github.com/yiisoft/yii2/blob/master/docs/guide-zh-CN/concept-behaviors.md ,如图1
2、安装 Yii2 的 ActiveRecord 软删除扩展,用 self-update 命令更新 Composer 为最新版本,用 update 命令获取依赖的最新版本,并且升级 composer.lock 文件,执行命令,如图2、图3
1 2 3 4 5 | composer require --prefer-dist yii2tech/ar-softdelete composer self-update composer global require "fxp/composer-asset-plugin" composer global update composer update |
3、数据库迁移,user 表的 status 字段,之前的注释为:状态,0:删除;10:活跃,调整其注释:状态,-1:删除;0:禁用;1:启用,执行命令,如图4
1 | .\yii migrate/create update_status_to_user |
4、编辑 \console\migrations\m180711_054308_update_status_to_user.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?php use yii\db\Migration; /** * Class m180711_054308_update_status_to_user */ class m180711_054308_update_status_to_user extends Migration { /** * {@inheritdoc} */ public function safeUp() { $this ->alterColumn( '{{%user}}' , 'status' , $this ->smallInteger(6)->notNull()->defaultValue(1)->comment( '状态,-1:删除;0:禁用;1:启用' )); $this ->alterColumn( '{{%user}}' , 'created_at' , $this ->integer()->notNull()->defaultValue(0)->comment( '创建时间' )); $this ->alterColumn( '{{%user}}' , 'updated_at' , $this ->integer()->notNull()->defaultValue(0)->comment( '更新时间' )); } /** * {@inheritdoc} */ public function safeDown() { $this ->alterColumn( '{{%user}}' , 'status' , $this ->smallInteger(6)->notNull()->defaultValue(10)->comment( '' )); $this ->alterColumn( '{{%user}}' , 'created_at' , $this ->integer()->notNull()->comment( '' )); $this ->alterColumn( '{{%user}}' , 'updated_at' , $this ->integer()->notNull()->comment( '' )); } /* // Use up()/down() to run migration code without a transaction. public function up() { } public function down() { echo "m180711_054308_update_status_to_user cannot be reverted.\n"; return false; } */ } |
5、执行数据库迁移命令,如图5
1 | .\yii migrate |
6、查看数据库表 user 结构,符合预期,如图6
7、扩展为 ActiveRecord 的所谓“软”删除提供支持,这意味着记录不会从数据库中删除,而是标记一些标记或状态,这表示它不再处于活动状态。附加行为至 \common\logics\User.php,将 STATUS_ACTIVE 替换为 STATUS_ENABLED,将 STATUS_DELETED 替换为 STATUS_DISABLED
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | <?php namespace common\logics; use Yii; use yii\base\NotSupportedException; use yii\behaviors\TimestampBehavior; use yii2tech\ar\softdelete\SoftDeleteBehavior; 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 = -1; //状态:删除 const STATUS_DISABLED = 0; //状态:禁用 const STATUS_ENABLED = 1; //状态:启用 /** * @inheritdoc */ public function behaviors() { return [ 'timestampBehavior' => [ 'class' => TimestampBehavior::className(), 'attributes' => [ self::EVENT_BEFORE_INSERT => [ 'created_at' , 'updated_at' ], self::EVENT_BEFORE_UPDATE => 'updated_at' , SoftDeleteBehavior::EVENT_BEFORE_SOFT_DELETE => 'updated_at' , ] ], 'softDeleteBehavior' => [ 'class' => SoftDeleteBehavior::className(), 'softDeleteAttributeValues' => [ 'status' => self::STATUS_DELETED ], ], ]; } /** * @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_ENABLED], [ 'status' , 'in' , 'range' => [self::STATUS_DELETED, self::STATUS_DISABLED, self::STATUS_ENABLED]], ]; } /** * @inheritdoc */ public static function findIdentity( $id ) { return static ::findOne([ 'id' => $id , 'status' => self::STATUS_ENABLED]); } /** * @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_ENABLED]); } /** * 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_ENABLED, ]); } /** * 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; } } |
8、在目录 \api 中将 const STATUS_ACTIVE = 10; //状态:活跃 批量替换为 const STATUS_ENABLED = 1; //状态:启用,相应注释也需要替换,将 const STATUS_DELETED = 0; //状态:已删除 批量替换为 const STATUS_DISABLED = 0; //状态:禁用
9、在目录 \ 中将 STATUS_ACTIVE 批量替换为 STATUS_ENABLED
10、打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model ,重新生成 /common/models 目录中的模型类文件,勾选 Generate ActiveQuery ,如图7
11、在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models\User 数据层,编辑 \common\logics\User.php,调整 rules(),后续数据层模型结构调整后,无需手动编辑 rules(),添加 find(),以使用自定义的查询类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /** * @inheritdoc */ public function rules() { $rules = [ [ 'status' , 'default' , 'value' => self::STATUS_ENABLED], [ 'status' , 'in' , 'range' => [self::STATUS_DELETED, self::STATUS_DISABLED, self::STATUS_ENABLED]], ]; $parentRules = parent::rules(); return ArrayHelper::merge( $rules , $parentRules ); } /** * {@inheritdoc} * @return UserQuery the active query used by this AR class. */ public static function find() { return new UserQuery(get_called_class()); } |
12、在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models\UserQuery.php 数据层,编辑 \common\logics\UserQuery.php,其为活动查询类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php namespace common\logics; /** * This is the ActiveQuery class for [[User]]. * * @see User */ class UserQuery extends \common\models\UserQuery { // 默认加上一些条件(不等于 状态:删除) public function init() { $this ->andWhere([ '!=' , 'status' , User::STATUS_DELETED]); parent::init(); } // 等于 状态:禁用 public function disabled() { return $this ->andWhere([ 'status' => User::STATUS_DISABLED]); } // 等于 状态:启用 public function enabled() { return $this ->andWhere([ 'status' => User::STATUS_ENABLED]); } } |
13、新建 \api\models\UserQuery.php,在api/models目录中的MySQL模型文件为业务逻辑相关(仅与api相关),继承至 \common\logics\UserQuery 逻辑层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php /** * Created by PhpStorm. * User: WangQiang * Date: 2018/07/11 * Time: 16:07 */ namespace api\models; /** * This is the ActiveQuery class for [[User]]. * * @see User */ class UserQuery extends \common\logics\UserQuery { // 默认加上一些条件(不等于 状态:禁用) public function init() { $this ->andWhere([ '!=' , 'status' , User::STATUS_DISABLED]); parent::init(); } } |
14、编辑 \api\models\User.php,添加 find()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php /** * Created by PhpStorm. * User: WangQiang * Date: 2018/04/04 * Time: 10:44 */ namespace api\models; class User extends \common\logics\User { /** * {@inheritdoc} * @return UserQuery the active query used by this AR class. */ public static function find() { return new UserQuery(get_called_class()); } } |
15、新建 \api\modules\v1\models\UserQuery.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php /** * Created by PhpStorm. * User: WangQiang * Date: 2018/07/11 * Time: 16:15 */ namespace api\modules\v1\models; /** * This is the ActiveQuery class for [[User]]. * * @see User */ class UserQuery extends \api\models\UserQuery { } |
16、编辑 \api\modules\v1\models\User.php,添加 find()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php /** * Created by PhpStorm. * User: WangQiang * Date: 2018/04/04 * Time: 16:04 */ namespace api\modules\v1\models; class User extends \api\models\User { /** * {@inheritdoc} * @return UserQuery the active query used by this AR class. */ public static function find() { return new UserQuery(get_called_class()); } } |
17、编辑 \api\rests\user\IndexAction.php,调整构建查询,使用活动查询类的方法,注:如果是面向后端的接口,则过滤删除的资源,如果是面向前端的接口,则过滤删除、禁用的资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | <?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\user; use Yii; use yii\data\ActiveDataProvider; /** * IndexAction implements the API endpoint for listing multiple models. * * For more details and usage information on IndexAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class IndexAction extends \yii\rest\IndexAction { /** * Prepares the data provider that should return the requested collection of the models. * @return ActiveDataProvider */ protected function prepareDataProvider() { $requestParams = Yii:: $app ->getRequest()->getBodyParams(); if ( empty ( $requestParams )) { $requestParams = Yii:: $app ->getRequest()->getQueryParams(); } $filter = null; if ( $this ->dataFilter !== null) { $this ->dataFilter = Yii::createObject( $this ->dataFilter); if ( $this ->dataFilter->load( $requestParams )) { $filter = $this ->dataFilter->build(); if ( $filter === false) { return $this ->dataFilter; } } } if ( $this ->prepareDataProvider !== null) { return call_user_func( $this ->prepareDataProvider, $this , $filter ); } /* @var $modelClass \yii\db\BaseActiveRecord */ $modelClass = $this ->modelClass; $query = $modelClass ::find()->enabled(); if (! empty ( $filter )) { $query ->andWhere( $filter ); } return Yii::createObject([ 'class' => ActiveDataProvider::className(), 'query' => $query , 'pagination' => [ 'params' => $requestParams , ], 'sort' => [ 'params' => $requestParams , ], ]); } } |
18、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | { "code": 10000, "message": "获取用户列表成功", "data": { "items": [ { "id": 2, "username": "111111", "auth_key": "bt7ZrxlSgiaWs3Zsh4w3ogvYyOvrMrcK", "password_hash": "$2y$13$ED00FoPy.mRZy2wNgvV4FurJ/JMmDvZzOEf6Xt7lo8iyCE6CXGoSW", "password_reset_token": null, "email": "222222@163.com", "status": 1, "created_at": 1531278558, "updated_at": 1531285341 }, { "id": 4, "username": "444444", "auth_key": "D9kAKrUh0tqpEvufckK94j_Xt_5oxxQ2", "password_hash": "$2y$13$n7meDsno37xnEMBj4DmYaO8esNe.uritq2VOq4.dEn.ycg6.NkS.K", "password_reset_token": null, "email": "444444@163.com", "status": 1, "created_at": 1531294458, "updated_at": 1531294458 } ], "_links": { "self": { } }, "_meta": { "totalCount": 2, "pageCount": 1, "currentPage": 1, "perPage": 20 } } } |
19、查看生成的 SQL 语句,符合预期,`status` != -1 在 \common\logics\UserQuery.php 中定义,`status` != 0 在 \api\models\UserQuery.php 中定义(面向前端),`status`=1 在 \api\rests\user\IndexAction.php 中定义
1 2 | SELECT COUNT (*) FROM ` user ` WHERE (`status` != 0) AND (`status` != -1) AND (`status`=1) SELECT * FROM ` user ` WHERE (`status` != 0) AND (`status` != -1) AND (`status`=1) LIMIT 20 |
20、编辑 \api\rests\user\ViewAction.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\user; use Yii; /** * ViewAction implements the API endpoint for returning the detailed information about a model. * * For more details and usage information on ViewAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class ViewAction extends Action { /** * Displays a model. * @param string $id the primary key of the model. * @return \yii\db\ActiveRecordInterface the model being displayed */ public function run( $id ) { $model = $this ->findModel( $id ); if ( $this ->checkAccess) { call_user_func( $this ->checkAccess, $this ->id, $model ); } return [ 'code' => 10000, 'message' => Yii::t( 'success' , '10002' ), 'data' => $model ]; } } |
21、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 ,200响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "code": 10000, "message": "获取用户详情成功", "data": { "id": 5, "username": "555555", "auth_key": "BWQRanWo3mtndyCIn3ZrsG0nBNF5Wl4p", "password_hash": "$2y$13$UyWuLtWyTzX9iFaWNevcDuFHeixAWge.NzWXcxGy9Rf7b3XPKOrAS", "password_reset_token": null, "email": "555555@163.com", "status": 1, "created_at": 1531297638, "updated_at": 1531297638 } } |
22、查看生成的 SQL 语句,符合预期,`status` != -1 在 \common\logics\UserQuery.php 中定义,`status` != 0 在 \api\models\UserQuery.php 中定义(面向前端),`id`=’5′ 在 \api\rests\user\ViewAction.php 中定义
1 | SELECT * FROM ` user ` WHERE (`status` != 0) AND (`status` != -1) AND (`id`= '5' ) |
23、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/3 ,404响应,用户不存在,其 status 值为 -1
1 2 3 4 5 6 7 | { "name": "Not Found", "message": "用户ID:3,不存在", "code": 20002, "status": 404, "type": "yii\\web\\NotFoundHttpException" } |
24、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,201响应,status 值默认为 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "code": 10000, "message": "Create user success", "data": { "username": "666666", "email": "666666@163.com", "password_hash": "$2y$13$T2fCO8/MvUKiBWTY4kzfXedXu.fbRwuTTxxwWhTVAewpN2fuavCeC", "auth_key": "x5kZ-25BUG_uW_7p8dqWLogridMJpAyd", "status": 1, "created_at": 1531298775, "updated_at": 1531298775, "id": 6 } } |
25、编辑 \api\models\UserUpdate.php,接口面向前端,则 status 不能够更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php namespace api\models; use yii\base\Model; /** * UserUpdate */ class UserUpdate extends Model { public $id ; public $email ; public $password ; /** * {@inheritdoc} */ public function rules() { return [ [ 'email' , 'trim' ], [ 'email' , 'required' ], [ 'email' , 'email' ], [ 'email' , 'string' , 'max' => 255], [ 'email' , 'unique' , 'targetClass' => '\api\models\User' , 'filter' => [ '!=' , 'id' , $this ->id]], [ 'password' , 'required' ], [ 'password' , 'string' , 'min' => 6], ]; } } |
26、编辑 \api\rests\user\UpdateAction.php,删除 status 相关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | <?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\user; use Yii; use yii\base\Model; use yii\db\ActiveRecord; use api\models\UserUpdate; use yii\web\ServerErrorHttpException; /** * UpdateAction implements the API endpoint for updating a model. * * For more details and usage information on UpdateAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class UpdateAction extends Action { /** * @var string the scenario to be assigned to the model before it is validated and updated. */ public $scenario = Model::SCENARIO_DEFAULT; /** * Updates an existing model. * @param string $id the primary key of the model. * @return \yii\db\ActiveRecordInterface the model being updated * @throws ServerErrorHttpException if there is any error when updating the model */ public function run( $id ) { /* @var $model ActiveRecord */ $model = $this ->findModel( $id ); if ( $this ->checkAccess) { call_user_func( $this ->checkAccess, $this ->id, $model ); } $userUpdate = new UserUpdate(); $userUpdate ->id = $id ; $userUpdate ->load(Yii:: $app ->getRequest()->getBodyParams(), '' ); if (! $userUpdate ->validate()) { if ( $userUpdate ->hasErrors()) { $response = Yii:: $app ->getResponse(); $response ->setStatusCode(422, 'Data Validation Failed.' ); foreach ( $userUpdate ->getFirstErrors() as $message ) { $firstErrors = $message ; break ; } return [ 'code' => 20004, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20004' ), [ 'firstErrors' => $firstErrors ]))]; } elseif (! $userUpdate ->hasErrors()) { throw new ServerErrorHttpException( 'Failed to create the object for unknown reason.' ); } } $model ->scenario = $this ->scenario; $model ->email = $userUpdate ->email; $model ->setPassword( $userUpdate ->password); $model ->generateAuthKey(); if ( $model ->save() === false) { if ( $model ->hasErrors()) { $response = Yii:: $app ->getResponse(); $response ->setStatusCode(422, 'Data Validation Failed.' ); foreach ( $model ->getFirstErrors() as $message ) { $firstErrors = $message ; break ; } return [ 'code' => 20004, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20004' ), [ 'firstErrors' => $firstErrors ]))]; } elseif (! $model ->hasErrors()) { throw new ServerErrorHttpException( 'Failed to update the object for unknown reason.' ); } } return [ 'code' => 10000, 'message' => Yii::t( 'success' , '10004' ), 'data' => $model ]; } } |
27、在 Postman 中,PUT http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/6 ,200响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "code": 10000, "message": "更新用户成功", "data": { "id": 6, "username": "666666", "auth_key": "CDCjtpL1hZWtj1KRs-p3SZb3cZUjgn9z", "password_hash": "$2y$13$eM6HydN2eSHT1nqPQ7PICOZCgN3wpXAiL0zvufwzi6knmUd22X7iq", "password_reset_token": null, "email": "666666@163.com", "status": 1, "created_at": 1531298775, "updated_at": 1531299330 } } |
28、编辑 \api\rests\user\DeleteAction.php,delete() 替换为 softDelete()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\user; use Yii; use yii\web\ServerErrorHttpException; /** * DeleteAction implements the API endpoint for deleting a model. * * For more details and usage information on DeleteAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class DeleteAction extends Action { /** * Deletes a model. * @param mixed $id id of the model to be deleted. * @throws ServerErrorHttpException on failure. */ public function run( $id ) { $model = $this ->findModel( $id ); if ( $this ->checkAccess) { call_user_func( $this ->checkAccess, $this ->id, $model ); } if ( $model ->softDelete() === false) { throw new ServerErrorHttpException( 'Failed to delete the object for unknown reason.' ); } return [ 'code' => 10000, 'message' => Yii::t( 'success' , '10005' )]; } } |
29、浏览 user 表数据,如图8
30、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/6 ,200响应
1 2 3 4 | { "code": 10000, "message": "删除用户成功" } |
31、查看生成的 SQL 语句,符合预期,`status` != -1 在 \common\logics\UserQuery.php 中定义,`status` != 0 在 \api\models\UserQuery.php 中定义(面向前端),`id`=’5′ 在 \api\rests\user\DeleteAction.php 中定义,`status`=-1 在 行为 softDeleteBehavior 中定义,`updated_at`=1531299598 在 行为 timestampBehavior 中定义
1 2 | SELECT * FROM ` user ` WHERE (`status` != 0) AND (`status` != -1) AND (`id`= '6' ) UPDATE ` user ` SET `status`=-1, `updated_at`=1531299598 WHERE `id`=6 |
32、浏览 user 表数据,status、updated_at 已经更新,如图9
33、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,201响应,status 值默认为 1
1 2 3 | email:777777@163.com password:777777 username:777777 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "code": 10000, "message": "创建用户成功", "data": { "username": "777777", "email": "777777@163.com", "password_hash": "$2y$13$/CioMoodaC.JTdMenKuJge9j9k97Uute4DnVmZz6Er7tzmtgqDA9G", "auth_key": "qnyEofUzxsf-mgkYJiiTBlL3rMFXFtzA", "status": 1, "created_at": 1531303629, "updated_at": 1531303629, "id": 7 } } |
34、查看生成的 SQL 语句,不符合预期(在执行唯一性验证时,不应该添加状态字段的条件),`status` != -1 在 \common\logics\UserQuery.php 中定义,`status` != 0 在 \api\models\UserQuery.php 中定义(面向前端),`id`=’5′ 在 \api\rests\user\DeleteAction.php 中定义,`status`=-1 在 行为 softDeleteBehavior 中定义,`updated_at`=1531299598 在 行为 timestampBehavior 中定义
1 2 3 | SELECT EXISTS( SELECT * FROM ` user ` WHERE (`status` != 0) AND (`status` != -1) AND (` user `.`username`= '777777' )) SELECT EXISTS( SELECT * FROM ` user ` WHERE (`status` != 0) AND (`status` != -1) AND (` user `.`email`= '777777@163.com' )) INSERT INTO ` user ` (`username`, `email`, `password_hash`, `auth_key`, `status`, `created_at`, `updated_at`) VALUES ( '777777' , '777777@163.com' , '$2y$13$/CioMoodaC.JTdMenKuJge9j9k97Uute4DnVmZz6Er7tzmtgqDA9G' , 'qnyEofUzxsf-mgkYJiiTBlL3rMFXFtzA' , 1, 1531303629, 1531303629) |
35、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/7 ,200响应
1 2 3 4 | { "code": 10000, "message": "删除用户成功" } |
36、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,500响应
1 2 3 | email:777777@163.com password:777777 username:777777 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | { "name": "Integrity constraint violation", "message": "SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '777777' for key 'username'\nThe SQL being executed was: INSERT INTO `user` (`username`, `email`, `password_hash`, `auth_key`, `status`, `created_at`, `updated_at`) VALUES ('777777', '777777@163.com', '$2y$13$1P6IWOqZ4kTbCPKFRnqBR.FR9FwfRVVGwBWQYTt3DxfstjyEL4X3e', 'tZPoUVJJTfjgjlu8fsynKLkaVTL0lRtq', 1, 1531303970, 1531303970)", "code": 23000, "type": "yii\\db\\IntegrityException", "file": "E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\Schema.php", "line": 664, "stack-trace": [ "#0 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\Command.php(1263): yii\\db\\Schema->convertException(Object(PDOException), 'INSERT INTO `us...')", "#1 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\Command.php(1075): yii\\db\\Command->internalExecute('INSERT INTO `us...')", "#2 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\Schema.php(433): yii\\db\\Command->execute()", "#3 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\ActiveRecord.php(549): yii\\db\\Schema->insert('{{%user}}', Array)", "#4 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\ActiveRecord.php(515): yii\\db\\ActiveRecord->insertInternal(NULL)", "#5 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\BaseActiveRecord.php(670): yii\\db\\ActiveRecord->insert(true, NULL)", "#6 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\api\\rests\\user\\CreateAction.php(72): yii\\db\\BaseActiveRecord->save()", "#7 [internal function]: api\\rests\\user\\CreateAction->run()", "#8 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Action.php(94): call_user_func_array(Array, Array)", "#9 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Controller.php(157): yii\\base\\Action->runWithParams(Array)", "#10 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Module.php(528): yii\\base\\Controller->runAction('create', Array)", "#11 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\web\\Application.php(103): yii\\base\\Module->runAction('v1/user/create', Array)", "#12 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))", "#13 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\api\\web\\index.php(17): yii\\base\\Application->run()", "#14 {main}" ], "error-info": [ "23000", 1062, "Duplicate entry '777777' for key 'username'" ], "previous": { "name": "Exception", "message": "SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '777777' for key 'username'", "code": "23000", "type": "PDOException", "file": "E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\Command.php", "line": 1258, "stack-trace": [ "#0 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\Command.php(1258): PDOStatement->execute()", "#1 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\Command.php(1075): yii\\db\\Command->internalExecute('INSERT INTO `us...')", "#2 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\Schema.php(433): yii\\db\\Command->execute()", "#3 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\ActiveRecord.php(549): yii\\db\\Schema->insert('{{%user}}', Array)", "#4 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\ActiveRecord.php(515): yii\\db\\ActiveRecord->insertInternal(NULL)", "#5 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\db\\BaseActiveRecord.php(670): yii\\db\\ActiveRecord->insert(true, NULL)", "#6 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\api\\rests\\user\\CreateAction.php(72): yii\\db\\BaseActiveRecord->save()", "#7 [internal function]: api\\rests\\user\\CreateAction->run()", "#8 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Action.php(94): call_user_func_array(Array, Array)", "#9 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Controller.php(157): yii\\base\\Action->runWithParams(Array)", "#10 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Module.php(528): yii\\base\\Controller->runAction('create', Array)", "#11 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\web\\Application.php(103): yii\\base\\Module->runAction('v1/user/create', Array)", "#12 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))", "#13 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\api\\web\\index.php(17): yii\\base\\Application->run()", "#14 {main}" ] } } |
37、分析结果,username、email、password_reset_token 皆是唯一索引,可是在执行唯一性验证时,其生成的SQL语句,在第 34 步骤,额外增加了条件:(`status` != 0) AND (`status` != -1),导致唯一性验证通过,进行执行插入 SQL 语句,报错。编辑 \common\logics\UserQuery.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php namespace common\logics; /** * This is the ActiveQuery class for [[User]]. * * @see User */ class UserQuery extends \common\models\UserQuery { // 不等于 状态:删除 public function notDeleted() { $this ->andWhere([ '!=' , 'status' , User::STATUS_DELETED]); } // 等于 状态:禁用 public function disabled() { return $this ->andWhere([ 'status' => User::STATUS_DISABLED]); } // 等于 状态:启用 public function enabled() { return $this ->andWhere([ 'status' => User::STATUS_ENABLED]); } } |
38、编辑 \api\models\UserQuery.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php /** * Created by PhpStorm. * User: WangQiang * Date: 2018/07/11 * Time: 16:07 */ namespace api\models; /** * This is the ActiveQuery class for [[User]]. * * @see User */ class UserQuery extends \common\logics\UserQuery { } |
39、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,422响应,符合预期
1 2 3 | email:777777@163.com password:777777 username:777777 |
1 2 3 4 | { "code": 20004, "message": "数据验证失败:Username的值\"777777\"已经被占用了。" } |
40、查看 POST 生成的 SQL 语句,符合预期
1 2 | SELECT EXISTS( SELECT * FROM ` user ` WHERE ` user `.`username`= '777777' ) SELECT EXISTS( SELECT * FROM ` user ` WHERE ` user `.`email`= '777777@163.com' ) |
41、添加状态的判断,如果未启用,响应失败,编辑 \api\rests\user\ViewAction.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\user; use Yii; /** * ViewAction implements the API endpoint for returning the detailed information about a model. * * For more details and usage information on ViewAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class ViewAction extends Action { /** * Displays a model. * @param string $id the primary key of the model. * @return \yii\db\ActiveRecordInterface the model being displayed */ public function run( $id ) { $model = $this ->findModel( $id ); if ( $this ->checkAccess) { call_user_func( $this ->checkAccess, $this ->id, $model ); } /* 判断状态,如果为删除,则返回失败 */ if ( $model ->status === $model ::STATUS_DELETED) { return [ 'code' => 20003, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20003' ), [ 'id' => $id ]))]; } /* 判断状态,如果为禁用,则返回失败 */ if ( $model ->status === $model ::STATUS_DISABLED) { return [ 'code' => 20804, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20804' ), [ 'id' => $id ]))]; } return [ 'code' => 10000, 'message' => Yii::t( 'success' , '10002' ), 'data' => $model ]; } } |
42、添加状态的判断,如果未启用,响应失败,编辑 \api\rests\user\UpdateAction.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | <?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\user; use Yii; use yii\base\Model; use yii\db\ActiveRecord; use api\models\UserUpdate; use yii\web\ServerErrorHttpException; /** * UpdateAction implements the API endpoint for updating a model. * * For more details and usage information on UpdateAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class UpdateAction extends Action { /** * @var string the scenario to be assigned to the model before it is validated and updated. */ public $scenario = Model::SCENARIO_DEFAULT; /** * Updates an existing model. * @param string $id the primary key of the model. * @return \yii\db\ActiveRecordInterface the model being updated * @throws ServerErrorHttpException if there is any error when updating the model */ public function run( $id ) { /* @var $model ActiveRecord */ $model = $this ->findModel( $id ); if ( $this ->checkAccess) { call_user_func( $this ->checkAccess, $this ->id, $model ); } /* 判断状态,如果为删除,则返回失败 */ if ( $model ->status === $model ::STATUS_DELETED) { return [ 'code' => 20003, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20003' ), [ 'id' => $id ]))]; } /* 判断状态,如果为禁用,则返回失败 */ if ( $model ->status === $model ::STATUS_DISABLED) { return [ 'code' => 20804, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20804' ), [ 'id' => $id ]))]; } $userUpdate = new UserUpdate(); $userUpdate ->id = $id ; $userUpdate ->load(Yii:: $app ->getRequest()->getBodyParams(), '' ); if (! $userUpdate ->validate()) { if ( $userUpdate ->hasErrors()) { $response = Yii:: $app ->getResponse(); $response ->setStatusCode(422, 'Data Validation Failed.' ); foreach ( $userUpdate ->getFirstErrors() as $message ) { $firstErrors = $message ; break ; } return [ 'code' => 20004, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20004' ), [ 'firstErrors' => $firstErrors ]))]; } elseif (! $userUpdate ->hasErrors()) { throw new ServerErrorHttpException( 'Failed to create the object for unknown reason.' ); } } $model ->scenario = $this ->scenario; $model ->email = $userUpdate ->email; $model ->setPassword( $userUpdate ->password); $model ->generateAuthKey(); if ( $model ->save() === false) { if ( $model ->hasErrors()) { $response = Yii:: $app ->getResponse(); $response ->setStatusCode(422, 'Data Validation Failed.' ); foreach ( $model ->getFirstErrors() as $message ) { $firstErrors = $message ; break ; } return [ 'code' => 20004, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20004' ), [ 'firstErrors' => $firstErrors ]))]; } elseif (! $model ->hasErrors()) { throw new ServerErrorHttpException( 'Failed to update the object for unknown reason.' ); } } return [ 'code' => 10000, 'message' => Yii::t( 'success' , '10004' ), 'data' => $model ]; } } |
43、添加状态的判断,如果未启用,响应失败,编辑 \api\rests\user\DeleteAction.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\user; use Yii; use yii\web\ServerErrorHttpException; /** * DeleteAction implements the API endpoint for deleting a model. * * For more details and usage information on DeleteAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class DeleteAction extends Action { /** * Deletes a model. * @param mixed $id id of the model to be deleted. * @throws ServerErrorHttpException on failure. */ public function run( $id ) { $model = $this ->findModel( $id ); if ( $this ->checkAccess) { call_user_func( $this ->checkAccess, $this ->id, $model ); } /* 判断状态,如果为删除,则返回失败 */ if ( $model ->status === $model ::STATUS_DELETED) { return [ 'code' => 20003, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20003' ), [ 'id' => $id ]))]; } /* 判断状态,如果为禁用,则返回失败 */ if ( $model ->status === $model ::STATUS_DISABLED) { return [ 'code' => 20804, 'message' => Yii::t( 'error' , Yii::t( 'error' , Yii::t( 'error' , '20804' ), [ 'id' => $id ]))]; } if ( $model ->softDelete() === false) { throw new ServerErrorHttpException( 'Failed to delete the object for unknown reason.' ); } return [ 'code' => 10000, 'message' => Yii::t( 'success' , '10005' )]; } } |
44、依次查看 POST、GET /users、GET /users/14、PUT /users/14、DELETE /users/14 生成的 SQL 语句,符合预期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | SELECT EXISTS( SELECT * FROM ` user ` WHERE ` user `.`username`= '141414' ) SELECT EXISTS( SELECT * FROM ` user ` WHERE ` user `.`email`= '141414@163.com' ) INSERT INTO ` user ` (`username`, `email`, `password_hash`, `auth_key`, `status`, `created_at`, `updated_at`) VALUES ( '141414' , '141414@163.com' , '$2y$13$AAlTFSKvl73Q4sR2.dFJtO2TzBuxVlZIib0cQXEv7KtZ9AZTm28E.' , 'nyZ5_0EZEeF4yZd_mg3D8lEWQa1-t9uk' , 1, 1531362962, 1531362962) SELECT COUNT (*) FROM ` user ` WHERE `status`=1 SELECT * FROM ` user ` WHERE `status`=1 LIMIT 20 SELECT * FROM ` user ` WHERE `id`= '14' SELECT * FROM ` user ` WHERE `id`= '14' SELECT EXISTS( SELECT * FROM ` user ` WHERE (` user `.`email`= '14141414@163.com' ) AND (`id` != '14' )) SELECT ` user `.`id` FROM ` user ` WHERE ` user `.`username`= '141414' LIMIT 2 SELECT ` user `.`id` FROM ` user ` WHERE ` user `.`email`= '14141414@163.com' LIMIT 2 UPDATE ` user ` SET `auth_key`= 'vQyuGpurq5EpM4G4EufxLuLwqwYfhT1n' , `password_hash`= '$2y$13$5dmvKnesGhoeJpG63k092OSgm0t2yN1yByOLFDump11LDmqnYLx9K' , `email`= '14141414@163.com' , `updated_at`=1531363151 WHERE `id`=14 SELECT * FROM ` user ` WHERE `id`= '14' UPDATE ` user ` SET `status`=-1, `updated_at`=1531363448 WHERE `id`=14 |
近期评论