diff --git a/Makefile b/Makefile index b31f823..9a60730 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,14 @@ # set user to "root" to run commands as root in docker USER=$$(whoami) # The docker command to execute commands directly in docker -DOCKER=docker-compose exec --user=$(USER) backend-php +DOCKER=docker-compose exec -T --user="$(USER)" backend-php # The PHP binary to use, you may add arguments to PHP here PHP=php # directories writeable by webserver WRITEABLE_DIRS=/app/runtime /app/logs /app/backend/web/assets +TESTCASE= +COMMAND= # default target lists general usage information default: @@ -43,7 +45,10 @@ stop: # run bash inside docker container bash: cli cli: - $(DOCKER) bash + docker-compose exec --user="$(USER)" backend-php bash + +run: + $(DOCKER) sh -c '$(COMMAND)' start-docker: docker-compose.override.yml runtime/build-docker config/components-dev.local.php backend/config/cookie-validation.key env.php stop docker-compose up -d @@ -79,7 +84,7 @@ backend/config/cookie-validation.key: ## Docker Runtime Tests ## test: tests/_data/dump.sql - $(DOCKER) vendor/bin/codecept run + $(DOCKER) vendor/bin/codecept run $(TESTCASE) clean: rm -rf tests/_data/dump.sql diff --git a/README.md b/README.md index 3828272..694f5a1 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,18 @@ The following list explains the directory structure in more detail: - `logs/` - log files - `runtime/` - temporary runtime files + +# Development + +Below commands are helpful while developing this project: + +```bash +./yii gii/api --openApiPath=/app/openapi/schema.yaml --generateMigrations=0 --generateControllers=0 --generateUrls=0 + +./yii gii/api --openApiPath=/app/openapi/schema.yaml --generateMigrations=1 --generateControllers=0 --generateUrls=0 --generateModels=0 --generateModelFaker=0 +``` + + # Support **Need help with your API project?** diff --git a/api/config/xdebug.ini b/api/config/xdebug.ini new file mode 100644 index 0000000..d34bbc7 --- /dev/null +++ b/api/config/xdebug.ini @@ -0,0 +1,9 @@ +;zend_extension=xdebug.so +xdebug.mode=debug +xdebug.start_with_request=yes +;;; if you are on macOS, use host.docker.internal to identify the host machine, due to a network limitation on mac (https://docs.docker.com/docker-for-mac/networking/#port-mapping) +;;; otherwise find host ip +xdebug.discover_client_host=yes +xdebug.client_host=host.docker.internal +xdebug.client_port=9003 +xdebug.idekey=XDEBUG_API diff --git a/backend/config/xdebug.ini b/backend/config/xdebug.ini new file mode 100644 index 0000000..ec55ea6 --- /dev/null +++ b/backend/config/xdebug.ini @@ -0,0 +1,9 @@ +;zend_extension=xdebug.so +xdebug.mode=debug +xdebug.start_with_request=yes +;;; if you are on macOS, use host.docker.internal to identify the host machine, due to a network limitation on mac (https://docs.docker.com/docker-for-mac/networking/#port-mapping) +;;; otherwise find host ip +xdebug.discover_client_host=yes +xdebug.client_host=host.docker.internal +xdebug.client_port=9003 +xdebug.idekey=XDEBUG_BACKEND diff --git a/backend/views/site/error.php b/backend/views/site/error.php new file mode 100644 index 0000000..4a37b25 --- /dev/null +++ b/backend/views/site/error.php @@ -0,0 +1,27 @@ +title = $name; +?> +
+ +

title) ?>

+ +
+ +
+ +

+ The above error occurred while the Web server was processing your request. +

+

+ Please contact us if you think this is a server error. Thank you. +

+ +
diff --git a/composer.json b/composer.json index b2394fb..735bbdd 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "config": { "platform": { - "php": "7.1.3" + "php": "7.4.26" }, "fxp-asset": { "enabled": false diff --git a/config/gii-generators.php b/config/gii-generators.php index a890af5..fe63a72 100644 --- a/config/gii-generators.php +++ b/config/gii-generators.php @@ -11,6 +11,5 @@ 'modelNamespace' => 'common\\models', 'fakerNamespace' => 'common\\models\\faker', 'migrationPath' => '@common/migrations', - ], ]; diff --git a/console/commands/FakerController.php b/console/commands/FakerController.php index e384159..8cf8b65 100644 --- a/console/commands/FakerController.php +++ b/console/commands/FakerController.php @@ -4,7 +4,7 @@ use yii\console\Controller; use yii\helpers\Console; -use yii\helpers\FileHelper; +use yii\helpers\{FileHelper, VarDumper}; use yii\helpers\StringHelper; /** @@ -16,11 +16,14 @@ public function actionIndex() { $fakers = FileHelper::findFiles(\Yii::getAlias('@common/models'), [ 'only' => ['*Faker.php'], + 'except' => ['BaseModelFaker.php'], ]); - foreach($fakers as $fakerFile) { - $className = 'common\\models\\' . StringHelper::basename($fakerFile, '.php'); - $this->stdout('Generating fake data for ' . StringHelper::basename($fakerFile, 'Faker.php') . '...'); + $sortedFakersModels = static::sortModels($fakers, '\\common\\models\\faker\\'); + + foreach($sortedFakersModels as $justModelClassName) { + $className = 'common\\models\\faker\\' . StringHelper::basename($justModelClassName, '.php').'Faker'; + $this->stdout('Generating fake data for ' . StringHelper::basename($justModelClassName, 'Faker.php') . '...'); $faker = new $className; for($i = 0; $i < 10; $i++) { $model = $faker->generateModel(); @@ -32,4 +35,70 @@ public function actionIndex() $this->stdout("done.\n", Console::BOLD, Console::FG_GREEN); } } + + public static function sortModels(array $fakers, string $fakerNamespace = 'app\\models\\') + { + $modelsDependencies = []; + foreach($fakers as $fakerFile) { + $className = $fakerNamespace . StringHelper::basename($fakerFile, '.php'); + $faker = new $className; + + $modelClassName = str_replace( + 'Faker', + '', + StringHelper::basename($fakerFile, '.php') + ); + + if (!method_exists($className, 'dependentOn')) { + $modelsDependencies[$modelClassName] = null; + } else { + $modelsDependencies[$modelClassName] = $className::dependentOn(); + } + } + + $standalone = array_filter($modelsDependencies, function ($elm) { + return $elm === null; + }); + + $dependent = array_filter($modelsDependencies, function ($elm) { + return $elm !== null; + }); + + $justDepenentModels = array_keys($dependent); + $sortedDependentModels = $justDepenentModels; + + foreach ($justDepenentModels as $model) { + if ($modelsDependencies[$model] !== null) { + foreach ($modelsDependencies[$model] as $dependentOn) { + if ($modelsDependencies[$dependentOn] !== null) { + // move $dependentOn before $model + + // move model to sort/order + // in that function if it is already before (sorted) then avoid it + static::moveModel($sortedDependentModels, $dependentOn, $model); + } + } + } + } + + $finalSortedModels = array_merge(array_keys($standalone), $sortedDependentModels); + return $finalSortedModels; + } + + public static function moveModel(&$sortedDependentModels, $dependentOn, $model) + { + $modelKey = array_search($model, $sortedDependentModels); + $depKey = array_search($dependentOn, $sortedDependentModels); + if ($depKey < $modelKey) { + return; + } + + unset($sortedDependentModels[$depKey]); + + $restRight = array_slice($sortedDependentModels, $modelKey); + $theKey = (($modelKey) < 0) ? 0 : ($modelKey); + $restLeft = array_slice($sortedDependentModels, 0, $theKey); + + $sortedDependentModels = array_merge($restLeft, [$dependentOn], $restRight); + } } diff --git a/console/config/components.php b/console/config/components.php index 4ea56aa..ee095c4 100644 --- a/console/config/components.php +++ b/console/config/components.php @@ -7,15 +7,18 @@ return [ 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, + 'flushInterval' => 1, 'targets' => [ [ 'class' => yii\log\FileTarget::class, + 'exportInterval' => 1, 'levels' => ['error', 'warning'], 'logFile' => '@logs/console-app.error.log', 'logVars' => [], ], [ 'class' => yii\log\FileTarget::class, + 'exportInterval' => 1, 'levels' => ['info'], 'logFile' => '@logs/console-app.info.log', 'logVars' => [], diff --git a/docker-compose.override.dist.yml b/docker-compose.override.dist.yml index 88a3c6c..2c9b4b4 100644 --- a/docker-compose.override.dist.yml +++ b/docker-compose.override.dist.yml @@ -5,15 +5,22 @@ version: '2' services: api-php: + environment: + PHP_ENABLE_XDEBUG: 1 ports: - '8337:80' + volumes: + - ./api/config/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini backend-php: + environment: + PHP_ENABLE_XDEBUG: 1 ports: - '8338:80' volumes: # Re-use local composer cache via host-volume - ~/.composer-docker/cache:/root/.composer/cache:delegated + - ./backend/config/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini db: volumes: diff --git a/docs/backend-bootstrap4.md b/docs/backend-bootstrap4.md index dc85056..f18584e 100644 --- a/docs/backend-bootstrap4.md +++ b/docs/backend-bootstrap4.md @@ -47,7 +47,7 @@ Steps: ``` -- Add a navigation to the mail layout: +- Add a navigation to the main layout: ```php diff --git a/docs/getting-started.md b/docs/getting-started.md index 6fe79af..ab7cd54 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -21,6 +21,7 @@ If you don't know how to create OpenAPI descriptions you can check out the follo - - + - - ... (if you happen to know better resources to look at, please [let us know](https://github.com/cebe/yii2-app-api/issues/new) Also check out the [OpenAPI Specification](https://spec.openapis.org/oas/v3.1.0) itself. diff --git a/openapi/schema.yaml b/openapi/schema.yaml new file mode 100644 index 0000000..d50402b --- /dev/null +++ b/openapi/schema.yaml @@ -0,0 +1,203 @@ +openapi: 3.0.3 +# Edit this schema and start your project +# This is sample schema +# To generate code which is based on this schema +# run commands mentioned Development section in README.md file +info: + title: 'Proxy-Service' + description: "" + version: 1.0.0 + contact: + name: '...' + email: you@example.com +servers: + - url: 'http://localhost:8937' + description: 'Local Dev API' +security: + - BasicAuth: [] +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + schemas: + Account: + description: user account + type: object + required: + - id + - name + properties: + id: + type: integer + name: + description: account name + type: string + maxLength: 40 + x-faker: 'substr($faker->userName(), 0, 40)' + + Domain: + description: domain + type: object + required: + - id + - name + - account + - created_at + properties: + id: + type: integer + name: + description: domain or sub-domain name, in DNS syntax, IDN are converted + type: string + maxLength: 128 + x-faker: '$faker->domainName' + account: + $ref: '#/components/schemas/Account' + + routings: + type: array + items: + $ref: '#/components/schemas/Routing' + + created_at: + readOnly: true + type: string + format: datetime + x-db-type: datetime + nullable: false + + Routing: + description: rounting specification + type: object + required: + - id + - domain + properties: + id: + type: integer + domain: + $ref: '#/components/schemas/Domain' + path: + type: string + maxLength: 255 + x-faker: '$faker->randomElement(["/", "/", "/", "/", "/api", "/tools", "/assets/web"])' + + ssl: + type: boolean + redirect_to_ssl: + type: boolean + + service: + type: string + maxLength: 255 + x-faker: '"http://tador.cebe.net/" . $faker->domainName' + + created_at: + readOnly: true + type: string + format: datetime + x-db-type: datetime + nullable: true + d123: + $ref: '#/components/schemas/D123' + a123: + $ref: '#/components/schemas/A123' + + D123: + description: desc + type: object + required: + - id + properties: + id: + type: integer + name: + type: string + A123: + description: desc + type: object + required: + - id + properties: + id: + type: integer + name: + type: string + b123: + $ref: '#/components/schemas/B123' + B123: + description: desc + type: object + required: + - id + properties: + id: + type: integer + name: + type: string + c123: + $ref: '#/components/schemas/C123' + C123: + description: desc + type: object + required: + - id + properties: + id: + type: integer + name: + type: string + E123: + description: desc + type: object + required: + - id + properties: + id: + type: integer + name: + type: string + b123: + $ref: '#/components/schemas/B123' + + +paths: + /: + get: + responses: [] + description: none + + + +# Dependencies: +# 'E123' => [ +# 0 => 'B123' +# ] +# 'Account' => null +# 'C123' => null +# 'Domain' => [ +# 0 => 'Account' +# ] +# 'B123' => [ +# 0 => 'C123' +# ] +# 'Routing' => [ +# 0 => 'Domain' +# 1 => 'D123' +# 2 => 'A123' +# ] +# 'D123' => null +# 'A123' => [ +# 0 => 'B123' +# ] + +# Sorted: +# 'Account', +# 'C123', +# 'D123', +# 'B123', +# 'E123', +# 'Domain', +# 'A123', +# 'Routing',