diff --git a/.gitignore b/.gitignore index c1eb769..ef0f0e4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ lumen-test/app lumen-test/database lumen-test/tests/tmp +.DS_Store .idea diff --git a/composer.lock b/composer.lock index 4294829..0ef7131 100644 --- a/composer.lock +++ b/composer.lock @@ -1,40 +1,40 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], "content-hash": "b87593e214674fc5ef7def2111571da5", "packages": [ { "name": "doctrine/inflector", - "version": "v1.1.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + "reference": "5527a48b7313d15261292c149e55e26eae771b0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "4.*" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Inflector\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -71,33 +71,35 @@ "singularize", "string" ], - "time": "2015-11-06T14:35:42+00:00" + "time": "2018-01-09T20:05:19+00:00" }, { "name": "fzaninotto/faker", - "version": "v1.6.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/fzaninotto/Faker.git", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123" + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", "shasum": "" }, "require": { - "php": "^5.3.3|^7.0" + "php": "^5.3.3 || ^7.0" }, "require-dev": { "ext-intl": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^1.5" }, "type": "library", "extra": { - "branch-alias": [] + "branch-alias": { + "dev-master": "1.8-dev" + } }, "autoload": { "psr-4": { @@ -119,38 +121,37 @@ "faker", "fixtures" ], - "time": "2016-04-29T12:21:54+00:00" + "time": "2018-07-12T10:23:15+00:00" }, { "name": "illuminate/console", - "version": "v5.4.19", + "version": "v5.7.11", "source": { "type": "git", "url": "https://github.com/illuminate/console.git", - "reference": "8ea19d470cdc0d6ab88269b1841dfd234cf308b8" + "reference": "d50a4638cfa6605b4ff5fc7030439194734c2731" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/console/zipball/8ea19d470cdc0d6ab88269b1841dfd234cf308b8", - "reference": "8ea19d470cdc0d6ab88269b1841dfd234cf308b8", + "url": "https://api.github.com/repos/illuminate/console/zipball/d50a4638cfa6605b4ff5fc7030439194734c2731", + "reference": "d50a4638cfa6605b4ff5fc7030439194734c2731", "shasum": "" }, "require": { - "illuminate/contracts": "5.4.*", - "illuminate/support": "5.4.*", - "nesbot/carbon": "~1.20", - "php": ">=5.6.4", - "symfony/console": "~3.2" + "illuminate/contracts": "5.7.*", + "illuminate/support": "5.7.*", + "php": "^7.1.3", + "symfony/console": "^4.1" }, "suggest": { - "guzzlehttp/guzzle": "Required to use the ping methods on schedules (~6.0).", - "mtdowling/cron-expression": "Required to use scheduling component (~1.0).", - "symfony/process": "Required to use scheduling component (~3.2)." + "dragonmantank/cron-expression": "Required to use scheduling component (^2.0).", + "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^6.0).", + "symfony/process": "Required to use scheduling component (^4.1)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "5.7-dev" } }, "autoload": { @@ -170,29 +171,31 @@ ], "description": "The Illuminate Console package.", "homepage": "https://laravel.com", - "time": "2017-03-23T15:59:01+00:00" + "time": "2018-10-18T03:39:45+00:00" }, { "name": "illuminate/contracts", - "version": "v5.4.19", + "version": "v5.7.11", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "ab2825726bee46a67c8cc66789852189dbef74a9" + "reference": "64df81d3382d876f1c1d3d5481d89c93b61b8279" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/ab2825726bee46a67c8cc66789852189dbef74a9", - "reference": "ab2825726bee46a67c8cc66789852189dbef74a9", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/64df81d3382d876f1c1d3d5481d89c93b61b8279", + "reference": "64df81d3382d876f1c1d3d5481d89c93b61b8279", "shasum": "" }, "require": { - "php": ">=5.6.4" + "php": "^7.1.3", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "5.7-dev" } }, "autoload": { @@ -212,37 +215,39 @@ ], "description": "The Illuminate Contracts package.", "homepage": "https://laravel.com", - "time": "2017-03-29T13:17:47+00:00" + "time": "2018-10-08T13:34:14+00:00" }, { "name": "illuminate/filesystem", - "version": "v5.4.19", + "version": "v5.7.11", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", - "reference": "7f656e3421b94d759627e891567380b50586f045" + "reference": "cbb5650be36d7370f7ae5f039d2143952fa58f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/7f656e3421b94d759627e891567380b50586f045", - "reference": "7f656e3421b94d759627e891567380b50586f045", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/cbb5650be36d7370f7ae5f039d2143952fa58f51", + "reference": "cbb5650be36d7370f7ae5f039d2143952fa58f51", "shasum": "" }, "require": { - "illuminate/contracts": "5.4.*", - "illuminate/support": "5.4.*", - "php": ">=5.6.4", - "symfony/finder": "~3.2" + "illuminate/contracts": "5.7.*", + "illuminate/support": "5.7.*", + "php": "^7.1.3", + "symfony/finder": "^4.1" }, "suggest": { - "league/flysystem": "Required to use the Flysystem local and FTP drivers (~1.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).", - "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0)." + "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", + "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", + "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (^1.0).", + "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "5.7-dev" } }, "autoload": { @@ -262,41 +267,43 @@ ], "description": "The Illuminate Filesystem package.", "homepage": "https://laravel.com", - "time": "2017-04-07T19:38:05+00:00" + "time": "2018-10-24T12:49:16+00:00" }, { "name": "illuminate/support", - "version": "v5.4.19", + "version": "v5.7.11", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "b8cb37e15331c59da51c8ee5838038baa22d7955" + "reference": "45bfc0cd080c51946f61c04e324c2b4c6df58a9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/b8cb37e15331c59da51c8ee5838038baa22d7955", - "reference": "b8cb37e15331c59da51c8ee5838038baa22d7955", + "url": "https://api.github.com/repos/illuminate/support/zipball/45bfc0cd080c51946f61c04e324c2b4c6df58a9d", + "reference": "45bfc0cd080c51946f61c04e324c2b4c6df58a9d", "shasum": "" }, "require": { - "doctrine/inflector": "~1.0", + "doctrine/inflector": "^1.1", "ext-mbstring": "*", - "illuminate/contracts": "5.4.*", - "paragonie/random_compat": "~1.4|~2.0", - "php": ">=5.6.4" + "illuminate/contracts": "5.7.*", + "nesbot/carbon": "^1.26.3", + "php": "^7.1.3" }, - "replace": { - "tightenco/collect": "self.version" + "conflict": { + "tightenco/collect": "<5.5.33" }, "suggest": { - "illuminate/filesystem": "Required to use the composer class (5.2.*).", - "symfony/process": "Required to use the composer class (~3.2).", - "symfony/var-dumper": "Required to use the dd function (~3.2)." + "illuminate/filesystem": "Required to use the composer class (5.7.*).", + "moontoast/math": "Required to use ordered UUIDs (^1.1).", + "ramsey/uuid": "Required to use Str::uuid() (^3.7).", + "symfony/process": "Required to use the composer class (^4.1).", + "symfony/var-dumper": "Required to use the dd function (^4.1)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.4-dev" + "dev-master": "5.7-dev" } }, "autoload": { @@ -319,39 +326,41 @@ ], "description": "The Illuminate Support package.", "homepage": "https://laravel.com", - "time": "2017-04-09T14:34:57+00:00" + "time": "2018-10-22T17:36:06+00:00" }, { "name": "nesbot/carbon", - "version": "1.22.1", + "version": "1.34.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc" + "reference": "19201b87f7dba2a7cbf1cccdf0e1da13c04ee9c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", - "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/19201b87f7dba2a7cbf1cccdf0e1da13c04ee9c9", + "reference": "19201b87f7dba2a7cbf1cccdf0e1da13c04ee9c9", "shasum": "" }, "require": { - "php": ">=5.3.0", - "symfony/translation": "~2.6 || ~3.0" + "php": ">=5.3.9", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2", - "phpunit/phpunit": "~4.0 || ~5.0" + "phpunit/phpunit": "^4.8.35 || ^5.7" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.23-dev" + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] } }, "autoload": { "psr-4": { - "Carbon\\": "src/Carbon/" + "": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -372,36 +381,35 @@ "datetime", "time" ], - "time": "2017-01-16T07:55:07+00:00" + "time": "2018-11-08T13:33:47+00:00" }, { - "name": "paragonie/random_compat", - "version": "v2.0.10", + "name": "psr/container", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", "shasum": "" }, "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "php": ">=5.3.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { - "files": [ - "lib/random.php" - ] + "psr-4": { + "Psr\\Container\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -409,31 +417,33 @@ ], "authors": [ { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "csprng", - "pseudorandom", - "random" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "time": "2017-03-13T16:27:32+00:00" + "time": "2017-02-14T16:28:37+00:00" }, { - "name": "psr/log", - "version": "1.0.2", + "name": "psr/simple-cache", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", "shasum": "" }, "require": { @@ -447,7 +457,7 @@ }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\SimpleCache\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -460,50 +470,56 @@ "homepage": "http://www.php-fig.org/" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Common interfaces for simple caching", "keywords": [ - "log", + "cache", + "caching", "psr", - "psr-3" + "psr-16", + "simple-cache" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2017-10-23T01:57:42+00:00" }, { "name": "symfony/console", - "version": "v3.2.8", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38" + "reference": "432122af37d8cd52fba1b294b11976e0d20df595" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", - "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", + "url": "https://api.github.com/repos/symfony/console/zipball/432122af37d8cd52fba1b294b11976e0d20df595", + "reference": "432122af37d8cd52fba1b294b11976e0d20df595", "shasum": "" }, "require": { - "php": ">=5.5.9", - "symfony/debug": "~2.8|~3.0", + "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0" }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, "require-dev": { "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.8|~3.0", - "symfony/filesystem": "~2.8|~3.0", - "symfony/process": "~2.8|~3.0" + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" }, "suggest": { - "psr/log": "For using the console logger", + "psr/log-implementation": "For using the console logger", "symfony/event-dispatcher": "", - "symfony/filesystem": "", + "symfony/lock": "", "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -530,86 +546,29 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-04-26T01:39:17+00:00" - }, - { - "name": "symfony/debug", - "version": "v3.2.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/fd6eeee656a5a7b384d56f1072243fe1c0e81686", - "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686", - "shasum": "" - }, - "require": { - "php": ">=5.5.9", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/class-loader": "~2.8|~3.0", - "symfony/http-kernel": "~2.8|~3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Debug Component", - "homepage": "https://symfony.com", - "time": "2017-04-19T20:17:50+00:00" + "time": "2018-10-31T09:30:44+00:00" }, { "name": "symfony/finder", - "version": "v3.2.8", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930" + "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", - "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", + "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", + "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -636,20 +595,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2018-10-03T08:47:56+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.3.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", "shasum": "" }, "require": { @@ -661,7 +620,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -695,44 +654,49 @@ "portable", "shim" ], - "time": "2016-11-14T01:06:16+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "symfony/translation", - "version": "v3.2.8", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "f4a04d2df710f81515df576b2de06bdeee518b83" + "reference": "aa04dc1c75b7d3da7bd7003104cd0cfc5dff635c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/f4a04d2df710f81515df576b2de06bdeee518b83", - "reference": "f4a04d2df710f81515df576b2de06bdeee518b83", + "url": "https://api.github.com/repos/symfony/translation/zipball/aa04dc1c75b7d3da7bd7003104cd0cfc5dff635c", + "reference": "aa04dc1c75b7d3da7bd7003104cd0cfc5dff635c", "shasum": "" }, "require": { - "php": ">=5.5.9", + "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/config": "<2.8" + "symfony/config": "<3.4", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/intl": "^2.8.18|^3.2.5", - "symfony/yaml": "~2.8|~3.0" + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/intl": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { - "psr/log": "To use logging capability in translator", + "psr/log-implementation": "To use logging capability in translator", "symfony/config": "", "symfony/yaml": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -759,7 +723,7 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:13:17+00:00" + "time": "2018-10-28T18:38:52+00:00" } ], "packages-dev": [], diff --git a/formats/fields.json b/formats/fields.json index eeb8f11..6d31af2 100644 --- a/formats/fields.json +++ b/formats/fields.json @@ -32,7 +32,8 @@ { "name": "factory", "type": "string", - "default": false + "default": false, + "required": false } ] } diff --git a/formats/relations-hasManyThrough.json b/formats/relations-hasManyThrough.json new file mode 100644 index 0000000..a445e10 --- /dev/null +++ b/formats/relations-hasManyThrough.json @@ -0,0 +1,19 @@ +{ + "type": "array", + "fields": { + "type": "object", + "separator": ":", + "fields": [ + "name", + { + "name": "model", + "type": "string" + }, + { + "name": "through", + "type": "string", + "default": false + } + ] + } +} \ No newline at end of file diff --git a/formats/relations-morphMany.json b/formats/relations-morphMany.json new file mode 100644 index 0000000..a3181b6 --- /dev/null +++ b/formats/relations-morphMany.json @@ -0,0 +1,19 @@ +{ + "type": "array", + "fields": { + "type": "object", + "separator": ":", + "fields": [ + "name", + { + "name": "model", + "type": "string" + }, + { + "name": "through", + "type": "string", + "default": "" + } + ] + } +} \ No newline at end of file diff --git a/formats/relations-morphTo.json b/formats/relations-morphTo.json new file mode 100644 index 0000000..ded0544 --- /dev/null +++ b/formats/relations-morphTo.json @@ -0,0 +1,15 @@ +{ + "type": "array", + "fields": { + "type": "object", + "separator": "|", + "fields": [ + "name", + { + "name": "nullable", + "type": "boolean", + "default": false + } + ] + } +} \ No newline at end of file diff --git a/lumen-test/.env b/lumen-test/.env index 0b97d69..ac7be13 100644 --- a/lumen-test/.env +++ b/lumen-test/.env @@ -12,7 +12,7 @@ DB_CONNECTION=sqlite # DB_USERNAME=homestead # DB_PASSWORD=secret -# CACHE_DRIVER=memcached +CACHE_DRIVER=file # SESSION_DRIVER=memcached # QUEUE_DRIVER=database diff --git a/lumen-test/app/Http/routes.php b/lumen-test/app/Http/routes.php index 632ded3..0650368 100644 --- a/lumen-test/app/Http/routes.php +++ b/lumen-test/app/Http/routes.php @@ -11,6 +11,6 @@ | */ -$app->get("/", function () use ($app) { - return $app->welcome(); +$router->get("/", function () use ($router) { + return 'Hello World'; }); diff --git a/lumen-test/bootstrap/app.php b/lumen-test/bootstrap/app.php index 52b393f..1461d8d 100644 --- a/lumen-test/bootstrap/app.php +++ b/lumen-test/bootstrap/app.php @@ -2,7 +2,11 @@ require_once __DIR__.'/../vendor/autoload.php'; -Dotenv::load(__DIR__.'/../'); +try { + (new Dotenv\Dotenv(__DIR__.'/../'))->load(); +} catch (Dotenv\Exception\InvalidPathException $e) { + // +} /* |-------------------------------------------------------------------------- @@ -92,7 +96,9 @@ | */ -$app->group(['namespace' => 'App\Http\Controllers'], function ($app) { +$app->router->group([ + 'namespace' => 'App\Http\Controllers', +], function ($router) { require __DIR__.'/../app/Http/routes.php'; }); diff --git a/lumen-test/clean.sh b/lumen-test/clean.sh index f11634f..bb605e8 100755 --- a/lumen-test/clean.sh +++ b/lumen-test/clean.sh @@ -1,3 +1,7 @@ +#!/usr/bin/env bash + +cd "$(dirname "$0")" + # models rm app/*.php 2> /dev/null @@ -11,13 +15,15 @@ echo "welcome(); });" > app/Http/routes.php -echo " routes/api.php +if [ -d "routes" ]; then + echo " routes/api.php +fi # Controllers rm app/Http/Controllers/*.php 2> /dev/null diff --git a/lumen-test/composer.json b/lumen-test/composer.json index a12ad8d..3e48093 100644 --- a/lumen-test/composer.json +++ b/lumen-test/composer.json @@ -5,14 +5,14 @@ "license": "MIT", "type": "project", "require": { - "php": ">=5.5.9", - "laravel/lumen-framework": "5.1.*", - "vlucas/phpdotenv": "~1.0" + "php": ">=7.1.3", + "laravel/lumen-framework": "5.5.*", + "vlucas/phpdotenv": "~2.2" }, "require-dev": { "phpunit/phpunit": "~4.0", "fzaninotto/faker": "~1.0", - "phpspec/phpspec": "2.0.0", + "phpspec/phpspec": "5.1.0", "codeception/codeception": "^2.2" }, "autoload": { diff --git a/lumen-test/tests/_support/_generated/AcceptanceTesterActions.php b/lumen-test/tests/_support/_generated/AcceptanceTesterActions.php index 83b4f6e..12f9402 100644 --- a/lumen-test/tests/_support/_generated/AcceptanceTesterActions.php +++ b/lumen-test/tests/_support/_generated/AcceptanceTesterActions.php @@ -1,14 +1,10 @@ -getScenario()->runStep(new \Codeception\Step\Action('assertArraySubset', func_get_args())); - } - - /** * [!] Method is generated. Documentation taken from corresponding module. * diff --git a/lumen-test/tests/_support/_generated/FunctionalTesterActions.php b/lumen-test/tests/_support/_generated/FunctionalTesterActions.php index 074ea91..f873932 100644 --- a/lumen-test/tests/_support/_generated/FunctionalTesterActions.php +++ b/lumen-test/tests/_support/_generated/FunctionalTesterActions.php @@ -1,12 +1,10 @@ -getScenario()->runStep(new \Codeception\Step\Action('assertArraySubset', func_get_args())); - } } diff --git a/lumen-test/tests/_support/_generated/UnitTesterActions.php b/lumen-test/tests/_support/_generated/UnitTesterActions.php index 9b2691f..0fe76df 100644 --- a/lumen-test/tests/_support/_generated/UnitTesterActions.php +++ b/lumen-test/tests/_support/_generated/UnitTesterActions.php @@ -1,13 +1,10 @@ -assertEquals($element->getChildrenCount(), 5); + * $I->assertEquals(5, $element->getChildrenCount()); * ``` * * Floating-point example: * ```php * assertEquals($calculator->add(0.1, 0.2), 0.3, 'Calculator should add the two numbers correctly.', 0.01); + * $I->assertEquals(0.3, $calculator->add(0.1, 0.2), 'Calculator should add the two numbers correctly.', 0.01); * ``` * * @param $expected @@ -56,13 +53,13 @@ public function assertEquals($expected, $actual, $message = null, $delta = null) * Regular example: * ```php * assertNotEquals($element->getChildrenCount(), 0); + * $I->assertNotEquals(0, $element->getChildrenCount()); * ``` * * Floating-point example: * ```php * assertNotEquals($calculator->add(0.1, 0.2), 0.4, 'Calculator should add the two numbers correctly.', 0.01); + * $I->assertNotEquals(0.4, $calculator->add(0.1, 0.2), 'Calculator should add the two numbers correctly.', 0.01); * ``` * * @param $expected @@ -326,6 +323,20 @@ public function assertTrue($condition, $message = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the condition is NOT true (everything but true) + * + * @param $condition + * @param string $message + * @see \Codeception\Module\Asserts::assertNotTrue() + */ + public function assertNotTrue($condition, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotTrue', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -340,6 +351,20 @@ public function assertFalse($condition, $message = null) { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the condition is NOT false (everything but false) + * + * @param $condition + * @param string $message + * @see \Codeception\Module\Asserts::assertNotFalse() + */ + public function assertNotFalse($condition, $message = null) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotFalse', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -441,7 +466,7 @@ public function assertArrayNotHasKey($key, $actual, $description = null) { * @param array $array * @param bool $strict * @param string $message - * @see \Codeception\Module::assertArraySubset() + * @see \Codeception\Module\Asserts::assertArraySubset() */ public function assertArraySubset($subset, $array, $strict = null, $message = null) { return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArraySubset', func_get_args())); @@ -540,9 +565,45 @@ public function fail($message) { * * @param $exception string or \Exception * @param $callback + * + * @deprecated Use expectThrowable instead * @see \Codeception\Module\Asserts::expectException() */ public function expectException($exception, $callback) { return $this->getScenario()->runStep(new \Codeception\Step\Action('expectException', func_get_args())); } + + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Handles and checks throwables (Exceptions/Errors) called inside the callback function. + * Either throwable class name or throwable instance should be provided. + * + * ```php + * expectThrowable(MyThrowable::class, function() { + * $this->doSomethingBad(); + * }); + * + * $I->expectThrowable(new MyException(), function() { + * $this->doSomethingBad(); + * }); + * ``` + * If you want to check message or throwable code, you can pass them with throwable instance: + * ```php + * expectThrowable(new MyError("Don't do bad things"), function() { + * $this->doSomethingBad(); + * }); + * ``` + * + * @param $throwable string or \Throwable + * @param $callback + * @see \Codeception\Module\Asserts::expectThrowable() + */ + public function expectThrowable($throwable, $callback) { + return $this->getScenario()->runStep(new \Codeception\Step\Action('expectThrowable', func_get_args())); + } } diff --git a/lumen-test/tests/acceptance/ControllerCommandCept.php b/lumen-test/tests/acceptance/ControllerCommandCept.php index 584ffd4..289b2d8 100644 --- a/lumen-test/tests/acceptance/ControllerCommandCept.php +++ b/lumen-test/tests/acceptance/ControllerCommandCept.php @@ -10,7 +10,7 @@ class TestsController extends Controller { - const MODEL = "App\\Test"; + const MODEL = \'App\\Test\'; use RESTActions; @@ -27,7 +27,7 @@ class TestsController extends Controller { class CategoriesController extends Controller { - const MODEL = "App\\Models\\Category"; + const MODEL = \'App\\Models\\Category\'; use RESTActions; @@ -36,11 +36,11 @@ class CategoriesController extends Controller { $I->deleteFile('./app/Http/Controllers/CategoriesController.php'); $I->openFile('./app/Http/routes.php'); $I->seeInThisFile(" -\$app->get('category', 'CategoriesController@all'); -\$app->get('category/{id}', 'CategoriesController@get'); -\$app->post('category', 'CategoriesController@add'); -\$app->put('category/{id}', 'CategoriesController@put'); -\$app->delete('category/{id}', 'CategoriesController@remove'); +\$router->get('category', 'CategoriesController@all'); +\$router->get('category/{id}', 'CategoriesController@get'); +\$router->post('category', 'CategoriesController@add'); +\$router->put('category/{id}', 'CategoriesController@put'); +\$router->delete('category/{id}', 'CategoriesController@remove'); "); $I->writeToFile('./app/Http/routes.php', 'get("/", function () use ($app) { - return $app->welcome(); +$router->get("/", function () use ($router) { + return \'Hello World\'; }); '); \ No newline at end of file diff --git a/lumen-test/tests/acceptance/ResourceCommandCept.php b/lumen-test/tests/acceptance/ResourceCommandCept.php index af4ec89..1ad976b 100644 --- a/lumen-test/tests/acceptance/ResourceCommandCept.php +++ b/lumen-test/tests/acceptance/ResourceCommandCept.php @@ -11,13 +11,13 @@ $I->seeInThisFile('namespace App;'); $I->seeInThisFile('class TaskCategory extends Model'); -$I->seeInThisFile('protected $fillable = ["name", "descr", "due", "project_id", "user_id"];'); +$I->seeInThisFile('protected $fillable = ["name", "descr", "due", "project_id", "creator_id"];'); $I->seeInThisFile('protected $dates = ["due"];'); $I->seeInThisFile( "public static \$rules = [\n". " \"name\" => \"requied\"," . PHP_EOL . " \"project_id\" => \"required|numeric\"," . PHP_EOL . -" \"user_id\" => \"required|numeric\",\n". +" \"creator_id\" => \"required|numeric\",\n". " ];"); $I->seeInThisFile( ' public function tags() @@ -53,11 +53,11 @@ " \$table->text('descr')->nullable();" . PHP_EOL . " \$table->timestamp('due');" . PHP_EOL . " \$table->integer('project_id')->unsigned();" . PHP_EOL. -" \$table->integer('user_id')->unsigned();\n" . +" \$table->integer('creator_id')->unsigned();\n" . " \$table->foreign('project_id')\n". " ->references('id')\n". " ->on('projects');" . PHP_EOL . -" \$table->foreign('user_id')\n". +" \$table->foreign('creator_id')\n". " ->references('id')\n". " ->on('users');\n". " \$table->timestamps();"); @@ -75,7 +75,7 @@ $I->seeInThisFile('class TaskCategoriesController extends Controller { - const MODEL = "App\TaskCategory"; + const MODEL = \'App\\TaskCategory\'; use RESTActions; @@ -86,11 +86,11 @@ // Checking routes $I->openFile('./app/Http/routes.php'); $I->seeInThisFile(' -$app->get(\'task-category\', \'TaskCategoriesController@all\'); -$app->get(\'task-category/{id}\', \'TaskCategoriesController@get\'); -$app->post(\'task-category\', \'TaskCategoriesController@add\'); -$app->put(\'task-category/{id}\', \'TaskCategoriesController@put\'); -$app->delete(\'task-category/{id}\', \'TaskCategoriesController@remove\');'); +$router->get(\'task-category\', \'TaskCategoriesController@all\'); +$router->get(\'task-category/{id}\', \'TaskCategoriesController@get\'); +$router->post(\'task-category\', \'TaskCategoriesController@add\'); +$router->put(\'task-category/{id}\', \'TaskCategoriesController@put\'); +$router->delete(\'task-category/{id}\', \'TaskCategoriesController@remove\');'); $I->writeToFile('./app/Http/routes.php', 'get("/", function () use ($app) { - return $app->welcome(); +$router->get("/", function () use ($router) { + return \'Hello World\'; }); '); diff --git a/lumen-test/tests/acceptance/ResourcesCommandCept.php b/lumen-test/tests/acceptance/ResourcesCommandCept.php index 467ee7b..8c5260d 100644 --- a/lumen-test/tests/acceptance/ResourcesCommandCept.php +++ b/lumen-test/tests/acceptance/ResourcesCommandCept.php @@ -45,25 +45,25 @@ // Checking routes $I->openFile('./app/Http/routes.php'); $I->seeInThisFile(' -$app->get(\'author\', \'AuthorsController@all\'); -$app->get(\'author/{id}\', \'AuthorsController@get\'); -$app->post(\'author\', \'AuthorsController@add\'); -$app->put(\'author/{id}\', \'AuthorsController@put\'); -$app->delete(\'author/{id}\', \'AuthorsController@remove\');'); +$router->get(\'author\', \'AuthorsController@all\'); +$router->get(\'author/{id}\', \'AuthorsController@get\'); +$router->post(\'author\', \'AuthorsController@add\'); +$router->put(\'author/{id}\', \'AuthorsController@put\'); +$router->delete(\'author/{id}\', \'AuthorsController@remove\');'); $I->seeInThisFile(' -$app->get(\'book\', \'BooksController@all\'); -$app->get(\'book/{id}\', \'BooksController@get\'); -$app->post(\'book\', \'BooksController@add\'); -$app->put(\'book/{id}\', \'BooksController@put\'); -$app->delete(\'book/{id}\', \'BooksController@remove\');'); +$router->get(\'book\', \'BooksController@all\'); +$router->get(\'book/{id}\', \'BooksController@get\'); +$router->post(\'book\', \'BooksController@add\'); +$router->put(\'book/{id}\', \'BooksController@put\'); +$router->delete(\'book/{id}\', \'BooksController@remove\');'); $I->seeInThisFile(' -$app->get(\'library\', \'LibrariesController@all\'); -$app->get(\'library/{id}\', \'LibrariesController@get\'); -$app->post(\'library\', \'LibrariesController@add\'); -$app->put(\'library/{id}\', \'LibrariesController@put\'); -$app->delete(\'library/{id}\', \'LibrariesController@remove\');'); +$router->get(\'library\', \'LibrariesController@all\'); +$router->get(\'library/{id}\', \'LibrariesController@get\'); +$router->post(\'library\', \'LibrariesController@add\'); +$router->put(\'library/{id}\', \'LibrariesController@put\'); +$router->delete(\'library/{id}\', \'LibrariesController@remove\');'); $I->writeToFile('./app/Http/routes.php', 'get("/", function () use ($app) { - return $app->welcome(); +$router->get("/", function () use ($router) { + return \'Hello World\'; }); '); diff --git a/lumen-test/tests/acceptance/ResourcesWithLaravelRoutesCept.php b/lumen-test/tests/acceptance/ResourcesWithLaravelRoutesCept.php index 66e8aba..ab1e994 100644 --- a/lumen-test/tests/acceptance/ResourcesWithLaravelRoutesCept.php +++ b/lumen-test/tests/acceptance/ResourcesWithLaravelRoutesCept.php @@ -108,6 +108,7 @@ "); $I->deleteFile('database/database.sqlite'); +$I->deleteDir('./routes'); // Checking database seeder // $I->openFile('./database/seeds/TaskCategoriesTableSeeder.php'); diff --git a/lumen-test/tests/acceptance/RouteCommandCept.php b/lumen-test/tests/acceptance/RouteCommandCept.php index 0ba89a7..3b86565 100644 --- a/lumen-test/tests/acceptance/RouteCommandCept.php +++ b/lumen-test/tests/acceptance/RouteCommandCept.php @@ -6,11 +6,11 @@ $I->seeInShellOutput('project-type routes generated'); $I->openFile('./app/Http/routes.php'); $I->seeInThisFile(" -\$app->get('project-type', 'ProjectTypesController@all'); -\$app->get('project-type/{id}', 'ProjectTypesController@get'); -\$app->post('project-type', 'ProjectTypesController@add'); -\$app->put('project-type/{id}', 'ProjectTypesController@put'); -\$app->delete('project-type/{id}', 'ProjectTypesController@remove'); +\$router->get('project-type', 'ProjectTypesController@all'); +\$router->get('project-type/{id}', 'ProjectTypesController@get'); +\$router->post('project-type', 'ProjectTypesController@add'); +\$router->put('project-type/{id}', 'ProjectTypesController@put'); +\$router->delete('project-type/{id}', 'ProjectTypesController@remove'); "); $I->writeToFile('./app/Http/routes.php', 'get("/", function () use ($app) { - return $app->welcome(); +$router->get("/", function () use ($router) { + return \'Hello World\'; }); '); @@ -36,11 +36,11 @@ $I->seeInShellOutput('foo routes generated'); $I->openFile('./app/Http/routes.php'); $I->seeInThisFile(" -\$app->get('foo', 'customController@all'); -\$app->get('foo/{id}', 'customController@get'); -\$app->post('foo', 'customController@add'); -\$app->put('foo/{id}', 'customController@put'); -\$app->delete('foo/{id}', 'customController@remove'); +\$router->get('foo', 'customController@all'); +\$router->get('foo/{id}', 'customController@get'); +\$router->post('foo', 'customController@add'); +\$router->put('foo/{id}', 'customController@put'); +\$router->delete('foo/{id}', 'customController@remove'); "); $I->writeToFile('./app/Http/routes.php', 'get("/", function () use ($app) { - return $app->welcome(); +$router->get("/", function () use ($router) { + return \'Hello World\'; }); '); @@ -78,8 +78,8 @@ | */ -$app->get("/", function () use ($app) { - return $app->version(); +$router->get("/", function () use ($router) { + return \'Hello World\'; }); '); @@ -87,10 +87,10 @@ $I->seeInShellOutput('foo routes generated'); $I->openFile('./routes/web.php'); $I->seeInThisFile(" -\$app->get('foo', 'customController@all'); -\$app->get('foo/{id}', 'customController@get'); -\$app->post('foo', 'customController@add'); -\$app->put('foo/{id}', 'customController@put'); -\$app->delete('foo/{id}', 'customController@remove'); +\$router->get('foo', 'customController@all'); +\$router->get('foo/{id}', 'customController@get'); +\$router->post('foo', 'customController@add'); +\$router->put('foo/{id}', 'customController@put'); +\$router->delete('foo/{id}', 'customController@remove'); "); $I->deleteDir('./routes'); diff --git a/src/Commands/BaseCommand.php b/src/Commands/BaseCommand.php index c85b406..545a699 100644 --- a/src/Commands/BaseCommand.php +++ b/src/Commands/BaseCommand.php @@ -39,7 +39,7 @@ protected function save($content, $path, $name, $force = false) } $this->makeDirectory($path); $this->fs->put($path, $content); - $this->info("{$name} generated !"); + $this->info("{$name} generated in {$path}!"); } protected function makeDirectory($path) @@ -49,9 +49,48 @@ protected function makeDirectory($path) } } - protected function spaces($n) + protected function spaces($n): string { return str_repeat(' ', $n); } + protected function getNamespace(string $path = null): string + { + if (empty($path)) { + $path = $this->option('path'); + } + + return implode('\\', array_map('studly_case', array_filter(array_map('trim', explode('/', $path)), function($value) { + return !empty($value); + }))); + } + + protected function parseValue($value, string $parser) + { + if(! $value){ + return false; + } + + if(! $this->option('parsed')){ + return $this->getArgumentParser($parser)->parse($value); + } + + return $value; + } + + protected function prependNamespace(string $value, string $path = null): string + { + if (strpos($value, '\\') === false) { + return $this->getNamespace($path) . '\\' . studly_case(str_singular($value)); + } + + return $value; + } + + protected function extractClassName(string $fqClassName): string + { + $names = array_reverse(explode("\\", $fqClassName)); + return $names[0]; + } + } diff --git a/src/Commands/ControllerCommand.php b/src/Commands/ControllerCommand.php index 96449fc..013da09 100644 --- a/src/Commands/ControllerCommand.php +++ b/src/Commands/ControllerCommand.php @@ -1,40 +1,44 @@ argument('model'); - $name = ''; - if(strrpos($model, "\\") === false){ - $name = $model; - $model = "App\\" . $model; - } else { - $name = explode("\\", $model); - $name = $name[count($name) - 1]; - } + $model = $this->argument('model'); + $name = ''; + if(strrpos($model, "\\") === false){ + $name = $model; + $model = "App\\" . $model; + } else { + $name = explode("\\", $model); + $name = $name[count($name) - 1]; + } $controller = ucwords(str_plural($name)) . 'Controller'; $content = $this->getTemplate('controller') - ->with([ - 'name' => $controller, - 'model' => $model - ]) - ->get(); - - $this->save($content, "./app/Http/Controllers/{$controller}.php", "{$controller}"); - if(! $this->option('no-routes')){ + ->with([ + 'name' => $controller, + 'model' => $model, + 'namespace' => $this->getNamespace(), + 'use' => ($this->getNamespace() != $this->getDefaultNamespace()?'use '.$this->getDefaultNamespace().'\Controller;'.PHP_EOL.'use '.$this->getDefaultNamespace().'\RESTActions;'.PHP_EOL:'') + ]) + ->get(); + + $this->save($content, "./{$this->option('path')}/{$controller}.php", "{$controller}"); + + if (! $this->option('no-routes')) { $options = [ 'resource' => snake_case($name, '-'), '--controller' => $controller, @@ -43,9 +47,19 @@ public function handle() if ($this->option('laravel')) { $options['--laravel'] = true; } + if ($this->option('routes')) { + $options['--path'] = $this->option('routes'); + } + if ($this->getNamespace() != $this->getDefaultNamespace()) { + $options['--controller-namespace'] = $this->getNamespace(); + } $this->call('wn:route', $options); } } + protected function getDefaultNamespace() { + return $this->getNamespace(ControllerCommand::DEFAULT_PATH); + } + } diff --git a/src/Commands/FactoryCommand.php b/src/Commands/FactoryCommand.php index fe84db0..14d3a23 100644 --- a/src/Commands/FactoryCommand.php +++ b/src/Commands/FactoryCommand.php @@ -19,9 +19,9 @@ public function handle() $file = $this->getFile(); - $content = $this->fs->get($file); + $content = ! $this->fs->exists($file) ? '' : $this->fs->get($file); - $content .= $this->getTemplate('factory') + $content .= $this->getTemplate(! $this->fs->exists($file) ? 'factory-create' : 'factory') ->with([ 'model' => $model, 'factory_fields' => $this->getFieldsContent() diff --git a/src/Commands/MigrationCommand.php b/src/Commands/MigrationCommand.php index 8fe23bd..7248f18 100644 --- a/src/Commands/MigrationCommand.php +++ b/src/Commands/MigrationCommand.php @@ -23,24 +23,24 @@ public function handle() $name = 'Create' . ucwords(camel_case($table)); $snakeName = snake_case($name); + $file = $this->option('file'); + if (! $file) { + $file = date('Y_m_d_His_') . $snakeName . '_table'; + $this->deleteOldMigration($snakeName); + } else { + $this->deleteOldMigration($file); + } + $content = $this->getTemplate('migration') ->with([ 'table' => $table, 'name' => $name, - 'schema' => $this->getSchema(), - 'additionals' => $this->getAdditionals(), - 'constraints' => $this->getConstraints() + 'schema' => $this->getSchema($this->option('schema')), + 'additionals' => $this->getAdditionals($this->option('add')), + 'constraints' => $this->getConstraints($this->option('keys')) ]) ->get(); - $file = $this->option('file'); - if(! $file){ - $file = date('Y_m_d_His_') . $snakeName . '_table'; - $this->deleteOldMigration($snakeName); - }else{ - $this->deleteOldMigration($file); - } - $this->save($content, "./database/migrations/{$file}.php", "{$table} migration"); } @@ -55,29 +55,13 @@ protected function deleteOldMigration($fileName) } } - protected function getSchema() + protected function getSchema($schema) { - $schema = $this->option('schema'); - if(! $schema){ - return $this->spaces(12) . "// Schema declaration"; - } - - $items = $schema; - if( ! $this->option('parsed')){ - $items = $this->getArgumentParser('schema')->parse($schema); - } - - $fields = []; - foreach ($items as $item) { - $fields[] = $this->getFieldDeclaration($item); - } - - return implode(PHP_EOL, $fields); + return $this->buildParameters($this->parseValue($schema, 'schema'), "// Schema declaration", [$this, 'getFieldDeclaration']); } - protected function getAdditionals() + protected function getAdditionals($additionals) { - $additionals = $this->option('add'); if (empty($additionals)) { return ''; } @@ -103,24 +87,26 @@ protected function getFieldDeclaration($parts) return " \$table" . implode('', $parts) . ';'; } - protected function getConstraints() + protected function getConstraints($keys) { - $keys = $this->option('keys'); - if(! $keys){ - return $this->spaces(12) . "// Constraints declaration"; - } + return $this->buildParameters($this->parseValue($keys, 'foreign-keys'), "// Constraints declaration", [$this, 'getConstraintDeclaration']); + } - $items = $keys; - if(! $this->option('parsed')){ - $items = $this->getArgumentParser('foreign-keys')->parse($keys); + protected function buildParameters($items, $emptyPlaceholder, $callback = null) { + if ($items === false) { + return $this->spaces(12) . $emptyPlaceholder; } - $constraints = []; + $parameters = []; foreach ($items as $item) { - $constraints[] = $this->getConstraintDeclaration($item); + if (!empty($callback) && is_callable($callback)) { + $parameters[] = call_user_func($callback, $item); + } else { + $parameters[] = $item; + } } - return implode(PHP_EOL, $constraints); + return implode(PHP_EOL, $parameters); } protected function getConstraintDeclaration($key) @@ -129,13 +115,13 @@ protected function getConstraintDeclaration($key) $key['column'] = 'id'; } if(! $key['table']){ - $key['table'] = str_plural(substr($key['name'], 0, count($key['name']) - 4)); + $key['table'] = substr($key['name'], 0, count($key['name']) - 4); } $constraint = $this->getTemplate('migration/foreign-key') ->with([ 'name' => $key['name'], - 'table' => $key['table'], + 'table' => snake_case(str_plural($key['table'])), 'column' => $key['column'] ]) ->get(); diff --git a/src/Commands/ModelCommand.php b/src/Commands/ModelCommand.php index e9781d8..32f456d 100644 --- a/src/Commands/ModelCommand.php +++ b/src/Commands/ModelCommand.php @@ -11,6 +11,11 @@ class ModelCommand extends BaseCommand { {--has-one= : hasOne relationships.} {--belongs-to= : belongsTo relationships.} {--belongs-to-many= : belongsToMany relationships.} + {--has-many-through= : hasManyThrough relationships.} + {--morph-to= : morphTo relationships.} + {--morph-many= : morphMany relationships.} + {--morph-to-many= : morphToMany relationships.} + {--morphed-by-many= : morphedByMany relationships.} {--rules= : fields validation rules.} {--timestamps=true : enables timestamps on the model.} {--path=app : where to store the model php file.} @@ -55,18 +60,18 @@ protected function getAsArrayFields($arg, $isOption = true) }, $arg)); } - protected function getNamespace() - { - return str_replace(' ', '\\', ucwords(str_replace('/', ' ', $this->option('path')))); - } - protected function getRelations() { $relations = array_merge([], $this->getRelationsByType('hasOne', 'has-one'), $this->getRelationsByType('hasMany', 'has-many'), $this->getRelationsByType('belongsTo', 'belongs-to'), - $this->getRelationsByType('belongsToMany', 'belongs-to-many', true) + $this->getRelationsByType('belongsToMany', 'belongs-to-many', true), + $this->getRelationsByType('hasManyThrough', 'has-many-through', false, 'relations-hasManyThrough'), + $this->getRelationsByType('morphTo', 'morph-to', false, 'relations-morphTo'), + $this->getRelationsByType('morphMany', 'morph-many', false, 'relations-morphMany'), + $this->getRelationsByType('morphToMany', 'morph-to-many', false, 'relations-morphMany'), + $this->getRelationsByType('morphedByMany', 'morphed-by-many', false, 'relations-morphMany') ); if(empty($relations)){ @@ -76,23 +81,44 @@ protected function getRelations() return implode(PHP_EOL, $relations); } - protected function getRelationsByType($type, $option, $withTimestamps = false) + protected function getRelationsByType($type, $option, $withTimestamps = false, $parser = 'relations') { $relations = []; $option = $this->option($option); if($option){ - $items = $this->getArgumentParser('relations')->parse($option); + $items = $this->getArgumentParser($parser)->parse($option); - $template = ($withTimestamps) ? 'model/relation-with-timestamps' : 'model/relation'; + $template = ($withTimestamps) ? 'model/relation-with-timestamps' : ($parser=='relations'?'model/relation':'model/relation-'.$type); $template = $this->getTemplate($template); foreach ($items as $item) { $item['type'] = $type; - if(! $item['model']){ - $item['model'] = $this->getNamespace() . '\\' . ucwords(str_singular($item['name'])); - } else if(strpos($item['model'], '\\') === false ){ - $item['model'] = $this->getNamespace() . '\\' . $item['model']; - } + if ($parser == 'relations') { + $item['model'] = $this->prependNamespace($item['model'] ?: $item['name']); + } else if ($parser != 'relations') { + switch($type) { + case "hasManyThrough": + if(! $item['through'] && $item['model']){ + $item['through'] = $this->prependNamespace($item['model']); + $item['model'] = $this->prependNamespace($item['name']); + } else { + $item['through'] = $this->prependNamespace($item['through']); + $item['model'] = $this->prependNamespace($item['model']); + } + break; + case "morphMany": + case "morphToMany": + case "morphedByMany": + if(! $item['through']){ + $item['through'] = $item['model']; + $item['model'] = $this->prependNamespace($item['name']); + } else { + $item['model'] = $this->prependNamespace($item['model']); + } + break; + + } + } $relations[] = $template->with($item)->get(); } } @@ -101,16 +127,13 @@ protected function getRelationsByType($type, $option, $withTimestamps = false) protected function getRules() { - $rules = $this->option('rules'); - if(! $rules){ - return " // Validation rules"; - } - $items = $rules; - if(! $this->option('parsed')){ - $items = $this->getArgumentParser('rules')->parse($rules); - } - $rules = []; + $items = $this->parseValue($this->option('rules'), 'rules'); + if ($items === false) { + return $this->spaces(8) . "// Validation rules"; + } + $template = $this->getTemplate('model/rule'); + $rules = []; foreach ($items as $item) { $rules[] = $template->with($item)->get(); } @@ -120,7 +143,7 @@ protected function getRules() protected function getAdditional() { - return $this->option('timestamps') == 'false' + return $this->option('timestamps') === 'false' ? " public \$timestamps = false;" . PHP_EOL . PHP_EOL : ''; } diff --git a/src/Commands/MorphTableCommand.php b/src/Commands/MorphTableCommand.php new file mode 100644 index 0000000..2a4778a --- /dev/null +++ b/src/Commands/MorphTableCommand.php @@ -0,0 +1,54 @@ +parseFields(); + + $this->call('wn:migration', [ + 'table' => snake_case(str_plural($this->argument('morphable'))), + '--schema' => $this->schema(), + '--keys' => $this->keys(), + '--file' => $this->option('file'), + '--parsed' => false, + '--force' => $this->option('force'), + '--add' => $this->option('add') + ]); + } + + protected function parseFields() + { + $this->fields = array_map(function($arg, $app) { + return snake_case(str_singular($this->argument($arg))) . "_" . $app; + }, ['model', 'morphable', 'morphable'], ['id', 'id', 'type']); + + } + + protected function schema() + { + return implode(' ', array_map(function($field) { + return $field . ':' . (substr($field, -3) == '_id' ? 'integer:unsigned' : 'string.50') . ':index'; + }, $this->fields)); + } + + protected function keys() + { + // return implode(' ', $this->fields); + return snake_case(str_singular($this->argument('model'))) . "_id"; + } + +} diff --git a/src/Commands/ResourceCommand.php b/src/Commands/ResourceCommand.php index 8274ce7..c8d2ff5 100644 --- a/src/Commands/ResourceCommand.php +++ b/src/Commands/ResourceCommand.php @@ -12,9 +12,18 @@ class ResourceCommand extends BaseCommand { {--has-one= : hasOne relationships.} {--belongs-to= : belongsTo relationships.} {--belongs-to-many= : belongsToMany relationships.} + {--has-many-through= : hasManyThrough relationships.} + {--morph-to= : morphTo relationships.} + {--morph-many= : morphMany relationships.} + {--morph-to-many= : morphToMany relationships.} + {--morphed-by-many= : morphedByMany relationships.} {--migration-file= : the migration file name.} {--add= : specifies additional columns like timestamps, softDeletes, rememberToken and nullableTimestamps.} {--path=app : where to store the model file.} + {--routes= : where to store the routes.} + {--no-routes : do not add routes.} + {--controller= : where to store the controllers file.} + {--no-controller : do not generate controllers.} {--parsed : tells the command that arguments have been already parsed. To use when calling the command from an other command and passing the parsed arguments and options} {--force= : override the existing files} {--laravel= : Use Laravel style route definitions} @@ -30,7 +39,7 @@ public function handle() $resourceName = $this->argument('name'); $modelName = ucwords(camel_case($resourceName)); - $tableName = str_plural($resourceName); + $tableName = snake_case(str_plural($resourceName)); // generating the model $this->call('wn:model', [ @@ -41,6 +50,11 @@ public function handle() '--has-one' => $this->option('has-one'), '--belongs-to' => $this->option('belongs-to'), '--belongs-to-many' => $this->option('belongs-to-many'), + '--has-many-through' => $this->option('has-many-through'), + '--morph-to' => $this->option('morph-to'), + '--morph-many' => $this->option('morph-many'), + '--morph-to-many' => $this->option('morph-to-many'), + '--morphed-by-many' => $this->option('morphed-by-many'), '--rules' => $this->rules(), '--path' => $this->option('path'), '--force' => $this->option('force'), @@ -60,24 +74,34 @@ public function handle() '--parsed' => true ]); - // generating REST actions trait if doesn't exist - if(! $this->fs->exists('./app/Http/Controllers/RESTActions.php')){ - $this->call('wn:controller:rest-actions'); - } - // generating the controller and routes - $controllerOptions = [ - 'model' => $modelName, - '--force' => $this->option('force'), - '--no-routes' => false, - ]; - if ($this->option('laravel')) { - $controllerOptions['--laravel'] = true; + if (! $this->option('no-controller')) { + // generating REST actions trait if doesn't exist + if(! $this->fs->exists('./app/Http/Controllers/RESTActions.php')){ + $this->call('wn:controller:rest-actions'); + } + + // generating the controller and routes + $controllerOptions = [ + 'model' => $modelName, + '--force' => $this->option('force'), + '--no-routes' => $this->option('no-routes'), + ]; + if ($this->option('laravel')) { + $controllerOptions['--laravel'] = true; + } + if ($this->option('routes')) { + $controllerOptions['--routes'] = $this->option('routes'); + } + if ($this->option('controller')) { + $controllerOptions['--path'] = $this->option('controller'); + } + $this->call('wn:controller', $controllerOptions); } - $this->call('wn:controller', $controllerOptions); // generating model factory $this->call('wn:factory', [ - 'model' => 'App\\' . $modelName, + 'model' => $this->getNamespace().'\\'.$modelName, + '--file' => './database/factories/'.str_plural($modelName).'.php', '--fields' => $this->factoryFields(), '--force' => $this->option('force'), '--parsed' => true @@ -103,16 +127,42 @@ protected function parseFields() ->parse($fields); } $this->fields = array_merge($this->fields, array_map(function($name) { - return [ - 'name' => $name, - 'schema' => [ - ['name' => 'integer', 'args' => []], - ['name' => 'unsigned', 'args' => []] - ], - 'rules' => 'required|numeric', + $return = [ + 'name' => $name['name'], + 'schema' => [], + 'rules' => '', 'tags' => ['fillable', 'key'], - 'factory' => 'key' + 'factory' => '' ]; + + if ($name['type'] == 'morphTo') { + if (substr($name['name'], -3) == "_id") { + $return['schema'] = [ + ['name' => 'integer', 'args' => []], + ['name' => 'unsigned', 'args' => []] + ]; + $return['rules'] = 'numeric'; + $return['factory'] = 'key'; + } else { + $return['schema'] = [ + ['name' => 'string', 'args' => ['50']] + ]; + } + if ($name['nullable']) { + $return['schema'][] = ['name' => 'nullable', 'args' => []]; + } else { + $return['rules'] = 'required'.(!empty($return['rules'])?'|'.$return['rules']:''); + } + } else { + $return['schema'] = [ + ['name' => 'integer', 'args' => []], + ['name' => 'unsigned', 'args' => []] + ]; + $return['rules'] = 'required|numeric'; + $return['factory'] = 'key'; + } + + return $return; }, $this->foreignKeys())); } @@ -149,33 +199,48 @@ protected function schema() }, $this->fields); } - protected function foreignKeys() + protected function foreignKeys($withMorph = true) { $belongsTo = $this->option('belongs-to'); - if(! $belongsTo) { + $morphTo = $this->option('morph-to'); + + if(! $belongsTo && (! $withMorph || ! $morphTo)) { return []; } - $relations = $this->getArgumentParser('relations')->parse($belongsTo); - return array_map(function($relation){ - $name = $relation['model'] ? $relation['model'] : $relation['name']; - $index = strrpos($name, "\\"); - if($index) { - $name = substr($name, $index + 1); - } - return snake_case(str_singular($name)) . '_id'; - }, $relations); + + $belongsTo = $belongsTo ? $this->getArgumentParser('relations')->parse($belongsTo) : []; + + $belongsTo = array_map(function($relation){ + return array("model" => camel_case(str_singular($this->extractClassName($relation['model'] ? $relation['model'] : $relation['name']))), "name" => snake_case(str_singular($this->extractClassName($relation['name']))) . '_id', "type" => "belongsTo"); + }, $belongsTo); + + if ($withMorph) { + $morphTo = $morphTo ? $this->getArgumentParser('relations-morphTo')->parse($morphTo) : []; + $morphTo = array_map(function($relation){ + $name = snake_case(str_singular($relation['name'])); + return array(array("name" => $name . '_id', "type" => "morphTo", "nullable" => $relation['nullable']), array("name" => $name . '_type', "type" => "morphTo", "nullable" => $relation['nullable'])); + }, $morphTo); + + // $morphed = []; + // array_walk_recursive($morphTo, function($a) use (&$morphed) { $morphed[] = $a; }); + $morphed = !empty($morphTo) ? call_user_func_array('array_merge', $morphTo) : array(); + + return array_merge($belongsTo, $morphed); + } + + return $belongsTo; } protected function migrationKeys() { return array_map(function($name) { return [ - 'name' => $name, + 'name' => snake_case($name['name']), 'column' => '', - 'table' => '', + 'table' => snake_case(str_plural($name['model'])), 'on_delete' => '', 'on_update' => '' ]; - }, $this->foreignKeys()); + }, $this->foreignKeys(false)); } protected function factoryFields() diff --git a/src/Commands/ResourcesCommand.php b/src/Commands/ResourcesCommand.php index b592bfa..6796a20 100644 --- a/src/Commands/ResourcesCommand.php +++ b/src/Commands/ResourcesCommand.php @@ -7,28 +7,172 @@ class ResourcesCommand extends BaseCommand { protected $signature = 'wn:resources - {file : Path to the file containing resources declarations} - {--path=app : where to store the model files.} + {files* : Paths to the files containing resources declarations} + {--path= : where to store the model files.} + {--routes= : where to store the routes.} + {--no-routes : do not add routes.} + {--controllers= : where to store the controllers.} + {--no-controllers : do not generate controllers.} + {--no-migration : do not migrate.} + {--check-only : only check supplied files for valide relationships.} + {--skip-check : skip validity check before processing.} {--force= : override the existing files} + {--force-redefine : Force model redefinition.} {--laravel= : Use Laravel style route definitions} - '; - protected $description = 'Generates multiple resources from a file'; + protected $description = 'Generates multiple resources from a couple of files'; protected $pivotTables = []; + protected $morphTables = []; + + private $checkedErrors = 0; + private $checkErrors = []; + private $checkInfo = []; public function handle() { - $content = $this->fs->get($this->argument('file')); + $files = $this->argument('files'); + $nodes = []; + + if (empty($files)) { + $this->error("No resource file(s) supplied!"); + return; + } + if (is_string($files)) { + $files = [ $files ]; + } + + foreach ($files as $file) { + $nodes = $this->mergeNodes($nodes, $this->readFile($file), $this->option('force-redefine')); + } + + $this->line(''); + $this->info('Bringing models to order...'); + + $nodes = $this->sortDependencies($nodes); + $pivotTables = $this->uniqueArray($this->getTables($nodes, 'pivotTables')); + $morphTables = $this->uniqueArray($this->getTables($nodes, 'morphTables')); + + if (! $this->option('skip-check')) { + $this->info('Checking Relationships...'); + $keys = array_keys($nodes); + foreach ($nodes as $model => $i) { + $this->checkRelations($i['belongsTo'], 'belongsTo', $i['filename'], $i['uniquename'], $keys); + // $this->checkRelations($i['hasManyThrough'], 'hasManyThrough', $file, $model); + } + $this->checkPivotRelations($nodes, $pivotTables, 'pivot'); + $this->checkPivotRelations($nodes, $morphTables, 'morph'); + } + + if ($this->checkedErrors > 0) { + $this->line(''); + if ($this->option('check-only')) { + $this->info('Checking only, we have found ' . $this->checkedErrors . ' errors.'); + } + $this->printErrors(); + } + + $proceed = (! $this->option('check-only') && $this->checkedErrors == 0) || $this->option('skip-check'); + if (! $this->option('check-only') && $this->checkedErrors > 0) { + $this->line(''); + $proceed = $this->confirm("We have found " . $this->checkedErrors . " errors. Are you sure you want to continue?"); + } + if ($proceed) { + $this->buildResources($nodes); + + // if (!$this->option('no-migration')) { + // $this->call('migrate'); // actually needed for pivot seeders ! + // } + + $this->line(''); + $this->buildTables('Pivot-Table', 'wn:pivot-table', 'model1', 'model2', $pivotTables); + + $this->line(''); + $this->buildTables('Morph-Table', 'wn:morph-table', 'model', 'morphable', $morphTables); + + if (!$this->option('no-migration')) { + $this->call('migrate'); + } + } + } + + protected function uniqueArray($array) + { + return array_map( + 'unserialize', + array_unique(array_map('serialize', $array)) + ); + } + + protected function readFile($file) + { + $this->info("Reading file ".$file); + + $content = $this->fs->get($file); $content = Yaml::parse($content); - $modelIndex = 0; + $nodes = []; + foreach ($content as $model => $i){ - $i = $this->getResourceParams($model, $i); + /* + $i['modelname'] = as originally in YAML defined + $i['name'] = as originally defined in snake_case + $i['uniquename']= for key in singular studly_case + */ + $i['filename'] = $file; + $i['modelname'] = $model; + $model = studly_case(str_singular($model)); + $i['uniquename'] = $model; + + $nodes[] = $this->getResourceParams($i); + } + + return $nodes; + } + + protected function mergeNodes($nodes, $toMerge, $forceRedefinition = false) { + foreach($toMerge as $node) { + $nodes = $this->mergeNode($nodes, $node, $forceRedefinition); + } + + return $nodes; + } + + protected function mergeNode($nodes, $toMerge, $forceRedefinition = false) { + if (empty($nodes[$toMerge['uniquename']]) || $forceRedefinition) { + if (!empty($nodes[$toMerge['uniquename']])) { + $this->checkError($toMerge['uniquename'] . ": forced to redefine (in file " . $nodes[$toMerge['uniquename']]['filename'] . ", redefined from file ".$toMerge['filename'].")"); + } + $nodes[$toMerge['uniquename']] = $toMerge; + } else { + $this->checkError($toMerge['uniquename'] . ": already defined (in file " . $nodes[$toMerge['uniquename']]['filename'] . ", trying to redefine from file ".$toMerge['filename']."; Use --force-redefine to force redefinition)"); + } + + return $nodes; + } + + protected function getTables($nodes, $key) { + $tables = []; + foreach($nodes as $node) { + if (!empty($node[$key])) { + $tables = array_merge($tables, $node[$key]); + } + } + + return $tables; + } + + protected function buildResources($nodes) + { + $modelIndex = 0; + $migrationIdLength = strlen((string)count($nodes)); + foreach ($nodes as $i) { $migrationName = 'Create' . ucwords(str_plural($i['name'])); - $migrationFile = date('Y_m_d_His') . '-' . str_pad($modelIndex , 3, 0, STR_PAD_LEFT) . '_' . snake_case($migrationName) . '_table'; + $migrationFile = date('Y_m_d_His') . '-' . str_pad($modelIndex , $migrationIdLength, 0, STR_PAD_LEFT) . '_' . snake_case($migrationName) . '_table'; + $this->line(''); + $this->info('Building Model ' . $i['uniquename']); $options = [ 'name' => $i['name'], @@ -38,29 +182,41 @@ public function handle() '--has-one' => $i['hasOne'], '--belongs-to' => $i['belongsTo'], '--belongs-to-many' => $i['belongsToMany'], - '--path' => $this->option('path'), + '--has-many-through' => $i['hasManyThrough'], + '--morph-to' => $i['morphTo'], + '--morph-many' => $i['morphMany'], + '--morph-to-many' => $i['morphToMany'], + '--morphed-by-many' => $i['morphedByMany'], + '--no-routes' => $this->option('no-routes'), + '--no-controller' => $this->option('no-controllers'), '--force' => $this->option('force'), - '--migration-file' => $migrationFile + '--migration-file' => $migrationFile, ]; if ($this->option('laravel')) { $options['--laravel'] = true; } + if ($this->option('routes')) { + $options['--routes'] = $this->option('routes'); + } + if ($this->option('controllers')) { + $options['--controller'] = $this->option('controllers'); + } + if ($this->option('path')) { + $options['--path'] = $this->option('path'); + } $this->call('wn:resource', $options); $modelIndex++; } + } - // $this->call('migrate'); // actually needed for pivot seeders ! - - $this->pivotTables = array_map( - 'unserialize', - array_unique(array_map('serialize', $this->pivotTables)) - ); - - foreach ($this->pivotTables as $tables) { - $this->call('wn:pivot-table', [ - 'model1' => $tables[0], - 'model2' => $tables[1], + protected function buildTables($type, $command, $model1, $model2, $tableAssignment) + { + foreach ($tableAssignment as $tables) { + $this->info('Building '.$type.' ' . $tables[0] . ' - ' . $tables[1]); + $this->call($command, [ + $model1 => $tables[0], + $model2 => $tables[1], '--force' => $this->option('force') ]); @@ -70,15 +226,18 @@ public function handle() // '--force' => $this->option('force') // ]); } - - $this->call('migrate'); } - protected function getResourceParams($modelName, $i) + protected function getResourceParams($i) { + $modelName = $i['modelname']; + + $i['filename'] = $i['filename']; $i['name'] = snake_case($modelName); + $i['modelname'] = $i['modelname']; + $i['uniquename'] = $i['uniquename']; - foreach(['hasMany', 'hasOne', 'add', 'belongsTo', 'belongsToMany'] as $relation){ + foreach(['hasMany', 'hasOne', 'add', 'belongsTo', 'belongsToMany', 'hasManyThrough', 'morphTo', 'morphMany', 'morphToMany', 'morphedByMany'] as $relation){ if(isset($i[$relation])){ $i[$relation] = $this->convertArray($i[$relation], ' ', ','); } else { @@ -87,21 +246,15 @@ protected function getResourceParams($modelName, $i) } if($i['belongsToMany']){ - $relations = $this->getArgumentParser('relations')->parse($i['belongsToMany']); - foreach ($relations as $relation){ - $table = ''; - - if(! $relation['model']){ - $table = snake_case($relation['name']); - } else { - $names = array_reverse(explode("\\", $relation['model'])); - $table = snake_case($names[0]); - } + $i['pivotTables'] = $this->belongsTo($i['name'], $modelName, $i['belongsToMany']); + } - $tables = [ str_singular($table), $i['name'] ]; - sort($tables); - $this->pivotTables[] = $tables; - } + if($i['morphToMany']){ + $i['morphTables'] = $this->morphToMany($modelName, $i['morphToMany']); + } + + if($i['morphedByMany']){ + $i['morphTables'] = array_merge($i['morphTables'], $this->morphedByMany($i['name'], $modelName, $i['morphedByMany'])); } $fields = []; @@ -114,15 +267,63 @@ protected function getResourceParams($modelName, $i) return $i; } + protected function parseRelations($parser, $relations, $callback) + { + $parsedRelations = []; + $relations = $this->getArgumentParser($parser)->parse($relations); + foreach ($relations as $relation){ + $parsedRelations[] = $callback($relation); + } + + return $parsedRelations; + } + + protected function getConditionalTableName($condition, $then, $else) + { + if($condition){ + return snake_case($this->extractClassName($then)); + } else { + return snake_case($this->extractClassName($else)); + } + } + + protected function belongsTo($name, $modelName, $belongsTo) + { + return $this->parseRelations('relations', $belongsTo, function($relation) use ($name, $modelName) { + $table = $this->getConditionalTableName(! $relation['model'], $relation['name'], $relation['model']); + $tables = [ str_singular($table), $name ]; + sort($tables); + $tables[] = $modelName; + return $tables; + }); + } + + protected function morphToMany($modelName, $morphToMany) + { + return $this->parseRelations('relations-morphMany', $morphToMany, function($relation) use ($modelName) { + $name = $this->getConditionalTableName(! $relation['through'], $relation['name'], $relation['model']); + return $this->getMorphableRelation($relation, $name, $modelName); + }); + } + + protected function morphedByMany($name, $modelName, $morphedByMany) + { + return $this->parseRelations('relations-morphMany', $morphedByMany, function($relation) use ($name, $modelName) { + return $this->getMorphableRelation($relation, $name, $modelName); + }); + } + + protected function getMorphableRelation($relation, $relationName, $modelName) { + $morphable = $this->getConditionalTableName(! $relation['through'], $relation['model'], $relation['through']); + return [ str_singular($relationName), str_singular($morphable), $modelName ]; + } + protected function serializeField($field) { $name = $field['name']; $schema = $this->convertArray(str_replace(':', '.', $field['schema']), ' ', ':'); - $rules = (isset($field['rules'])) ? trim($field['rules']) : ''; - // Replace space by comma - $rules = str_replace(' ', ',', $rules); - - $tags = $this->convertArray($field['tags'], ' ', ','); + $rules = (isset($field['rules'])) ? $this->convertArray(trim($field['rules']), ' ', '|') : ''; + $tags = !empty($field['tags']) ? $this->convertArray($field['tags'], ' ', ',') : ''; $string = "{$name};{$schema};{$rules};{$tags}"; @@ -140,4 +341,122 @@ protected function convertArray($list, $old, $new) })); } + private function sortDependencies($nodes) { + $load_order = array(); + $seen = array(); + + foreach($nodes as $key => $item) { + $tmp = $this->getDependencies($nodes, $key, $seen); + + // if($tmp[2] === false) { + $load_order = array_merge($load_order, $tmp[0]); + $seen = $tmp[1]; + // } + } + + return $load_order; + } + + private function getDependencies($nodes, $key, $seen = array()) { + if(array_key_exists($key, $seen) === true) { + return array(array(), $seen); + } + + + if(!empty($nodes[$key])) { + $order = array(); + // $failed = array(); + + if($nodes[$key]['belongsTo']) { + $deps = $this->getArgumentParser('relations')->parse($nodes[$key]['belongsTo']); + foreach($deps as $dependency) { + if(! $dependency['model']){ + $dependency['model'] = $dependency['name']; + } else if(strpos($dependency['model'], '\\') !== false ){ + $dependency['model'] = substr($dependency['model'], strpos($dependency['model'], '\\')+1); // Cut offs first namespace part + } + $dependency['model'] = studly_case(str_singular($dependency['model'])); + if ($dependency['model'] != $key) { + $tmp = $this->getDependencies($nodes, $dependency['model'], $seen); + + $order = array_merge($order, $tmp[0]); + $seen = $tmp[1]; + } + + // if($tmp[2] !== false) { + // $failed = array_merge($tmp[2], $failed); + // } + } + } + $seen[$key] = true; + $order[$key] = $nodes[$key]; + // $failed = (count($failed) > 0) ? $failed : false; + + return array($order, $seen);//, $failed + } + + return array(array(), $seen);//, array($item) + } + + protected function checkError($message, $model = "", $file = "") { + $this->checkErrors[] = array("message" => $message, "model" => $model, "file" => $file); + $this->checkedErrors++; + } + + protected function checkInfo($message, $model = "", $file = "") { + $this->checkInfo[] = array("message" => $message, "model" => $model, "file" => $file); + } + + protected function printErrors() { + foreach ($this->checkErrors as $error) { + $this->error($error['message']); + } + foreach ($this->checkInfo as $info) { + $this->info($info['message']); + } + } + + protected function checkRelations($relations, $type, $file, $model, $keys) { + if ($relations) { + $position = array_search($model, $keys); + $relations = $this->getArgumentParser('relations')->parse($relations); + foreach($relations as $relation) { + $rModel = $relation['model'] ? $relation['model'] : $relation['name']; + $search = array_search(studly_case(str_singular($rModel)), $keys); + if (($search === false || $search > $position) && !class_exists($this->prependNamespace($rModel)) && !class_exists($this->prependNamespace($rModel, 'App'))) { + $this->checkError(studly_case(str_singular($rModel)) . ": undefined (used in " . $type . "-relationship of model " . $model . " in file " . $file . ")"); + } else if (class_exists($this->prependNamespace($rModel))) { + $this->checkInfo(studly_case(str_singular($rModel)) . ": already defined in Namespace " . $this->getNamespace() . " (used in " . $type . "-relationship of model " . $model . " in file " . $file . ")"); + } else if (class_exists($this->prependNamespace($rModel, 'App'))) { + $this->checkInfo(studly_case(str_singular($rModel)) . ": already defined in Namespace App\\ (used in " . $type . "-relationship of model " . $model . " in file " . $file . ")"); + } + } + } + } + + protected function checkPivotRelations($nodes, $relations, $relationType) { + if ($relations) { + foreach($relations as $relation) { + $relation['0'] = studly_case(str_singular($relation['0'])); + $relation['1'] = studly_case(str_singular($relation['1'])); + $relation['2'] = studly_case(str_singular($relation['2'])); + + $this->checkRelation($nodes, $relationType, $relation['0'], $relation['2']); + if ($relationType == "pivot") { + $this->checkRelation($nodes, $relationType, $relation['1'], $relation['2']); + } + } + } + } + + protected function checkRelation($nodes, $relationType, $relation, $model) { + if (empty($nodes[$relation]) && !class_exists($this->getNamespace() . '\\' . $relation) && !class_exists('App\\' . $relation)) { + $this->checkError($relation . ": undefined (used in " . $relationType . "-based relationship of model " . $model . " in file " . $nodes[$model]['filename'] . ")"); + } else if (class_exists($this->getNamespace() . '\\' . ucwords(camel_case($relation)))) { + $this->checkInfo(studly_case(str_singular($relation)) . ": already defined in Namespace " . $this->getNamespace() . " (used in " . $relationType . "-based relationship of model " . $model . " in file " . $nodes[$model]['filename'] . ")"); + } else if (class_exists('App\\' . ucwords(camel_case($relation)))) { + $this->checkInfo(studly_case(str_singular($relation)) . ": already defined in Namespace App\\ (used in " . $relationType . "-based relationship of model " . $model . " in file " . $nodes[$model]['filename'] . ")"); + } + } + } diff --git a/src/Commands/RouteCommand.php b/src/Commands/RouteCommand.php index f358f86..a2cbb24 100644 --- a/src/Commands/RouteCommand.php +++ b/src/Commands/RouteCommand.php @@ -5,32 +5,34 @@ class RouteCommand extends BaseCommand { - protected $signature = 'wn:route - {resource : Name of the resource.} + protected $signature = 'wn:route + {resource : Name of the resource.} {--controller= : Name of the RESTful controller.} + {--controller-namespace= : Namespace of the RESTful controller if not default.} + {--path= : file to store the routes to, will be created if not existing.} {--laravel= : Use Laravel style route definitions} '; - protected $description = 'Generates RESTful routes.'; + protected $description = 'Generates RESTful routes.'; public function handle() { $resource = $this->argument('resource'); $laravelRoutes = $this->option('laravel'); $templateFile = 'routes'; - $routesPath = 'routes/web.php'; + $routesPath = $this->option('path') ?: 'routes/web.php'; if ($laravelRoutes) { $templateFile = 'routes-laravel'; - $routesPath = 'routes/api.php'; + $routesPath = $this->option('path') ?: 'routes/api.php'; if (!$this->fs->isFile($routesPath)) { - if (!$this->fs->isDirectory('./routes')) { - $this->fs->makeDirectory('./routes'); + if (!$this->fs->isDirectory(\dirname($routesPath))) { + $this->fs->makeDirectory(\dirname($routesPath)); } $this->fs->put($routesPath, " -get('/user', function (Request \$request) { return \$request->user(); }); @@ -49,6 +51,9 @@ public function handle() "); } } + if ($this->option('controller-namespace')) { + $templateFile .= '-namespace'; + } if (!$this->fs->isFile($routesPath)) { $routesPath = 'app/Http/routes.php'; @@ -58,7 +63,8 @@ public function handle() $content .= PHP_EOL . $this->getTemplate($templateFile) ->with([ 'resource' => $resource, - 'controller' => $this->getController() + 'controller' => $this->getController(), + 'namespace' => $this->option('controller-namespace'), ]) ->get(); $this->save($content, $routesPath, "{$resource} routes", true); diff --git a/src/CommandsServiceProvider.php b/src/CommandsServiceProvider.php index 4518151..badfd4e 100644 --- a/src/CommandsServiceProvider.php +++ b/src/CommandsServiceProvider.php @@ -15,6 +15,7 @@ public function register() $this->registerResourceCommand(); $this->registerResourcesCommand(); $this->registerPivotTableCommand(); + $this->registerMorphTableCommand(); $this->registerFactoryCommand(); // registerSeederCommand // registerPivotSeederCommand @@ -84,6 +85,13 @@ protected function registerPivotTableCommand(){ $this->commands('command.wn.pivot-table'); } + protected function registerMorphTableCommand(){ + $this->app->singleton('command.wn.morph-table', function($app){ + return $app['Wn\Generators\Commands\MorphTableCommand']; + }); + $this->commands('command.wn.morph-table'); + } + protected function registerFactoryCommand(){ $this->app->singleton('command.wn.factory', function($app){ return $app['Wn\Generators\Commands\FactoryCommand']; diff --git a/templates/controller.wnt b/templates/controller.wnt index a25b548..05e425d 100644 --- a/templates/controller.wnt +++ b/templates/controller.wnt @@ -1,5 +1,5 @@ -define({{model}}::class, function ($faker) { + return [ +{{factory_fields}} + ]; +}); diff --git a/templates/model/relation-hasManyThrough.wnt b/templates/model/relation-hasManyThrough.wnt new file mode 100644 index 0000000..180fa46 --- /dev/null +++ b/templates/model/relation-hasManyThrough.wnt @@ -0,0 +1,4 @@ + public function {{name}}() + { + return $this->{{type}}("{{model}}", "{{through}}"); + } diff --git a/templates/model/relation-morphMany.wnt b/templates/model/relation-morphMany.wnt new file mode 100644 index 0000000..180fa46 --- /dev/null +++ b/templates/model/relation-morphMany.wnt @@ -0,0 +1,4 @@ + public function {{name}}() + { + return $this->{{type}}("{{model}}", "{{through}}"); + } diff --git a/templates/model/relation-morphTo.wnt b/templates/model/relation-morphTo.wnt new file mode 100644 index 0000000..af094e7 --- /dev/null +++ b/templates/model/relation-morphTo.wnt @@ -0,0 +1,4 @@ + public function {{name}}() + { + return $this->{{type}}(); + } diff --git a/templates/model/relation-morphToMany.wnt b/templates/model/relation-morphToMany.wnt new file mode 100644 index 0000000..180fa46 --- /dev/null +++ b/templates/model/relation-morphToMany.wnt @@ -0,0 +1,4 @@ + public function {{name}}() + { + return $this->{{type}}("{{model}}", "{{through}}"); + } diff --git a/templates/model/relation-morphedByMany.wnt b/templates/model/relation-morphedByMany.wnt new file mode 100644 index 0000000..180fa46 --- /dev/null +++ b/templates/model/relation-morphedByMany.wnt @@ -0,0 +1,4 @@ + public function {{name}}() + { + return $this->{{type}}("{{model}}", "{{through}}"); + } diff --git a/templates/routes-laravel-namespace.wnt b/templates/routes-laravel-namespace.wnt new file mode 100644 index 0000000..9ebaaeb --- /dev/null +++ b/templates/routes-laravel-namespace.wnt @@ -0,0 +1,10 @@ +/** + * Routes for resource {{namespace}}/{{resource}} + */ +Route::namespace('{{namespace}}')->prefix('{{resource}}')->group(function () { + Route::get('/', '{{controller}}@all'); + Route::get('/{id}', '{{controller}}@get'); + Route::post('/', '{{controller}}@add'); + Route::put('/{id}', '{{controller}}@put'); + Route::delete('/{id}', '{{controller}}@remove'); +}); diff --git a/templates/routes-namespace.wnt b/templates/routes-namespace.wnt new file mode 100644 index 0000000..9f48cbd --- /dev/null +++ b/templates/routes-namespace.wnt @@ -0,0 +1,10 @@ +/** + * Routes for resource {{namespace}}/{{resource}} + */ + $router->group(['namespace' => '{{namespace}}', 'prefix' => '{{resource}}'], function ($router) { + $router->get('/', '{{controller}}@all'); + $router->get('/{id}', '{{controller}}@get'); + $router->post('/', '{{controller}}@add'); + $router->put('/{id}', '{{controller}}@put'); + $router->delete('/{id}', '{{controller}}@remove'); + });