Ở bài trước chúng ta đã cùng nhau khởi tạo 1 ứng dụng sử dụng Yii2 framework và kết nối cơ sở dữ liệu cho ứng dụng cũng như tạo các table cho ứng dụng.
Ở bài này chúng ta sẽ đi vào chi tiết việc code API để thực hiện việc thêm, sửa, xóa các member.
Tạo model Members và model Profile
Thay vì phải viết các câu lệnh SQL thông thường để truy vấn đến DB, Yii2 cung cấp cho chúng ta các lớp Active Record để thực hiện việc thao tác với cơ sở dữ liệu theo đúng chuẩn mô hình MVC.
Chúng ta sẽ tạo các model và controller bằng công cụ rất hữu ích của Yii2 đó là Gii: http://localhost/restful-api-tutorial/web/index.php?r=gii
Chúng ta sẽ lần lượt tạo các model cho members và profile, sau khi tạo xong sẽ thấy 2 file model được tạo trong thư mục /restful-api-tutorial/models/
// Members model namespace app\models; use Yii; /** * This is the model class for table "members". * * @property int $id * @property string $username * @property string $password * @property int|null $status * @property string|null $last_login * @property string|null $created_at * @property string|null $updated_at */ class Members extends \yii\db\ActiveRecord { /** * {@inheritdoc} */ public static function tableName() { return 'members'; } /** * {@inheritdoc} */ public function rules() { return [ [['username', 'password'], 'required'], ['username', 'unique', 'message' => 'This username has already been taken.', 'on' => 'create'],// chỉ check unique trong trường hợp tạo mới member [['status'], 'integer'], [['last_login', 'created_at', 'updated_at'], 'safe'], [['username', 'password'], 'string', 'max' => 250], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'ID', 'username' => 'Username', 'password' => 'Password', 'status' => 'Status', 'last_login' => 'Last Login', 'created_at' => 'Created At', 'updated_at' => 'Updated At', ]; } // sau khi save password sẽ được mã hóa public function beforeSave($insert) { if (!parent::beforeSave($insert)) { return false; } $this->password = Yii::$app->security->generatePasswordHash($this->password); return true; } }
// Profile Model namespace app\models; use Yii; /** * This is the model class for table "profile". * * @property int $id * @property int $user_id * @property string|null $first_name * @property string|null $last_name * @property string|null $email * @property string|null $birthday */ class Profile extends \yii\db\ActiveRecord { /** * {@inheritdoc} */ public static function tableName() { return 'profile'; } /** * {@inheritdoc} */ public function rules() { return [ [['user_id'], 'required'], [['user_id'], 'integer'], [['birthday'], 'safe'], [['first_name', 'last_name', 'email'], 'string', 'max' => 250], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'ID', 'user_id' => 'User ID', 'first_name' => 'First Name', 'last_name' => 'Last Name', 'email' => 'Email', 'birthday' => 'Birthday', ]; } }
Tiếp theo chúng ta sẽ viết các API cho các chức năng cơ bản của ứng dụng: Thêm, Sửa, xóa member.
Yii2 cung cấp cho chúng ta Class yii\rest\Controller là lớp cơ sở của restful API của Yii2, chúng ta sẽ viết class controller ApiController extends lớp này như sau:
namespace app\controllers; use yii\rest\Controller; use yii\helpers\Json; class ApiController extends Controller { }
Mục đích của class ApiController là để định nghĩa các method, kiểm tra bảo mật và trả về dữ liệu dưới dạng JSON, và các class khác như MembersController hoặc ProfileController sẽ extends lớp này.
Để tránh trường hợp bất kỳ ai cũng có thể truy cập vào các api của ứng dụng chúng ta cần định nghĩa 1 biến const có tên là API_PUBLICKEY
const API_PUBLICKEY = 'LINXHQ-DA01992182'; // có thể tự định nghĩa giá trị tùy ý.
Và chúng ta sẽ tiến hành kiểm tra tất cả các request từ client nếu không có khai báo API_PUBLICKEY và giá trị không đúng thì sẽ không có quyền truy cập vào các API.
public function beforeAction($action) { parent::beforeAction($action); if (!isset($_SERVER['HTTP_PUBLICKEY']) || $_SERVER['HTTP_PUBLICKEY'] != self::API_PUBLICKEY) return $this->_sendResponse(401, Json::encode(array('data' => 'You do not possess the right PUBLIC KEY'))); else return true; }
Kết quả nếu không truyền API_PUBLICKEY trên header của postman
Tiếp theo chúng ta sẽ viết hàm để custom data trả về
public function _sendResponse($status = 200, $body = '', $content_type = 'application/json') { // set the status $status_header = 'HTTP/1.1 ' . $status . ' '; header($status_header); if($status == 200) { // send the body // and the content type header('Content-type: ' . $content_type); echo $body; } // we need to create the body if none is passed else { // create some body messages $message = ''; // this is purely optional, but makes the pages a little nicer to read // for your users. Since you won't likely send a lot of different status codes, // this also shouldn't be too ponderous to maintain switch($status) { case 401: $message = 'You must be authorized to view this page.'; break; case 404: $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.'; break; case 500: $message = 'The server encountered an error processing your request.'; break; case 501: $message = 'The requested method is not implemented.'; break; } // servers don't always have a signature turned on // (this is an apache directive "ServerSignature On") //$signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE']; header('Content-type: ' . $content_type); echo $body; } exit(); }
Định nghĩa các method có quyền truy cập
protected function verbs() { return [ 'index' => ['GET', 'HEAD'], 'view' => ['GET', 'HEAD'], 'create' => ['POST'], 'update' => ['PUT', 'PATCH'], 'delete' => ['DELETE'], ]; }
Ta sẽ viết lại class ApiController hoàn chỉnh như sau:
namespace app\controllers; use yii\rest\Controller; use yii\helpers\Json; class ApiController extends Controller { const API_PUBLICKEY = 'LINXHQ-DA01992182'; protected function verbs() { return [ 'index' => ['GET', 'HEAD'], 'view' => ['GET', 'HEAD'], 'create' => ['POST'], 'update' => ['PUT', 'PATCH'], 'delete' => ['DELETE'], ]; } public function beforeAction($action) { parent::beforeAction($action); if (!isset($_SERVER['HTTP_PUBLICKEY']) || $_SERVER['HTTP_PUBLICKEY'] != self::API_PUBLICKEY) return $this->_sendResponse(401, Json::encode(array('error' => 'You do not possess the right PUBLIC KEY'))); else return true; } public function _sendResponse($status = 200, $body = '', $content_type = 'application/json') { // set the status $status_header = 'HTTP/1.1 ' . $status . ' '; header($status_header); if($status == 200) { // send the body // and the content type header('Content-type: ' . $content_type); echo $body; } // we need to create the body if none is passed else { // create some body messages $message = ''; // this is purely optional, but makes the pages a little nicer to read // for your users. Since you won't likely send a lot of different status codes, // this also shouldn't be too ponderous to maintain switch($status) { case 401: $message = 'You must be authorized to view this page.'; break; case 404: $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.'; break; case 500: $message = 'The server encountered an error processing your request.'; break; case 501: $message = 'The requested method is not implemented.'; break; } // servers don't always have a signature turned on header('Content-type: ' . $content_type); echo $body; } exit(); } }
Tiếp theo chúng ta sẽ tạo MembersController extends class ApiController như sau:
namespace app\controllers; use app\controllers\ApiController; use app\models\Members; use app\models\Profile; use yii\helpers\Json; use yii\web\ServerErrorHttpException; use Yii; class MembersController extends ApiController { public $modelClass = 'app\models\Members'; }
API create member, khi tạo member thì sẽ tương ứng tạo profile cho member
public function actionCreate() { $request = Yii::$app->getRequest(); $model = new Members(); // get request from raw body postman $member_request = $request->getRawBody(); $jsondata = (array) Json::decode($member_request); $message = []; $response = []; // kiểm tra input có để trống không if(!isset($jsondata) || $jsondata == "") { $response['message'] = 'failed'; $response['error'] = "Incorrect Json format!"; return $this->_sendResponse(400, Json::encode($response)); } // Kiểm tra first name if(!isset($jsondata['first_name']) || $jsondata['first_name'] == "") { $response['message'] = 'failed'; $response['error']['first_name'] = "First name can not be blank"; return $this->_sendResponse(500, Json::encode($response)); } // Kiểm tra last name if(!isset($jsondata['last_name']) || $jsondata['last_name'] == "") { $response['message'] = 'failed'; $response['error']['last_name'] = "Last name can not be blank"; return $this->_sendResponse(500, Json::encode($response)); } // kiểm tra email if(!isset($jsondata['email']) || $jsondata['email'] == "") { $response['message'] = 'failed'; $response['error']['email'] = "email can not be blank"; return $this->_sendResponse(500, Json::encode($response)); } //set attributes với data gửi lên $model->attributes = $jsondata; $model->status = 1; // kiểm tra nếu data không thỏa mãn. if (!$model->validate()) { $response['message'] = 'failed'; $response['error']['member'] = $model->getErrors(); return $this->_sendResponse(500, Json::encode($response)); } if($model->save()) { // tạo profile tương ứng với member $profile = new Profile(); $profile->user_id = $model->id; $profile->first_name = $jsondata['first_name']; $profile->last_name = $jsondata['last_name']; $profile->email = $jsondata['email']; $profile->save(); $response['message'] = 'success'; $response['data']['member'] = $model; $response['data']['profile'] = $profile; return $this->_sendResponse(200, Json::encode($response)); } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to create the object for unknown reason.'); } }
Input
{ "username": "demo1", "password": "demo12345", "first_name": "Test", "last_name": "Demo", "email": "demo@linxhq.com" }
Header
PUBLICKEY: LINXHQ-DA01992182
Output
{ "message": "success", "data": { "member": { "username": "demo3", "password": "$2y$13$Uy199vfvWbdfShdciBtKXOFRA0/fZ2.YILvBoS2JZdu2JNOY4ry6G", "status": 1, "id": 16 }, "profile": { "user_id": 16, "first_name": "Test", "last_name": "Demo", "email": "demo@linxhq.com", "id": 13 } } }
Kết quả postman
API Update member
public function actionUpdate() { $request = Yii::$app->getRequest(); $member_request = $request->getRawBody(); $jsondata = (array) Json::decode($member_request); $response = []; if(!isset($jsondata) || $jsondata == "") { $response['message'] = 'failed'; $response['error'] = 'Incorrect Json format!'; return $this->_sendResponse(400, Json::encode($response)); } if(empty($jsondata['id'])) { $response['message'] = 'failed'; $response['error'] = 'Member ID can not be blank!'; return $this->_sendResponse(404, Json::encode($response)); } $model = Members::findOne($jsondata['id']); $model->attributes = $jsondata; if (!$model->validate()) { $response['message'] = 'failed'; $response['error']['member'] = $model->getErrors(); return $this->_sendResponse(500, Json::encode($response)); } if($model->save()) { $profile = Profile::find()->where(['user_id' => $model->id])->one(); $profile->first_name = isset($jsondata['first_name']) ? $jsondata['first_name'] : $profile->first_name; $profile->last_name = isset($jsondata['last_name']) ? $jsondata['last_name'] : $profile->last_name; $profile->email = isset($jsondata['email']) ? $jsondata['email'] : $profile->email; $profile->save(false); $response['message'] = 'success'; $response['data']['member'] = $model; $response['data']['profile'] = $profile; return $this->_sendResponse(200, Json::encode($response)); } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to create the object for unknown reason.'); } }
Input
{ "id": 9, "username": "demo4", "password": "demo12345", "first_name": "Test", "last_name": "Demo", "email": "demo@linxhq.com" }
Output
{ "message": "success", "data": { "member": { "id": 9, "username": "demo4", "password": "$2y$13$.3Y5H/gRloLSjjgAmOZKF.gorvZU2XlicU41wH2pJv6EUdfYwRTUC", "status": 1, "last_login": null, "created_at": null, "updated_at": null }, "profile": { "id": 6, "user_id": 9, "first_name": "Test", "last_name": "Demo", "email": "demo@linxhq.com", "birthday": null } } }
Method: PUT
Kết quả postman
API Delete
public function actionDelete() { $request = Yii::$app->getRequest(); $member_request = $request->getRawBody(); $jsondata = (array) Json::decode($member_request); $response = []; if(!isset($jsondata['id'])) { $response['message'] = 'failed'; $response['error'] = 'Member ID can not be blank!'; return $this->_sendResponse(500, Json::encode($response)); } $model = Members::findOne($jsondata['id']); if($model->delete()) { $profile = Profile::find()->where(['user_id' => $model->id])->one(); $profile->delete(); } $response['message'] = 'success'; return $this->_sendResponse(200, Json::encode($response)); }
Method: Delete
Input
{ "id": 4 }
Output
{ "message": "success" }
Kết quả postman
API List Member
public function actionIndex() { $model = Members::find()->all(); $response = []; $response['message'] = 'success'; if($model) { foreach ($model as $member) { $result = []; $result['member'][] = $member; $profile = Profile::find()->where(['user_id' => $member->id])->one(); $result['profile'][] = $profile; $response['data'][] = $result; } } return $this->_sendResponse(200, Json::encode($response)); }
Method Get
Output
{ "message": "success", "data": [ { "member": [ { "id": 5, "username": "dungnv2", "password": "$2y$13$h4DdGmOkvStdTDLdSf4eV.YOrCb/jd0UbFECs6zMHI7RTal3IhYqS", "status": 1, "last_login": null, "created_at": null, "updated_at": null } ], "profile": [ { "id": 2, "user_id": 5, "first_name": "Nguyen", "last_name": "Dung", "email": "dungnv@linxhq.com", "birthday": null } ] }, ] }
Postman
Class MembersController hoàn chỉnh như sau:
namespace app\controllers; use app\controllers\ApiController; use app\models\Members; use app\models\Profile; use yii\helpers\Json; use yii\web\ServerErrorHttpException; use Yii; class MembersController extends ApiController { public $modelClass = 'app\models\Members'; public function actionIndex() { $model = Members::find()->all(); $response = []; $response['message'] = 'success'; if($model) { foreach ($model as $member) { $result = []; $result['member'][] = $member; $profile = Profile::find()->where(['user_id' => $member->id])->one(); $result['profile'][] = $profile; $response['data'][] = $result; } } return $this->_sendResponse(200, Json::encode($response)); } public function actionCreate() { $request = Yii::$app->getRequest(); $model = new Members(); // get request from raw body postman $member_request = $request->getRawBody(); $jsondata = (array) Json::decode($member_request); $message = []; $response = []; // kiểm tra input có để trống không if(!isset($jsondata) || $jsondata == "") { $response['message'] = 'failed'; $response['error'] = "Incorrect Json format!"; return $this->_sendResponse(400, Json::encode($response)); } // Kiểm tra first name if(!isset($jsondata['first_name']) || $jsondata['first_name'] == "") { $response['message'] = 'failed'; $response['error']['first_name'] = "First name can not be blank"; return $this->_sendResponse(500, Json::encode($response)); } // Kiểm tra last name if(!isset($jsondata['last_name']) || $jsondata['last_name'] == "") { $response['message'] = 'failed'; $response['error']['last_name'] = "Last name can not be blank"; return $this->_sendResponse(500, Json::encode($response)); } // kiểm tra email if(!isset($jsondata['email']) || $jsondata['email'] == "") { $response['message'] = 'failed'; $response['error']['email'] = "email can not be blank"; return $this->_sendResponse(500, Json::encode($response)); } //set attributes với data gửi lên $model->attributes = $jsondata; $model->status = 1; // nếu data không thỏa mãn. if (!$model->validate()) { $response['message'] = 'failed'; $response['error']['member'] = $model->getErrors(); return $this->_sendResponse(500, Json::encode($response)); } if($model->save()) { // tạo profile tương ứng với member $profile = new Profile(); $profile->user_id = $model->id; $profile->first_name = $jsondata['first_name']; $profile->last_name = $jsondata['last_name']; $profile->email = $jsondata['email']; $profile->save(); $response['message'] = 'success'; $response['data']['member'] = $model; $response['data']['profile'] = $profile; return $this->_sendResponse(200, Json::encode($response)); } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to create the object for unknown reason.'); } } public function actionUpdate() { $request = Yii::$app->getRequest(); $member_request = $request->getRawBody(); $jsondata = (array) Json::decode($member_request); $response = []; if(!isset($jsondata) || $jsondata == "") { $response['message'] = 'failed'; $response['error'] = 'Incorrect Json format!'; return $this->_sendResponse(400, Json::encode($response)); } if(empty($jsondata['id'])) { $response['message'] = 'failed'; $response['error'] = 'Member ID can not be blank!'; return $this->_sendResponse(404, Json::encode($response)); } $model = Members::findOne($jsondata['id']); $model->attributes = $jsondata; if (!$model->validate()) { $response['message'] = 'failed'; $response['error']['member'] = $model->getErrors(); return $this->_sendResponse(500, Json::encode($response)); } if($model->save()) { $profile = Profile::find()->where(['user_id' => $model->id])->one(); $profile->first_name = isset($jsondata['first_name']) ? $jsondata['first_name'] : $profile->first_name; $profile->last_name = isset($jsondata['last_name']) ? $jsondata['last_name'] : $profile->last_name; $profile->email = isset($jsondata['email']) ? $jsondata['email'] : $profile->email; $profile->save(false); $response['message'] = 'success'; $response['data']['member'] = $model; $response['data']['profile'] = $profile; return $this->_sendResponse(200, Json::encode($response)); } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to create the object for unknown reason.'); } } public function actionDelete() { $request = Yii::$app->getRequest(); $member_request = $request->getRawBody(); $jsondata = (array) Json::decode($member_request); $response = []; if(!isset($jsondata['id'])) { $response['message'] = 'failed'; $response['error'] = 'Member ID can not be blank!'; return $this->_sendResponse(500, Json::encode($response)); } $model = Members::findOne($jsondata['id']); if($model->delete()) { $profile = Profile::find()->where(['user_id' => $model->id])->one(); $profile->delete(); } $response['message'] = 'success'; return $this->_sendResponse(200, Json::encode($response)); } }
Vậy là chúng ta đã hoàn thành viết API create, update, delete và list member cho ứng dụng của chúng ta.
Các bạn có thể xem link tham khảo tại đây: https://www.yiiframework.com/doc/guide/2.0/en/rest-quick-start