Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve: Methods naming for non crud actions #144 #13

Merged
merged 18 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,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
35 changes: 32 additions & 3 deletions src/generator/ApiGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@

namespace cebe\yii2openapi\generator;

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 @@ -20,9 +17,13 @@
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 yii\db\pgsql\Schema as PgSqlSchema;
use yii\gii\CodeFile;
use yii\gii\Generator;
use yii\helpers\Html;
Expand Down Expand Up @@ -473,6 +474,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 @@ -521,4 +523,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
Loading