Skip to content

Commit

Permalink
Merge #13 remote-tracking branch 'php-openapi/144-methods-naming-for-…
Browse files Browse the repository at this point in the history
…non-crud-actions'

* php-openapi/144-methods-naming-for-non-crud-actions:
  Refactor
  Remove TODO
  Refactor and add docs
  Add more concrete tests
  Complete the test
  Fix failing tests
  Add fix & test for cebe#84
  Add docs about URL rules config
  Add docs
  Add more tests
  Fix bug
  Fix style
  Fix failing test
  Fix bug - WIP
  Fix this issue
  Fix issue in generated url rule config
  Fix issue: `actionCreateinvoicePayment` instead of `actionCreateInvoicePayment`
  Initial commit to create this PR
  • Loading branch information
cebe committed Nov 12, 2024
2 parents 9126089 + 5f7e993 commit 1e837f3
Show file tree
Hide file tree
Showing 35 changed files with 921 additions and 18 deletions.
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,81 @@ Provide custom database table column name in case of relationship column. This w
- x-fk-column-name: redelivery_of # this will create `redelivery_of` column instead of `redelivery_of_id`
```
### `x-route`

To customize route (controller ID/action ID) for a path, use custom key `x-route` with value `<controller ID>/<action ID>`. It can be used for non-crud paths. It must be used under HTTP method key but not
directly under the `paths` key of OpenAPI spec. Example:

```yaml
paths:
/payments/invoice/{invoice}:
parameters:
- name: invoice
in: path
description: lorem ipsum
required: true
schema:
type: integer
post:
x-route: 'payments/invoice'
summary: Pay Invoice
description: Pay for Invoice with given invoice number
requestBody:
description: Record new payment for an invoice
content:
application/json:
schema:
$ref: '#/components/schemas/Payments'
required: true
responses:
'200':
description: Successfully paid the invoice
content:
application/json:
schema:
$ref: '#/components/schemas/Success'
```

It won't generate `actionCreateInvoice` in `PaymentsController.php` file, but will generate `actionInvoice` instead in
same file.

Generated URL rules config for above is (in `urls.rest.php` or pertinent file):
```php
'POST payments/invoice/<invoice:\d+>' => 'payments/invoice',
'payments/invoice/<invoice:\d+>' => 'payments/options',
```

Also, if same action is needed for HTTP GET and POST then use same value for `x-route`. Example:

```yaml
paths:
/a1/b1:
get:
x-route: 'abc/xyz'
operationId: opnid1
summary: List
description: Lists
responses:
'200':
description: The Response
post:
x-route: 'abc/xyz'
operationId: opnid2
summary: create
description: create
responses:
'200':
description: The Response
```

Generated URL rules config for above is (in `urls.rest.php` or pertinent file):
```php
'GET a1/b1' => 'abc/xyz',
'POST a1/b1' => 'abc/xyz',
'a1/b1' => 'abc/options',
```
`x-route` does not support [Yii Modules](https://www.yiiframework.com/doc/guide/2.0/en/structure-modules).

## Many-to-Many relation definition

There are two ways for define many-to-many relations:
Expand Down
36 changes: 33 additions & 3 deletions src/generator/ApiGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
namespace cebe\yii2openapi\generator;

use cebe\yii2openapi\lib\items\DbModel;
use yii\db\mysql\Schema as MySqlSchema;
use SamIT\Yii2\MariaDb\Schema as MariaDbSchema;
use yii\db\pgsql\Schema as PgSqlSchema;
use cebe\openapi\Reader;
use cebe\openapi\spec\OpenApi;
use cebe\yii2openapi\lib\Config;
Expand All @@ -21,9 +18,14 @@
use cebe\yii2openapi\lib\generators\RestActionGenerator;
use cebe\yii2openapi\lib\generators\TransformersGenerator;
use cebe\yii2openapi\lib\generators\UrlRulesGenerator;
use cebe\yii2openapi\lib\items\FractalAction;
use cebe\yii2openapi\lib\items\RestAction;
use cebe\yii2openapi\lib\PathAutoCompletion;
use cebe\yii2openapi\lib\SchemaToDatabase;
use Yii;
use yii\db\mysql\Schema as MySqlSchema;
use SamIT\Yii2\MariaDb\Schema as MariaDbSchema;
use yii\db\pgsql\Schema as PgSqlSchema;
use yii\gii\CodeFile;
use yii\gii\Generator;
use yii\helpers\Html;
Expand Down Expand Up @@ -485,6 +487,7 @@ public function generate():array
$urlRulesGenerator = Yii::createObject(UrlRulesGenerator::class, [$config, $actions]);
$files = $urlRulesGenerator->generate();

$actions = static::removeDuplicateActions($actions); // in case of non-crud actions having custom route `x-route` set
$controllersGenerator = Yii::createObject(ControllersGenerator::class, [$config, $actions]);
$files->merge($controllersGenerator->generate());

Expand Down Expand Up @@ -533,4 +536,31 @@ public static function isMariaDb():bool
{
return strpos(Yii::$app->db->schema->getServerVersion(), 'MariaDB') !== false;
}

/**
* @param RestAction[]|FractalAction[] $actions
* @return RestAction[]|FractalAction[]
* https://github.com/cebe/yii2-openapi/issues/84
*/
public static function removeDuplicateActions(array $actions): array
{
$actions = array_filter($actions, function ($action) {
/** @var $action RestAction|FractalAction */
if ($action instanceof RestAction && $action->isDuplicate) {
return false;
}
return true;
});

$actions = array_map(function ($action) {
/** @var $action RestAction|FractalAction */
if ($action instanceof RestAction && $action->zeroParams) {
$action->idParam = null;
$action->params = [];
}
return $action;
}, $actions);

return $actions;
}
}
8 changes: 7 additions & 1 deletion src/generator/default/urls.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<?php

use yii\helpers\VarDumper;

?>
<?= '<?php' ?>

/**
* OpenAPI UrlRules
*
* This file is auto generated.
*/
<?php $rules = \yii\helpers\VarDumper::export($urls);?>
<?php /** @var array $urls */
$rules = VarDumper::export($urls); ?>
return <?= str_replace('\\\\', '\\', $rules); ?>;
5 changes: 5 additions & 0 deletions src/lib/CustomSpecAttr.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,9 @@ class CustomSpecAttr
* Foreign key column name. See README for usage docs
*/
public const FK_COLUMN_NAME = 'x-fk-column-name';

/**
* Custom route (controller ID/action ID) instead of auto-generated. See README for usage docs. https://github.com/cebe/yii2-openapi/issues/144
*/
public const ROUTE = 'x-route';
}
1 change: 1 addition & 0 deletions src/lib/generators/ControllersGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ protected function makeCustomController(
$params = array_map(static function ($param) {
return ['name' => $param];
}, $action->getParamNames());

$reflection->addMethod(
$action->actionMethodName,
$params,
Expand Down
8 changes: 6 additions & 2 deletions src/lib/generators/JsonActionGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ class JsonActionGenerator extends RestActionGenerator
* @throws \yii\base\InvalidConfigException
* @throws \cebe\openapi\exceptions\UnresolvableReferenceException
*/
protected function prepareAction(string $method, Operation $operation, RouteData $routeData):BaseObject
{
protected function prepareAction(
string $method,
Operation $operation,
RouteData $routeData,
?string $customRoute = null
): BaseObject {
$actionType = $this->resolveActionType($routeData, $method);
$modelClass = ResponseSchema::guessModelClass($operation, $actionType);
$expectedRelations = in_array($actionType, ['list', 'view'])
Expand Down
38 changes: 34 additions & 4 deletions src/lib/generators/RestActionGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use cebe\openapi\spec\PathItem;
use cebe\openapi\spec\Reference;
use cebe\yii2openapi\lib\Config;
use cebe\yii2openapi\lib\CustomSpecAttr;
use cebe\yii2openapi\lib\items\RestAction;
use cebe\yii2openapi\lib\items\RouteData;
use cebe\yii2openapi\lib\openapi\ResponseSchema;
Expand Down Expand Up @@ -58,6 +59,7 @@ public function generate():array
return array_merge(...$actions);
}

private $allCustomRoutes = [];
/**
* @param string $path
* @param \cebe\openapi\spec\PathItem $pathItem
Expand All @@ -71,7 +73,24 @@ protected function resolvePath(string $path, PathItem $pathItem):array

$routeData = Yii::createObject(RouteData::class, [$pathItem, $path, $this->config->urlPrefixes]);
foreach ($pathItem->getOperations() as $method => $operation) {
$actions[] = $this->prepareAction($method, $operation, $routeData);
$customRoute = null;
if (isset($operation->{CustomSpecAttr::ROUTE})) { # https://github.com/cebe/yii2-openapi/issues/144
$customRoute = $operation->{CustomSpecAttr::ROUTE};
}

$action = $this->prepareAction($method, $operation, $routeData, $customRoute);
if ($customRoute !== null) {
if (in_array($customRoute, array_keys($this->allCustomRoutes))) {
$action->isDuplicate = true;
if ($action->params !== $this->allCustomRoutes[$customRoute]->params) {
$this->allCustomRoutes[$customRoute]->zeroParams = true;
}
} else {
$action->isDuplicate = false;
$this->allCustomRoutes[$customRoute] = $action;
}
}
$actions[] = $action;
}
return $actions;
}
Expand All @@ -84,8 +103,12 @@ protected function resolvePath(string $path, PathItem $pathItem):array
* @throws \cebe\openapi\exceptions\UnresolvableReferenceException
* @throws \yii\base\InvalidConfigException
*/
protected function prepareAction(string $method, Operation $operation, RouteData $routeData):BaseObject
{
protected function prepareAction(
string $method,
Operation $operation,
RouteData $routeData,
?string $customRoute = null
): BaseObject {
$actionType = $this->resolveActionType($routeData, $method);
$modelClass = ResponseSchema::guessModelClass($operation, $actionType);
$responseWrapper = ResponseSchema::findResponseWrapper($operation, $modelClass);
Expand All @@ -106,12 +129,19 @@ protected function prepareAction(string $method, Operation $operation, RouteData
$controllerId = isset($this->config->controllerModelMap[$modelClass])
? Inflector::camel2id($this->config->controllerModelMap[$modelClass])
: Inflector::camel2id($modelClass);
} elseif (!empty($customRoute)) {
$controllerId = explode('/', $customRoute)[0];
} else {
$controllerId = $routeData->controller;
}
$action = Inflector::camel2id($routeData->action);
if (!empty($customRoute)) {
$actionType = '';
$action = explode('/', $customRoute)[1];
}
return Yii::createObject(RestAction::class, [
[
'id' => trim("$actionType{$routeData->action}", '-'),
'id' => trim("$actionType-$action", '-'),
'controllerId' => $controllerId,
'urlPath' => $routeData->path,
'requestMethod' => strtoupper($method),
Expand Down
23 changes: 21 additions & 2 deletions src/lib/items/RestAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,32 @@ final class RestAction extends BaseObject
*/
public $responseWrapper;

/**
* @var bool
* @see $isDuplicate
* https://github.com/cebe/yii2-openapi/issues/84
* see `x-route` in README.md
* Used for generating only one action for paths like: `GET /calendar/domains` and `GET /calendar/domains/{id}` given that they have same `x-route`.
* If duplicates routes have same params then `false`, else action is generated with no (0) params `true`
*/
public $zeroParams = false;

/**
* @var bool
* https://github.com/cebe/yii2-openapi/issues/84
* Generate only one action for paths like: `GET /calendar/domains` and `GET /calendar/domains/{id}` given that they have same `x-route`.
* @see $zeroParams
* see `x-route` in README.md
*/
public $isDuplicate = false;

public function getRoute():string
{
if ($this->prefix && !empty($this->prefixSettings)) {
$prefix = $this->prefixSettings['module'] ?? $this->prefix;
return trim($prefix, '/').'/'.$this->controllerId.'/'.$this->id;
return trim($prefix, '/') . '/' . $this->controllerId . '/' . $this->id;
}
return $this->controllerId.'/'.$this->id;
return $this->controllerId . '/' . $this->id;
}

public function getOptionsRoute():string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
/**
* OpenAPI UrlRules
*
* This file is auto generated.
*/
return [
'GET fruit/mango' => 'fruits/mango',
'GET fruits/mango' => 'fruit/mango',
'POST fruits/mango' => 'fruit/create-mango',
'GET animal/goat' => 'animal/goat',
'POST animal/goat' => 'animal/create-goat',
'POST payments/invoice/<invoice:\d+>' => 'payments/invoice',
'GET payments/invoice-payment' => 'payment/invoice-payment',
'GET a1/b1' => 'abc/xyz',
'POST a1/b1' => 'abc/xyz',
'GET aa2/bb2' => 'payments/xyz2',
'fruit/mango' => 'fruits/options',
'fruits/mango' => 'fruit/options',
'animal/goat' => 'animal/options',
'payments/invoice/<invoice:\d+>' => 'payments/options',
'payments/invoice-payment' => 'payment/options',
'a1/b1' => 'abc/options',
'aa2/bb2' => 'payments/options',
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace app\controllers;

class AbcController extends \app\controllers\base\AbcController
{

public function checkAccess($action, $model = null, $params = [])
{
//TODO implement checkAccess
}

public function actionXyz()
{
//TODO implement actionXyz
}


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace app\controllers;

class AnimalController extends \app\controllers\base\AnimalController
{

public function checkAccess($action, $model = null, $params = [])
{
//TODO implement checkAccess
}

public function actionGoat()
{
//TODO implement actionGoat
}

public function actionCreateGoat()
{
//TODO implement actionCreateGoat
}


}

Loading

0 comments on commit 1e837f3

Please sign in to comment.