diff --git a/.idea/php-test-framework.xml b/.idea/php-test-framework.xml new file mode 100644 index 00000000..5aa0e33f --- /dev/null +++ b/.idea/php-test-framework.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/composer.json b/composer.json index 81731254..0028702e 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "illuminate/contracts": ">=5.1", "illuminate/filesystem": ">=5.1", "illuminate/console": ">=5.1", - "php-libs/observable": "^1.0" + "php-libs/observable": "^1.3", + "php-libs/value-states": "^1.3" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/composer.lock b/composer.lock index 6da42dcb..7ee45541 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "811e4566886ac6e2a5a71c710bd3c214", + "content-hash": "42ab9c20c5e6de27e9fab27a0869a8ef", "packages": [ { "name": "composer/package-versions-deprecated", @@ -81,40 +81,39 @@ }, { "name": "doctrine/cache", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "13e3381b25847283a91948d04640543941309727" + "reference": "a9c1b59eba5a08ca2770a76eddb88922f504e8e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", - "reference": "13e3381b25847283a91948d04640543941309727", + "url": "https://api.github.com/repos/doctrine/cache/zipball/a9c1b59eba5a08ca2770a76eddb88922f504e8e0", + "reference": "a9c1b59eba5a08ca2770a76eddb88922f504e8e0", "shasum": "" }, "require": { "php": "~7.1 || ^8.0" }, "conflict": { - "doctrine/common": ">2.2,<2.4" + "doctrine/common": ">2.2,<2.4", + "psr/cache": ">=3" }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^6.0", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0", - "predis/predis": "~1.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0", + "symfony/cache": "^4.4 || ^5.2" }, "suggest": { "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" @@ -161,7 +160,7 @@ ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.10.x" + "source": "https://github.com/doctrine/cache/tree/1.11.0" }, "funding": [ { @@ -177,37 +176,39 @@ "type": "tidelift" } ], - "time": "2020-07-07T18:54:01+00:00" + "time": "2021-04-13T14:46:17+00:00" }, { "name": "doctrine/dbal", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "ee6d1260d5cc20ec506455a585945d7bdb98662c" + "reference": "5ba62e7e40df119424866064faf2cef66cb5232a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/ee6d1260d5cc20ec506455a585945d7bdb98662c", - "reference": "ee6d1260d5cc20ec506455a585945d7bdb98662c", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/5ba62e7e40df119424866064faf2cef66cb5232a", + "reference": "5ba62e7e40df119424866064faf2cef66cb5232a", "shasum": "" }, "require": { "composer/package-versions-deprecated": "^1.11.99", "doctrine/cache": "^1.0", + "doctrine/deprecations": "^0.5.3", "doctrine/event-manager": "^1.0", "php": "^7.3 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.1", - "jetbrains/phpstorm-stubs": "^2019.1", - "phpstan/phpstan": "^0.12.40", + "doctrine/coding-standard": "8.2.0", + "jetbrains/phpstorm-stubs": "2020.2", + "phpstan/phpstan": "0.12.81", "phpstan/phpstan-strict-rules": "^0.12.2", - "phpunit/phpunit": "^9.4", - "psalm/plugin-phpunit": "^0.10.0", + "phpunit/phpunit": "9.5.0", + "psalm/plugin-phpunit": "0.13.0", + "squizlabs/php_codesniffer": "3.6.0", "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "^3.17.2" + "vimeo/psalm": "4.6.4" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -216,11 +217,6 @@ "bin/doctrine-dbal" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\DBAL\\": "src" @@ -272,7 +268,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.0.0" + "source": "https://github.com/doctrine/dbal/tree/3.1.0" }, "funding": [ { @@ -288,7 +284,50 @@ "type": "tidelift" } ], - "time": "2020-11-15T18:20:41+00:00" + "time": "2021-04-19T17:51:23+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" }, { "name": "doctrine/event-manager", @@ -481,16 +520,16 @@ }, { "name": "illuminate/collections", - "version": "v8.34.0", + "version": "v8.41.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "e18d6e4cf03dd597bc3ecd86fefc2023d0c7a5e8" + "reference": "deccb956d38710f3f8baf36dd876c3fa1585ec22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/e18d6e4cf03dd597bc3ecd86fefc2023d0c7a5e8", - "reference": "e18d6e4cf03dd597bc3ecd86fefc2023d0c7a5e8", + "url": "https://api.github.com/repos/illuminate/collections/zipball/deccb956d38710f3f8baf36dd876c3fa1585ec22", + "reference": "deccb956d38710f3f8baf36dd876c3fa1585ec22", "shasum": "" }, "require": { @@ -531,20 +570,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-19T00:05:33+00:00" + "time": "2021-04-22T21:08:09+00:00" }, { "name": "illuminate/console", - "version": "v8.34.0", + "version": "v8.41.0", "source": { "type": "git", "url": "https://github.com/illuminate/console.git", - "reference": "dcc1fd330b7ea8fcf259bbf73243bfedc98e45a3" + "reference": "395002ac2d4ec404c42e6e97997f4236dc8ab2b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/console/zipball/dcc1fd330b7ea8fcf259bbf73243bfedc98e45a3", - "reference": "dcc1fd330b7ea8fcf259bbf73243bfedc98e45a3", + "url": "https://api.github.com/repos/illuminate/console/zipball/395002ac2d4ec404c42e6e97997f4236dc8ab2b6", + "reference": "395002ac2d4ec404c42e6e97997f4236dc8ab2b6", "shasum": "" }, "require": { @@ -591,11 +630,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-16T21:53:44+00:00" + "time": "2021-04-26T12:39:58+00:00" }, { "name": "illuminate/container", - "version": "v8.34.0", + "version": "v8.41.0", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", @@ -646,16 +685,16 @@ }, { "name": "illuminate/contracts", - "version": "v8.34.0", + "version": "v8.41.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "121cea1d8b8772bc7fee99c71ecf0f57c1d77b3b" + "reference": "64abbe2aeee0855a11cfce49d0ea08a0aa967cd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/121cea1d8b8772bc7fee99c71ecf0f57c1d77b3b", - "reference": "121cea1d8b8772bc7fee99c71ecf0f57c1d77b3b", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/64abbe2aeee0855a11cfce49d0ea08a0aa967cd2", + "reference": "64abbe2aeee0855a11cfce49d0ea08a0aa967cd2", "shasum": "" }, "require": { @@ -690,20 +729,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-12T14:45:30+00:00" + "time": "2021-05-06T14:58:48+00:00" }, { "name": "illuminate/database", - "version": "v8.34.0", + "version": "v8.41.0", "source": { "type": "git", "url": "https://github.com/illuminate/database.git", - "reference": "74a165fd07b36cc0ea3558fa391b762867af87e8" + "reference": "6277b39728bce436d2509d215223137d87265792" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/74a165fd07b36cc0ea3558fa391b762867af87e8", - "reference": "74a165fd07b36cc0ea3558fa391b762867af87e8", + "url": "https://api.github.com/repos/illuminate/database/zipball/6277b39728bce436d2509d215223137d87265792", + "reference": "6277b39728bce436d2509d215223137d87265792", "shasum": "" }, "require": { @@ -758,20 +797,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-23T15:12:51+00:00" + "time": "2021-05-11T13:24:37+00:00" }, { "name": "illuminate/filesystem", - "version": "v8.34.0", + "version": "v8.41.0", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", - "reference": "25e31d4f114baf1f99110bb5fc7538f26b4367cb" + "reference": "8ef5902052c5b3bb4a6c1c3afc399f30e7723cb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/25e31d4f114baf1f99110bb5fc7538f26b4367cb", - "reference": "25e31d4f114baf1f99110bb5fc7538f26b4367cb", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/8ef5902052c5b3bb4a6c1c3afc399f30e7723cb8", + "reference": "8ef5902052c5b3bb4a6c1c3afc399f30e7723cb8", "shasum": "" }, "require": { @@ -820,11 +859,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-21T13:46:59+00:00" + "time": "2021-04-05T18:45:36+00:00" }, { "name": "illuminate/macroable", - "version": "v8.34.0", + "version": "v8.41.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", @@ -870,16 +909,16 @@ }, { "name": "illuminate/support", - "version": "v8.34.0", + "version": "v8.41.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "b7b27e758b68aad44558c62e7374328835895386" + "reference": "31e91a12f0aac770d02a05b5d5771829132213b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/b7b27e758b68aad44558c62e7374328835895386", - "reference": "b7b27e758b68aad44558c62e7374328835895386", + "url": "https://api.github.com/repos/illuminate/support/zipball/31e91a12f0aac770d02a05b5d5771829132213b4", + "reference": "31e91a12f0aac770d02a05b5d5771829132213b4", "shasum": "" }, "require": { @@ -934,20 +973,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-21T13:37:37+00:00" + "time": "2021-05-10T13:42:57+00:00" }, { "name": "nesbot/carbon", - "version": "2.46.0", + "version": "2.48.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "2fd2c4a77d58a4e95234c8a61c5df1f157a91bf4" + "reference": "d3c447f21072766cddec3522f9468a5849a76147" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2fd2c4a77d58a4e95234c8a61c5df1f157a91bf4", - "reference": "2fd2c4a77d58a4e95234c8a61c5df1f157a91bf4", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/d3c447f21072766cddec3522f9468a5849a76147", + "reference": "d3c447f21072766cddec3522f9468a5849a76147", "shasum": "" }, "require": { @@ -1027,20 +1066,20 @@ "type": "tidelift" } ], - "time": "2021-02-24T17:30:44+00:00" + "time": "2021-05-07T10:08:30+00:00" }, { "name": "php-libs/observable", - "version": "1.0", + "version": "1.3", "source": { "type": "git", "url": "https://github.com/php-libs/observable.git", - "reference": "68efabfa33f9bbe1e25e1b7c95e6bcd7fc5b62c5" + "reference": "0f6b33db27228c7800556a2c11e1ca7a7c80871e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-libs/observable/zipball/68efabfa33f9bbe1e25e1b7c95e6bcd7fc5b62c5", - "reference": "68efabfa33f9bbe1e25e1b7c95e6bcd7fc5b62c5", + "url": "https://api.github.com/repos/php-libs/observable/zipball/0f6b33db27228c7800556a2c11e1ca7a7c80871e", + "reference": "0f6b33db27228c7800556a2c11e1ca7a7c80871e", "shasum": "" }, "require": { @@ -1059,9 +1098,44 @@ "description": "Provides clases to simplify observer pattern implementation", "support": { "issues": "https://github.com/php-libs/observable/issues", - "source": "https://github.com/php-libs/observable/tree/1.0" + "source": "https://github.com/php-libs/observable/tree/1.3" + }, + "time": "2021-05-19T07:15:17+00:00" + }, + { + "name": "php-libs/value-states", + "version": "1.3", + "source": { + "type": "git", + "url": "https://github.com/php-libs/value-state.git", + "reference": "d9dcc44c5826dfbd7e7350bf5b217654151bf773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-libs/value-state/zipball/d9dcc44c5826dfbd7e7350bf5b217654151bf773", + "reference": "d9dcc44c5826dfbd7e7350bf5b217654151bf773", + "shasum": "" + }, + "require": { + "php": "^8.0", + "php-libs/observable": "^1.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpLibs\\ValueState\\": "src/PhpLibs/ValueState" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extends php-libs/observer to track if a values have been initialized or modified (changed after initialization)", + "support": { + "issues": "https://github.com/php-libs/value-state/issues", + "source": "https://github.com/php-libs/value-state/tree/1.3" }, - "time": "2021-05-06T20:42:12+00:00" + "time": "2021-05-19T07:16:58+00:00" }, { "name": "psr/container", @@ -1164,16 +1238,16 @@ }, { "name": "symfony/console", - "version": "v5.2.5", + "version": "v5.2.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79" + "reference": "864568fdc0208b3eba3638b6000b69d2386e6768" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/938ebbadae1b0a9c9d1ec313f87f9708609f1b79", - "reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79", + "url": "https://api.github.com/repos/symfony/console/zipball/864568fdc0208b3eba3638b6000b69d2386e6768", + "reference": "864568fdc0208b3eba3638b6000b69d2386e6768", "shasum": "" }, "require": { @@ -1241,7 +1315,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.5" + "source": "https://github.com/symfony/console/tree/v5.2.8" }, "funding": [ { @@ -1257,20 +1331,20 @@ "type": "tidelift" } ], - "time": "2021-03-06T13:42:15+00:00" + "time": "2021-05-11T15:45:21+00:00" }, { "name": "symfony/finder", - "version": "v5.2.4", + "version": "v5.2.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "0d639a0943822626290d169965804f79400e6a04" + "reference": "eccb8be70d7a6a2230d05f6ecede40f3fdd9e252" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0d639a0943822626290d169965804f79400e6a04", - "reference": "0d639a0943822626290d169965804f79400e6a04", + "url": "https://api.github.com/repos/symfony/finder/zipball/eccb8be70d7a6a2230d05f6ecede40f3fdd9e252", + "reference": "eccb8be70d7a6a2230d05f6ecede40f3fdd9e252", "shasum": "" }, "require": { @@ -1302,7 +1376,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.2.4" + "source": "https://github.com/symfony/finder/tree/v5.2.8" }, "funding": [ { @@ -1318,7 +1392,7 @@ "type": "tidelift" } ], - "time": "2021-02-15T18:55:04+00:00" + "time": "2021-05-10T14:39:23+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1808,16 +1882,16 @@ }, { "name": "symfony/process", - "version": "v5.2.4", + "version": "v5.2.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f" + "reference": "98cb8eeb72e55d4196dd1e36f1f16e7b3a9a088e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f", - "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "url": "https://api.github.com/repos/symfony/process/zipball/98cb8eeb72e55d4196dd1e36f1f16e7b3a9a088e", + "reference": "98cb8eeb72e55d4196dd1e36f1f16e7b3a9a088e", "shasum": "" }, "require": { @@ -1850,7 +1924,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.2.4" + "source": "https://github.com/symfony/process/tree/v5.3.0-BETA1" }, "funding": [ { @@ -1866,25 +1940,25 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-04-08T10:27:02+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/container": "^1.0" + "psr/container": "^1.1" }, "suggest": { "symfony/service-implementation": "" @@ -1892,7 +1966,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -1929,7 +2003,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/master" + "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" }, "funding": [ { @@ -1945,20 +2019,20 @@ "type": "tidelift" } ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2021-04-01T10:43:52+00:00" }, { "name": "symfony/string", - "version": "v5.2.4", + "version": "v5.2.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "4e78d7d47061fa183639927ec40d607973699609" + "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/4e78d7d47061fa183639927ec40d607973699609", - "reference": "4e78d7d47061fa183639927ec40d607973699609", + "url": "https://api.github.com/repos/symfony/string/zipball/01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db", + "reference": "01b35eb64cac8467c3f94cd0ce2d0d376bb7d1db", "shasum": "" }, "require": { @@ -2012,7 +2086,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.4" + "source": "https://github.com/symfony/string/tree/v5.2.8" }, "funding": [ { @@ -2028,20 +2102,20 @@ "type": "tidelift" } ], - "time": "2021-02-16T10:20:28+00:00" + "time": "2021-05-10T14:56:10+00:00" }, { "name": "symfony/translation", - "version": "v5.2.5", + "version": "v5.2.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "0947ab1e3aabd22a6bef393874b2555d2bb976da" + "reference": "445caa74a5986f1cc9dd91a2975ef68fa7cb2068" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/0947ab1e3aabd22a6bef393874b2555d2bb976da", - "reference": "0947ab1e3aabd22a6bef393874b2555d2bb976da", + "url": "https://api.github.com/repos/symfony/translation/zipball/445caa74a5986f1cc9dd91a2975ef68fa7cb2068", + "reference": "445caa74a5986f1cc9dd91a2975ef68fa7cb2068", "shasum": "" }, "require": { @@ -2105,7 +2179,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.2.5" + "source": "https://github.com/symfony/translation/tree/v5.2.8" }, "funding": [ { @@ -2121,20 +2195,20 @@ "type": "tidelift" } ], - "time": "2021-03-06T07:59:01+00:00" + "time": "2021-05-07T13:41:16+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105" + "reference": "95c812666f3e91db75385749fe219c5e494c7f95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e2eaa60b558f26a4b0354e1bbb25636efaaad105", - "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95", + "reference": "95c812666f3e91db75385749fe219c5e494c7f95", "shasum": "" }, "require": { @@ -2146,7 +2220,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -2183,7 +2257,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.3.0" + "source": "https://github.com/symfony/translation-contracts/tree/v2.4.0" }, "funding": [ { @@ -2199,7 +2273,7 @@ "type": "tidelift" } ], - "time": "2020-09-28T13:05:58+00:00" + "time": "2021-03-23T23:28:01+00:00" }, { "name": "voku/portable-ascii", @@ -2783,16 +2857,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.10.4", + "version": "v4.10.5", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", + "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", "shasum": "" }, "require": { @@ -2833,9 +2907,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" }, - "time": "2020-12-20T10:01:03+00:00" + "time": "2021-05-03T19:11:20+00:00" }, { "name": "phar-io/manifest", @@ -3175,16 +3249,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.5", + "version": "9.2.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" + "reference": "f6293e1b30a2354e8428e004689671b83871edde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", "shasum": "" }, "require": { @@ -3240,7 +3314,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" }, "funding": [ { @@ -3248,7 +3322,7 @@ "type": "github" } ], - "time": "2020-11-28T06:44:49+00:00" + "time": "2021-03-28T07:26:59+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4610,16 +4684,16 @@ }, { "name": "symfony/config", - "version": "v5.2.7", + "version": "v5.2.8", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "3817662ada105c8c4d1afdb4ec003003efd1d8d8" + "reference": "8dfa5f8adea9cd5155920069224beb04f11d6b7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/3817662ada105c8c4d1afdb4ec003003efd1d8d8", - "reference": "3817662ada105c8c4d1afdb4ec003003efd1d8d8", + "url": "https://api.github.com/repos/symfony/config/zipball/8dfa5f8adea9cd5155920069224beb04f11d6b7e", + "reference": "8dfa5f8adea9cd5155920069224beb04f11d6b7e", "shasum": "" }, "require": { @@ -4668,7 +4742,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.2.7" + "source": "https://github.com/symfony/config/tree/v5.2.8" }, "funding": [ { @@ -4684,20 +4758,20 @@ "type": "tidelift" } ], - "time": "2021-04-07T16:07:52+00:00" + "time": "2021-05-07T13:41:16+00:00" }, { "name": "symfony/dependency-injection", - "version": "v5.2.7", + "version": "v5.2.8", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "6ca378b99e3c9ba6127eb43b68389fb2b7348577" + "reference": "024e929da5a994cbab0ce2291d332f7edf926acf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6ca378b99e3c9ba6127eb43b68389fb2b7348577", - "reference": "6ca378b99e3c9ba6127eb43b68389fb2b7348577", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/024e929da5a994cbab0ce2291d332f7edf926acf", + "reference": "024e929da5a994cbab0ce2291d332f7edf926acf", "shasum": "" }, "require": { @@ -4755,7 +4829,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.2.7" + "source": "https://github.com/symfony/dependency-injection/tree/v5.2.8" }, "funding": [ { @@ -4771,7 +4845,7 @@ "type": "tidelift" } ], - "time": "2021-04-24T14:32:26+00:00" + "time": "2021-05-11T16:07:35+00:00" }, { "name": "symfony/deprecation-contracts", diff --git a/config/reliese.php b/config/reliese.php index 4a45859c..f7bcf0de 100644 --- a/config/reliese.php +++ b/config/reliese.php @@ -1,10 +1,12 @@ ['information_schema', 'performance_schema', 'mysql', 'sys']], // except audit.log_.* tables - ['schemas' => ['audit'], 'tables' => ['/^log_.*$/']], + //['schemas' => ['audit'], 'tables' => ['/^log_.*$/']], // except any table that ends in migrations or matches 'phinx' on all schemas ['schemas' => [$all], 'tables' => ['/^.*migrations$/', 'phinx']], // except soft delete columns on all tables for all schemas - ['schemas' => [$all], 'tables' => [$all], 'columns' => ['deleted_on']] + //['schemas' => [$all], 'tables' => [$all], 'columns' => ['deleted_on']] ], ], /* @@ -527,24 +529,59 @@ ], ], // endregion Model Generator Config + + // region Data Access Generator Config + DataAccessGeneratorConfiguration::class => [ + 'Path' => app_path().'/DataAccess/PrimaryDatabase', + 'Namespace' => 'app\DataAccess\PrimaryDatabase', + //'ClassPrefix' => '', + 'ClassSuffix' => 'DataAccess', + 'ParentClassPrefix' => 'Abstract', + ], + // endregion Data Access Generator Config + + // region Data Attribute Generator Config + DataAttributeGeneratorConfiguration::class => [ + 'Path' => app_path().'/DataAttribute/PrimaryDatabase', + 'Namespace' => 'App\DataAttribute\Objects', + 'ClassPrefix' => 'With', + 'ClassSuffix' => 'Trait', + 'ParentClassPrefix' => 'Abstract', + ], + // endregion Data Attribute Generator Config + // region Data Transport Generator Config - DataTransportGeneratorConfiguration::class => [ + + DataTransportObjectGeneratorConfiguration::class => [ 'Path' => app_path().'/DataTransportObjects', 'Namespace' => 'App\DataTransportObjects', 'ClassSuffix' => 'Dto', 'ParentClassPrefix' => 'Abstract', + 'UseValueStateTracking' => true, 'ObservableProperties' => [ 'BeforeChange' => false, 'AfterChange' => false, ], ], // endregion Data Transport Generator Config + + // region Data Transport Collection Generator Config + DataTransportCollectionGeneratorConfiguration::class => [ + 'Path' => app_path().'/DataTransport/Collections', + 'Namespace' => 'App\DataTransport\Collections', + 'ClassSuffix' => 'Dto', + 'ParentClassPrefix' => 'Abstract', + ], + // endregion Data Transport Collection Generator Config + // region Data Map Generator Config ModelDataMapGeneratorConfiguration::class => [ 'Path' => app_path().'/DataMaps/PrimaryDatabase', 'Namespace' => 'App\DataMaps\PrimaryDatabase', 'ClassSuffix' => 'Map', 'ParentClassPrefix' => 'Abstract', + 'AccessorTraitNamespace' => 'app\DataMapAccessors\PrimaryDatabase', + 'AccessorTraitPath' => app_path().'/DataMapAccessors/PrimaryDatabase', ], // endregion Data Map Generator Config ] diff --git a/src/Analyser/Doctrine/DoctrineDatabaseAnalyser.php b/src/Analyser/Doctrine/DoctrineDatabaseAnalyser.php index 82dff7d4..2fe1ac36 100644 --- a/src/Analyser/Doctrine/DoctrineDatabaseAnalyser.php +++ b/src/Analyser/Doctrine/DoctrineDatabaseAnalyser.php @@ -60,7 +60,7 @@ public function analyseDatabase(DatabaseBlueprintConfiguration $databaseBlueprin continue; } - $schemaAnalyser = $this->getSchemaAnalyser($databaseBlueprint, $schemaName); + $schemaAnalyser = $this->getSchemaAnalyser($databaseBlueprint, $schemaName, $databaseBlueprintConfiguration); $databaseBlueprint->addSchemaBlueprint( // TODO: Add support for Views $schemaAnalyser->analyseSchemaObjectStructures() @@ -71,7 +71,7 @@ public function analyseDatabase(DatabaseBlueprintConfiguration $databaseBlueprin * Analyse foreign key constraint relationships which could potentially span schemas */ foreach ($schemaNames as $schemaName) { - $schemaAnalyser = $this->getSchemaAnalyser($databaseBlueprint, $schemaName); + $schemaAnalyser = $this->getSchemaAnalyser($databaseBlueprint, $schemaName, $databaseBlueprintConfiguration); foreach ($schemaAnalyser->getTableDefinitions() as $tableName => $doctrineTableDefinition) { @@ -164,7 +164,11 @@ protected function getSchemaNames(): array * * @return DoctrineSchemaAnalyser */ - protected function getSchemaAnalyser(DatabaseBlueprint $databaseBlueprint, string $schemaName): DoctrineSchemaAnalyser + protected function getSchemaAnalyser( + DatabaseBlueprint $databaseBlueprint, + string $schemaName, + DatabaseBlueprintConfiguration $databaseBlueprintConfiguration + ): DoctrineSchemaAnalyser { if (array_key_exists($schemaName, $this->schemaAnalysers)) { return $this->schemaAnalysers[$schemaName]; @@ -178,7 +182,8 @@ protected function getSchemaAnalyser(DatabaseBlueprint $databaseBlueprint, strin $databaseBlueprint, $this, $schemaSpecificConnection, - $schemaSpecificDoctrineSchemaManager + $schemaSpecificDoctrineSchemaManager, + $databaseBlueprintConfiguration ); } } diff --git a/src/Analyser/Doctrine/DoctrineSchemaAnalyser.php b/src/Analyser/Doctrine/DoctrineSchemaAnalyser.php index 509ccbc1..f29f96cb 100644 --- a/src/Analyser/Doctrine/DoctrineSchemaAnalyser.php +++ b/src/Analyser/Doctrine/DoctrineSchemaAnalyser.php @@ -16,6 +16,7 @@ use Reliese\Blueprint\IndexBlueprint; use Reliese\Blueprint\SchemaBlueprint; use Reliese\Blueprint\TableBlueprint; +use Reliese\Configuration\DatabaseBlueprintConfiguration; /** * Class DoctrineSchemaAnalyser @@ -27,6 +28,11 @@ class DoctrineSchemaAnalyser */ private DatabaseBlueprint $databaseBlueprint; + /** + * @var DatabaseBlueprintConfiguration + */ + private DatabaseBlueprintConfiguration $databaseBlueprintConfiguration; + /** * @var DoctrineDatabaseAnalyser */ @@ -66,7 +72,8 @@ public function __construct( DatabaseBlueprint $databaseBlueprint, DoctrineDatabaseAnalyser $doctrineDatabaseAnalyser, ConnectionInterface $connection, - AbstractSchemaManager $doctrineSchemaManager + AbstractSchemaManager $doctrineSchemaManager, + DatabaseBlueprintConfiguration $databaseBlueprintConfiguration ) { $this->schemaName = $schemaName; @@ -74,6 +81,7 @@ public function __construct( $this->doctrineDatabaseAnalyser = $doctrineDatabaseAnalyser; $this->schemaSpecificConnection = $connection; $this->doctrineSchemaManager = $doctrineSchemaManager; + $this->databaseBlueprintConfiguration = $databaseBlueprintConfiguration; } /** @@ -110,6 +118,13 @@ public function analyseSchemaObjectStructures(): SchemaBlueprint $tableDefinitions = $this->getDoctrineSchemaManager()->listTables(); if (!empty($tableDefinitions)) { foreach ($tableDefinitions as $tableDefinition) { + $isExcluded = $this->databaseBlueprintConfiguration + ->getSchemaFilter() + ->isExcludedTable($this->getSchemaName(), $tableDefinition->getName()) + ; + if ($isExcluded) { + continue; + } /* * Keep for future use */ @@ -189,6 +204,15 @@ private function analyseTableColumns(Table $tableDefinition, TableBlueprint $tab } foreach ($columnDefinitions as $columnDefinition) { + $isExcluded = $this->databaseBlueprintConfiguration->getSchemaFilter()->isExcludedColumn( + $this->getSchemaName(), + $tableDefinition->getName(), + $columnDefinition->getName() + ); + if ($isExcluded) { + continue; + } + $columnBlueprint = $this->analyseColumn($tableBlueprint, $columnDefinition); $tableBlueprint->addColumnBlueprint($columnBlueprint); } @@ -258,7 +282,7 @@ private function analyseIndex(TableBlueprint $tableBlueprint, Index $indexDefini $indexDefinition->getName(), $columnBlueprints, $indexDefinition->isPrimary(), - false + $indexDefinition->isUnique() ); } diff --git a/src/Blueprint/ColumnOwnerInterface.php b/src/Blueprint/ColumnOwnerInterface.php index d896d313..c099e674 100644 --- a/src/Blueprint/ColumnOwnerInterface.php +++ b/src/Blueprint/ColumnOwnerInterface.php @@ -7,7 +7,7 @@ * * @package Reliese\Blueprint */ -interface ColumnOwnerInterface +interface ColumnOwnerInterface extends SchemaMemberInterface { /** * @param ColumnBlueprint $columnBlueprint @@ -30,11 +30,4 @@ public function getColumnBlueprints(): array; * @return string[] */ public function getColumnNames(): array; - - /** - * returns "Schema.[TableName |View Name]" - * - * @return string - */ - public function getUniqueName(): string; } diff --git a/src/Blueprint/ForeignKeyBlueprint.php b/src/Blueprint/ForeignKeyBlueprint.php index 1cc23303..be42906f 100644 --- a/src/Blueprint/ForeignKeyBlueprint.php +++ b/src/Blueprint/ForeignKeyBlueprint.php @@ -101,4 +101,28 @@ public function getReferencedTableBlueprint() : ColumnOwnerInterface { return $this->referencedTable; } + + /** + * @return ColumnBlueprint[] + */ + public function getReferencedColumns() : array + { + return $this->referencedColumns; + } + + public function getFkColumnPairs():array + { + $columns = []; + $i = -1; + foreach ($this->referencingColumns as $referencingColumn) { + $i++; + $columns[$i][0] = $referencingColumn; + } + $i = -1; + foreach ($this->referencedColumns as $referencedColumn) { + $i++; + $columns[$i][1] = $referencedColumn; + } + return $columns; + } } diff --git a/src/Blueprint/IndexBlueprint.php b/src/Blueprint/IndexBlueprint.php index 20312ef9..29ffd83e 100644 --- a/src/Blueprint/IndexBlueprint.php +++ b/src/Blueprint/IndexBlueprint.php @@ -8,6 +8,7 @@ class IndexBlueprint implements ColumnOwnerInterface { use ColumnOwnerTrait; + use SchemaMemberTrait; /** * @var string @@ -79,4 +80,14 @@ public function isUnique(): bool { return $this->isUnique || $this->isPrimaryKey(); } + + public function getSchemaMemberType(): SchemaMemberType + { + return SchemaMemberType::Index(); + } + + public function getName(): string + { + return $this->indexName; + } } diff --git a/src/Blueprint/SchemaMemberInterface.php b/src/Blueprint/SchemaMemberInterface.php index 7bdde788..bc2a6c26 100644 --- a/src/Blueprint/SchemaMemberInterface.php +++ b/src/Blueprint/SchemaMemberInterface.php @@ -18,6 +18,11 @@ interface SchemaMemberInterface */ public function getName(): string; + /** + * @return string + */ + public function getUniqueName(): string; + /** * Returns an instance of SchemaMemberType * diff --git a/src/Blueprint/SchemaMemberTrait.php b/src/Blueprint/SchemaMemberTrait.php index 85af65b4..7b149a4b 100644 --- a/src/Blueprint/SchemaMemberTrait.php +++ b/src/Blueprint/SchemaMemberTrait.php @@ -54,4 +54,18 @@ public function setName(string $name) { $this->name = $name; } + + /** + * @return string + */ + public function getUniqueName(): string + { + if (empty($this->getSchemaBlueprint()->getSchemaName())) { + return $this->getName(); + } + + return sprintf('%s.%s', + $this->getSchemaBlueprint()->getSchemaName(), + $this->getName()); + } } \ No newline at end of file diff --git a/src/Blueprint/TableBlueprint.php b/src/Blueprint/TableBlueprint.php index 92a08353..5e7ee474 100644 --- a/src/Blueprint/TableBlueprint.php +++ b/src/Blueprint/TableBlueprint.php @@ -97,16 +97,6 @@ public function getSchemaMemberType(): SchemaMemberType return SchemaMemberType::Table(); } - /** - * @return string - */ - public function getUniqueName(): string - { - return sprintf('%s.%s', - $this->getSchemaBlueprint()->getSchemaName(), - $this->getName()); - } - /** * @return ForeignKeyBlueprint[] */ @@ -126,4 +116,54 @@ public function getForeignKeyBlueprintsGroupedByReferencedTable() : array } return $results; } + + /** + * @param bool $includePrimaryKey + * + * @return array[] + */ + public function getUniqueColumnGroups(bool $includePrimaryKey = true): array + { + $uniqueColumnGroups = []; + + foreach ($this->indexBlueprints as $indexBlueprint) { + if ($indexBlueprint->isPrimaryKey() && !$includePrimaryKey) { + continue; + } + if (!$indexBlueprint->isUnique()) { + continue; + } + + $uniqueColumnGroups[] = $indexBlueprint->getColumnBlueprints(); + } + + return $uniqueColumnGroups; + } + + /** + * @param bool $includePrimaryKey + * + * @return IndexBlueprint[] + */ + public function getUniqueIndexes(bool $includePrimaryKey = true): array + { + $uniqueIndexes = []; + + foreach ($this->indexBlueprints as $indexBlueprint) { + if ($indexBlueprint->isPrimaryKey() && !$includePrimaryKey) { + continue; + } + if (!$indexBlueprint->isUnique()) { + continue; + } + + $uniqueIndexes[] = $indexBlueprint; + } + + usort($uniqueIndexes, function (IndexBlueprint $a, IndexBlueprint $b) { + return strncasecmp($a->getName(), + $b->getName(), mb_strlen($a->getName()));}); + + return $uniqueIndexes; + } } diff --git a/src/Command/DataAccess/DataAccessGenerateCommand.php b/src/Command/DataAccess/DataAccessGenerateCommand.php new file mode 100644 index 00000000..82658701 --- /dev/null +++ b/src/Command/DataAccess/DataAccessGenerateCommand.php @@ -0,0 +1,140 @@ +signature .= self::$configurationProfileOptionDescription; + parent::__construct(); + + $this->models = $models; + $this->config = $config; + } + + /** + * Execute the console command. + * + * @param AnalyserFactory $analyserFactory + * @param RelieseConfigurationFactory $relieseConfigurationFactory + */ + public function handle( + AnalyserFactory $analyserFactory, + RelieseConfigurationFactory $relieseConfigurationFactory, + ) { + $relieseConfiguration = $relieseConfigurationFactory->getRelieseConfiguration($this->getConfigurationProfileName()); + $connection = $this->getConnection(); + $schema = $this->getSchema($connection); + $table = $this->getTable(); + + /* + * TODO: allow command line options to modify state of the $relieseConfiguration graph + */ + + /* + * Create the correct analyser for the configuration profile + */ + $databaseAnalyser = $analyserFactory->databaseAnalyser($relieseConfiguration); + + /* + * Allow the $databaseAnalyser to create the Database Blueprint + */ + $databaseBlueprint = $databaseAnalyser->analyseDatabase($relieseConfiguration->getDatabaseBlueprintConfiguration()); + + /* + * Generate class files + */ + $dataAccessGenerator = new DataAccessGenerator($relieseConfiguration); + + $schemaBlueprint = $databaseBlueprint->getSchemaBlueprint($schema); + + if (!empty($table)) { + // Generate only for the specified table + $tableBlueprint = $schemaBlueprint->getTableBlueprint($table); + $dataAccessGenerator->fromTableBlueprint($tableBlueprint); + return; + } + + /* + * Display the data that would be used to perform code generation + */ + foreach ($schemaBlueprint->getTableBlueprints() as $tableBlueprint) { + $dataAccessGenerator->fromTableBlueprint($tableBlueprint); + } + } + + /** + * @return string + */ + protected function getConnection() + { + return $this->option('connection') ?: $this->config->get('database.default'); + } + + /** + * @param $connection + * + * @return string + */ + protected function getSchema($connection) + { + return $this->option('schema') ?: $this->config->get("database.connections.$connection.database"); + } + + /** + * @return string + */ + protected function getTable() + { + return $this->option('table'); + } +} diff --git a/src/Command/DataAttribute/DataAttributeGenerateCommand.php b/src/Command/DataAttribute/DataAttributeGenerateCommand.php new file mode 100644 index 00000000..9478957e --- /dev/null +++ b/src/Command/DataAttribute/DataAttributeGenerateCommand.php @@ -0,0 +1,131 @@ +signature .= self::$configurationProfileOptionDescription; + parent::__construct(); + + $this->models = $models; + $this->config = $config; + } + + /** + * Execute the console command. + * + * @param AnalyserFactory $analyserFactory + * @param RelieseConfigurationFactory $relieseConfigurationFactory + */ + public function handle( + AnalyserFactory $analyserFactory, + RelieseConfigurationFactory $relieseConfigurationFactory, + ) { + $relieseConfiguration = $relieseConfigurationFactory->getRelieseConfiguration($this->getConfigurationProfileName()); + $connection = $this->getConnection(); + $schema = $this->getSchema($connection); + $table = $this->getTable(); + + /* + * TODO: allow command line options to modify state of the $relieseConfiguration graph + */ + + /* + * Create the correct analyser for the configuration profile + */ + $databaseAnalyser = $analyserFactory->databaseAnalyser($relieseConfiguration); + + /* + * Allow the $databaseAnalyser to create the Database Blueprint + */ + $databaseBlueprint = $databaseAnalyser->analyseDatabase($relieseConfiguration->getDatabaseBlueprintConfiguration()); + + /* + * Generate class files + */ + $dataAttributeGenerator = new DataAttributeGenerator($relieseConfiguration); + + $schemaBlueprint = $databaseBlueprint->getSchemaBlueprint($schema); + + /* + * Display the data that would be used to perform code generation + */ + foreach ($schemaBlueprint->getTableBlueprints() as $tableBlueprint) { + $dataAttributeGenerator->fromColumnBlueprint($tableBlueprint); + } + } + + /** + * @return string + */ + protected function getConnection() + { + return $this->option('connection') ?: $this->config->get('database.default'); + } + + /** + * @param $connection + * + * @return string + */ + protected function getSchema($connection) + { + return $this->option('schema') ?: $this->config->get("database.connections.$connection.database"); + } + + /** + * @return string + */ + protected function getTable() + { + return $this->option('table'); + } +} diff --git a/src/Command/DataMap/ModelDataMapGenerateCommand.php b/src/Command/DataMap/ModelDataMapGenerateCommand.php index b7808cba..b1a4b21e 100644 --- a/src/Command/DataMap/ModelDataMapGenerateCommand.php +++ b/src/Command/DataMap/ModelDataMapGenerateCommand.php @@ -9,8 +9,9 @@ use Reliese\Coders\Model\Factory; use Reliese\Command\ConfigurationProfileOptionTrait; use Reliese\Configuration\RelieseConfigurationFactory; +use Reliese\Generator\DataAttribute\DataAttributeGenerator; use Reliese\Generator\DataMap\ModelDataMapGenerator; -use Reliese\Generator\DataTransport\DataTransportGenerator; +use Reliese\Generator\DataTransport\DataTransportObjectGenerator; use Reliese\Generator\Model\ModelGenerator; /** @@ -86,9 +87,7 @@ public function handle( /* * Create the correct analyser for the configuration profile */ - $databaseAnalyser = $analyserFactory->databaseAnalyser( - $relieseConfiguration - ); + $databaseAnalyser = $analyserFactory->databaseAnalyser($relieseConfiguration); /* * Allow the $databaseAnalyser to create the Database Blueprint @@ -102,11 +101,7 @@ public function handle( /* * Create a ModelDataMapGenerator */ - $modelDataMapGenerator = new ModelDataMapGenerator( - $relieseConfiguration->getModelDataMapGeneratorConfiguration(), - new ModelGenerator($relieseConfiguration->getModelGeneratorConfiguration()), - new DataTransportGenerator($relieseConfiguration->getDataTransportGeneratorConfiguration()), - ); + $modelDataMapGenerator = new ModelDataMapGenerator($relieseConfiguration); if (!empty($table)) { // Generate only for the specified table diff --git a/src/Command/DataTransport/DataTransportGenerateCommand.php b/src/Command/DataTransport/DataTransportGenerateCommand.php index e5e8a9e9..42e2c77a 100644 --- a/src/Command/DataTransport/DataTransportGenerateCommand.php +++ b/src/Command/DataTransport/DataTransportGenerateCommand.php @@ -9,7 +9,8 @@ use Reliese\Coders\Model\Factory; use Reliese\Command\ConfigurationProfileOptionTrait; use Reliese\Configuration\RelieseConfigurationFactory; -use Reliese\Generator\DataTransport\DataTransportGenerator; +use Reliese\Generator\DataAttribute\DataAttributeGenerator; +use Reliese\Generator\DataTransport\DataTransportObjectGenerator; /** * Class DataTransportGenerateCommand @@ -94,9 +95,7 @@ public function handle( /* * Generate class files */ - $dataTransportGenerator = new DataTransportGenerator( - $relieseConfiguration->getDataTransportGeneratorConfiguration() - ); + $dataTransportGenerator = new DataTransportObjectGenerator($relieseConfiguration); $schemaBlueprint = $databaseBlueprint->getSchemaBlueprint($schema); diff --git a/src/Command/Model/NewModelGenerateCommand.php b/src/Command/Model/NewModelGenerateCommand.php index 43beba85..00312c5a 100644 --- a/src/Command/Model/NewModelGenerateCommand.php +++ b/src/Command/Model/NewModelGenerateCommand.php @@ -100,9 +100,7 @@ public function handle( /* * Create the correct analyser for the configuration profile */ - $databaseAnalyser = $analyserFactory->databaseAnalyser( - $relieseConfiguration - ); + $databaseAnalyser = $analyserFactory->databaseAnalyser($relieseConfiguration); /* * Allow the $databaseAnalyser to create the Database Blueprint @@ -110,7 +108,7 @@ public function handle( $databaseBlueprint = $databaseAnalyser->analyseDatabase($relieseConfiguration->getDatabaseBlueprintConfiguration()); // TODO: Apply Command Line options that override the configuration values - $modelGenerator = new ModelGenerator($relieseConfiguration->getModelGeneratorConfiguration()); + $modelGenerator = new ModelGenerator($relieseConfiguration); $schemaBlueprint = $databaseBlueprint->getSchemaBlueprint($schema); diff --git a/src/Configuration/DataAccessGeneratorConfiguration.php b/src/Configuration/DataAccessGeneratorConfiguration.php new file mode 100644 index 00000000..b511ba20 --- /dev/null +++ b/src/Configuration/DataAccessGeneratorConfiguration.php @@ -0,0 +1,88 @@ +path = $configuration['Path']; + $this->namespace = $configuration['Namespace']; + $this->classPrefix = $configuration['ClassPrefix'] ?? ''; + $this->classSuffix = $configuration['ClassSuffix'] ?? ''; + $this->parentClassPrefix = $configuration['ParentClassPrefix'] ?? ''; + } + + /** + * @return mixed|string + */ + public function getClassPrefix(): mixed + { + return $this->classPrefix; + } + + /** + * @return mixed + */ + public function getClassSuffix(): mixed + { + return $this->classSuffix; + } + + /** + * @return string + */ + public function getNamespace(): string + { + return $this->namespace; + } + + /** + * @return mixed + */ + public function getParentClassPrefix(): mixed + { + return $this->parentClassPrefix; + } + + /** + * @return string + */ + public function getPath(): string + { + return $this->path; + } +} diff --git a/src/Configuration/DataAttributeGeneratorConfiguration.php b/src/Configuration/DataAttributeGeneratorConfiguration.php new file mode 100644 index 00000000..7c16c8f1 --- /dev/null +++ b/src/Configuration/DataAttributeGeneratorConfiguration.php @@ -0,0 +1,74 @@ +path = $configuration['Path']; + $this->namespace = $configuration['Namespace']; + $this->traitPrefix = $configuration['TraitPrefix'] ?? ""; + $this->traitSuffix = $configuration['TraitSuffix'] ?? ""; + } + + /** + * @return mixed + */ + public function getTraitPrefix(): mixed + { + return $this->traitPrefix; + } + + /** + * @return mixed + */ + public function getTraitSuffix(): mixed + { + return $this->traitSuffix; + } + + /** + * @return string + */ + public function getNamespace(): string + { + return $this->namespace; + } + + /** + * @return string + */ + public function getPath(): string + { + return $this->path; + } +} diff --git a/src/Configuration/DataTransportCollectionGeneratorConfiguration.php b/src/Configuration/DataTransportCollectionGeneratorConfiguration.php new file mode 100644 index 00000000..663212e7 --- /dev/null +++ b/src/Configuration/DataTransportCollectionGeneratorConfiguration.php @@ -0,0 +1,74 @@ +path = $configuration['Path']; + $this->namespace = $configuration['Namespace']; + $this->classSuffix = $configuration['ClassSuffix'] ?? ''; + $this->parentClassPrefix = $configuration['ParentClassPrefix'] ?? ''; + } + + /** + * @return mixed + */ + public function getClassSuffix(): mixed + { + return $this->classSuffix; + } + + /** + * @return string + */ + public function getNamespace(): string + { + return $this->namespace; + } + + /** + * @return mixed + */ + public function getParentClassPrefix(): mixed + { + return $this->parentClassPrefix; + } + + /** + * @return string + */ + public function getPath(): string + { + return $this->path; + } +} diff --git a/src/Configuration/DataTransportGeneratorConfiguration.php b/src/Configuration/DataTransportGeneratorConfiguration.php index 007b99b3..34aaac17 100644 --- a/src/Configuration/DataTransportGeneratorConfiguration.php +++ b/src/Configuration/DataTransportGeneratorConfiguration.php @@ -3,109 +3,10 @@ namespace Reliese\Configuration; /** - * Class DataTransportGeneratorConfiguration + * Class DataTransportObjectGeneratorConfiguration * * @deprecated Please use DataTransportObjectGeneratorConfiguration */ -class DataTransportGeneratorConfiguration +class DataTransportGeneratorConfiguration extends DataTransportObjectGeneratorConfiguration { - /** - * @var string - */ - private string $classSuffix; - - /** - * @var string - */ - private string $namespace; - - /** - * @var string - */ - private string $parentClassPrefix; - - /** - * @var string - */ - private string $path; - - /** - * @var bool - */ - private bool $useBeforeChangeObservableProperties = false; - - /** - * @var bool - */ - private bool $useAfterChangeObservableProperties = false; - - /** - * DataTransportGeneratorConfiguration constructor. - * - * @param array $configuration - */ - public function __construct(array $configuration) - { - $this->path = $configuration['Path']; - $this->namespace = $configuration['Namespace']; - $this->classSuffix = $configuration['ClassSuffix']; - $this->parentClassPrefix = $configuration['ParentClassPrefix']; - if (\array_key_exists('ObservableProperties', $configuration)) { - $observableConfig = $configuration['ObservableProperties']; - if (\array_key_exists('BeforeChange', $observableConfig)) { - $this->useBeforeChangeObservableProperties = $observableConfig['BeforeChange']; - } - if (\array_key_exists('AfterChange', $observableConfig)) { - $this->useBeforeChangeObservableProperties = $observableConfig['AfterChange']; - } - } - } - - /** - * @return mixed - */ - public function getClassSuffix(): mixed - { - return $this->classSuffix; - } - - /** - * @return string - */ - public function getNamespace(): string - { - return $this->namespace; - } - - /** - * @return mixed - */ - public function getParentClassPrefix(): mixed - { - return $this->parentClassPrefix; - } - - /** - * @return string - */ - public function getPath(): string - { - return $this->path; - } - - /** - * @return bool - */ - public function useAfterChangeObservableProperties(): bool - { - return $this->useAfterChangeObservableProperties; - } - - /** - * @return bool - */ - public function useBeforeChangeObservableProperties(): bool - { - return $this->useBeforeChangeObservableProperties; - } -} +} \ No newline at end of file diff --git a/src/Configuration/DataTransportObjectGeneratorConfiguration.php b/src/Configuration/DataTransportObjectGeneratorConfiguration.php index 27f39613..f8c70841 100644 --- a/src/Configuration/DataTransportObjectGeneratorConfiguration.php +++ b/src/Configuration/DataTransportObjectGeneratorConfiguration.php @@ -3,10 +3,118 @@ namespace Reliese\Configuration; /** - * Class DataTransportObjectGeneratorConfiguration - * This class was created to facilitate renaming of DataTransportGeneratorConfiguration to - * DataTransportObjectGeneratorConfiguration + * Class DataTransportGeneratorConfiguration */ -class DataTransportObjectGeneratorConfiguration extends DataTransportGeneratorConfiguration +class DataTransportObjectGeneratorConfiguration { -} \ No newline at end of file + /** + * @var string + */ + private string $classSuffix; + + /** + * @var string + */ + private string $namespace; + + /** + * @var string + */ + private string $parentClassPrefix; + + /** + * @var string + */ + private string $path; + + /** + * DataTransportObjectGeneratorConfiguration constructor. + * @var bool + */ + private bool $useBeforeChangeObservableProperties = false; + + /** + * @var bool + */ + private bool $useAfterChangeObservableProperties = false; + + private $useValueStateTracking; + + /** + * DataTransportGeneratorConfiguration constructor. + * + * @param array $configuration + */ + public function __construct(array $configuration) + { + $this->path = $configuration['Path']; + $this->namespace = $configuration['Namespace']; + $this->classSuffix = $configuration['ClassSuffix'] ?? ''; + $this->parentClassPrefix = $configuration['ParentClassPrefix'] ?? ''; + $this->classSuffix = $configuration['ClassSuffix']; + $this->parentClassPrefix = $configuration['ParentClassPrefix']; + $this->useValueStateTracking = $configuration['UseValueStateTracking'] ?? false; + if (\array_key_exists('ObservableProperties', $configuration)) { + $observableConfig = $configuration['ObservableProperties']; + if (\array_key_exists('BeforeChange', $observableConfig)) { + $this->useBeforeChangeObservableProperties = true === $observableConfig['BeforeChange']; + } + if (\array_key_exists('AfterChange', $observableConfig)) { + $this->useAfterChangeObservableProperties = true === $observableConfig['AfterChange']; + } + } + } + + /** + * @return mixed + */ + public function getClassSuffix(): mixed + { + return $this->classSuffix; + } + + /** + * @return string + */ + public function getNamespace(): string + { + return $this->namespace; + } + + /** + * @return mixed + */ + public function getParentClassPrefix(): mixed + { + return $this->parentClassPrefix; + } + + /** + * @return string + */ + public function getPath(): string + { + return $this->path; + } + + /** + * @return bool + */ + public function getUseAfterChangeObservableProperties(): bool + { + return $this->useAfterChangeObservableProperties; + } + + /** + * @return bool + */ + public function getUseBeforeChangeObservableProperties(): bool + { + return $this->useBeforeChangeObservableProperties; + } + + public function getUseValueStateTracking(): bool + { + return $this->useValueStateTracking; + } +} diff --git a/src/Configuration/ModelDataMapGeneratorConfiguration.php b/src/Configuration/ModelDataMapGeneratorConfiguration.php index fa425366..c56b6f61 100644 --- a/src/Configuration/ModelDataMapGeneratorConfiguration.php +++ b/src/Configuration/ModelDataMapGeneratorConfiguration.php @@ -7,6 +7,16 @@ */ class ModelDataMapGeneratorConfiguration { + /** + * @var mixed|string + */ + private $accessorTraitNamespace; + + /** + * @var mixed|string + */ + private $accessorTraitPath; + /** * @var mixed */ @@ -28,17 +38,18 @@ class ModelDataMapGeneratorConfiguration private string $path; /** - * DataTransportGeneratorConfiguration constructor. + * DataTransportObjectGeneratorConfiguration constructor. * * @param array $configuration */ public function __construct(array $configuration) { - $this->path = $configuration['Path']; $this->namespace = $configuration['Namespace']; - $this->classSuffix = $configuration['ClassSuffix']; - $this->parentClassPrefix = $configuration['ParentClassPrefix']; + $this->classSuffix = $configuration['ClassSuffix'] ?? ''; + $this->parentClassPrefix = $configuration['ParentClassPrefix'] ?? ''; + $this->accessorTraitNamespace = $configuration['AccessorTraitNamespace'] ?? ''; + $this->accessorTraitPath = $configuration['AccessorTraitPath'] ?? ''; } /** @@ -72,4 +83,20 @@ public function getPath(): string { return $this->path; } + + /** + * @return ?string + */ + public function getAccessorTraitNamespace(): ?string + { + return $this->accessorTraitNamespace; + } + + /** + * @return mixed|string + */ + public function getAccessorTraitPath(): mixed + { + return $this->accessorTraitPath; + } } diff --git a/src/Configuration/RelieseConfiguration.php b/src/Configuration/RelieseConfiguration.php index 669af714..e673f465 100644 --- a/src/Configuration/RelieseConfiguration.php +++ b/src/Configuration/RelieseConfiguration.php @@ -13,9 +13,9 @@ class RelieseConfiguration protected ModelDataMapGeneratorConfiguration $modelDataMapGeneratorConfiguration; /** - * @var DataTransportGeneratorConfiguration + * @var DataTransportObjectGeneratorConfiguration */ - protected DataTransportGeneratorConfiguration $dataTransportGeneratorConfiguration; + protected DataTransportObjectGeneratorConfiguration $dataTransportGeneratorConfiguration; /** * @var DatabaseAnalyserConfiguration @@ -37,29 +37,45 @@ class RelieseConfiguration */ private string $configurationProfileName; + /** + * @var DataAccessGeneratorConfiguration + */ + private DataAccessGeneratorConfiguration $dataAccessGeneratorConfiguration; + + /** + * @var DataAttributeGeneratorConfiguration + */ + private DataAttributeGeneratorConfiguration $dataAttributeGeneratorConfiguration; + /** * RelieseConfiguration constructor. * * @param string $configurationProfileName * @param ModelDataMapGeneratorConfiguration $modelDataMapGeneratorConfiguration - * @param DataTransportGeneratorConfiguration $dataTransportGeneratorConfiguration + * @param DataAccessGeneratorConfiguration $dataAccessGeneratorConfiguration + * @param DataTransportObjectGeneratorConfiguration $dataTransportGeneratorConfiguration * @param DatabaseAnalyserConfiguration $databaseAnalyserConfiguration + * @param DataAttributeGeneratorConfiguration $dataAttributeGeneratorConfiguration * @param DatabaseBlueprintConfiguration $databaseBlueprintConfiguration * @param ModelGeneratorConfiguration $modelGeneratorConfiguration */ public function __construct(string $configurationProfileName, ModelDataMapGeneratorConfiguration $modelDataMapGeneratorConfiguration, - DataTransportGeneratorConfiguration $dataTransportGeneratorConfiguration, + DataAccessGeneratorConfiguration $dataAccessGeneratorConfiguration, + DataTransportObjectGeneratorConfiguration $dataTransportGeneratorConfiguration, DatabaseAnalyserConfiguration $databaseAnalyserConfiguration, + DataAttributeGeneratorConfiguration $dataAttributeGeneratorConfiguration, DatabaseBlueprintConfiguration $databaseBlueprintConfiguration, ModelGeneratorConfiguration $modelGeneratorConfiguration,) { $this->configurationProfileName = $configurationProfileName; $this->modelDataMapGeneratorConfiguration = $modelDataMapGeneratorConfiguration; + $this->dataAccessGeneratorConfiguration = $dataAccessGeneratorConfiguration; $this->dataTransportGeneratorConfiguration = $dataTransportGeneratorConfiguration; $this->databaseAnalyserConfiguration = $databaseAnalyserConfiguration; $this->databaseBlueprintConfiguration = $databaseBlueprintConfiguration; $this->modelGeneratorConfiguration = $modelGeneratorConfiguration; + $this->dataAttributeGeneratorConfiguration = $dataAttributeGeneratorConfiguration; } /** @@ -70,6 +86,19 @@ public function getConfigurationProfileName(): string return $this->configurationProfileName; } + public function getDataAccessGeneratorConfiguration() + { + return $this->dataAccessGeneratorConfiguration; + } + + /** + * @return DataAttributeGeneratorConfiguration + */ + public function getDataAttributeGeneratorConfiguration(): DataAttributeGeneratorConfiguration + { + return $this->dataAttributeGeneratorConfiguration; + } + /** * @return ModelDataMapGeneratorConfiguration */ @@ -79,9 +108,9 @@ public function getModelDataMapGeneratorConfiguration(): ModelDataMapGeneratorCo } /** - * @return DataTransportGeneratorConfiguration + * @return DataTransportObjectGeneratorConfiguration */ - public function getDataTransportGeneratorConfiguration(): DataTransportGeneratorConfiguration + public function getDataTransportGeneratorConfiguration(): DataTransportObjectGeneratorConfiguration { return $this->dataTransportGeneratorConfiguration; } diff --git a/src/Configuration/RelieseConfigurationFactory.php b/src/Configuration/RelieseConfigurationFactory.php index b007d531..3323d040 100644 --- a/src/Configuration/RelieseConfigurationFactory.php +++ b/src/Configuration/RelieseConfigurationFactory.php @@ -2,7 +2,6 @@ namespace Reliese\Configuration; -use Illuminate\Support\Facades\Log; use InvalidArgumentException; use Reliese\PackagePaths; use function array_key_exists; @@ -54,6 +53,11 @@ public function __construct( $this->relieseConfigurationProfiles = $relieseConfigurationProfiles ?? include(PackagePaths::getExampleConfigFilePath()); } + /** + * @param string $configurationProfileName + * + * @return RelieseConfiguration + */ public function getRelieseConfiguration(string $configurationProfileName): RelieseConfiguration { // TODO: figure out how to make logging work w/ tests as well @@ -67,36 +71,54 @@ public function getRelieseConfiguration(string $configurationProfileName): Relie return new RelieseConfiguration( $configurationProfileName, - $this->getDataMapGeneratorConfiguration($configurationProfile), - $this->getDataTransportGeneratorConfiguration($configurationProfile), + $this->getModelDataMapGeneratorConfiguration($configurationProfile), + $this->getDataAccessGeneratorConfiguration($configurationProfile), + $this->getDataTransportObjectGeneratorConfiguration($configurationProfile), $this->getDatabaseAnalyserConfiguration($configurationProfile), + $this->getDataAttributeGeneratorConfiguration($configurationProfile), $this->getDatabaseBlueprintConfiguration($configurationProfile), $this->getModelGeneratorConfiguration($configurationProfile) ); } - protected function getDataMapGeneratorConfiguration(array $configurationProfile): ModelDataMapGeneratorConfiguration + /** + * @param array $configurationProfile + * + * @return DataAttributeGeneratorConfiguration + */ + protected function getDataAttributeGeneratorConfiguration(array $configurationProfile): DataAttributeGeneratorConfiguration { - $key = ModelDataMapGeneratorConfiguration::class; + $key = DataAttributeGeneratorConfiguration::class; if (!array_key_exists($key, $configurationProfile)) { throw new InvalidArgumentException("Unable to locate configuration block for \"{$key}\""); } - return new ModelDataMapGeneratorConfiguration($configurationProfile[$key]); + return new DataAttributeGeneratorConfiguration($configurationProfile[$key]); } - protected function getDataTransportGeneratorConfiguration(array $configurationProfile): DataTransportGeneratorConfiguration + /** + * @param array $configurationProfile + * + * @return DataTransportObjectGeneratorConfiguration + */ + protected function getDataTransportObjectGeneratorConfiguration(array $configurationProfile): DataTransportObjectGeneratorConfiguration { - $key = DataTransportGeneratorConfiguration::class; + + $key = DataTransportObjectGeneratorConfiguration::class; if (!array_key_exists($key, $configurationProfile)) { throw new InvalidArgumentException("Unable to locate configuration block for \"{$key}\""); } - return new DataTransportGeneratorConfiguration($configurationProfile[$key]); + return new DataTransportObjectGeneratorConfiguration($configurationProfile[$key]); } + /** + * @param array $configurationProfile + * + * @return DatabaseAnalyserConfiguration + */ protected function getDatabaseAnalyserConfiguration(array $configurationProfile): DatabaseAnalyserConfiguration { $key = DatabaseAnalyserConfiguration::class; @@ -108,6 +130,11 @@ protected function getDatabaseAnalyserConfiguration(array $configurationProfile) return new DatabaseAnalyserConfiguration($configurationProfile[$key]); } + /** + * @param array $configurationProfile + * + * @return DatabaseBlueprintConfiguration + */ protected function getDatabaseBlueprintConfiguration(array $configurationProfile): DatabaseBlueprintConfiguration { $key = DatabaseBlueprintConfiguration::class; @@ -119,6 +146,26 @@ protected function getDatabaseBlueprintConfiguration(array $configurationProfile return new DatabaseBlueprintConfiguration($configurationProfile[$key]); } + /** + * @param array $configurationProfile + * + * @return ModelDataMapGeneratorConfiguration + */ + protected function getModelDataMapGeneratorConfiguration(array $configurationProfile): ModelDataMapGeneratorConfiguration + { + $key = ModelDataMapGeneratorConfiguration::class; + if (!array_key_exists($key, $configurationProfile)) { + throw new InvalidArgumentException("Unable to locate configuration block for \"$key\""); + } + + return new ModelDataMapGeneratorConfiguration($configurationProfile[$key]); + } + + /** + * @param array $configurationProfile + * + * @return ModelGeneratorConfiguration + */ protected function getModelGeneratorConfiguration(array $configurationProfile): ModelGeneratorConfiguration { $key = ModelGeneratorConfiguration::class; @@ -129,4 +176,19 @@ protected function getModelGeneratorConfiguration(array $configurationProfile): return new ModelGeneratorConfiguration($configurationProfile[$key]); } + + /** + * @param mixed $configurationProfile + * + * @return DataAccessGeneratorConfiguration + */ + private function getDataAccessGeneratorConfiguration(mixed $configurationProfile): DataAccessGeneratorConfiguration + { + $key = DataAccessGeneratorConfiguration::class; + if (!array_key_exists($key, $configurationProfile)) { + throw new InvalidArgumentException("Unable to locate configuration block for \"$key\""); + } + + return new DataAccessGeneratorConfiguration($configurationProfile[$key]); + } } diff --git a/src/Generator/DataAccess/DataAccessGenerator.php b/src/Generator/DataAccess/DataAccessGenerator.php new file mode 100644 index 00000000..19cf2d90 --- /dev/null +++ b/src/Generator/DataAccess/DataAccessGenerator.php @@ -0,0 +1,783 @@ +dataAccessGeneratorConfiguration = $relieseConfiguration->getDataAccessGeneratorConfiguration(); + /* + * TODO: inject a MySql / Postgress or other DataType mapping as needed + */ + $this->dataTypeMap = new MySqlDataTypeMap(); + $this->modelGenerator = new ModelGenerator($relieseConfiguration); + $this->dataTransportObjectGenerator = new DataTransportObjectGenerator($relieseConfiguration); + $this->modelDataMapGenerator = new ModelDataMapGenerator($relieseConfiguration); + } + + /** + * @param TableBlueprint $tableBlueprint + */ + public function fromTableBlueprint(TableBlueprint $tableBlueprint) + { + $classDefinition = $this->generateClassDefinition($tableBlueprint); + $abstractClassDefinition = $this->generateAbstractClassDefinition($tableBlueprint); + + /* + * TODO: Add generic methods like "get by id" + */ + + /* + * Write the Class Files + */ + $this->writeClassFiles($classDefinition, $abstractClassDefinition); + } + + public function generateClassDefinition(TableBlueprint $tableBlueprint): ClassDefinition + { + $abstractClassDefinition = $this->generateAbstractClassDefinition($tableBlueprint); + + $className = $this->getClassName($tableBlueprint); + $namespace = $this->getClassNamespace($tableBlueprint); + $classDefinition = new ClassDefinition($className, $namespace); + $classDefinition->setParentClass($abstractClassDefinition->getFullyQualifiedName()); + + return $classDefinition; + } + + public function generateAbstractClassDefinition(TableBlueprint $tableBlueprint): ClassDefinition + { + $abstractClassName = $this->getAbstractClassName($tableBlueprint); + $abstractNamespace = $this->getAbstractClassNamespace($tableBlueprint); + $abstractClassDefinition = new ClassDefinition($abstractClassName, + $abstractNamespace, + AbstractEnum::abstractEnum()); + + $modelObjectTypeDefinition + = new ObjectTypeDefinition($this->modelGenerator->getFullyQualifiedClassName($tableBlueprint)); + + $abstractClassDefinition + # include the WithDataMap trait + ->addTrait(new ClassTraitDefinition($this->modelDataMapGenerator->generateModelDataMapAccessorTrait($this->modelDataMapGenerator->getFullyQualifiedClassName($tableBlueprint)) + ->getFullyQualifiedName())) + ->addConstant(new ClassConstantDefinition($this->getMapFromFailedConstantName($modelObjectTypeDefinition), + $this->getMapFromFailedConstantName($modelObjectTypeDefinition), + VisibilityEnum::protectedEnum()),) + ->addConstant(new ClassConstantDefinition($this->getMapToFailedConstantName($modelObjectTypeDefinition), + $this->getMapToFailedConstantName($modelObjectTypeDefinition), + VisibilityEnum::protectedEnum())) + ; + + $this->addFetchByUniqueColumnMethods($tableBlueprint, $abstractClassDefinition); + + $abstractClassDefinition->addMethodDefinition($this->generateCreateMethod($tableBlueprint, + $abstractClassDefinition)); + + $abstractClassDefinition->addMethodDefinition($this->generateUpdateMethod($tableBlueprint, + $abstractClassDefinition)); + + return $abstractClassDefinition; + } + + /** + * @param TableBlueprint $tableBlueprint + * + * @return string + */ + public function getFullyQualifiedClassName(TableBlueprint $tableBlueprint): string + { + return $this->getClassNamespace($tableBlueprint) . '\\' . $this->getClassName($tableBlueprint); + } + + public function getClassNamespace(TableBlueprint $tableBlueprint): string + { + return $this->dataAccessGeneratorConfiguration->getNamespace(); + } + + public function getClassName(TableBlueprint $tableBlueprint): string + { + return ClassNameTool::snakeCaseToClassName($this->dataAccessGeneratorConfiguration->getClassPrefix(), + $tableBlueprint->getName(), + $this->dataAccessGeneratorConfiguration->getClassSuffix()); + } + + public function getFetchByUniqueColumnFunctionName(ColumnBlueprint $uniqueColumn): string + { + return "fetchBy" . Str::studly($uniqueColumn->getColumnName()); + } + + private function generateCreateMethod(TableBlueprint $tableBlueprint, + ClassDefinition $classDefinition): ClassMethodDefinition + { + $modelObjectTypeDefinition + = new ObjectTypeDefinition($this->modelGenerator->getFullyQualifiedClassName($tableBlueprint)); + $mySqlErrorTypesObjectTypeDefinition = new ObjectTypeDefinition('\app\DataStores\MySql\MySqlErrorTypes'); + $queryExceptionTypeDefinition = new ObjectTypeDefinition('\Illuminate\Database\QueryException'); + $logMessageObjectTypeDefinition = new ObjectTypeDefinition('\app\Patterns\Log\LogMessage'); + $logExceptionObjectTypeDefinition = new ObjectTypeDefinition('\app\Patterns\Log\LogException'); + $returnTypeObjectTypeDefinition + = new ObjectTypeDefinition('\app\Patterns\MethodResponses\CreateMethodResponse'); + $dtoTypeObjectTypeDefinition + = new ObjectTypeDefinition($this->dataTransportObjectGenerator->getFullyQualifiedClassName($tableBlueprint)); + $modelTypeObjectTypeDefinition + = new ObjectTypeDefinition($this->modelGenerator->getFullyQualifiedClassName($tableBlueprint)); + $modelDataMapTraitObjectTypeDefinition + = new ObjectTypeDefinition($this->modelDataMapGenerator->generateModelDataMapAccessorTrait($this->modelDataMapGenerator->getFullyQualifiedClassName($tableBlueprint)) + ->getFullyQualifiedName()); + $classDefinition->addImport($modelObjectTypeDefinition) + ->addImport($mySqlErrorTypesObjectTypeDefinition) + ->addImport($logMessageObjectTypeDefinition) + ->addImport($logExceptionObjectTypeDefinition) + ->addImport($returnTypeObjectTypeDefinition) + ->addImport($dtoTypeObjectTypeDefinition) + ->addImport($modelTypeObjectTypeDefinition) + ->addImport($modelDataMapTraitObjectTypeDefinition) + ; + + $functionName = "create"; + $returnType = PhpTypeEnum::objectOfType($returnTypeObjectTypeDefinition->getFullyQualifiedName()); + $modelVariableName = $this->modelGenerator->getClassAsVariableName($tableBlueprint); + + $methodFailedConstantName = sprintf('%s_CREATE_FAILED', + Str::upper($modelTypeObjectTypeDefinition->getImportableName())); + $classDefinition->addConstant(new ClassConstantDefinition($methodFailedConstantName, + $methodFailedConstantName, + VisibilityEnum::protectedEnum())); + $dtoParameterDefinition = $this->getDtoFunctionParameterDefinition($dtoTypeObjectTypeDefinition); + $exceptionVariableName = 'exception'; + + /* + * Build Unique Constraint Error Handling Conditions + */ + $catchBlockStatements = new StatementDefinitionCollection(); + $catchBlockStatements->addStatementDefinition((new CommentBlockStatementDefinition())->addLine("Treat unique key errors as validation failures")) + ->addStatementDefinition((new StatementBlockDefinition(new RawStatementDefinition(sprintf("if (!empty(\$%s->errorInfo[1]) && %s::UNIQUE_KEY_VIOLATION_TYPE_ID === \$%s->errorInfo[1])", + $exceptionVariableName, + $mySqlErrorTypesObjectTypeDefinition->getImportableName(), + $exceptionVariableName))))->addStatementDefinition($foreachBlock + = (new StatementBlockDefinition(new RawStatementDefinition(sprintf("foreach (\$%s->errorInfo as \$value)", + $exceptionVariableName,)))))) + ; + + foreach ($tableBlueprint->getUniqueIndexes(false) as $uniqueIndexBlueprint) { + $validationMessageMethod = $this->getUniqueKeyViolationValidationMessageMethod($classDefinition, + $uniqueIndexBlueprint); + + $classDefinition->addMethodDefinition($validationMessageMethod); + + $foreachBlock->addStatementDefinition((new StatementBlockDefinition(new RawStatementDefinition(sprintf("if (str_contains(\$value, '%s'))", + $uniqueIndexBlueprint->getName()))))->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::invalid([\$this->%s()]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $validationMessageMethod->getFunctionName())))); + } + + $catchBlockStatements->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::error([new %s(static::%s), new %s(\$%s),]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $logMessageObjectTypeDefinition->getImportableName(), + $methodFailedConstantName, + $logExceptionObjectTypeDefinition->getImportableName(), + $exceptionVariableName))); + /* + * Build Class Method definition + */ + $classMethodDefinition = new ClassMethodDefinition($functionName, $returnType, [$dtoParameterDefinition]); + $classMethodDefinition->appendBodyStatement(// new model statement + new RawStatementDefinition(sprintf("\$%s = new %s();", + $modelVariableName, + $modelTypeObjectTypeDefinition->getImportableName()))) + ->appendBodyStatement((new StatementBlockDefinition(new RawStatementDefinition(sprintf("if (!\$this->%s()->from%s(\$%s, \$%s))", + $this->modelDataMapGenerator->getModelMapAccessorTraitMethodName($tableBlueprint), + $dtoTypeObjectTypeDefinition->getImportableName(), + $modelVariableName, + $dtoParameterDefinition->getParameterName()))))->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::error([new %s(static::%s), new %s(static::%s)]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $logMessageObjectTypeDefinition->getImportableName(), + $this->getMapFromFailedConstantName($modelTypeObjectTypeDefinition), + $logMessageObjectTypeDefinition->getImportableName(), + $methodFailedConstantName)))) + // try save block + ->appendBodyStatement( + $this->getSaveModelTryBlock($modelVariableName, + $returnTypeObjectTypeDefinition, + $logMessageObjectTypeDefinition, + $methodFailedConstantName, + $queryExceptionTypeDefinition, + $exceptionVariableName, + $catchBlockStatements)) + // after try block, map model to dto + ->appendBodyStatement($this->getMapToDtoStatementBlock($tableBlueprint, + $dtoTypeObjectTypeDefinition, + $modelVariableName, + $dtoParameterDefinition, + $this->getMapToFailedConstantName($modelObjectTypeDefinition), + $returnTypeObjectTypeDefinition)) + # return created + ->appendBodyStatement(new RawStatementDefinition(sprintf("return %s::created();", + $returnTypeObjectTypeDefinition->getImportableName()))) + ; + + return $classMethodDefinition; + } + + private function generateUpdateMethod(TableBlueprint $tableBlueprint, + ClassDefinition $classDefinition): ClassMethodDefinition + { + $modelObjectTypeDefinition + = new ObjectTypeDefinition($this->modelGenerator->getFullyQualifiedClassName($tableBlueprint)); + $mySqlErrorTypesObjectTypeDefinition = new ObjectTypeDefinition('\app\DataStores\MySql\MySqlErrorTypes'); + $queryExceptionTypeDefinition = new ObjectTypeDefinition('\Illuminate\Database\QueryException'); + $logMessageObjectTypeDefinition = new ObjectTypeDefinition('\app\Patterns\Log\LogMessage'); + $logExceptionObjectTypeDefinition = new ObjectTypeDefinition('\app\Patterns\Log\LogException'); + $returnTypeObjectTypeDefinition + = new ObjectTypeDefinition('\app\Patterns\MethodResponses\UpdateMethodResponse'); + $dtoTypeObjectTypeDefinition + = new ObjectTypeDefinition($this->dataTransportObjectGenerator->getFullyQualifiedClassName($tableBlueprint)); + $modelTypeObjectTypeDefinition + = new ObjectTypeDefinition($this->modelGenerator->getFullyQualifiedClassName($tableBlueprint)); + $modelDataMapTraitObjectTypeDefinition + = new ObjectTypeDefinition($this->modelDataMapGenerator->generateModelDataMapAccessorTrait($this->modelDataMapGenerator->getFullyQualifiedClassName($tableBlueprint)) + ->getFullyQualifiedName()); + $classDefinition->addImport($modelObjectTypeDefinition) + ->addImport($mySqlErrorTypesObjectTypeDefinition) + ->addImport($logMessageObjectTypeDefinition) + ->addImport($logExceptionObjectTypeDefinition) + ->addImport($returnTypeObjectTypeDefinition) + ->addImport($dtoTypeObjectTypeDefinition) + ->addImport($modelTypeObjectTypeDefinition) + ->addImport($modelDataMapTraitObjectTypeDefinition) + ; + + $functionName = "update"; + $returnType = PhpTypeEnum::objectOfType($returnTypeObjectTypeDefinition->getFullyQualifiedName()); + $modelVariableName = $this->modelGenerator->getClassAsVariableName($tableBlueprint); + + $methodFailedConstantName = sprintf('%s_UPDATE_FAILED', + Str::upper($modelTypeObjectTypeDefinition->getImportableName())); + $classDefinition->addConstant(new ClassConstantDefinition($methodFailedConstantName, + $methodFailedConstantName, + VisibilityEnum::protectedEnum())); + $dtoParameterDefinition = $this->getDtoFunctionParameterDefinition($dtoTypeObjectTypeDefinition); + $exceptionVariableName = 'exception'; + + /* + * Build Unique Constraint Error Handling Conditions + */ + $catchBlockStatements = new StatementDefinitionCollection(); + $catchBlockStatements->addStatementDefinition((new CommentBlockStatementDefinition())->addLine("Treat unique key errors as validation failures")) + ->addStatementDefinition((new StatementBlockDefinition(new RawStatementDefinition(sprintf("if (!empty(\$%s->errorInfo[1]) && %s::UNIQUE_KEY_VIOLATION_TYPE_ID === \$%s->errorInfo[1])", + $exceptionVariableName, + $mySqlErrorTypesObjectTypeDefinition->getImportableName(), + $exceptionVariableName))))->addStatementDefinition($foreachBlock + = (new StatementBlockDefinition(new RawStatementDefinition(sprintf("foreach (\$%s->errorInfo as \$value)", + $exceptionVariableName,)))))) + ; + + foreach ($tableBlueprint->getUniqueIndexes(false) as $uniqueIndexBlueprint) { + $validationMessageMethod = $this->getUniqueKeyViolationValidationMessageMethod($classDefinition, + $uniqueIndexBlueprint); + + $classDefinition->addMethodDefinition($validationMessageMethod); + + $foreachBlock->addStatementDefinition((new StatementBlockDefinition(new RawStatementDefinition(sprintf("if (str_contains(\$value, '%s'))", + $uniqueIndexBlueprint->getName()))))->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::invalid([\$this->%s()]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $validationMessageMethod->getFunctionName())))); + } + + $catchBlockStatements->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::error([new %s(static::%s), new %s(\$%s),]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $logMessageObjectTypeDefinition->getImportableName(), + $methodFailedConstantName, + $logExceptionObjectTypeDefinition->getImportableName(), + $exceptionVariableName))); + /* + * Define Find Model by Dto Id try block + */ + $findModelTryBlock = new TryBlockDefinition(); + $findModelTryBlock->addStatementDefinition(// find model statement + new RawStatementDefinition(sprintf("\$%s = %s::find(\$%s->getId());", + $modelVariableName, + $modelTypeObjectTypeDefinition->getImportableName(), + $dtoParameterDefinition->getParameterName()))) + ->addCatchStatements(PhpTypeEnum::objectOfType(\Exception::class), + $exceptionVariableName, + (new StatementDefinitionCollection())->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::error([new LogMessage(static::%s), new LogException(\$%s),]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $methodFailedConstantName, + $exceptionVariableName)))) + ; + /* + * Define if model->save() Try Block + */ + $modelSaveTryBlock = new TryBlockDefinition(); + $modelSaveTryBlock + ->addStatementDefinition( + (new StatementBlockDefinition( + new RawStatementDefinition( + sprintf( + "if (!\$%s->save())", + $modelVariableName)))) + ->addStatementDefinition( + new RawStatementDefinition( + sprintf( + "return %s::error([new %s(static::%s)]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $logMessageObjectTypeDefinition->getImportableName(), + $methodFailedConstantName + ))) + ); + // if (!$model->save()) + /* + * Build Class Method definition + */ + $classMethodDefinition = new ClassMethodDefinition($functionName, $returnType, [$dtoParameterDefinition]); + $classMethodDefinition + // Add try find model + ->appendBodyStatement($findModelTryBlock) + // Add if model not found + ->appendBodyStatement($this->getModelNotFoundStatementBlock($modelVariableName, + $returnTypeObjectTypeDefinition)) + // Add map DTO to Model block + ->appendBodyStatement($this->getMapFromDtoStatementBlock($tableBlueprint, + $dtoTypeObjectTypeDefinition, + $modelVariableName, + $dtoParameterDefinition, + $returnTypeObjectTypeDefinition, + $logMessageObjectTypeDefinition, + $modelTypeObjectTypeDefinition, + $methodFailedConstantName)) + // try save block + ->appendBodyStatement( + $this->getSaveModelTryBlock($modelVariableName, + $returnTypeObjectTypeDefinition, + $logMessageObjectTypeDefinition, + $methodFailedConstantName, + $queryExceptionTypeDefinition, + $exceptionVariableName, + $catchBlockStatements)) + // after try block, map model to dto + ->appendBodyStatement($this->getMapToDtoStatementBlock($tableBlueprint, + $dtoTypeObjectTypeDefinition, + $modelVariableName, + $dtoParameterDefinition, + $this->getMapToFailedConstantName($modelObjectTypeDefinition), + $returnTypeObjectTypeDefinition)) + # return updated + ->appendBodyStatement(new RawStatementDefinition(sprintf("return %s::updated();", + $returnTypeObjectTypeDefinition->getImportableName()))) + ; + + return $classMethodDefinition; + } + + /** + * Calls \Reliese\Generator\DataAccess\DataAccessGenerator::generateFetchByUniqueColumnMethodDefinition foreach + * unique column + * + * @param TableBlueprint $tableBlueprint + * @param ClassDefinition $abstractClassDefinition + * + * @return array + */ + private function addFetchByUniqueColumnMethods(TableBlueprint $tableBlueprint, + ClassDefinition $abstractClassDefinition) + { + $uniqueColumnGroups = $tableBlueprint->getUniqueColumnGroups(); + + foreach ($uniqueColumnGroups as $uniqueColumnGroup) { + if (1 < count($uniqueColumnGroup)) { + continue; + } + + $uniqueColumn = array_pop($uniqueColumnGroup); + + $abstractClassDefinition->addMethodDefinition($this->generateFetchByUniqueColumnMethodDefinition($abstractClassDefinition, + $tableBlueprint, + $uniqueColumn)); + } + } + + /** + * Example Output + * + * public function fetchByExternalKey(AccountDto $accountDto): FetchMethodResponse + * { + * try { + * $account = Account::where(Account::EXTERNAL_KEY, $accountDto->getExternalKey())->first(); + * } catch (\Exception $exception) { + * return FetchMethodResponse::error([new LogMessage(static::ACCOUNT_FETCH_BY_EXTERNAL_KEY_FAILED), new + * LogException($exception),]); + * } + * if (!$account) { + * return FetchMethodResponse::notFound(); + * } + * if (!$this->getAccountMap()->toAccountDto($account, $accountDto)) { + * return FetchMethodResponse::error([new LogMessage(self::ACCOUNT_MAP_TO_DTO_FAILED)]); + * } + * return FetchMethodResponse::found(); + * } + * + * + * @param ClassDefinition $classDefinition + * @param TableBlueprint $tableBlueprint + * @param ColumnBlueprint $uniqueColumn + * + * @return ClassMethodDefinition + */ + private function generateFetchByUniqueColumnMethodDefinition(ClassDefinition $classDefinition, + TableBlueprint $tableBlueprint, + ColumnBlueprint $uniqueColumn): ClassMethodDefinition + { + $logMessageObjectTypeDefinition = new ObjectTypeDefinition('\app\Patterns\Log\LogMessage'); + $logExceptionObjectTypeDefinition = new ObjectTypeDefinition('\app\Patterns\Log\LogException'); + $returnTypeObjectTypeDefinition = new ObjectTypeDefinition('\app\Patterns\MethodResponses\FetchMethodResponse'); + $dtoTypeObjectTypeDefinition + = new ObjectTypeDefinition($this->dataTransportObjectGenerator->getFullyQualifiedClassName($tableBlueprint)); + $modelObjectTypeDefinition + = new ObjectTypeDefinition($this->modelGenerator->getFullyQualifiedClassName($tableBlueprint)); + $modelDataMapTraitObjectTypeDefinition + = new ObjectTypeDefinition($this->modelDataMapGenerator->generateModelDataMapAccessorTrait($this->modelDataMapGenerator->getFullyQualifiedClassName($tableBlueprint)) + ->getFullyQualifiedName()); + $classDefinition->addImport($logMessageObjectTypeDefinition) + ->addImport($logExceptionObjectTypeDefinition) + ->addImport($returnTypeObjectTypeDefinition) + ->addImport($dtoTypeObjectTypeDefinition) + ->addImport($modelObjectTypeDefinition) + ->addImport($modelDataMapTraitObjectTypeDefinition) + ; + + $columnCamelName = Str::studly($uniqueColumn->getColumnName()); + + $modelVariableName = $this->modelGenerator->getClassAsVariableName($tableBlueprint); + + $dtoParameterDefinition = $this->getDtoFunctionParameterDefinition($dtoTypeObjectTypeDefinition); + + $classMethodDefinition = new ClassMethodDefinition($this->getFetchByUniqueColumnFunctionName($uniqueColumn), + PhpTypeEnum::objectOfType($returnTypeObjectTypeDefinition->getFullyQualifiedName()), + [$dtoParameterDefinition]); + + $methodFailedConstantName = sprintf('%s_FETCH_BY_%s_FAILED', + Str::upper($modelObjectTypeDefinition->getImportableName()), + Str::upper(Str::snake($columnCamelName))); + + $classDefinition + # include _FETCH_BY__FAILED constant + ->addConstant(new ClassConstantDefinition($methodFailedConstantName, + $methodFailedConstantName, + VisibilityEnum::protectedEnum())); + + $exceptionVariableName = 'exception'; + + $columnConstantDefinition = $this->modelGenerator->generateColumnConstantDefinition($uniqueColumn); + $classMethodDefinition->appendBodyStatement((new TryBlockDefinition())->addStatementDefinition(# include the Eloquent query to fetch the model by the unique column value + new RawStatementDefinition(sprintf("\$%s = %s::where(%s::%s, \$%s->get%s())->first();", + $modelVariableName, + $modelObjectTypeDefinition->getImportableName(), + $modelObjectTypeDefinition->getImportableName(), + $columnConstantDefinition->getName(), + $dtoParameterDefinition->getParameterName(), + $columnCamelName))) + ->addCatchStatements(PhpTypeEnum::objectOfType(\Exception::class), + $exceptionVariableName, + (new StatementDefinitionCollection())->addStatementDefinition(new RawStatementDefinition(sprintf("return FetchMethodResponse::error([new LogMessage(static::%s), new LogException(\$%s),]);", + $methodFailedConstantName, + $exceptionVariableName))))) + # include the conditional check to determine if it was found + ->appendBodyStatement($this->getModelNotFoundStatementBlock($modelVariableName, + $returnTypeObjectTypeDefinition)) + # include the conditional check to determine if it was mapped successfully + ->appendBodyStatement($this->getMapToDtoStatementBlock($tableBlueprint, + $dtoTypeObjectTypeDefinition, + $modelVariableName, + $dtoParameterDefinition, + $this->getMapToFailedConstantName($modelObjectTypeDefinition), + $returnTypeObjectTypeDefinition)) + ->appendBodyStatement(new RawStatementDefinition("return FetchMethodResponse::found();")) + ; + + return $classMethodDefinition; + } + + private function getAbstractClassName(TableBlueprint $tableBlueprint): string + { + return $this->dataAccessGeneratorConfiguration->getParentClassPrefix() . $this->getClassName($tableBlueprint); + } + + private function getAbstractClassNamespace(TableBlueprint $tableBlueprint): string + { + return $this->getClassNamespace($tableBlueprint) . '\\Generated'; + } + + /** + * @param ClassDefinition $classDefinition + * @param ClassDefinition $abstractClassDefinition + */ + private function writeClassFiles(ClassDefinition $classDefinition, + ClassDefinition $abstractClassDefinition,): void + { + $classFormatter = new ClassFormatter(); + + $dtoClassPhpCode = $classFormatter->format($classDefinition); + $abstractDtoPhpCode = $classFormatter->format($abstractClassDefinition); + // echo "\n---Class---\n$dtoClassPhpCode\n\n\n---Base Class---\n$abstractDtoPhpCode\n\n"; + + $dtoClassFolder = $this->dataAccessGeneratorConfiguration->getPath(); + $abstractDtoClassFolder = $dtoClassFolder . DIRECTORY_SEPARATOR . 'Generated'; + if (!is_dir($dtoClassFolder)) { + \mkdir($dtoClassFolder, 0755, true); + } + if (!is_dir($abstractDtoClassFolder)) { + \mkdir($abstractDtoClassFolder, 0755, true); + } + + $dtoFilePath = $dtoClassFolder . DIRECTORY_SEPARATOR . $classDefinition->getName() . '.php'; + $abstractDtoFilePath = $abstractDtoClassFolder + . DIRECTORY_SEPARATOR + . $abstractClassDefinition->getName() + . '.php'; + + if (!\file_exists($dtoFilePath)) { + \file_put_contents($dtoFilePath, $dtoClassPhpCode); + } + \file_put_contents($abstractDtoFilePath, $abstractDtoPhpCode); + } + + private function getMapToFailedConstantName(ObjectTypeDefinition $modelObjectTypeDefinition) + { + return sprintf('%s_MAP_TO_DTO_FAILED', + Str::upper($modelObjectTypeDefinition->getImportableName())); + } + + private function getMapFromFailedConstantName(ObjectTypeDefinition $modelObjectTypeDefinition) + { + return sprintf('%s_MAP_FROM_DTO_FAILED', + Str::upper($modelObjectTypeDefinition->getImportableName())); + } + + private function getDtoFunctionParameterDefinition(ObjectTypeDefinition $dtoTypeObjectTypeDefinition): FunctionParameterDefinition + { + return new FunctionParameterDefinition($this->getDtoVariableName($dtoTypeObjectTypeDefinition), + PhpTypeEnum::objectOfType($dtoTypeObjectTypeDefinition->getFullyQualifiedName())); + } + + private function getDtoVariableName(ObjectTypeDefinition $dtoTypeObjectTypeDefinition): string + { + $name = $dtoTypeObjectTypeDefinition->getImportableName(); + return strtolower($name[0]) . substr($name, 1); + } + + private function getUniqueKeyViolationValidationMessageMethod(ClassDefinition $classDefinition, + IndexBlueprint $indexBlueprint): ClassMethodDefinition + { + $returnType = new ObjectTypeDefinition(\Symfony\Component\Translation\TranslatableMessage::class); + $classDefinition->addImport($returnType); + + $functionName = sprintf("getUniqueKeyValidationMessageFor%s", + Str::studly($indexBlueprint->getName())); + + return new ClassMethodDefinition($functionName, + PhpTypeEnum::objectOfType($returnType->getFullyQualifiedName()), + [], + VisibilityEnum::protectedEnum(), + InstanceEnum::instanceEnum(), + AbstractEnum::abstractEnum()); + } + + /** + * @param TableBlueprint $tableBlueprint + * @param ObjectTypeDefinition $dtoTypeObjectTypeDefinition + * @param string $modelVariableName + * @param FunctionParameterDefinition $dtoParameterDefinition + * @param string $mapToFailedConstantName + * @param ObjectTypeDefinition $returnObjectTypeDefinition + * + * @return StatementBlockDefinition + */ + private function getMapToDtoStatementBlock(TableBlueprint $tableBlueprint, + ObjectTypeDefinition $dtoTypeObjectTypeDefinition, + string $modelVariableName, + FunctionParameterDefinition $dtoParameterDefinition, + string $mapToFailedConstantName, + ObjectTypeDefinition $returnObjectTypeDefinition): StatementBlockDefinition + { + return (new StatementBlockDefinition(new RawStatementDefinition(sprintf(// "if (!$this->accountMap->toAccountDto($accountModel, $accountDto))" + "if (!\$this->%s()->to%s(\$%s, \$%s))", + $this->modelDataMapGenerator->getModelMapAccessorTraitMethodName($tableBlueprint), + $dtoTypeObjectTypeDefinition->getImportableName(), + $modelVariableName, + $dtoParameterDefinition->getParameterName()))))->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::error([new LogMessage(self::%s)]);", + $returnObjectTypeDefinition->getImportableName(), + $mapToFailedConstantName))); + } + + /** + * Example statement block output + * + * if (!$this->getPersonMap()->fromPersonDto($person, $personDto)) { + * return UpdateMethodResponse::error( + * [new LogMessage(static::PERSON_MAP_FROM_DTO_FAILED), new LogMessage(static::PERSON_UPDATE_FAILED)]); + * } + * + * + * @param TableBlueprint $tableBlueprint + * @param ObjectTypeDefinition $dtoTypeObjectTypeDefinition + * @param string $modelVariableName + * @param FunctionParameterDefinition $dtoParameterDefinition + * @param ObjectTypeDefinition $returnTypeObjectTypeDefinition + * @param ObjectTypeDefinition $logMessageObjectTypeDefinition + * @param ObjectTypeDefinition $modelTypeObjectTypeDefinition + * @param string $methodFailedConstantName + * + * @return StatementBlockDefinition + */ + private function getMapFromDtoStatementBlock(TableBlueprint $tableBlueprint, + ObjectTypeDefinition $dtoTypeObjectTypeDefinition, + string $modelVariableName, + FunctionParameterDefinition $dtoParameterDefinition, + ObjectTypeDefinition $returnTypeObjectTypeDefinition, + ObjectTypeDefinition $logMessageObjectTypeDefinition, + ObjectTypeDefinition $modelTypeObjectTypeDefinition, + string $methodFailedConstantName): StatementBlockDefinition + { + return (new StatementBlockDefinition(new RawStatementDefinition(sprintf("if (!\$this->%s()->from%s(\$%s, \$%s))", + $this->modelDataMapGenerator->getModelMapAccessorTraitMethodName($tableBlueprint), + $dtoTypeObjectTypeDefinition->getImportableName(), + $modelVariableName, + $dtoParameterDefinition->getParameterName()))))->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::error([new %s(static::%s), new %s(static::%s)]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $logMessageObjectTypeDefinition->getImportableName(), + $this->getMapFromFailedConstantName($modelTypeObjectTypeDefinition), + $logMessageObjectTypeDefinition->getImportableName(), + $methodFailedConstantName))); + } + + /** + * @param string $modelVariableName + * @param ObjectTypeDefinition $returnTypeObjectTypeDefinition + * + * @return StatementBlockDefinition + */ + private function getModelNotFoundStatementBlock(string $modelVariableName, + ObjectTypeDefinition $returnTypeObjectTypeDefinition): StatementBlockDefinition + { + return (new StatementBlockDefinition(new RawStatementDefinition(sprintf("if (!\$%s)", + $modelVariableName))))->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::notFound();", + $returnTypeObjectTypeDefinition->getImportableName()))); + } + + /** + * @param string $modelVariableName + * @param ObjectTypeDefinition $returnTypeObjectTypeDefinition + * @param ObjectTypeDefinition $logMessageObjectTypeDefinition + * @param string $methodFailedConstantName + * @param ObjectTypeDefinition $queryExceptionTypeDefinition + * @param string $exceptionVariableName + * @param StatementDefinitionCollection $catchBlockStatements + * + * @return TryBlockDefinition + */ + private function getSaveModelTryBlock(string $modelVariableName, + ObjectTypeDefinition $returnTypeObjectTypeDefinition, + ObjectTypeDefinition $logMessageObjectTypeDefinition, + string $methodFailedConstantName, + ObjectTypeDefinition $queryExceptionTypeDefinition, + string $exceptionVariableName, + StatementDefinitionCollection $catchBlockStatements): TryBlockDefinition + { + return (new TryBlockDefinition()) + // if (!$model->save() + ->addStatementDefinition((new StatementBlockDefinition(new RawStatementDefinition(sprintf("if (!\$%s->save())", + $modelVariableName))))->addStatementDefinition(new RawStatementDefinition(sprintf("return %s::error([new %s(static::%s)]);", + $returnTypeObjectTypeDefinition->getImportableName(), + $logMessageObjectTypeDefinition->getImportableName(), + $methodFailedConstantName)))) + // begin catch block + ->addCatchStatements(PhpTypeEnum::objectOfType($queryExceptionTypeDefinition->getFullyQualifiedName()), + $exceptionVariableName, + $catchBlockStatements) + ; +} +} diff --git a/src/Generator/DataAttribute/DataAttributeGenerator.php b/src/Generator/DataAttribute/DataAttributeGenerator.php new file mode 100644 index 00000000..75256080 --- /dev/null +++ b/src/Generator/DataAttribute/DataAttributeGenerator.php @@ -0,0 +1,155 @@ +dataAttributeGeneratorConfiguration = $relieseConfiguration->getDataAttributeGeneratorConfiguration(); + /* + * TODO: inject a MySql / Postgress or other DataType mapping as needed + */ + $this->dataTypeMap = new MySqlDataTypeMap(); + } + + /** + * @param TableBlueprint $tableBlueprint + */ + public function fromColumnBlueprint(TableBlueprint $tableBlueprint) + { + foreach ($tableBlueprint->getColumnBlueprints() as $columnBlueprint) { + + $traitName = $this->getTraitName($tableBlueprint, $columnBlueprint); + + $namespace = $this->getTraitNamespace($tableBlueprint, $columnBlueprint); + + $traitDefinition = new TraitDefinition($traitName, $namespace); + + $traitDefinition + ->addClassComment( + sprintf("Generated from table column %s.%s", $tableBlueprint->getName(), $columnBlueprint->getColumnName()) + ) + ->addClassComment( + "This file is only generated if it does not already exist. To regenerate, remove this file." + ) + ; + + $propertyName = ClassNameTool::columnNameToPropertyName($columnBlueprint->getColumnName()); + + $phpTypeEnum = $this->dataTypeMap->getPhpTypeEnumFromDatabaseType( + $columnBlueprint->getDataType(), + $columnBlueprint->getMaximumCharacters(), + $columnBlueprint->getNumericPrecision(), + $columnBlueprint->getNumericScale(), + $columnBlueprint->getIsNullable() + ); + + $traitDefinition->addProperty( + (new ClassPropertyDefinition($propertyName, $phpTypeEnum)) + ->withSetter() + ->withGetter() + ); + + /* + * Write the Class Files + */ + $this->writeTraitFile($traitDefinition); + } + } + + /** + * @param TableBlueprint $tableBlueprint + * @param ColumnBlueprint $columnBlueprint + * + * @return string + */ + public function getFullyQualifiedTraitName(TableBlueprint $tableBlueprint, ColumnBlueprint $columnBlueprint): string + { + return $this->getTraitNamespace($tableBlueprint, $columnBlueprint).'\\'.$this->getTraitName($tableBlueprint, $columnBlueprint); + } + + public function getTraitNamespace(TableBlueprint $tableBlueprint, ColumnBlueprint $columnBlueprint): string + { + $tableClassName = ClassNameTool::snakeCaseToClassName(null, $tableBlueprint->getName(), null); + return $this->dataAttributeGeneratorConfiguration->getNamespace().'\\'.$tableClassName; + } + + public function getTraitName( + TableBlueprint $tableBlueprint, + ColumnBlueprint $columnBlueprint + ): string { + return ClassNameTool::snakeCaseToClassName( + $this->dataAttributeGeneratorConfiguration->getTraitPrefix(), + $columnBlueprint->getColumnName(), + $this->dataAttributeGeneratorConfiguration->getTraitSuffix(), + ); + } + + /** + * @param TraitDefinition $traitDefinition + */ + private function writeTraitFile( + TraitDefinition $traitDefinition + ): void + { + $classFormatter = new ClassFormatter(); + + $traitPhpCode = $classFormatter->format($traitDefinition); +// echo "\n---Trait---\n$traitPhpCode\n\n"; + + $traitDirectory = $this->dataAttributeGeneratorConfiguration->getPath(); + $namespaceParts = \explode('\\', $traitDefinition->getNamespace()); + $traitDirectory .= '/'.\array_pop($namespaceParts); + echo "\n---Trait Path---\n$traitDirectory\n\n"; + + if (!is_dir($traitDirectory)) { + \mkdir($traitDirectory, 0755, true); + } + + $traitFilePath = $traitDirectory . DIRECTORY_SEPARATOR . $traitDefinition->getName() . '.php'; +// echo "\n---Trait File---\n$traitFilePath\n\n"; + if (!\file_exists($traitFilePath)) { + \file_put_contents($traitFilePath, $traitPhpCode); + } else { + Log::warning("File already exists, skipped: $traitFilePath"); + } + } +} diff --git a/src/Generator/DataMap/ModelDataMapGenerator.php b/src/Generator/DataMap/ModelDataMapGenerator.php index 7f7dd0ae..cde2fe08 100644 --- a/src/Generator/DataMap/ModelDataMapGenerator.php +++ b/src/Generator/DataMap/ModelDataMapGenerator.php @@ -2,20 +2,33 @@ namespace Reliese\Generator\DataMap; +use app\DataTransport\Objects\PrimaryDatabase\OrganizationDto; +use app\Services\Identity\AccountService; use Illuminate\Support\Str; +use Reliese\Blueprint\ColumnBlueprint; use Reliese\Blueprint\DatabaseBlueprint; use Reliese\Blueprint\TableBlueprint; +use Reliese\Configuration\DataTransportObjectGeneratorConfiguration; use Reliese\Configuration\ModelDataMapGeneratorConfiguration; -use Reliese\Generator\DataTransport\DataTransportGenerator; +use Reliese\Configuration\RelieseConfiguration; +use Reliese\Generator\DataTransport\DataTransportObjectGenerator; use Reliese\Generator\Model\ModelGenerator; use Reliese\Generator\MySqlDataTypeMap; use Reliese\MetaCode\Definition\ClassDefinition; use Reliese\MetaCode\Definition\ClassMethodDefinition; +use Reliese\MetaCode\Definition\ClassPropertyDefinition; +use Reliese\MetaCode\Definition\ClassTraitDefinition; use Reliese\MetaCode\Definition\FunctionParameterDefinition; use Reliese\MetaCode\Definition\RawStatementDefinition; +use Reliese\MetaCode\Definition\StatementBlockDefinition; +use Reliese\MetaCode\Definition\StatementDefinitionCollection; +use Reliese\MetaCode\Definition\TraitDefinition; +use Reliese\MetaCode\Enum\InstanceEnum; use Reliese\MetaCode\Enum\PhpTypeEnum; +use Reliese\MetaCode\Enum\VisibilityEnum; use Reliese\MetaCode\Format\ClassFormatter; use Reliese\MetaCode\Tool\ClassNameTool; +use Reliese\MetaCode\Writers\CodeWriter; use function file_exists; use function file_put_contents; use function mkdir; @@ -27,9 +40,24 @@ class ModelDataMapGenerator { /** - * @var DataTransportGenerator + * @var DataTransportObjectGenerator */ - private DataTransportGenerator $dataTransportGenerator; + private DataTransportObjectGenerator $dataTransportObjectGenerator; + + /** + * @var DataTransportObjectGeneratorConfiguration + */ + private DataTransportObjectGeneratorConfiguration $dataTransportObjectGeneratorConfiguration; + + /** + * @var ClassDefinition[] + */ + private array $generatedAbstractModelDataMapClassDefinitions = []; + + /** + * @var ClassDefinition[] + */ + private array $generatedModelDataMapClassDefinitions = []; /** * @var ModelDataMapGeneratorConfiguration @@ -54,23 +82,19 @@ class ModelDataMapGenerator /** * ModelDataMapGenerator constructor. * - * @param ModelDataMapGeneratorConfiguration $modelDataMapGeneratorConfiguration - * @param ModelGenerator $modelGenerator - * @param DataTransportGenerator $dataTransportGenerator + * @param RelieseConfiguration $relieseConfiguration */ - public function __construct( - ModelDataMapGeneratorConfiguration $modelDataMapGeneratorConfiguration, - ModelGenerator $modelGenerator, - DataTransportGenerator $dataTransportGenerator - ) { - $this->modelDataMapGeneratorConfiguration = $modelDataMapGeneratorConfiguration; + public function __construct(RelieseConfiguration $relieseConfiguration) + { + $this->modelDataMapGeneratorConfiguration = $relieseConfiguration->getModelDataMapGeneratorConfiguration(); /* * TODO: inject a MySql / Postgress or other DataType mapping as needed */ $this->dataTypeMap = new MySqlDataTypeMap(); - $this->modelGenerator = $modelGenerator; - $this->dataTransportGenerator = $dataTransportGenerator; + $this->modelGenerator = new ModelGenerator($relieseConfiguration); + $this->dataTransportObjectGenerator = new DataTransportObjectGenerator($relieseConfiguration); + $this->dataTransportObjectGeneratorConfiguration = $relieseConfiguration->getDataTransportGeneratorConfiguration(); } /** @@ -104,8 +128,8 @@ public function fromTableBlueprint(TableBlueprint $tableBlueprint) /* * Determine the dto Class name and namespace */ - $dtoClassName = $this->dataTransportGenerator->getClassName($tableBlueprint); - $dtoFullyQualifiedClassName = $this->dataTransportGenerator->getFullyQualifiedClassName($tableBlueprint); + $dtoClassName = $this->dataTransportObjectGenerator->getClassName($tableBlueprint); + $dtoFullyQualifiedClassName = $this->dataTransportObjectGenerator->getFullyQualifiedClassName($tableBlueprint); $dtoParameterName = ClassNameTool::classNameToParameterName($dtoClassName); $dtoParameterType = PhpTypeEnum::objectOfType($dtoFullyQualifiedClassName); @@ -147,6 +171,112 @@ public function fromTableBlueprint(TableBlueprint $tableBlueprint) // dd($dtoPropertyAssignmentStatement); // dd([$modelParameterName, $modelConstant, $dtoParameterName, $setterName]); } + /* + * Look through FK Dto properties and Model FKs to assign DTO + */ + /* + * Example: + if ($account->relationLoaded('organization')) { + $organizationDto = $accountDto->getOrganizationDto() ?? new OrganizationDto(); + if ($this->organizationMap->toOrganizationDto($account->organization, $organizationDto)) { + $accountDto->setOrganizationDto($organizationDto); + } else { + return false; + } + } + */ + $requiredMapAccessorTraits = []; + $fkDtoProperties = $this->dataTransportObjectGenerator->getForeignKeyDtoPropertyDefinitions($tableBlueprint); + foreach ($fkDtoProperties as $fkDtoProperty) { +// $fkDtoProperty = $fkDtoProperties[$foreignKeyBlueprint->getName()]; + + $fkDtoFullyQualifiedClassName = $fkDtoProperty->getPhpTypeEnum()->getFullyQualifiedObjectClassName(); + $fkDtoClassName = ClassNameTool::fullyQualifiedClassNameToClassName($fkDtoFullyQualifiedClassName); + $fkModelName = \substr($fkDtoClassName, 0, \strlen($fkDtoClassName)-3); + $modelRelationshipName = str::snake($fkModelName); + $fkModelMapType = $fkModelName.'Map'; +// $fkModelMapTraitType = $this->modelDataMapGeneratorConfiguration->getAccessorTraitNamespace() +// ."\\$fkModelMapType"; + $fkModelMapGetter = "get$fkModelMapType"; + $fkModelMapMapping = "to$fkDtoClassName"; + $relationshipName = str::snake($fkModelName); + $fkDtoVariableName = ClassNameTool::dtoClassNameToVariableName($fkDtoClassName); + $fkDtoGetterName = ClassNameTool::variableNameToGetterName($fkDtoVariableName); + $fkDtoSetterName = ClassNameTool::variableNameToSetterName($fkDtoVariableName); + + if (!\array_key_exists($fkModelMapType, $requiredMapAccessorTraits)) { + $requiredMapAccessorTraits[$fkModelMapType] + = $traitDefinition + = $this->generateModelDataMapAccessorTrait($this->getClassNamespaceByType($fkModelMapType)); + $modelMapAbstractClassDefinition->addTrait(new ClassTraitDefinition($traitDefinition->getFullyQualifiedName())); + } + + $statements = new StatementBlockDefinition( + /* + * example output: + * if ($account->relationLoaded('organization')) { + */ + new RawStatementDefinition(\sprintf("if (\$%s->relationLoaded('%s'))", $modelParameterName, $relationshipName)) + ); + $statements->addStatementDefinition( + new RawStatementDefinition( + /* + * example output: + * $organizationDto = $accountDto->getOrganizationDto() ?? new OrganizationDto(); + */ + \sprintf( + "\$%s = \$%s->%s() ?? new %s();", + $fkDtoVariableName, + $dtoParameterName, + $fkDtoGetterName, + $fkDtoFullyQualifiedClassName + ) + ) + ); + + $mapFkDtoStatement = new StatementBlockDefinition( + new RawStatementDefinition( + /* + * example output: + * if ($this->getOrganizationMap()->toOrganizationDto($account->organization, $organizationDto)) + */ + \sprintf( + "if (\$this->%s()->%s(\$%s->%s, \$%s))", + $fkModelMapGetter, + $fkModelMapMapping, + $modelParameterName, + $modelRelationshipName, + $fkDtoVariableName + ) + ) + ); + $mapFkDtoStatement + ->addStatementDefinition( + new RawStatementDefinition( + /* + * example output: + * $accountDto->setOrganizationDto($organizationDto); + */ + \sprintf( + "\$%s->%s(\$%s);", + $dtoParameterName, + $fkDtoSetterName, + $fkDtoVariableName, + ) + ) + ) + ->setBlockSuffixStatement( + (new StatementBlockDefinition(new RawStatementDefinition(" else "))) + ->addStatementDefinition(new RawStatementDefinition("return false;")) + ); + + $statements->addStatementDefinition($mapFkDtoStatement); + $mapToDtoMethodDefinition->appendBodyStatement($statements); + } + + + + $mapToDtoMethodDefinition->appendBodyStatement(new RawStatementDefinition("return true;")); $modelMapAbstractClassDefinition->addMethodDefinition($mapToDtoMethodDefinition); @@ -165,10 +295,26 @@ public function fromTableBlueprint(TableBlueprint $tableBlueprint) foreach ($tableBlueprint->getColumnBlueprints() as $columnBlueprint) { $modelConstant = '\\'.$modelParameterType->toDeclarationType().'::'.Str::upper($columnBlueprint->getColumnName()); $getterName = ClassNameTool::columnNameToGetterName($columnBlueprint->getColumnName()); - $dtoPropertyAssignmentStatement = "\${$modelParameterName}[{$modelConstant}] = \${$dtoParameterName}->{$getterName}();"; - $mapToDtoMethodDefinition->appendBodyStatement(new RawStatementDefinition($dtoPropertyAssignmentStatement)); - // dd($dtoPropertyAssignmentStatement); - // dd([$modelParameterName, $modelConstant, $dtoParameterName, $setterName]); + $propertyConstant = ClassPropertyDefinition::getPropertyNameConstantName( + ClassNameTool::columnNameToPropertyName($columnBlueprint->getColumnName()) + ); + + $dtoPropertyAssignmentStatement = new RawStatementDefinition( + "\${$modelParameterName}[{$modelConstant}] = \${$dtoParameterName}->{$getterName}();" + ); + + if ($this->dataTransportObjectGeneratorConfiguration->getUseValueStateTracking()) { + $conditionalAssignmentBlock + = new StatementBlockDefinition( + new RawStatementDefinition(\sprintf("if (\$%s->getValueWasInitialized(%s::%s))", + $dtoParameterName, + $dtoClassName, + $propertyConstant))); + $conditionalAssignmentBlock->addStatementDefinition($dtoPropertyAssignmentStatement); + $mapToDtoMethodDefinition->appendBodyStatement($conditionalAssignmentBlock); + } else { + $mapToDtoMethodDefinition->appendBodyStatement($dtoPropertyAssignmentStatement); + } } $mapToDtoMethodDefinition->appendBodyStatement(new RawStatementDefinition("return true;")); @@ -177,9 +323,68 @@ public function fromTableBlueprint(TableBlueprint $tableBlueprint) /* * Write the Class Files */ + $codeWriter = new CodeWriter(); + foreach ($requiredMapAccessorTraits as $traitDefinition) { + $traitSource = (new ClassFormatter())->format($traitDefinition); + $filePath = $this->modelDataMapGeneratorConfiguration->getAccessorTraitPath().'/' + .$traitDefinition->getClassName().".php"; + $codeWriter->overwriteClassDefinition($filePath, $traitSource); + } + + $traitDefinition = $this->generateModelDataMapAccessorTrait($modelMapClassDefinition->getFullyQualifiedName()); + $traitSource = (new ClassFormatter())->format($traitDefinition); + $filePath = $this->modelDataMapGeneratorConfiguration->getAccessorTraitPath().'/' + .$traitDefinition->getClassName().".php"; + $codeWriter->overwriteClassDefinition($filePath, $traitSource); + $this->writeClassFiles($modelMapClassDefinition, $modelMapAbstractClassDefinition); } + public function generateModelDataMapAccessorTrait(string $fullyQualifiedModelDataMapClass) : TraitDefinition + { + $dataMapClass = ClassNameTool::fullyQualifiedClassNameToClassName($fullyQualifiedModelDataMapClass); + $dataMapNamespace = ClassNameTool::fullyQualifiedClassNameToNamespace($fullyQualifiedModelDataMapClass); + + $namespace = $this->modelDataMapGeneratorConfiguration->getAccessorTraitNamespace(); + $className = ClassNameTool::snakeCaseToClassName('With', $dataMapClass, null); + + $traitDefinition = new TraitDefinition($className, $namespace); + + $traitDefinition + ->addClassComment( + sprintf("Generated Accessor Trait for %s", $fullyQualifiedModelDataMapClass) + ) + ->addClassComment( + "This file is only generated if it does not already exist. To regenerate, remove this file." + ) + ; + + $propertyName = ClassNameTool::classNameToParameterName($dataMapClass); + $phpTypeEnum = PhpTypeEnum::nullableObjectOfType($fullyQualifiedModelDataMapClass); + + $getterStatementBlock = (new StatementDefinitionCollection()) + ->addStatementDefinition( + new RawStatementDefinition( + \sprintf( + "return \$this->%s ?? app(%s::class);", + $propertyName, + ClassNameTool::globalClassFQN($fullyQualifiedModelDataMapClass) + ) + ) + ); + + $property = (new ClassPropertyDefinition($propertyName, $phpTypeEnum)) + ->withGetter( + VisibilityEnum::protectedEnum(), + InstanceEnum::instanceEnum(), + $getterStatementBlock + ); + + $traitDefinition->addProperty($property); + + return $traitDefinition; + } + /** * @param TableBlueprint $tableBlueprint * @@ -235,6 +440,11 @@ public function getAbstractClassNamespace(TableBlueprint $tableBlueprint): strin return $this->getClassNamespace($tableBlueprint) .'\\Generated'; } + public function getModelMapAccessorTraitMethodName(TableBlueprint $tableBlueprint): string + { + return 'get'.$this->getClassName($tableBlueprint); + } + /** * @param ClassDefinition $classDefinition * @param ClassDefinition $abstractClassDefinition @@ -252,10 +462,10 @@ private function writeClassFiles( $classFolder = $this->modelDataMapGeneratorConfiguration->getPath(); $abstractClassFolder = $classFolder . DIRECTORY_SEPARATOR . 'Generated'; if (!is_dir($classFolder)) { - mkdir($classFolder, 0777, true); + \mkdir($classFolder, 0755, true); } if (!is_dir($abstractClassFolder)) { - mkdir($abstractClassFolder, 0777, true); + \mkdir($abstractClassFolder, 0755, true); } $classFilePath = $classFolder . DIRECTORY_SEPARATOR . $classDefinition->getClassName() . '.php'; @@ -266,4 +476,9 @@ private function writeClassFiles( } file_put_contents($abstractFilePath, $abstractPhpCode); } + + private function getClassNamespaceByType(string $fkModelMapType): string + { + return $this->modelDataMapGeneratorConfiguration->getNamespace()."\\".$fkModelMapType; + } } diff --git a/src/Generator/DataTransport/DataTransportCollectionGenerator.php b/src/Generator/DataTransport/DataTransportCollectionGenerator.php new file mode 100644 index 00000000..c1aaa533 --- /dev/null +++ b/src/Generator/DataTransport/DataTransportCollectionGenerator.php @@ -0,0 +1,13 @@ +dataTransportGeneratorConfiguration = $dataTransportGeneratorConfiguration; + public function __construct( + RelieseConfiguration $relieseConfiguration + ) { + $this->dataTransportGeneratorConfiguration = $relieseConfiguration->getDataTransportGeneratorConfiguration(); /* * TODO: inject a MySql / Postgress or other DataType mapping as needed */ $this->dataTypeMap = new MySqlDataTypeMap(); + $this->dataAttributeGenerator = new DataAttributeGenerator($relieseConfiguration); } /** @@ -57,17 +88,69 @@ public function __construct(DataTransportGeneratorConfiguration $dataTransportGe */ public function fromTableBlueprint(TableBlueprint $tableBlueprint) { - $className = $this->getClassName($tableBlueprint); + $dtoAbstractClassDefinition = $this->generateAbstractDataTransportObjectClassDefinition($tableBlueprint); + $dtoClassDefinition = $this->generateDataTransportObjectClassDefinition($tableBlueprint); - $abstractClassName = $this->getAbstractClassName($tableBlueprint); + /* + * Write the Class Files + */ + $this->writeClassFiles($dtoClassDefinition, $dtoAbstractClassDefinition); + } - $namespace = $this->getClassNamespace($tableBlueprint); + /** + * @param TableBlueprint $tableBlueprint + * + * @return string + */ + public function getFullyQualifiedClassName(ColumnOwnerInterface $tableBlueprint): string + { + return $this->getClassNamespace($tableBlueprint).'\\'.$this->getClassName($tableBlueprint); + } - $abstractNamespace = $this->getAbstractClassNamespace($tableBlueprint); + public function getClassNamespace(TableBlueprint $tableBlueprint): string + { + return $this->dataTransportGeneratorConfiguration->getNamespace(); + } - $dtoAbstractClassDefinition = new ClassDefinition($abstractClassName, $abstractNamespace); + public function getClassName(ColumnOwnerInterface $tableBlueprint): string + { + return ClassNameTool::snakeCaseToClassName( + null, + $tableBlueprint->getName(), + $this->dataTransportGeneratorConfiguration->getClassSuffix() + ); + } + + public function generateAbstractDataTransportObjectClassDefinition(TableBlueprint $tableBlueprint): ClassDefinition + { + if (\array_key_exists($tableBlueprint->getUniqueName(), + $this->generatedAbstractDataTransportObjectClassDefinitions) + ) { + return $this->generatedAbstractDataTransportObjectClassDefinitions[$tableBlueprint->getUniqueName()]; + } + + $dtoAbstractClassDefinition = new ClassDefinition( + $this->getAbstractClassName($tableBlueprint), + $this->getAbstractClassNamespace($tableBlueprint) + ); + + if ($this->dataTransportGeneratorConfiguration->getUseValueStateTracking()) { + /* + * Add interface: + * ValueStateProviderInterface + * include: + * use WithValueStateManager; + * And bind the tracking in the constructor: + * $this->bindValueChangeStateTracking(); + */ + $dtoAbstractClassDefinition + ->addInterface(ValueStateProviderInterface::class) + ->addTrait(new ClassTraitDefinition(WithValueStateManager::class)) + ->addConstructorStatement(new RawStatementDefinition("\$this->bindValueChangeStateTracking();")) + ; + } - if ($this->dataTransportGeneratorConfiguration->useBeforeChangeObservableProperties()) { + if ($this->dataTransportGeneratorConfiguration->getUseBeforeChangeObservableProperties()) { $dtoAbstractClassDefinition->addInterface( \PhpLibs\Observable\BeforeValueChangeObservableInterface::class ); @@ -76,7 +159,7 @@ public function fromTableBlueprint(TableBlueprint $tableBlueprint) ); } - if ($this->dataTransportGeneratorConfiguration->useAfterChangeObservableProperties()) { + if ($this->dataTransportGeneratorConfiguration->getUseAfterChangeObservableProperties()) { $dtoAbstractClassDefinition->addInterface( \PhpLibs\Observable\AfterValueChangeObservableInterface::class ); @@ -84,9 +167,6 @@ public function fromTableBlueprint(TableBlueprint $tableBlueprint) new ClassTraitDefinition(\PhpLibs\Observable\AfterValueChangeObservableTrait::class) ); } - - $dtoClassDefinition = new ClassDefinition($className, $namespace); - $dtoClassDefinition->setParentClass($dtoAbstractClassDefinition->getFullyQualifiedName()); foreach ($tableBlueprint->getColumnBlueprints() as $columnBlueprint) { @@ -97,12 +177,21 @@ public function fromTableBlueprint(TableBlueprint $tableBlueprint) $columnBlueprint->getMaximumCharacters(), $columnBlueprint->getNumericPrecision(), $columnBlueprint->getNumericScale(), - $columnBlueprint->getIsNullable() + // This value must always be true in order to allow for partial DTOs. + // Otherwise and error is raised when attempting to read a property that has not been assigned a value + true //$columnBlueprint->getIsNullable() ); + /* + * Use a property defined directly on the class + */ $columnClassProperty = (new ClassPropertyDefinition($propertyName, $phpTypeEnum)) - ->setIsBeforeChangeObservable($this->dataTransportGeneratorConfiguration->useBeforeChangeObservableProperties()) - ->setIsAfterChangeObservable($this->dataTransportGeneratorConfiguration->useBeforeChangeObservableProperties()) + ->setIsBeforeChangeObservable( + $this->dataTransportGeneratorConfiguration->getUseBeforeChangeObservableProperties() + ) + ->setIsAfterChangeObservable( + $this->dataTransportGeneratorConfiguration->getUseAfterChangeObservableProperties() + ) ->withSetter() ->withGetter() ; @@ -110,34 +199,29 @@ public function fromTableBlueprint(TableBlueprint $tableBlueprint) $dtoAbstractClassDefinition->addProperty($columnClassProperty); } - /* - * Write the Class Files - */ - $this->writeClassFiles($dtoClassDefinition, $dtoAbstractClassDefinition); - } + foreach ($this->getForeignKeyDtoPropertyDefinitions($tableBlueprint) as $fkDtoProperty) { + $dtoAbstractClassDefinition->addProperty($fkDtoProperty); + } - /** - * @param TableBlueprint $tableBlueprint - * - * @return string - */ - public function getFullyQualifiedClassName(TableBlueprint $tableBlueprint): string - { - return $this->getClassNamespace($tableBlueprint).'\\'.$this->getClassName($tableBlueprint); + return $dtoAbstractClassDefinition; } - public function getClassNamespace(TableBlueprint $tableBlueprint): string + public function generateDataTransportObjectClassDefinition(TableBlueprint $tableBlueprint): ClassDefinition { - return $this->dataTransportGeneratorConfiguration->getNamespace(); - } + if (\array_key_exists($tableBlueprint->getUniqueName(), $this->generatedDataTransportObjectClassDefinitions)) { + return $this->generatedDataTransportObjectClassDefinitions[$tableBlueprint->getUniqueName()]; + } - public function getClassName(TableBlueprint $tableBlueprint): string - { - return ClassNameTool::snakeCaseToClassName( - null, - $tableBlueprint->getName(), - $this->dataTransportGeneratorConfiguration->getClassSuffix() + $dtoClassDefinition = new ClassDefinition( + $this->getClassName($tableBlueprint), + $this->getClassNamespace($tableBlueprint) ); + $dtoClassDefinition->setParentClass( + $this->generateAbstractDataTransportObjectClassDefinition($tableBlueprint)->getFullyQualifiedName() + ); + + return $this->generatedDataTransportObjectClassDefinitions[$tableBlueprint->getUniqueName()] + = $dtoClassDefinition; } private function getAbstractClassName(TableBlueprint $tableBlueprint): string @@ -168,10 +252,10 @@ private function writeClassFiles( $dtoClassFolder = $this->dataTransportGeneratorConfiguration->getPath(); $abstractDtoClassFolder = $dtoClassFolder . DIRECTORY_SEPARATOR . 'Generated'; if (!is_dir($dtoClassFolder)) { - mkdir($dtoClassFolder, 0777, true); + \mkdir($dtoClassFolder, 0755, true); } if (!is_dir($abstractDtoClassFolder)) { - mkdir($abstractDtoClassFolder, 0777, true); + \mkdir($abstractDtoClassFolder, 0755, true); } $dtoFilePath = $dtoClassFolder . DIRECTORY_SEPARATOR . $classDefinition->getClassName() . '.php'; @@ -183,5 +267,178 @@ private function writeClassFiles( file_put_contents($abstractDtoFilePath, $abstractDtoPhpCode); } + + public function generateForeignKeyDtoPropertyDefinition( + TableBlueprint $tableBlueprint, + ForeignKeyBlueprint $foreignKeyBlueprint + ) : ClassPropertyDefinition { + + if (\array_key_exists($foreignKeyBlueprint->getName(), $this->generatedForeignKeyDtoPropertyDefinitions)) { + return $this->generatedForeignKeyDtoPropertyDefinitions[$foreignKeyBlueprint->getName()]; + } + + $fkDtoProperty = null; + $dtoVariableName = null; + + $referencedTableBlueprint = $foreignKeyBlueprint->getReferencedTableBlueprint(); + + $fkDtoClassName = $this->getClassName($referencedTableBlueprint); + + $dtoVariableName = ClassNameTool::dtoClassNameToVariableName($fkDtoClassName); + + $fkDtoProperty = new ClassPropertyDefinition( + $dtoVariableName, + PhpTypeEnum::nullableObjectOfType($this->getFullyQualifiedClassName($referencedTableBlueprint)) + ); + + $fkDtoProperty + ->setIsBeforeChangeObservable($this->dataTransportGeneratorConfiguration->getUseBeforeChangeObservableProperties()) + ->setIsAfterChangeObservable($this->dataTransportGeneratorConfiguration->getUseBeforeChangeObservableProperties()) + ->withSetter() + ->withGetter() + ; + + $commonColumns = []; + /* + * Track which columns should be updated when this property is set + */ + foreach ($foreignKeyBlueprint->getFkColumnPairs() as $columns) { + /** + * @var ColumnBlueprint $referencingColumn + * @var ColumnBlueprint $referencedColumn + */ + [$referencingColumn, $referencedColumn] = $columns; + + $commonColumns[$referencingColumn->getColumnName().' = '.$referencedColumn->getColumnName()] = + [$referencingColumn, $referencedColumn]; + } + + foreach ($commonColumns as $columnPairs) { + [$referencingColumn, $referencedColumn] = $columnPairs; + + $propertyConstant = ClassPropertyDefinition::getPropertyNameConstantName( + ClassNameTool::columnNameToPropertyName($referencedColumn->getColumnName()) + ); + + $propertyGetterCall = \sprintf( + "\$%s->%s()", + $dtoVariableName, + ClassNameTool::columnNameToGetterName($referencedColumn->getColumnName()), + ); + + /* + * If the referenced value was initialized, then set the fk value to match + */ + $ifInitialized = new RawStatementDefinition( + sprintf( + "if (\$%s->getValueWasInitialized(%s::%s) && !empty(%s))", + $dtoVariableName, + $fkDtoClassName, + $propertyConstant, + $propertyGetterCall + ) + ); + + $setFkFieldIfInitialized = new StatementBlockDefinition($ifInitialized); + $setFkFieldIfInitialized->addStatementDefinition( + new RawStatementDefinition( + \sprintf( + "\$this->%s(%s);", + ClassNameTool::columnNameToSetterName($referencingColumn->getColumnName()), + $propertyGetterCall, + ) + ) + ); + + $fkDtoProperty->addAdditionalSetterOperation($setFkFieldIfInitialized); + } + return $fkDtoProperty; + } + + /** + * @param TableBlueprint $tableBlueprint + * TODO: figure out how to cache these so they are not rebuilt on every call + * @return ClassPropertyDefinition[] + */ + public function getForeignKeyDtoPropertyDefinitions(TableBlueprint $tableBlueprint): array + { + $results = []; + + if (empty($tableBlueprint->getForeignKeyBlueprints())) { + return $results; + } + + foreach ($tableBlueprint->getForeignKeyBlueprints() as $foreignKeyBlueprint) { + $fkDtoProperty = $this->generateForeignKeyDtoPropertyDefinition($tableBlueprint, $foreignKeyBlueprint); + $results[] = $fkDtoProperty; + } + return $results; + + /** + * Examine FKs + * @var ForeignKeyBlueprint $foreignKeyBlueprint + */ + foreach ($tableBlueprint->getForeignKeyBlueprintsGroupedByReferencedTable() as + $referencedTableName => $foreignKeyBlueprints + ) { + $commonColumns = []; + $fkDtoProperty = null; + $dtoVariableName = null; + + $referencedTableBlueprint = null; + foreach ($foreignKeyBlueprints as $foreignKeyName => $foreignKeyBlueprint) { + + $referencedTableBlueprint ??= $foreignKeyBlueprint->getReferencedTableBlueprint(); + + $fkDtoClassName = $this->getClassName($referencedTableBlueprint); + + $dtoVariableName = ClassNameTool::dtoClassNameToVariableName($fkDtoClassName); + // + if (\is_null($fkDtoProperty)) { + $fkDtoProperty = ( + new ClassPropertyDefinition( + $dtoVariableName, + PhpTypeEnum::nullableObjectOfType( + $this->getFullyQualifiedClassName($referencedTableBlueprint) + ) + ) + ) + ->setIsBeforeChangeObservable($this->dataTransportGeneratorConfiguration->getUseBeforeChangeObservableProperties()) + ->setIsAfterChangeObservable($this->dataTransportGeneratorConfiguration->getUseBeforeChangeObservableProperties()) + ->withSetter() + ->withGetter() + ; + } + + foreach ($foreignKeyBlueprint->getFkColumnPairs() as $columns) { + /** + * @var ColumnBlueprint $referencingColumn + * @var ColumnBlueprint $referencedColumn + */ + [$referencingColumn, $referencedColumn] = $columns; + + $commonColumns[$referencingColumn->getColumnName().' = '.$referencedColumn->getColumnName()] = + [$referencingColumn, $referencedColumn]; + } + } + + foreach ($commonColumns as $columnPairs) { + [$referencingColumn, $referencedColumn] = $columnPairs; + + $fkDtoProperty->addAdditionalSetterOperation( + new RawStatementDefinition( + \sprintf( + "\$this->%s(\$%s->%s());", + ClassNameTool::columnNameToSetterName($referencingColumn->getColumnName()), + $dtoVariableName, + ClassNameTool::columnNameToGetterName($referencedColumn->getColumnName()), + ) + ) + ); + } + $results[] = $fkDtoProperty; + } + return $results; + } } diff --git a/src/Generator/Model/Eloquent/EloquentModelGenerator.php b/src/Generator/Model/Eloquent/EloquentModelGenerator.php deleted file mode 100644 index c93c6651..00000000 --- a/src/Generator/Model/Eloquent/EloquentModelGenerator.php +++ /dev/null @@ -1,13 +0,0 @@ -modelGeneratorConfiguration = $modelGeneratorConfiguration; + $this->modelGeneratorConfiguration = $relieseConfiguration->getModelGeneratorConfiguration(); /* * TODO: inject a MySql / Postgress or other DataType mapping as needed */ @@ -136,6 +138,26 @@ public function getAbstractClassNamespace(TableBlueprint $tableBlueprint): strin return $this->getClassNamespace($tableBlueprint) .'\\Generated'; } + /** + * @param ColumnBlueprint $columnBlueprint + * + * @return ClassConstantDefinition + */ + public function generateColumnConstantDefinition(ColumnBlueprint $columnBlueprint): ClassConstantDefinition + { + return new ClassConstantDefinition( + ClassNameTool::columnNameToConstantName($columnBlueprint->getColumnName()), + $columnBlueprint->getColumnName(), + VisibilityEnum::publicEnum() + ); + } + + public function getClassAsVariableName(TableBlueprint $tableBlueprint): string + { + $name = $this->getClassName($tableBlueprint); + return strtolower($name[0]).substr($name, 1); + } + /** * @return string */ diff --git a/src/MetaCode/Definition/ClassDefinition.php b/src/MetaCode/Definition/ClassDefinition.php index 657496d0..ab227156 100644 --- a/src/MetaCode/Definition/ClassDefinition.php +++ b/src/MetaCode/Definition/ClassDefinition.php @@ -2,6 +2,8 @@ namespace Reliese\MetaCode\Definition; +use Reliese\MetaCode\Enum\AbstractEnum; +use Reliese\MetaCode\Tool\ClassNameTool; use RuntimeException; /** @@ -9,6 +11,11 @@ */ class ClassDefinition implements ImportableInterface, CodeDefinitionInterface { + /** + * @var AbstractEnum + */ + private ?AbstractEnum $abstractEnumType; + /** * @var bool[] Array keys are fully qualified interface names */ @@ -19,6 +26,26 @@ class ClassDefinition implements ImportableInterface, CodeDefinitionInterface */ private string $className; + /** + * @var string[] + */ + private array $classComments = []; + + /** + * @var ClassConstantDefinition[] + */ + private array $constants = []; + + /** + * @var ImportableInterface[] + */ + private array $imports = []; + + /** + * @var ClassMethodDefinition[] + */ + private array $methods = []; + /** * @var string */ @@ -40,9 +67,9 @@ class ClassDefinition implements ImportableInterface, CodeDefinitionInterface private string $filePath; /** - * @var ImportableInterface[] + * @var ClassPropertyDefinition[] */ - private array $imports = []; + private array $properties = []; /** * @var ClassTraitDefinition[] @@ -50,40 +77,55 @@ class ClassDefinition implements ImportableInterface, CodeDefinitionInterface private array $traits = []; /** - * @var ClassConstantDefinition[] - */ - private array $constants = []; - - /** - * @var ClassPropertyDefinition[] + * @param string $comment + * + * @return $this */ - private array $properties = []; + public function addClassComment(string $comment): static + { + $this->classComments[] = $comment; + return $this; + } /** - * @var ClassMethodDefinition[] + * @param ClassConstantDefinition $constant + * + * @return $this */ - private array $methods = []; + public function addConstant(ClassConstantDefinition $constant): static + { + $this->constants[$constant->getName()] = $constant; + return $this; + } /** * ClassDefinition constructor. * - * @param string $name - * @param string $namespace + * @param string $className + * @param string $namespace + * @param ?AbstractEnum $abstractEnumType */ public function __construct( - string $name, - string $namespace + string $className, + string $namespace, + ?AbstractEnum $abstractEnumType = null ) { - $this->className = $name; + $this->className = $className; $this->namespace = trim($namespace, '\\'); + $this->constructorStatementsCollection = new StatementDefinitionCollection(); + $this->abstractEnumType = $abstractEnumType ?? AbstractEnum::concreteEnum(); } /** * @param string $fullyQualifiedInterfaceName + * + * @return $this */ - public function addInterface(string $fullyQualifiedInterfaceName) + public function addInterface(string $fullyQualifiedInterfaceName): static { + $fullyQualifiedInterfaceName = ClassNameTool::globalClassFQN($fullyQualifiedInterfaceName); $this->interfaces[$fullyQualifiedInterfaceName] = true; + return $this; } /* @@ -97,12 +139,31 @@ public function setFilePath(string $filePath): ClassDefinition return $this; } + /** + * @param ImportableInterface $import + * + * @return $this + */ + public function addImport(ImportableInterface $import): static + { + // We'll assume this class is already imported to shorten references to itself + if (strcmp($import->getFullyQualifiedImportableName(), $this->getFullyQualifiedImportableName()) === 0) { + return $this; + } + + $this->imports[$import->getImportableName()] = $import; + return $this; + } + /** * @param ClassMethodDefinition $classMethodDefinition + * + * @return $this */ - public function addMethodDefinition(ClassMethodDefinition $classMethodDefinition) + public function addMethodDefinition(ClassMethodDefinition $classMethodDefinition) : static { $this->methods[$classMethodDefinition->getFunctionName()] = $classMethodDefinition; + return $this; } /** @@ -119,13 +180,27 @@ public function hasInterfaces(): bool } /** - * @param string $fullyQualifiedClassName + * @param ClassPropertyDefinition $classPropertyDefinition * * @return $this */ - public function setParentClass(string $fullyQualifiedClassName): ClassDefinition + public function addProperty(ClassPropertyDefinition $classPropertyDefinition): ClassDefinition { - $this->parentClassName = $fullyQualifiedClassName; + $this->properties[$classPropertyDefinition->getVariableName()] = $classPropertyDefinition; + + if ($classPropertyDefinition->getIsBeforeChangeObservable() + || $classPropertyDefinition->getIsAfterChangeObservable()) { + + $this->addConstant( + new ClassConstantDefinition( + ClassPropertyDefinition::getPropertyNameConstantName( + $classPropertyDefinition->getVariableName() + ), + $classPropertyDefinition->getVariableName() + ) + ); + } + return $this; } @@ -134,15 +209,12 @@ public function setParentClass(string $fullyQualifiedClassName): ClassDefinition */ public function hasParentClass(): bool { - return !is_null($this->parentClassName); + return !empty($this->parentClassName); } - /** - * @return string - */ - public function getParentClassName() : string + public function getClassComments(): array { - return $this->parentClassName; + return $this->classComments; } /** @@ -153,6 +225,12 @@ public function getClassName(): string return $this->className; } + /** + * @deprecated use getClassName instead + * @return string + */ + public function getName(): string { return $this->getClassName(); } + /** * @return string */ @@ -170,14 +248,16 @@ public function getNamespace(): string } /** - * @param ClassPropertyDefinition $classPropertyDefinition - * - * @return $this + * @return ClassConstantDefinition[] */ - public function addProperty(ClassPropertyDefinition $classPropertyDefinition): ClassDefinition + public function getConstants(): array { - $this->properties[$classPropertyDefinition->getVariableName()] = $classPropertyDefinition; - return $this; + return $this->constants; + } + + public function getFullyQualifiedImportableName(): string + { + return trim($this->getFullyQualifiedName(), '\\'); } /** @@ -194,14 +274,6 @@ public function addProperties(array $classPropertyDefinitions): ClassDefinition return $this; } - /** - * @return ClassPropertyDefinition[] - */ - public function getProperties(): array - { - return $this->properties; - } - /** * @param string $propertyName * @@ -234,18 +306,17 @@ public function getMethods(): array return $this->methods; } - public function addConstant(ClassConstantDefinition $constant): static + public function getParentClassName(): string { - $this->constants[$constant->getName()] = $constant; - return $this; + return $this->parentClassName; } /** - * @return ClassConstantDefinition[] + * @return ClassPropertyDefinition[] */ - public function getConstants(): array + public function getProperties(): array { - return $this->constants; + return $this->properties; } /** @@ -273,6 +344,14 @@ public function addTraits(array $traitDefinitions): static return $this; } + /** + * @return string + */ + public function getStructureType(): string + { + return 'class'; + } + /** * @return ClassTraitDefinition[] */ @@ -297,19 +376,9 @@ public function hasTrait(string $fullyQualifiedTraitName): bool return false; } - /** - * @param ImportableInterface $import - * - * @return $this - */ - public function addImport(ImportableInterface $import): static + public function setParentClass(string $fullyQualifiedClassName): ClassDefinition { - // We'll assume this class is already imported to shorten references to itself - if (strcmp($import->getFullyQualifiedImportableName(), $this->getFullyQualifiedImportableName()) === 0) { - return $this; - } - - $this->imports[$import->getImportableName()] = $import; + $this->parentClassName = $fullyQualifiedClassName; return $this; } @@ -351,6 +420,20 @@ public function addConstants(array $constants): static return $this; } + private StatementDefinitionCollection $constructorStatementsCollection; + + public function getConstructorStatementsCollection(): StatementDefinitionCollection + { + return $this->constructorStatementsCollection; + } + + public function addConstructorStatement(StatementDefinitionInterface $statementDefinition): static + { + $this->getConstructorStatementsCollection() + ->addStatementDefinition($statementDefinition); + return $this; + } + /** * @todo: Put this on a helper class * @@ -372,14 +455,6 @@ public function getImports(): array return $this->imports; } - /** - * @return string - */ - public function getFullyQualifiedImportableName(): string - { - return trim($this->getFullyQualifiedName(), '\\'); - } - /** * @return string */ @@ -414,4 +489,12 @@ public function getFilePath(): string { return $this->filePath; } + + /** + * @return AbstractEnum + */ + public function getAbstractEnumType(): AbstractEnum + { + return $this->abstractEnumType; + } } diff --git a/src/MetaCode/Definition/ClassPropertyDefinition.php b/src/MetaCode/Definition/ClassPropertyDefinition.php index 79bc2d39..1d123a01 100644 --- a/src/MetaCode/Definition/ClassPropertyDefinition.php +++ b/src/MetaCode/Definition/ClassPropertyDefinition.php @@ -6,6 +6,7 @@ use Reliese\MetaCode\Enum\InstanceEnum; use Reliese\MetaCode\Enum\PhpTypeEnum; use Reliese\MetaCode\Enum\VisibilityEnum; +use Reliese\MetaCode\Tool\ClassNameTool; /** * Class ClassPropertyDefinition @@ -17,11 +18,21 @@ class ClassPropertyDefinition */ private ?InstanceEnum $getterInstanceEnum = null; + /** + * @var StatementDefinitionInterface|null + */ + private ?StatementDefinitionInterface $getterMethodBody; + /** * @var VisibilityEnum|null */ private ?VisibilityEnum $getterVisibilityEnum = null; + /** + * @var StatementDefinitionInterface[] + */ + private array $additionalSetterOperations = []; + /** * @var InstanceEnum|null */ @@ -134,7 +145,10 @@ public function getSetterInstanceEnum(): InstanceEnum */ public function getSetterMethodDefinition(ClassDefinition $containingClass): ClassMethodDefinition { - $param = new FunctionParameterDefinition($this->getVariableName(), $this->getPhpTypeEnum()); + $param = new FunctionParameterDefinition( + $this->getVariableName(), + $this->getPhpTypeEnum() + ); $setter = new ClassMethodDefinition( $this->getSetterMethodName(), PhpTypeEnum::staticTypeEnum(), @@ -145,20 +159,30 @@ public function getSetterMethodDefinition(ClassDefinition $containingClass): Cla $this->getSetterInstanceEnum(), ); - if ($this->getIsBeforeChangeObservable() && $containingClass->hasTrait('BeforeValueChangeObservableTrait')) { - $setter->appendBodyStatement(new RawStatementDefinition(\sprintf("\$this->raiseBeforeValueChange('%s', \$this->%s, \$\%s);\n", - $this->getVariableName(), + if ($this->getIsBeforeChangeObservable() ) { + $setter->appendBodyStatement( + new RawStatementDefinition( + \sprintf( + "\$this->raiseBeforeValueChange(static::%s, \$this->%s, \$%s);\n", + ClassPropertyDefinition::getPropertyNameConstantName($this->getVariableName()), $this->getVariableName(), - $param->getParameterName(),))); + $param->getParameterName(), + ) + ) + ); } $setter->appendBodyStatement(new RawStatementDefinition(\sprintf("\$this->%s = $%s;\n", $this->getVariableName(), $param->getParameterName()))); + foreach ($this->additionalSetterOperations as $additionalSetterOperation) { + $setter->appendBodyStatement($additionalSetterOperation); + } + if ($this->getIsAfterChangeObservable() && $containingClass->hasTrait('AfterValueChangeObservableTrait')) { - $setter->appendBodyStatement(new RawStatementDefinition(\sprintf("\$this->raiseAfterValueChange('%s', \$this->%s);\n", - $this->getVariableName(), + $setter->appendBodyStatement(new RawStatementDefinition(\sprintf("\$this->raiseAfterValueChange(static::%s, \$this->%s);\n", + ClassPropertyDefinition::getPropertyNameConstantName($this->getVariableName()), $this->getVariableName()))); } @@ -230,16 +254,20 @@ public function setIsBeforeChangeObservable(bool $isBeforeChangeObservable): Cla } /** - * @param VisibilityEnum|null $getterVisibilityEnum - * @param InstanceEnum|null $getterInstanceEnum + * @param VisibilityEnum|null $getterVisibilityEnum + * @param InstanceEnum|null $getterInstanceEnum + * @param StatementDefinitionInterface|null $statementDefinitionInterface * * @return $this */ - public function withGetter(?VisibilityEnum $getterVisibilityEnum = null, - ?InstanceEnum $getterInstanceEnum = null): ClassPropertyDefinition - { + public function withGetter( + ?VisibilityEnum $getterVisibilityEnum = null, + ?InstanceEnum $getterInstanceEnum = null, + ?StatementDefinitionInterface $statementDefinitionInterface = null + ): ClassPropertyDefinition { $this->getterVisibilityEnum = $getterVisibilityEnum ?? VisibilityEnum::publicEnum(); $this->getterInstanceEnum = $getterInstanceEnum ?? InstanceEnum::instanceEnum(); + $this->getterMethodBody = $statementDefinitionInterface; return $this; } @@ -289,4 +317,48 @@ public function setValue(mixed $value): static return $this; } + /** + * @param StatementDefinitionInterface $statementDefinition + * + * @return $this + */ + public function addAdditionalSetterOperation(StatementDefinitionInterface $statementDefinition): static + { + $this->additionalSetterOperations[] = $statementDefinition; + return $this; + } + + /** + * @param ClassDefinition $classDefinition + * + * @return ClassMethodDefinition + */ + public function getGetterMethodDefinition(ClassDefinition $classDefinition) : ClassMethodDefinition + { + return $this->getterMethodDefinition ??= $this->defaultGetterMethodDefinition(); + } + + /** + * @return ClassMethodDefinition + */ + protected function defaultGetterMethodDefinition(): ClassMethodDefinition + { + $getterFunctionName = ClassNameTool::variableNameToGetterName($this->getVariableName()); + $getterFunctionType = $this->getPhpTypeEnum(); + + $classMethod = new ClassMethodDefinition($getterFunctionName, $getterFunctionType); + + if ($this->getterMethodBody instanceof StatementDefinitionInterface) { + $classMethod->appendBodyStatement($this->getterMethodBody); + } else { + $classMethod->appendBodyStatement(new RawStatementDefinition('return $this->' . $this->getVariableName() . ';')); + } + + return $classMethod; + } + + public static function getPropertyNameConstantName(string $propertyName): string + { + return ClassNameTool::identifierNameToConstantName($propertyName)."_PROPERTY"; + } } diff --git a/src/MetaCode/Definition/ClassTraitDefinition.php b/src/MetaCode/Definition/ClassTraitDefinition.php index 2db0e598..bb7f0b1e 100644 --- a/src/MetaCode/Definition/ClassTraitDefinition.php +++ b/src/MetaCode/Definition/ClassTraitDefinition.php @@ -4,6 +4,7 @@ namespace Reliese\MetaCode\Definition; +use Reliese\MetaCode\Tool\ClassNameTool; class ClassTraitDefinition implements ImportableInterface { private string $name; @@ -17,11 +18,8 @@ class ClassTraitDefinition implements ImportableInterface */ public function __construct(string $fullyQualifiedTraitName) { - $parts = explode('\\', \ltrim($fullyQualifiedTraitName, '\\')); - $name = \array_pop($parts); - $namespace = \implode('\\', $parts); - $this->name = $name; - $this->namespace = trim($namespace, '\\'); + $this->name = ClassNameTool::fullyQualifiedClassNameToClassName($fullyQualifiedTraitName); + $this->namespace = trim(ClassNameTool::fullyQualifiedClassNameToNamespace($fullyQualifiedTraitName)); } /** diff --git a/src/MetaCode/Definition/CommentBlockStatementDefinition.php b/src/MetaCode/Definition/CommentBlockStatementDefinition.php new file mode 100644 index 00000000..e2ceee8d --- /dev/null +++ b/src/MetaCode/Definition/CommentBlockStatementDefinition.php @@ -0,0 +1,35 @@ +text[] = $line; + return $this; + } + + public function toPhpCode(IndentationProviderInterface $indentationProvider, int $blockDepth): string + { + if (empty($this->text)) { + return ""; + } + + $statements[] = $indentationProvider->getIndentation($blockDepth)."/**"; + foreach ($this->text as $line) { + $statements[] = $indentationProvider->getIndentation($blockDepth).' * '.$line; + } + $statements[] = $indentationProvider->getIndentation($blockDepth)." */"; + return \implode("\n", $statements); + } +} \ No newline at end of file diff --git a/src/MetaCode/Definition/RawStatementDefinition.php b/src/MetaCode/Definition/RawStatementDefinition.php index f7cebaf7..736abae6 100644 --- a/src/MetaCode/Definition/RawStatementDefinition.php +++ b/src/MetaCode/Definition/RawStatementDefinition.php @@ -2,6 +2,7 @@ namespace Reliese\MetaCode\Definition; +use Reliese\MetaCode\Format\IndentationProviderInterface; /** * Class RawStatementDefinition */ @@ -11,11 +12,11 @@ class RawStatementDefinition implements StatementDefinitionInterface public function __construct(string $rawPhpCode) { - $this->rawPhpCode = $rawPhpCode; + $this->rawPhpCode = trim($rawPhpCode); } - public function toPhpCode(): string + public function toPhpCode(IndentationProviderInterface $indentationProvider, int $blockDepth): string { - return $this->rawPhpCode; + return $indentationProvider->getIndentation($blockDepth).$this->rawPhpCode; } } diff --git a/src/MetaCode/Definition/StatementBlockDefinition.php b/src/MetaCode/Definition/StatementBlockDefinition.php new file mode 100644 index 00000000..883ee6ea --- /dev/null +++ b/src/MetaCode/Definition/StatementBlockDefinition.php @@ -0,0 +1,82 @@ +blockPrefixStatement = $blockPrefixStatement; + $this->statementDefinitionCollection = $statementDefinitionCollection ?? new StatementDefinitionCollection(); + } + + private ?StatementDefinitionInterface $blockSuffixStatement = null; + public function addStatementDefinition(StatementDefinitionInterface $statementDefinition) : static + { + $this->statementDefinitionCollection->addStatementDefinition($statementDefinition); + return $this; + } + + public function hasStatements(): bool + { + return $this->statementDefinitionCollection->hasStatements(); + } + /** + * @return string + */ + public function toPhpCode(IndentationProviderInterface $indentationProvider, int $blockDepth): string + { + $prefixStatement = ""; + $suffixStatement = ""; + + if ($this->blockPrefixStatement instanceof StatementDefinitionInterface) { + $prefixStatement = $this->blockPrefixStatement->toPhpCode($indentationProvider, $blockDepth)." "; + } + + if ($this->blockSuffixStatement instanceof StatementBlockDefinition) { + $suffixStatement = " ".ltrim($this->blockSuffixStatement->toPhpCode($indentationProvider, $blockDepth)); + } + + return \sprintf( + "%s{\n%s\n%s}%s\n", + $prefixStatement, + $this->statementDefinitionCollection->toPhpCode($indentationProvider, $blockDepth + 1), + $indentationProvider->getIndentation($blockDepth), + $suffixStatement + ); + } + + /** + * @param StatementDefinitionInterface|null $blockSuffixStatement + * + * @return StatementBlockDefinition + */ + public function setBlockSuffixStatement(?StatementDefinitionInterface $blockSuffixStatement): StatementBlockDefinition + { + $this->blockSuffixStatement = $blockSuffixStatement; + return $this; + } +} diff --git a/src/MetaCode/Definition/StatementDefinitionCollection.php b/src/MetaCode/Definition/StatementDefinitionCollection.php new file mode 100644 index 00000000..66005d2e --- /dev/null +++ b/src/MetaCode/Definition/StatementDefinitionCollection.php @@ -0,0 +1,43 @@ +statementDefinitions[] = $statementDefinition; + return $this; + } + + /** + * @return string + */ + public function toPhpCode(IndentationProviderInterface $indentationProvider, int $blockDepth): string + { + $statements = []; + foreach ($this->statementDefinitions as $statementDefinition) { + $statements[] = $statementDefinition->toPhpCode($indentationProvider, $blockDepth); + } + return \implode("\n", $statements); + } + + public function hasStatements(): bool + { + return !empty($this->statementDefinitions); + } +} diff --git a/src/MetaCode/Definition/StatementDefinitionCollectionInterface.php b/src/MetaCode/Definition/StatementDefinitionCollectionInterface.php new file mode 100644 index 00000000..c5c03222 --- /dev/null +++ b/src/MetaCode/Definition/StatementDefinitionCollectionInterface.php @@ -0,0 +1,10 @@ +toDeclarationType()), + $exceptionVariableName + ) + ); + + $this->setBlockSuffixStatement( + (new StatementBlockDefinition( + $catchStatement, + $catchStatementDefinitionCollection + )) + ); + + return $this; + } +} diff --git a/src/MetaCode/Enum/AbstractEnum.php b/src/MetaCode/Enum/AbstractEnum.php index fed94664..9348613c 100644 --- a/src/MetaCode/Enum/AbstractEnum.php +++ b/src/MetaCode/Enum/AbstractEnum.php @@ -7,7 +7,7 @@ */ class AbstractEnum { - protected const CONCRETE_TYPE_ID = 0; + protected const CONCRETE_TYPE_ID = 10; protected const ABSTRACT_TYPE_ID = 20; @@ -48,10 +48,10 @@ public static function concreteEnum(): AbstractEnum return static::$abstractEnumInstance = new static(static::CONCRETE_TYPE_ID); } - public function toReservedWord() : string + public function toReservedWord(bool $includeTrailingSpace = false) : string { if (static::isAbstract()) { - return 'abstract'; + return 'abstract' . ($includeTrailingSpace ? ' ' : ''); } if (static::isConcrete()) { @@ -68,7 +68,7 @@ public function __toString(): string } if (static::isAbstract()) { - return 'static'; + return 'abstract'; } return 'UNKNOWN'; diff --git a/src/MetaCode/Enum/PhpTypeEnum.php b/src/MetaCode/Enum/PhpTypeEnum.php index 5b9826d3..ab3a0d53 100644 --- a/src/MetaCode/Enum/PhpTypeEnum.php +++ b/src/MetaCode/Enum/PhpTypeEnum.php @@ -30,6 +30,13 @@ class PhpTypeEnum protected const STATIC_TYPE_ID = 70; protected const NULLABLE_STATIC_TYPE_ID = 75; + protected const NOT_DEFINED_TYPE_ID = 80; + + /** + * @var PhpTypeEnum + */ + private static ?PhpTypeEnum $notDefined = null; + private static ?PhpTypeEnum $stringTypeInstance = null; private static ?PhpTypeEnum $nullableStringTypeInstance = null; @@ -73,12 +80,25 @@ class PhpTypeEnum */ private bool $isNullable = false; - private function __construct($phpTypeId, $isNullable = false) + private function __construct($phpTypeId, $isNullable) { $this->phpTypeId = $phpTypeId; $this->isNullable = $isNullable; } + public static function notDefined(): PhpTypeEnum + { + if (static::$notDefined) { + return static::$notDefined; + } + return static::$notDefined = new static(static::NOT_DEFINED_TYPE_ID, true); + } + + public function isDefined(): bool + { + return static::NOT_DEFINED_TYPE_ID !== $this->phpTypeId; + } + public function isNullable(): bool { return $this->isNullable; @@ -195,7 +215,7 @@ public static function stringType(): PhpTypeEnum if (static::$stringTypeInstance) { return static::$stringTypeInstance; } - return static::$stringTypeInstance = new static(static::STRING_TYPE_ID); + return static::$stringTypeInstance = new static(static::STRING_TYPE_ID, false); } public static function nullableStringType(): PhpTypeEnum @@ -211,7 +231,7 @@ public static function intType(): PhpTypeEnum if (static::$intTypeInstance) { return static::$intTypeInstance; } - return static::$intTypeInstance = new static(static::INT_TYPE_ID); + return static::$intTypeInstance = new static(static::INT_TYPE_ID, false); } public static function nullableIntType(): PhpTypeEnum @@ -219,7 +239,7 @@ public static function nullableIntType(): PhpTypeEnum if (static::$nullableIntTypeInstance) { return static::$nullableIntTypeInstance; } - return static::$nullableIntTypeInstance = new static(static::NULLABLE_INT_TYPE_ID); + return static::$nullableIntTypeInstance = new static(static::NULLABLE_INT_TYPE_ID, true); } public static function floatType(): PhpTypeEnum @@ -227,7 +247,7 @@ public static function floatType(): PhpTypeEnum if (static::$floatTypeInstance) { return static::$floatTypeInstance; } - return static::$floatTypeInstance = new static(static::FLOAT_TYPE_ID); + return static::$floatTypeInstance = new static(static::FLOAT_TYPE_ID, false); } public static function nullableFloatType(): PhpTypeEnum @@ -235,7 +255,7 @@ public static function nullableFloatType(): PhpTypeEnum if (static::$nullableFloatTypeInstance) { return static::$nullableFloatTypeInstance; } - return static::$nullableFloatTypeInstance = new static(static::NULLABLE_FLOAT_TYPE_ID); + return static::$nullableFloatTypeInstance = new static(static::NULLABLE_FLOAT_TYPE_ID, true); } public static function boolType(): PhpTypeEnum @@ -243,7 +263,7 @@ public static function boolType(): PhpTypeEnum if (static::$boolTypeInstance) { return static::$boolTypeInstance; } - return static::$boolTypeInstance = new static(static::BOOL_TYPE_ID); + return static::$boolTypeInstance = new static(static::BOOL_TYPE_ID, false); } public static function nullableBoolType(): PhpTypeEnum @@ -251,7 +271,7 @@ public static function nullableBoolType(): PhpTypeEnum if (static::$nullableBoolTypeInstance) { return static::$nullableBoolTypeInstance; } - return static::$nullableBoolTypeInstance = new static(static::NULLABLE_BOOL_TYPE_ID); + return static::$nullableBoolTypeInstance = new static(static::NULLABLE_BOOL_TYPE_ID, true); } public static function arrayType(string $containedTypeName): PhpTypeEnum @@ -259,7 +279,7 @@ public static function arrayType(string $containedTypeName): PhpTypeEnum if (\array_key_exists($containedTypeName, static::$arrayTypeInstances)) { return static::$arrayTypeInstances[$containedTypeName]; } - return static::$arrayTypeInstances[$containedTypeName] = (new static(static::ARRAY_TYPE_ID)) + return static::$arrayTypeInstances[$containedTypeName] = (new static(static::ARRAY_TYPE_ID, false)) ->setContainedTypeName($containedTypeName); } @@ -268,7 +288,7 @@ public static function nullableArrayType(string $containedTypeName): PhpTypeEnum if (\array_key_exists($containedTypeName, static::$nullableArrayTypeInstances)) { return static::$nullableArrayTypeInstances[$containedTypeName]; } - return static::$nullableArrayTypeInstances[$containedTypeName] = (new static(static::NULLABLE_ARRAY_TYPE_ID)) + return static::$nullableArrayTypeInstances[$containedTypeName] = (new static(static::NULLABLE_ARRAY_TYPE_ID, true)) ->setContainedTypeName($containedTypeName); } @@ -278,7 +298,7 @@ public static function objectOfType(string $fullyQualifiedClassNameOfObjectType) return static::$objectTypeInstance[$fullyQualifiedClassNameOfObjectType]; } return static::$objectTypeInstance[$fullyQualifiedClassNameOfObjectType] - = (new static(static::OBJECT_TYPE_ID))->setObjectClassType($fullyQualifiedClassNameOfObjectType); + = (new static(static::OBJECT_TYPE_ID, false))->setObjectClassType($fullyQualifiedClassNameOfObjectType); } public static function nullableObjectOfType(string $fullyQualifiedClassNameOfObjectType): PhpTypeEnum @@ -287,7 +307,7 @@ public static function nullableObjectOfType(string $fullyQualifiedClassNameOfObj return static::$nullableObjectTypeInstance[$fullyQualifiedClassNameOfObjectType]; } return static::$nullableObjectTypeInstance[$fullyQualifiedClassNameOfObjectType] - = (new static(static::NULLABLE_OBJECT_TYPE_ID))->setObjectClassType($fullyQualifiedClassNameOfObjectType); + = (new static(static::NULLABLE_OBJECT_TYPE_ID, true))->setObjectClassType($fullyQualifiedClassNameOfObjectType); } public static function staticTypeEnum(): PhpTypeEnum @@ -295,7 +315,7 @@ public static function staticTypeEnum(): PhpTypeEnum if (static::$staticTypeInstance) { return static::$staticTypeInstance; } - return static::$staticTypeInstance = new static(static::STATIC_TYPE_ID); + return static::$staticTypeInstance = new static(static::STATIC_TYPE_ID, false); } public static function nullableStaticTypeEnum(): PhpTypeEnum @@ -303,7 +323,7 @@ public static function nullableStaticTypeEnum(): PhpTypeEnum if (static::$nullableStaticTypeInstance) { return static::$nullableStaticTypeInstance; } - return static::$nullableStaticTypeInstance = new static(static::NULLABLE_STATIC_TYPE_ID); + return static::$nullableStaticTypeInstance = new static(static::NULLABLE_STATIC_TYPE_ID, true); } public function toDeclarationType() : string @@ -364,6 +384,10 @@ public function toDeclarationType() : string return '?static'; } + if (static::NOT_DEFINED_TYPE_ID === $this->phpTypeId) { + return ''; + } + throw new RuntimeException(__METHOD__." Died because ".__CLASS__." was misused."); } @@ -425,6 +449,10 @@ public function toAnnotationTypeString() : string return 'nullable static'; } + if (static::NOT_DEFINED_TYPE_ID === $this->phpTypeId) { + return ''; + } + throw new RuntimeException(__METHOD__." Died because ".__CLASS__." was misused."); } @@ -437,6 +465,11 @@ public function __toString(): string return __METHOD__.' failed'; } + public function getFullyQualifiedObjectClassName(): ?string + { + return '\\'.trim($this->fullyQualifiedObjectClassName, '\\'); + } + private function setContainedTypeName(string $containedTypeName) : PhpTypeEnum { $this->containedTypeName = $containedTypeName; diff --git a/src/MetaCode/Format/ClassFormatter.php b/src/MetaCode/Format/ClassFormatter.php index 3cc2b907..c287b247 100644 --- a/src/MetaCode/Format/ClassFormatter.php +++ b/src/MetaCode/Format/ClassFormatter.php @@ -16,8 +16,13 @@ /** * Class ClassFormatter */ -class ClassFormatter +class ClassFormatter implements IndentationProviderInterface { + /** + * @param ClassDefinition $classDefinition + * + * @return string + */ public function format(ClassDefinition $classDefinition): string { $depth = 0; @@ -32,6 +37,7 @@ public function format(ClassDefinition $classDefinition): string $body[] = $this->formatTraits($classDefinition, $depth); $body[] = $this->formatConstants($classDefinition, $depth); $body[] = $this->formatProperties($classDefinition, $depth); + $body[] = $this->formatConstructor($classDefinition, $depth); $body[] = $this->formatMethods($classDefinition, $depth); $lines[] = "getClassName() . "\n"; + $lines[] = ' * ' . Str::studly($classDefinition->getStructureType()) . ' ' . $classDefinition->getName() . "\n"; $lines[] = " * \n"; $lines[] = " * Created by Reliese\n"; + foreach ($classDefinition->getClassComments() as $line) { + $lines[] = " * \n * ".$line."\n"; + } $lines[] = " */\n"; - $lines[] = 'class ' . $classDefinition->getClassName(); + $lines[] = $classDefinition->getAbstractEnumType()->toReservedWord(true) + . Str::lower($classDefinition->getStructureType()) + . ' ' + . $classDefinition->getClassName(); if (!empty($parent)) { $lines[] = ' extends ' . $parent; @@ -73,7 +85,7 @@ public function format(ClassDefinition $classDefinition): string * * @return string */ - private function getIndentation(int $depth): string + public function getIndentation(int $depth): string { return str_repeat($this->getIndentationSymbol(), $depth); } @@ -81,7 +93,7 @@ private function getIndentation(int $depth): string /** * @return string */ - private function getIndentationSymbol(): string + public function getIndentationSymbol(): string { return ' '; } @@ -99,7 +111,9 @@ private function prepareGettersAndSetters(ClassDefinition $classDefinition): voi ); } if ($property->hasGetter()) { - $this->appendGetter($property, $classDefinition); + $classDefinition->addMethodDefinition( + $property->getGetterMethodDefinition($classDefinition) + ); } } } @@ -198,19 +212,21 @@ private function formatProperties(ClassDefinition $classDefinition, int $depth): private function formatProperty(ClassDefinition $classDefinition, ClassPropertyDefinition $property, int $depth): string { - $statement = $this->getIndentation($depth) - . $property->getVisibilityEnum()->toReservedWord() - . ' ' - . $this->shortenTypeHint($classDefinition, $property->getPhpTypeEnum()) - . ' $' - . $property->getVariableName() - ; - + $defaultValueString = ''; if ($property->hasValue()) { - $statement .= ' = ' . var_export($property->getValue(), true); + $defaultValueString = ' = '. var_export($property->getValue(), true); + } elseif ($property->getPhpTypeEnum()->isNullable()) { + $defaultValueString = ' = null'; } - return $statement . ';'; + return $this->getIndentation($depth) + . $property->getVisibilityEnum()->toReservedWord() + . ' ' + . $this->shortenTypeHint($classDefinition, $property->getPhpTypeEnum()) + . ' $' + . $property->getVariableName() + . $defaultValueString + . ';'; } /** @@ -238,19 +254,6 @@ private function appendSetter(ClassPropertyDefinition $property, ClassDefinition $classDefinition->addMethodDefinition($getter); } - /** - * @param ClassPropertyDefinition $property - * @param ClassDefinition $classDefinition - */ - private function appendGetter(ClassPropertyDefinition $property, ClassDefinition $classDefinition): void - { - $getter = new ClassMethodDefinition('get' . Str::studly($property->getVariableName()), - $property->getPhpTypeEnum()); - $getter->appendBodyStatement(new RawStatementDefinition('return $this->' . $property->getVariableName() . ';')); - - $classDefinition->addMethodDefinition($getter); - } - /** * @param ClassDefinition $classDefinition * @param int $depth @@ -272,14 +275,14 @@ private function formatMethod(ClassDefinition $classDefinition, ClassMethodDefin { $signature = $this->getIndentation($depth); - if ($method->getAbstractEnum()->isAbstract()) { - $signature .= $method->getAbstractEnum()->toReservedWord() . ' '; - } - if ($method->getVisibilityEnum()) { $signature .= $method->getVisibilityEnum()->toReservedWord() . ' '; } + if ($method->getAbstractEnum()->isAbstract()) { + $signature .= $method->getAbstractEnum()->toReservedWord() . ' '; + } + $signature .= 'function ' . $method->getFunctionName() . '('; $parameters = []; @@ -291,16 +294,23 @@ private function formatMethod(ClassDefinition $classDefinition, ClassMethodDefin $signature .= implode(', ', $parameters); - $signature .= '): '; - $signature .= $this->shortenTypeHint($classDefinition, $method->getReturnPhpTypeEnum()); + $signature .= ')'; + if ($method->getReturnPhpTypeEnum()->isDefined()) { + /* + * This condition is required because constructors do not have return types + */ + $signature .= ": ". $this->shortenTypeHint($classDefinition, $method->getReturnPhpTypeEnum()); + } + if ($method->getAbstractEnum()->isAbstract()) { + return $signature . ";\n"; + } $signature .= "\n"; $signature .= $this->getIndentation($depth) . "{\n"; $blockDepth = $depth + 1; foreach ($method->getBlockStatements() as $statement) { - $signature .= $this->getIndentation($blockDepth) - . $statement->toPhpCode() + $signature .= $statement->toPhpCode($this, $blockDepth) . "\n"; } @@ -357,4 +367,16 @@ private function shortenTypeHint(ClassDefinition $classDefinition, PhpTypeEnum $ return $typeHint; } + + private function formatConstructor(ClassDefinition $classDefinition, int $depth): string + { + if (!$classDefinition->getConstructorStatementsCollection()->hasStatements()) { + return ""; + } + + $constructorMethodDefinition = new ClassMethodDefinition('__construct', PhpTypeEnum::notDefined()); + $constructorMethodDefinition->appendBodyStatement($classDefinition->getConstructorStatementsCollection()); + + return $this->formatMethod($classDefinition, $constructorMethodDefinition, $depth+1); + } } diff --git a/src/MetaCode/Format/IndentationProviderInterface.php b/src/MetaCode/Format/IndentationProviderInterface.php new file mode 100644 index 00000000..3e1da368 --- /dev/null +++ b/src/MetaCode/Format/IndentationProviderInterface.php @@ -0,0 +1,21 @@ + ['information_schema', 'performance_schema', 'mysql', 'sys']], // except audit.log_.* tables - ['schemas' => ['audit'], 'tables' => ['/^log_.*$/']], + //['schemas' => ['audit'], 'tables' => ['/^log_.*$/']], // except any table that ends in migrations or matches 'phinx' on all schemas ['schemas' => [$all], 'tables' => ['/^.*migrations$/', 'phinx']], // except soft delete columns on all tables for all schemas - ['schemas' => [$all], 'tables' => [$all], 'columns' => ['deleted_on']] + //['schemas' => [$all], 'tables' => [$all], 'columns' => ['deleted_on']] ], ], /* @@ -126,7 +128,7 @@ | */ - 'Path' => $appRoot.'Models', + 'Path' => $appRoot.'/Models', /* |-------------------------------------------------------------------------- @@ -527,18 +529,50 @@ ], ], // endregion Model Generator Config + + // region Data Access Generator Config + DataAccessGeneratorConfiguration::class => [ + 'Path' => $appRoot.'/DataAccess/PrimaryDatabase', + 'Namespace' => 'app\DataAccess\PrimaryDatabase', + //'ClassPrefix' => '', + 'ClassSuffix' => 'DataAccess', + 'ParentClassPrefix' => 'Abstract', + ], + // endregion Data Access Generator Config + + // region Data Attribute Generator Config + DataAttributeGeneratorConfiguration::class => [ + 'Path' => $appRoot.'/DataAttribute/PrimaryDatabase', + 'Namespace' => 'App\DataAttribute\Objects', + 'ClassPrefix' => 'With', + 'ClassSuffix' => 'Trait', + 'ParentClassPrefix' => 'Abstract', + ], + // endregion Data Attribute Generator Config + // region Data Transport Generator Config - DataTransportGeneratorConfiguration::class => [ + DataTransportObjectGeneratorConfiguration::class => [ 'Path' => $appRoot.'/DataTransportObjects', 'Namespace' => 'App\DataTransportObjects', 'ClassSuffix' => 'Dto', 'ParentClassPrefix' => 'Abstract', + 'UseValueStateTracking' => true, 'ObservableProperties' => [ 'BeforeChange' => false, 'AfterChange' => false, ], ], // endregion Data Transport Generator Config + + // region Data Transport Collection Generator Config + DataTransportCollectionGeneratorConfiguration::class => [ + 'Path' => $appRoot.'/DataTransport/Collections', + 'Namespace' => 'App\DataTransport\Collections', + 'ClassSuffix' => 'Dto', + 'ParentClassPrefix' => 'Abstract', + ], + // endregion Data Transport Collection Generator Config + // region Data Map Generator Config ModelDataMapGeneratorConfiguration::class => [ 'Path' => $appRoot.'/DataMaps/PrimaryDatabase', diff --git a/tests/Behat/Contexts/Generator/DataTransportObjectGeneratorContext.php b/tests/Behat/Contexts/Generator/DataTransportObjectGeneratorContext.php index b0ee04be..ac895b1a 100644 --- a/tests/Behat/Contexts/Generator/DataTransportObjectGeneratorContext.php +++ b/tests/Behat/Contexts/Generator/DataTransportObjectGeneratorContext.php @@ -2,24 +2,12 @@ namespace Tests\Behat\Contexts\Generator; -use Behat\Behat\Tester\Exception\PendingException; -use Reliese\Generator\DataTransport\DataTransportGenerator; use Reliese\Generator\DataTransport\DataTransportObjectGenerator; -use Tests\Behat\Contexts\FeatureContext; /** * Class DataTransportObjectGeneratorContext */ class DataTransportObjectGeneratorContext extends GeneratorContexts { - public function getDataTransportObjectGenerator(): DataTransportObjectGenerator - { - return new DataTransportObjectGenerator( - $this->getConfigurationContexts() - ->getDataTransportObjectGeneratorConfigurationContext() - ->getDataTransportObjectGeneratorConfiguration() - ); - } - /** * @When /^DataTransportObjectGenerator generates Abstract Dto from Schema "([^"]*)" Table "([^"]*)"$/ */ diff --git a/tests/MetaCode/Format/ClassFormatterTest.php b/tests/MetaCode/Format/ClassFormatterTest.php index 9189a467..eacb37fb 100644 --- a/tests/MetaCode/Format/ClassFormatterTest.php +++ b/tests/MetaCode/Format/ClassFormatterTest.php @@ -356,7 +356,7 @@ public function it_formats_a_class_with_one_property_of_type_nullable_global_obj */ class OneClass { - private ?DateTime \$aProperty; + private ?DateTime \$aProperty = null; } PHP; @@ -393,8 +393,8 @@ public function it_formats_a_class_with_two_properties_of_type_nullable_global_o */ class OneClass { - private ?DateTime \$aProperty; - private ?DateTime \$anotherProperty; + private ?DateTime \$aProperty = null; + private ?DateTime \$anotherProperty = null; } PHP; @@ -431,7 +431,7 @@ public function it_formats_a_class_with_one_nullable_property() */ class OneClass { - private ?string \$aProperty; + private ?string \$aProperty = null; } PHP; @@ -807,7 +807,6 @@ class OneClass public function setOneProperty(string \$oneProperty): static { \$this->oneProperty = \$oneProperty; - return \$this; } @@ -831,6 +830,6 @@ public function getOneProperty(): string $classOutput = $classFormatter->format($classDefinition); - $this->assertEquals($expectedClassOutput, $classOutput); + $this->assertEquals($expectedClassOutput, $classOutput, "\n $expectedClassOutput \n VERSUS \n $classOutput"); } }