From 14955c3d0ea6104f0c5bb3fcc28295e8083df57b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 26 Jun 2019 14:55:16 +0200 Subject: [PATCH] support custom parameter naming for model ID --- src/generator/ApiGenerator.php | 6 ++ src/generator/default/controller.php | 83 ++++++++++++++++--- tests/specs/petstore.yaml | 39 ++++++++- tests/specs/petstore/config/urls.rest.php | 4 +- .../petstore/controllers/PetController.php | 11 ++- tests/specs/petstore_arrayref.yaml | 36 +++++++- .../petstore_arrayref/config/urls.rest.php | 2 + .../controllers/PetController.php | 52 ++++++++++-- .../petstore_namespace/config/rest-urls.php | 4 +- .../mycontrollers/PetController.php | 11 ++- .../controllers/PetController.php | 27 ++++-- 11 files changed, 248 insertions(+), 27 deletions(-) diff --git a/src/generator/ApiGenerator.php b/src/generator/ApiGenerator.php index 02ce1563..df3e6f17 100644 --- a/src/generator/ApiGenerator.php +++ b/src/generator/ApiGenerator.php @@ -349,6 +349,7 @@ protected function generateUrls() $action = []; $params = false; $actionParams = []; + $idParam = null; foreach ($parts as $p => $part) { if (preg_match('/\{(.*)\}/', $part, $m)) { $params = true; @@ -358,6 +359,9 @@ protected function generateUrls() } else { $actionParams[$m[1]] = null; } + if ($idParam === null && preg_match('/\bid\b/i', Inflector::camel2id($m[1]))) { + $idParam = $m[1]; + } // TODO add regex to param based on openAPI type } elseif ($params) { $action[] = $part; @@ -395,6 +399,7 @@ protected function generateUrls() 'pattern' => $pattern, 'route' => "$controller/$a$action", 'actionParams' => $actionParams, + 'idParam' => $idParam, 'openApiOperation' => $operation, 'modelClass' => $modelClass !== null ? $this->modelNamespace . '\\' . $modelClass : null, 'responseWrapper' => $responseWrapper, @@ -566,6 +571,7 @@ protected function generateControllers() $c[$parts[0]][] = [ 'id' => $parts[1], 'params' => array_keys($url['actionParams']), + 'idParam' => $url['idParam'] ?? null, 'modelClass' => $url['modelClass'], 'responseWrapper' => $url['responseWrapper'], ]; diff --git a/src/generator/default/controller.php b/src/generator/default/controller.php index a8fb28f5..6ea5eb96 100644 --- a/src/generator/default/controller.php +++ b/src/generator/default/controller.php @@ -1,12 +1,49 @@ yii\rest\IndexAction::class, - 'view' => yii\rest\ViewAction::class, - 'create' => yii\rest\CreateAction::class, - 'update' => yii\rest\UpdateAction::class, - 'delete' => yii\rest\UpdateAction::class, + 'index' => [ + 'class' => yii\rest\IndexAction::class, + ], + 'view' => [ + 'class' => yii\rest\ViewAction::class, + 'implementation' => <<<'PHP' + $model = $this->findModel($id); + $this->checkAccess(ACTION_ID, $model); + return $model; +PHP + ], + 'create' => [ + 'class' => yii\rest\CreateAction::class, + ], + 'update' => [ + 'class' => yii\rest\UpdateAction::class, + 'implementation' => <<<'PHP' + $model = $this->findModel($id); + $this->checkAccess(ACTION_ID, $model); + + $model->load(Yii::$app->getRequest()->getBodyParams(), ''); + if ($model->save() === false && !$model->hasErrors()) { + throw new ServerErrorHttpException('Failed to update the object for unknown reason.'); + } + + return $model; +PHP + ], + 'delete' => [ + 'class' => yii\rest\DeleteAction::class, + 'implementation' => <<<'PHP' + $model = $this->findModel($id); + $this->checkAccess(ACTION_ID, $model); + + if ($model->delete() === false) { + throw new ServerErrorHttpException('Failed to delete the object for unknown reason.'); + } + + \Yii::$app->response->setStatusCode(204); +PHP + ], ]; +$findModel = []; ?> @@ -21,9 +58,9 @@ public function actions() + if (isset($modelActions[$action['id']], $action['modelClass']) && ($action['idParam'] === null || $action['idParam'] === 'id')): ?> => [ - 'class' => \::class, + 'class' => \::class, 'modelClass' => , 'checkAccess' => [$this, 'checkAccess'], ], @@ -84,11 +121,19 @@ public function checkAccess($action, $model = null, $params = []) { // TODO implement checkAccess } - + public function () { - // TODO implement + } + + $methodName): ?> + /** + * Returns the model based on the primary key given. + * If the data model is not found, a 404 HTTP exception will be raised. + * @param string $id the ID of the model to be loaded. + * @return \ the model found + * @throws NotFoundHttpException if the model cannot be found. + */ + public function ($id) + { + $model = \::findOne($id); + if ($model !== null) { + return $model; + } + throw new NotFoundHttpException("Object not found: $id"); + } } diff --git a/tests/specs/petstore.yaml b/tests/specs/petstore.yaml index 29a79433..d8dd2340 100644 --- a/tests/specs/petstore.yaml +++ b/tests/specs/petstore.yaml @@ -53,14 +53,14 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - /pets/{petId}: + /pets/{id}: get: summary: Info for a specific pet operationId: showPetById tags: - pets parameters: - - name: petId + - name: id in: path required: true description: The id of the pet to retrieve @@ -79,6 +79,41 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + patch: + summary: update a specific pet + operationId: updatePetById + tags: + - pets + parameters: + - name: id + in: path + required: true + description: The id of the pet to update + schema: + type: string + responses: + '200': + description: The updated pet + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + delete: + summary: delete a specific pet + operationId: deletePetById + tags: + - pets + parameters: + - name: id + in: path + required: true + description: The id of the pet to delete + schema: + type: string + responses: + '204': + description: successfully deleted pet + components: schemas: Pet: diff --git a/tests/specs/petstore/config/urls.rest.php b/tests/specs/petstore/config/urls.rest.php index 92e45521..85cedb50 100644 --- a/tests/specs/petstore/config/urls.rest.php +++ b/tests/specs/petstore/config/urls.rest.php @@ -7,5 +7,7 @@ return array ( 'GET pets' => 'pet/index', 'POST pets' => 'pet/create', - 'GET pets/' => 'pet/view', + 'GET pets/' => 'pet/view', + 'DELETE pets/' => 'pet/delete', + 'PATCH pets/' => 'pet/update', ); diff --git a/tests/specs/petstore/controllers/PetController.php b/tests/specs/petstore/controllers/PetController.php index fd08fa6b..103a8d2b 100644 --- a/tests/specs/petstore/controllers/PetController.php +++ b/tests/specs/petstore/controllers/PetController.php @@ -22,6 +22,16 @@ public function actions() 'modelClass' => \app\models\Pet::class, 'checkAccess' => [$this, 'checkAccess'], ], + 'delete' => [ + 'class' => \yii\rest\DeleteAction::class, + 'modelClass' => \app\models\Pet::class, + 'checkAccess' => [$this, 'checkAccess'], + ], + 'update' => [ + 'class' => \yii\rest\UpdateAction::class, + 'modelClass' => \app\models\Pet::class, + 'checkAccess' => [$this, 'checkAccess'], + ], 'options' => [ 'class' => \yii\rest\OptionsAction::class, ], @@ -44,5 +54,4 @@ public function checkAccess($action, $model = null, $params = []) { // TODO implement checkAccess } - } diff --git a/tests/specs/petstore_arrayref.yaml b/tests/specs/petstore_arrayref.yaml index 630e67f0..ff47d135 100644 --- a/tests/specs/petstore_arrayref.yaml +++ b/tests/specs/petstore_arrayref.yaml @@ -30,7 +30,7 @@ paths: schema: type: string content: - application/json: + application/json: schema: $ref: "#/components/schemas/Pets" default: @@ -75,6 +75,40 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" + patch: + summary: update a specific pet + operationId: updatePetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to update + schema: + type: string + responses: + '200': + description: The updated pet + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + delete: + summary: delete a specific pet + operationId: deletePetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to delete + schema: + type: string + responses: + '204': + description: successfully deleted pet components: schemas: Pet: diff --git a/tests/specs/petstore_arrayref/config/urls.rest.php b/tests/specs/petstore_arrayref/config/urls.rest.php index 92e45521..3a7d7647 100644 --- a/tests/specs/petstore_arrayref/config/urls.rest.php +++ b/tests/specs/petstore_arrayref/config/urls.rest.php @@ -8,4 +8,6 @@ 'GET pets' => 'pet/index', 'POST pets' => 'pet/create', 'GET pets/' => 'pet/view', + 'DELETE pets/' => 'pet/delete', + 'PATCH pets/' => 'pet/update', ); diff --git a/tests/specs/petstore_arrayref/controllers/PetController.php b/tests/specs/petstore_arrayref/controllers/PetController.php index fd08fa6b..5255b03c 100644 --- a/tests/specs/petstore_arrayref/controllers/PetController.php +++ b/tests/specs/petstore_arrayref/controllers/PetController.php @@ -17,11 +17,6 @@ public function actions() 'modelClass' => \app\models\Pet::class, 'checkAccess' => [$this, 'checkAccess'], ], - 'view' => [ - 'class' => \yii\rest\ViewAction::class, - 'modelClass' => \app\models\Pet::class, - 'checkAccess' => [$this, 'checkAccess'], - ], 'options' => [ 'class' => \yii\rest\OptionsAction::class, ], @@ -45,4 +40,51 @@ public function checkAccess($action, $model = null, $params = []) // TODO implement checkAccess } + public function actionView($petId) + { + $model = $this->findPetModel($petId); + $this->checkAccess('view', $model); + return $model; + } + + public function actionDelete($petId) + { + $model = $this->findPetModel($petId); + $this->checkAccess('delete', $model); + + if ($model->delete() === false) { + throw new ServerErrorHttpException('Failed to delete the object for unknown reason.'); + } + + \Yii::$app->response->setStatusCode(204); + } + + public function actionUpdate($petId) + { + $model = $this->findPetModel($petId); + $this->checkAccess('update', $model); + + $model->load(Yii::$app->getRequest()->getBodyParams(), ''); + if ($model->save() === false && !$model->hasErrors()) { + throw new ServerErrorHttpException('Failed to update the object for unknown reason.'); + } + + return $model; + } + + /** + * Returns the Pet model based on the primary key given. + * If the data model is not found, a 404 HTTP exception will be raised. + * @param string $id the ID of the model to be loaded. + * @return \app\models\Pet the model found + * @throws NotFoundHttpException if the model cannot be found. + */ + public function findPetModel($id) + { + $model = \app\models\Pet::findOne($id); + if ($model !== null) { + return $model; + } + throw new NotFoundHttpException("Object not found: $id"); + } } diff --git a/tests/specs/petstore_namespace/config/rest-urls.php b/tests/specs/petstore_namespace/config/rest-urls.php index 92e45521..85cedb50 100644 --- a/tests/specs/petstore_namespace/config/rest-urls.php +++ b/tests/specs/petstore_namespace/config/rest-urls.php @@ -7,5 +7,7 @@ return array ( 'GET pets' => 'pet/index', 'POST pets' => 'pet/create', - 'GET pets/' => 'pet/view', + 'GET pets/' => 'pet/view', + 'DELETE pets/' => 'pet/delete', + 'PATCH pets/' => 'pet/update', ); diff --git a/tests/specs/petstore_namespace/mycontrollers/PetController.php b/tests/specs/petstore_namespace/mycontrollers/PetController.php index 4cfea353..6d84017d 100644 --- a/tests/specs/petstore_namespace/mycontrollers/PetController.php +++ b/tests/specs/petstore_namespace/mycontrollers/PetController.php @@ -22,6 +22,16 @@ public function actions() 'modelClass' => \app\mymodels\Pet::class, 'checkAccess' => [$this, 'checkAccess'], ], + 'delete' => [ + 'class' => \yii\rest\DeleteAction::class, + 'modelClass' => \app\mymodels\Pet::class, + 'checkAccess' => [$this, 'checkAccess'], + ], + 'update' => [ + 'class' => \yii\rest\UpdateAction::class, + 'modelClass' => \app\mymodels\Pet::class, + 'checkAccess' => [$this, 'checkAccess'], + ], 'options' => [ 'class' => \yii\rest\OptionsAction::class, ], @@ -44,5 +54,4 @@ public function checkAccess($action, $model = null, $params = []) { // TODO implement checkAccess } - } diff --git a/tests/specs/petstore_wrapped/controllers/PetController.php b/tests/specs/petstore_wrapped/controllers/PetController.php index 883756fe..8caddcbf 100644 --- a/tests/specs/petstore_wrapped/controllers/PetController.php +++ b/tests/specs/petstore_wrapped/controllers/PetController.php @@ -17,11 +17,6 @@ public function actions() 'modelClass' => \app\models\Pet::class, 'checkAccess' => [$this, 'checkAccess'], ], - 'view' => [ - 'class' => \yii\rest\ViewAction::class, - 'modelClass' => \app\models\Pet::class, - 'checkAccess' => [$this, 'checkAccess'], - ], 'options' => [ 'class' => \yii\rest\OptionsAction::class, ], @@ -63,4 +58,26 @@ public function checkAccess($action, $model = null, $params = []) // TODO implement checkAccess } + public function actionView($petId) + { + $model = $this->findPetModel($petId); + $this->checkAccess('view', $model); + return $model; + } + + /** + * Returns the Pet model based on the primary key given. + * If the data model is not found, a 404 HTTP exception will be raised. + * @param string $id the ID of the model to be loaded. + * @return \app\models\Pet the model found + * @throws NotFoundHttpException if the model cannot be found. + */ + public function findPetModel($id) + { + $model = \app\models\Pet::findOne($id); + if ($model !== null) { + return $model; + } + throw new NotFoundHttpException("Object not found: $id"); + } }