├── .gitignore ├── LICENSE ├── README.md ├── clarc-laravel-plugin.iml ├── composer.json ├── phpunit.xml.dist ├── src ├── ClarcProvider.php ├── Code │ └── ClarcModuleCodes.php ├── Commands │ ├── ClarcCommand.php │ ├── Initialize │ │ └── ClarcInitializeCommand.php │ ├── Make │ │ ├── ClarcMakeCommand.php │ │ ├── ClarcMakeCommandObjectCreatePresenter.php │ │ └── ClarcMakeCommandProviderAppendUseCasePresenter.php │ └── Shared │ │ └── Menu │ │ ├── ClarcCommandSharedMenu.php │ │ └── ClarcCommandSharedMenuOption.php ├── Config │ └── LaravelConfig.php ├── LaravelSourceFileBuilder │ ├── LaravelControllerSourceFileBuilder.php │ └── LaravelPresenterSourceFileBuilder.php └── UseCases │ ├── ClarcObject │ └── Create │ │ ├── ClarcObjectCreateInputData.php │ │ ├── ClarcObjectCreateInputPort.php │ │ ├── ClarcObjectCreateInteractor.php │ │ ├── ClarcObjectCreateOutputData.php │ │ ├── ClarcObjectCreateOutputPortInterface.php │ │ └── ClarcObjectUseCaseCreateOutputCollector.php │ └── ClarcProvider │ └── AppendUseCase │ ├── ClarcProviderAppendUseCaseInputData.php │ ├── ClarcProviderAppendUseCaseInputPortInterface.php │ ├── ClarcProviderAppendUseCaseInteractor.php │ ├── ClarcProviderAppendUseCaseOutputData.php │ ├── ClarcProviderAppendUseCaseOutputPortInterface.php │ └── Scripts │ ├── AppendUseCaseScriptInterface.php │ └── UseCaseSettingScript.php └── tests ├── BasicCodeGenerate └── BasicCodeGenerateTest.php ├── ClarcObjectCreateInteractor ├── ClarcObjectCreateInteractorTest.php └── TestClarcObjectCreatePresenter.php ├── ClarcProviderAppendUseCaseInteractor ├── ClarcProviderAppendUseCaseInteractorTest.php └── TestClarcProviderAppendUseCaseUseCasePresenter.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | composer.phar 4 | composer.lock 5 | /vendor/ 6 | .phpunit.result.cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Masanobu Naruse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clarc-laravel-plugin 2 | 3 | クリーンアーキテクチャの実装例にしたがって、必要なモジュール群をスキャフォールディングするプラグインです。 4 | 5 | https://nrslib.com/phpcon-2019-proposal/ 6 | 7 | 現在開発中です。 8 | 9 | # Commands 10 | 11 | ## clarc:init 12 | 13 | 初期化コマンドです。 14 | ClarcProvider と ClarcMiddleWare が作成されます。 15 | ClarcProvider を Provider として登録し、ClarcMiddleWare を MiddleWare として登録してください。 16 | ClarcProvider は後述の clarc:make 実行時にインジェクション対象を設定する箇所です。 17 | 18 | ## clarc:make 19 | 20 | クリーンアーキテクチャの実装例にしたがって、必要なモジュール群をスキャフォールディングします。 21 | 入力に従い下記データが生成されます。 22 | 23 | - Controller 24 | - InputData 25 | - InputPort 26 | - Interactor 27 | - OutputPort 28 | - OutputData 29 | - Presenter 30 | - ViewModel 31 | -------------------------------------------------------------------------------- /clarc-laravel-plugin.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nrslib/clarc-laravel-plugin", 3 | "require": { 4 | "php": "^7.2", 5 | "nrslib/cfg": "0.*", 6 | "nrslib/clarc-php-core": "dev-master", 7 | "illuminate/support": "^6.2", 8 | "laravel/framework": "^6.2" 9 | }, 10 | "require-dev": { 11 | "phpunit/phpunit": "^8.0" 12 | }, 13 | "autoload": { 14 | "psr-4": { 15 | "nrslib\\ClarcLaravelPlugin\\": "src/" 16 | } 17 | }, 18 | "autoload-dev": { 19 | "psr-4": { 20 | "nrslib\\ClarcLaravelPluginTests\\": "tests/" 21 | } 22 | }, 23 | "extra": { 24 | "laravel": { 25 | "providers": [ 26 | "nrslib\\ClarcLaravelPlugin\\ClarcProvider" 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | tests 4 | 5 | 6 | 7 | src 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/ClarcProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('command.Clarc.make', function ($app) { 19 | return $app['nrslib\ClarcLaravelPlugin\Commands\Make\ClarcMakeCommand']; 20 | }); 21 | $this->commands('command.Clarc.make'); 22 | 23 | $this->app->singleton('command.Clarc.initialize', function ($app) { 24 | return $app['nrslib\ClarcLaravelPlugin\Commands\Initialize\ClarcInitializeCommand']; 25 | }); 26 | $this->commands('command.Clarc.initialize'); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Code/ClarcModuleCodes.php: -------------------------------------------------------------------------------- 1 | app->singleton(ClarcMiddleWare::class); 31 | $this->registerUseCases(); 32 | } 33 | 34 | /** 35 | * register use cases 36 | */ 37 | private function registerUseCases() 38 | { 39 | } 40 | } 41 | '; 42 | 43 | public static $debugProvider = 44 | ''; 45 | 46 | public static $middleWare = 47 | 'router = $router; 76 | } 77 | 78 | /** 79 | * @param mixed $data 80 | * @see \Illuminate\Routing\Router::toResponse() 81 | */ 82 | public function setData($data): void 83 | { 84 | $this->data = $data; 85 | } 86 | 87 | /** 88 | * Handle an incoming request. 89 | * 90 | * @param Request $request 91 | * @param \Closure $next 92 | * @return mixed 93 | */ 94 | public function handle($request, Closure $next) 95 | { 96 | $response = $next($request); 97 | if ($this->data === null) { 98 | return $response; 99 | } 100 | 101 | return $this->router->prepareResponse($this->router->getCurrentRequest(), $this->data); 102 | } 103 | } 104 | '; 105 | } -------------------------------------------------------------------------------- /src/Commands/ClarcCommand.php: -------------------------------------------------------------------------------- 1 | info('Welcome to Clarc'); 14 | $this->separator(); 15 | $this->info($text); 16 | $this->separator(); 17 | } 18 | 19 | public function need(string $text) 20 | { 21 | while (true) { 22 | $result = $this->ask($text); 23 | if (!empty($result)) { 24 | return $result; 25 | } 26 | } 27 | } 28 | 29 | public function separator() 30 | { 31 | $this->line('---------------------------------------------------------------'); 32 | } 33 | 34 | public function newline() 35 | { 36 | $this->line(''); 37 | } 38 | } -------------------------------------------------------------------------------- /src/Commands/Initialize/ClarcInitializeCommand.php: -------------------------------------------------------------------------------- 1 | title('Initialize clarc'); 19 | 20 | $middlewareFilePath = LaravelConfig::DIR_MIDDLEWARE . 'ClarcMiddleWare.php'; 21 | $this->info('Creating file ' . $middlewareFilePath); 22 | file_put_contents($middlewareFilePath, ClarcModuleCodes::$middleWare); 23 | $this->info('Wrote ' . realpath($middlewareFilePath)); 24 | $this->newline(); 25 | 26 | $clarcProviderFilePath = LaravelConfig::DIR_PROVIDER . 'ClarcProvider.php'; 27 | $this->info('Creating file' . $clarcProviderFilePath); 28 | file_put_contents($clarcProviderFilePath, ClarcModuleCodes::$clarcProvider); 29 | $this->info('Wrote ' . realpath($clarcProviderFilePath)); 30 | $this->newline(); 31 | 32 | $this->info('Please append ClarcProvider to app.php'); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Commands/Make/ClarcMakeCommand.php: -------------------------------------------------------------------------------- 1 | title('Make usecase'); 35 | 36 | $this->info('Please input your controller name.'); 37 | $controllerName = $this->need('controller name'); 38 | $controllerName = ucfirst($controllerName); 39 | 40 | $this->info('Please input your action name.'); 41 | $actionName = $this->need('action name.'); 42 | $actionName = ucfirst($actionName); 43 | 44 | $schema = new UseCaseSchema($controllerName, $actionName); 45 | $objectCreatePresenter = new ClarcMakeCommandObjectCreatePresenter($this, $schema); 46 | $alreadyRegistered = !$this->checkFiles($controllerName, $actionName, $objectCreatePresenter); 47 | if ($alreadyRegistered) { 48 | $this->info('process ended.'); 49 | return; 50 | } 51 | 52 | $this->info('Please select input data fields'); 53 | $inputDataFields = $this->askFields(); 54 | $this->info('Please select output data fields'); 55 | $outputDataFields = $this->askFields(); 56 | 57 | $this->createClarcObjects($objectCreatePresenter, $controllerName, $actionName, $inputDataFields, $outputDataFields); 58 | $this->registerDependency($controllerName, $actionName); 59 | 60 | $this->info('process ended.'); 61 | } 62 | 63 | /** 64 | * @param ClarcMakeCommandObjectCreatePresenter $objectCreatePresenter 65 | * @param string $controllerName 66 | * @param string $actionName 67 | * @param TypeAndName[] $inputDataFields 68 | * @param TypeAndName[] $outputDataFields 69 | */ 70 | private function createClarcObjects(ClarcMakeCommandObjectCreatePresenter $objectCreatePresenter, string $controllerName, string $actionName, array $inputDataFields, array $outputDataFields) 71 | { 72 | $objectCreateInteractor = new ClarcObjectCreateInteractor( 73 | $objectCreatePresenter, 74 | new ClassRenderer(), 75 | new InterfaceRenderer() 76 | ); 77 | $currentControllerContent = $this->getCurrentControllerContent($controllerName); 78 | $objectCreateInputData = new ClarcObjectCreateInputData( 79 | $controllerName, 80 | $currentControllerContent, 81 | $actionName, 82 | $inputDataFields, 83 | $outputDataFields 84 | ); 85 | $objectCreateInteractor->handle($objectCreateInputData); 86 | } 87 | 88 | /** 89 | * @param string $controllerName 90 | * @param string $actionName 91 | */ 92 | private function registerDependency(string $controllerName, string $actionName) 93 | { 94 | $presenter = new ClarcMakeCommandProviderAppendUseCasePresenter(); 95 | 96 | $currentProviderCode = file_get_contents(LaravelConfig::FILE_CLARC_PROVIDER); 97 | 98 | $identifer = $controllerName . $actionName; 99 | $inputData = new ClarcProviderAppendUseCaseInputData( 100 | $currentProviderCode, 101 | '', 102 | $identifer, 103 | $this->createUsingTarget(LaravelConfig::NAMESPACE_INPUT_PORT, $controllerName, $actionName, 'InputPortInterface', true), 104 | $this->createUsingTarget(LaravelConfig::NAMESPACE_INTERACTOR, $controllerName, $actionName, 'Interactor'), 105 | $this->createUsingTarget(LaravelConfig::NAMESPACE_OUTPUT_PORT, $controllerName, $actionName, 'OutputPortInterface', true), 106 | $this->createUsingTarget(LaravelConfig::NAMESPACE_PRESENTER, $controllerName, $actionName, 'Presenter') 107 | ); 108 | $interactor = new ClarcProviderAppendUseCaseInteractor($presenter); 109 | $interactor->handle($inputData); 110 | } 111 | 112 | /** 113 | * @param string $packagePrefix 114 | * @param string $controllerName 115 | * @param string $actionName 116 | * @param string $suffix 117 | * @param bool $appendActionNameToNameSpace 118 | * @return string 119 | */ 120 | private function createUsingTarget(string $packagePrefix, string $controllerName, string $actionName, string $suffix, bool $appendActionNameToNameSpace = false) 121 | { 122 | $prefix = '\\' . $packagePrefix . '\\' . $controllerName; 123 | if ($appendActionNameToNameSpace) { 124 | return $prefix . '\\' . $actionName . '\\' . $controllerName . $actionName . $suffix; 125 | } else { 126 | return $prefix . '\\' . $controllerName . $actionName . $suffix; 127 | } 128 | } 129 | 130 | /** 131 | * @return array 132 | */ 133 | private function askFields(): array 134 | { 135 | $results = []; 136 | 137 | while (true) { 138 | $this->separator(); 139 | $choice = $this->choice('Please select field type', ['int', 'string', 'array', 'user defined', 'finish'], 'finish'); 140 | 141 | if ($choice === 'finish') { 142 | goto finish; 143 | } 144 | 145 | $this->info('Please input field information'); 146 | 147 | $name = $this->need('name'); 148 | if (key_exists($name, $results)) { 149 | $this->error('"' . $name . '" is already registered.'); 150 | $this->separator(); 151 | $this->info('current fields'); 152 | $this->separator(); 153 | $this->showFields($results); 154 | continue; 155 | } 156 | 157 | $namespace = null; 158 | $type = $choice; 159 | if ($choice === 'user defined') { 160 | $namespace = $this->need('namespace'); 161 | $type = $this->need('type'); 162 | } 163 | $fieldInfo = new TypeAndName( 164 | $type, 165 | $name, 166 | $namespace 167 | ); 168 | 169 | $results[$fieldInfo->name] = $fieldInfo; 170 | } 171 | 172 | finish: 173 | return $results; 174 | } 175 | 176 | /** 177 | * @param string $controllerName 178 | * @param string $actionName 179 | * @param ClarcMakeCommandObjectCreatePresenter $presenter 180 | * @return bool 181 | */ 182 | private function checkFiles(string $controllerName, string $actionName, ClarcMakeCommandObjectCreatePresenter $presenter): bool 183 | { 184 | $overwrites = $presenter->willBeOverwriteFiles($controllerName, $actionName); 185 | if (empty($overwrites)) { 186 | return true; 187 | } 188 | 189 | $this->info('The following file will be overwritten.'); 190 | $this->separator(); 191 | foreach ($overwrites as $file) { 192 | $fullPath = realpath($file); 193 | $this->info($fullPath); 194 | } 195 | 196 | return $this->confirm('ok?', false); 197 | } 198 | 199 | /** 200 | * @param TypeAndName[] $fields 201 | */ 202 | private function showFields(array $fields) 203 | { 204 | $firstLine = true; 205 | foreach ($fields as $field) { 206 | if (!$firstLine) { 207 | $this->newline(); 208 | } 209 | $this->info('name: ' . $field->name); 210 | $this->info('type: ' . $field->type); 211 | if ($field->hasNamespace()) { 212 | $this->info('namespace: ' . $field->namespace); 213 | } 214 | $firstLine = false; 215 | } 216 | } 217 | 218 | /** 219 | * @param string $controllerName 220 | * @return string|null 221 | */ 222 | private function getCurrentControllerContent(string $controllerName): ?string 223 | { 224 | $controllerFile = LaravelConfig::DIR_CONTROLLER . $controllerName . 'Controller.php'; 225 | if (!file_exists($controllerFile)) { 226 | return null; 227 | } 228 | 229 | $content = file_get_contents($controllerFile); 230 | if (empty($content)) { 231 | return null; 232 | } 233 | 234 | return $content; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/Commands/Make/ClarcMakeCommandObjectCreatePresenter.php: -------------------------------------------------------------------------------- 1 | command = $command; 27 | $this->schema = $schema; 28 | } 29 | 30 | public function output(ClarcObjectCreateOutputData $outputData) 31 | { 32 | $this->putSourceFile(LaravelConfig::DIR_CONTROLLER, $outputData->getControllerSourceFile()); 33 | $this->putSourceFile($this->appendSchemaDirectory(LaravelConfig::DIR_INPUT_PORT, true), $outputData->getInputDataSourceFile()); 34 | $this->putSourceFile($this->appendSchemaDirectory(LaravelConfig::DIR_INPUT_PORT, true), $outputData->getInputPortSourceFile()); 35 | $this->putSourceFile($this->appendSchemaDirectory(LaravelConfig::DIR_INTERACTOR), $outputData->getInteractorSourceFile()); 36 | $this->putSourceFile($this->appendSchemaDirectory(LaravelConfig::DIR_OUTPUT_PORT, true), $outputData->getOutputDataSourceFile()); 37 | $this->putSourceFile($this->appendSchemaDirectory(LaravelConfig::DIR_OUTPUT_PORT, true), $outputData->getOutputPortSourceFile()); 38 | $this->putSourceFile($this->appendSchemaDirectory(LaravelConfig::DIR_PRESENTER), $outputData->getPresenterSourceFile()); 39 | $this->putSourceFile($this->appendSchemaDirectory(LaravelConfig::DIR_VIEWMODEL, true), $outputData->getViewModelSourceFile()); 40 | 41 | $this->command->newline(); 42 | } 43 | 44 | private function appendSchemaDirectory(string $path, bool $appendUsecase = false) { 45 | if ($appendUsecase) { 46 | return $path . '\\' . $this->schema->categoryName . '\\' . $this->schema->usecaseName . '\\'; 47 | } else { 48 | return $path . '\\' . $this->schema->categoryName . '\\'; 49 | } 50 | } 51 | 52 | public function willBeOverwriteFiles(string $controllerName, string $actionName) { 53 | $prefix = $controllerName . $actionName; 54 | $checkTargets = [ 55 | LaravelConfig::DIR_INPUT_PORT . $prefix . 'InputPortInterface.php', 56 | LaravelConfig::DIR_INPUT_PORT . $prefix . 'InputData.php', 57 | LaravelConfig::DIR_INTERACTOR . $prefix . 'Interactor.php', 58 | LaravelConfig::DIR_OUTPUT_PORT . $prefix . 'OutputPortInterface.php', 59 | LaravelConfig::DIR_OUTPUT_PORT . $prefix . 'OutputData.php', 60 | LaravelConfig::DIR_PRESENTER . $prefix . 'Presenter.php', 61 | LaravelConfig::DIR_VIEWMODEL . $prefix . 'ViewModel.php', 62 | ]; 63 | 64 | $overwrites = []; 65 | foreach ($checkTargets as $checkTarget) { 66 | if (file_exists($checkTarget)) { 67 | array_push($overwrites, $checkTarget); 68 | } 69 | } 70 | 71 | return $overwrites; 72 | } 73 | 74 | private function putSourceFile(string $directory, SourceFileData $sourceFileData) 75 | { 76 | if (!file_exists($directory)) { 77 | mkdir($directory, '0777', true); 78 | } 79 | $file = $directory . $sourceFileData->getClassName() . '.php'; 80 | $this->command->info('Creating file ' . $file); 81 | file_put_contents($file, $sourceFileData->getContents()); 82 | $this->command->info('Wrote ' . realpath($file)); 83 | } 84 | } -------------------------------------------------------------------------------- /src/Commands/Make/ClarcMakeCommandProviderAppendUseCasePresenter.php: -------------------------------------------------------------------------------- 1 | getClarcProviderCode()); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Commands/Shared/Menu/ClarcCommandSharedMenu.php: -------------------------------------------------------------------------------- 1 | options = $options; 17 | } 18 | 19 | public function getOptions() 20 | { 21 | return $this->options; 22 | } 23 | 24 | public function interact(string $selector): bool 25 | { 26 | foreach ($this->options as $option) 27 | { 28 | if ($option->match($selector)) 29 | { 30 | $option->interact(); 31 | return true; 32 | } 33 | } 34 | 35 | return false; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Commands/Shared/Menu/ClarcCommandSharedMenuOption.php: -------------------------------------------------------------------------------- 1 | selector = $selector; 20 | $this->name = $name; 21 | } 22 | 23 | public function match(string $word): bool 24 | { 25 | return strtolower($word) === strtolower($this->selector); 26 | } 27 | 28 | public function toString() 29 | { 30 | return $this->selector . '. ' . $this->name; 31 | } 32 | 33 | public abstract function interact(); 34 | } -------------------------------------------------------------------------------- /src/Config/LaravelConfig.php: -------------------------------------------------------------------------------- 1 | renderer = $renderer; 35 | $this->currentControllerContent = $currentControllerContent; 36 | } 37 | 38 | public function build(UseCaseSchema $schema, string $namespace, string $inputPortName, string $inputPortNamespace): SourceFileData 39 | { 40 | $name = $schema->categoryName . 'Controller'; 41 | 42 | $clazz = $this->prepareMeta($name, $namespace, $inputPortNamespace, $inputPortName); 43 | $actionName = lcfirst($schema->usecaseName); 44 | 45 | if (!$this->existMethod($clazz->getMethodsSetting()->getMethods(), $actionName)) { 46 | $clazz->setupClass() 47 | ->addUse($inputPortNamespace . '\\' . $inputPortName); 48 | $clazz->getMethodsSetting() 49 | ->addMethod($actionName, function ($methodDefinition) use ($actionName, $inputPortName) { 50 | $methodDefinition 51 | ->setAccessLevel(AccessLevel::public()) 52 | ->addArgument('inputPort', $inputPortName) 53 | ->addBody('// TODO: Implement ' . $actionName . '() method.'); 54 | }); 55 | } 56 | 57 | $contents = $this->renderer->render($clazz); 58 | 59 | return new SourceFileData($name, $contents); 60 | } 61 | 62 | private function prepareMeta(string $name, string $namespace, string $inputPortNamespace, string $inputPortName) 63 | { 64 | if (is_null($this->currentControllerContent)) { 65 | $clazz = new ClassMeta($name, $namespace); 66 | $clazz->setupClass() 67 | ->addUse('Illuminate\Routing\Controller as BaseController') 68 | ->setExtend('BaseController'); 69 | return $clazz; 70 | } else { 71 | $parser = new ClassParser(); 72 | $meta = $parser->parse($this->currentControllerContent); 73 | return $meta; 74 | } 75 | } 76 | 77 | /** 78 | * @param MethodDefinition[] $methods 79 | * @param string $name 80 | * @return bool 81 | */ 82 | private function existMethod(array $methods, string $name): bool 83 | { 84 | foreach ($methods as $method) { 85 | if ($method->getName() === $name) { 86 | return true; 87 | } 88 | } 89 | return false; 90 | } 91 | } -------------------------------------------------------------------------------- /src/LaravelSourceFileBuilder/LaravelPresenterSourceFileBuilder.php: -------------------------------------------------------------------------------- 1 | classRenderer = $classRenderer; 29 | } 30 | 31 | 32 | function build(UseCaseSchema $schema, string $namespace, string $outputDataName, string $outputPortName, string $outputPortNamespace): SourceFileData 33 | { 34 | $name = $schema->fullName() . 'Presenter'; 35 | 36 | $clazz = new ClassMeta($name , $namespace); 37 | $clazz->setupClass() 38 | ->addUse($outputPortNamespace . '\\' . $outputPortName) 39 | ->addUse($outputPortNamespace . '\\' . $outputDataName) 40 | ->addUse(LaravelConfig::NAMESPACE_MIDDLEWARE . '\\' . 'ClarcMiddleware') 41 | ->addUse(LaravelConfig::NAMESPACE_VIEWMODEL . '\\' . $schema->categoryName . '\\' . $schema->usecaseName . '\\' . $schema->fullName() . 'ViewModel') 42 | ->addImplement($outputPortName) 43 | ->setConstructor(function ($definition) { 44 | $definition->addArgument('middleware', 'ClarcMiddleware') 45 | ->addBody('$this->middleware = $middleware;'); 46 | }); 47 | 48 | $clazz->setupFields() 49 | ->addField('middleware', 'ClarcMiddleware'); 50 | 51 | $clazz->setupMethods() 52 | ->addMethod('output', function ($methodDefine) use ($outputDataName, $schema) { 53 | $methodDefine->addArgument('outputData', $outputDataName) 54 | ->setAccessLevel(AccessLevel::public()) 55 | ->addBody('$viewModel = new ' . $schema->fullName() . 'ViewModel($outputData);') 56 | ->addBody('$this->middleware->setData(view(\'view_resource\', compact(\'viewModel\')));'); 57 | }); 58 | 59 | $contents = $this->classRenderer->render($clazz); 60 | 61 | return new SourceFileData($name, $contents); 62 | } 63 | } -------------------------------------------------------------------------------- /src/UseCases/ClarcObject/Create/ClarcObjectCreateInputData.php: -------------------------------------------------------------------------------- 1 | controllerName = $controllerName; 47 | $this->currentControllerContent = $currentControllerContent; 48 | $this->actionName = $actionName; 49 | $this->inputDataFields = $inputDataFields; 50 | $this->outputDataFields = $outputDataFields; 51 | } 52 | } -------------------------------------------------------------------------------- /src/UseCases/ClarcObject/Create/ClarcObjectCreateInputPort.php: -------------------------------------------------------------------------------- 1 | outputPort = $outputPort; 44 | $this->classRenderer = $classRenderer; 45 | $this->interfaceRenderer = $interfaceRenderer; 46 | } 47 | 48 | public function handle(ClarcObjectCreateInputData $inputData) 49 | { 50 | $inputPortNameSpace = LaravelConfig::NAMESPACE_INPUT_PORT . '\\' . $inputData->controllerName . '\\' . $inputData->actionName; 51 | $namespaces = new UseCaseCreateNamespaceData( 52 | LaravelConfig::NAMESPACE_CONTROLLER, 53 | $inputPortNameSpace, 54 | LaravelConfig::NAMESPACE_INTERACTOR . '\\' . $inputData->controllerName, 55 | LaravelConfig::NAMESPACE_OUTPUT_PORT . '\\' . $inputData->controllerName . '\\' . $inputData->actionName, 56 | LaravelConfig::NAMESPACE_PRESENTER . '\\' . $inputData->controllerName, 57 | LaravelConfig::NAMESPACE_VIEWMODEL . '\\' . $inputData->controllerName . '\\' . $inputData->actionName 58 | ); 59 | $usecaseCreateOutputData = $this->executeClarcCore($inputData, $namespaces); 60 | 61 | $outputData = new ClarcObjectCreateOutputData( 62 | $usecaseCreateOutputData->getControllerSourceFile(), 63 | $usecaseCreateOutputData->getInputPortSourceFile(), 64 | $usecaseCreateOutputData->getInteractorSourceFile(), 65 | $usecaseCreateOutputData->getInputDataSourceFile(), 66 | $usecaseCreateOutputData->getOutputPortSourceFile(), 67 | $usecaseCreateOutputData->getOutputDataSourceFile(), 68 | $usecaseCreateOutputData->getPresenterSourceFile(), 69 | $usecaseCreateOutputData->getViewModelSourceFile() 70 | ); 71 | $this->outputPort->output($outputData); 72 | } 73 | 74 | /** 75 | * @param ClarcObjectCreateInputData $inputData 76 | * @param UseCaseCreateNamespaceData $namespaces 77 | * @return UseCaseCreateOutputData 78 | */ 79 | public function executeClarcCore(ClarcObjectCreateInputData $inputData, UseCaseCreateNamespaceData $namespaces): UseCaseCreateOutputData 80 | { 81 | $usecaseCreateDataCollector = new ClarcObjectUseCaseCreateOutputCollector(); 82 | $usecaseCreateInteractor = new UseCaseCreateInteractor( 83 | $usecaseCreateDataCollector, 84 | $this->classRenderer, 85 | $this->interfaceRenderer, 86 | new LaravelControllerSourceFileBuilder($this->classRenderer, $inputData->currentControllerContent), 87 | new LaravelPresenterSourceFileBuilder($this->classRenderer)); 88 | $usecaseInputData = new UseCaseCreateInputData( 89 | $namespaces, 90 | new UseCaseSchema($inputData->controllerName, $inputData->actionName), 91 | $inputData->inputDataFields, 92 | $inputData->outputDataFields 93 | ); 94 | 95 | $usecaseCreateInteractor->handle($usecaseInputData); 96 | 97 | return $usecaseCreateDataCollector->getRecentOutputData(); 98 | } 99 | } -------------------------------------------------------------------------------- /src/UseCases/ClarcObject/Create/ClarcObjectCreateOutputData.php: -------------------------------------------------------------------------------- 1 | controllerSourceFile = $controllerSourceFile; 63 | $this->inputPortSourceFile = $inputPortSourceFile; 64 | $this->interactorSourceFile = $interactorDataSourceFile; 65 | $this->inputDataSourceFile = $inputDataSourceFile; 66 | $this->outputPortSourceFile = $outputPortSourceFile; 67 | $this->outputDataSourceFile = $outputDataSourceFile; 68 | $this->presenterSourceFile = $presenterSourceFile; 69 | $this->viewModelSourceFile = $viewModelSourceFile; 70 | } 71 | 72 | /** 73 | * @return SourceFileData 74 | */ 75 | public function getControllerSourceFile(): SourceFileData 76 | { 77 | return $this->controllerSourceFile; 78 | } 79 | 80 | /** 81 | * @return SourceFileData 82 | */ 83 | public function getInputPortSourceFile(): SourceFileData 84 | { 85 | return $this->inputPortSourceFile; 86 | } 87 | 88 | /** 89 | * @return SourceFileData 90 | */ 91 | public function getInteractorSourceFile(): SourceFileData 92 | { 93 | return $this->interactorSourceFile; 94 | } 95 | 96 | /** 97 | * @return SourceFileData 98 | */ 99 | public function getInputDataSourceFile(): SourceFileData 100 | { 101 | return $this->inputDataSourceFile; 102 | } 103 | 104 | /** 105 | * @return SourceFileData 106 | */ 107 | public function getOutputPortSourceFile(): SourceFileData 108 | { 109 | return $this->outputPortSourceFile; 110 | } 111 | 112 | /** 113 | * @return SourceFileData 114 | */ 115 | public function getOutputDataSourceFile(): SourceFileData 116 | { 117 | return $this->outputDataSourceFile; 118 | } 119 | 120 | /** 121 | * @return SourceFileData 122 | */ 123 | public function getPresenterSourceFile(): SourceFileData 124 | { 125 | return $this->presenterSourceFile; 126 | } 127 | 128 | /** 129 | * @return SourceFileData 130 | */ 131 | public function getViewModelSourceFile(): SourceFileData 132 | { 133 | return $this->viewModelSourceFile; 134 | } 135 | } -------------------------------------------------------------------------------- /src/UseCases/ClarcObject/Create/ClarcObjectCreateOutputPortInterface.php: -------------------------------------------------------------------------------- 1 | recentOutputData; 23 | } 24 | 25 | function output(UseCaseCreateOutputData $outputData) 26 | { 27 | $this->recentOutputData = $outputData; 28 | } 29 | } -------------------------------------------------------------------------------- /src/UseCases/ClarcProvider/AppendUseCase/ClarcProviderAppendUseCaseInputData.php: -------------------------------------------------------------------------------- 1 | clarcProviderCode = $clarcProviderCode; 62 | $this->clarcDebugProviderCode = $clarcDebugProviderCode; 63 | $this->identifer = $identifer; 64 | $this->inputPortName = $inputPortName; 65 | $this->interactorName = $interactorName; 66 | $this->outputPortName = $outputPortName; 67 | $this->presenterName = $presenterName; 68 | } 69 | } -------------------------------------------------------------------------------- /src/UseCases/ClarcProvider/AppendUseCase/ClarcProviderAppendUseCaseInputPortInterface.php: -------------------------------------------------------------------------------- 1 | outputPort = $outputPort; 26 | } 27 | 28 | 29 | function handle(ClarcProviderAppendUseCaseInputData $inputData) 30 | { 31 | $parser = new ClassParser(); 32 | $classMeta = $parser->parse($inputData->clarcProviderCode); 33 | 34 | $classMeta->setupMethods() 35 | ->updateMethod('registerUseCases', function ($definition) use ($inputData) { 36 | $body = $definition->getBody(); 37 | $definition->clearBody(); 38 | 39 | $scripts = $this->divideScripts($body); 40 | 41 | $comment = '// ' . $inputData->identifer; 42 | $inputPortBindCode = $this->makeBindCode($inputData->inputPortName, $inputData->interactorName); 43 | $outputPortBindCode = $this->makeBindCode($inputData->outputPortName, $inputData->presenterName); 44 | $script = new UseCaseSettingScript($comment, $inputPortBindCode, $outputPortBindCode); 45 | 46 | if (!$this->exists($scripts, $script)) { 47 | array_push($scripts, $script); 48 | } 49 | 50 | usort($scripts, function ($l, $r) { 51 | return $l->getKey() < $r->getKey() ? -1 : 1; 52 | }); 53 | 54 | foreach ($scripts as $script) { 55 | $lines = $script->getScripts(); 56 | foreach ($lines as $line) 57 | { 58 | $definition->addBody($line); 59 | } 60 | } 61 | }); 62 | 63 | $renderer = new ClassRenderer(); 64 | $rendered = $renderer->render($classMeta); 65 | 66 | $outputData = new ClarcProviderAppendUseCaseOutputData($rendered); 67 | 68 | $this->outputPort->output($outputData); 69 | } 70 | 71 | private function makeBindCode(string $interfaceName, string $implementName) 72 | { 73 | return '$this->app->bind( ' . $interfaceName . '::class, ' . $implementName . '::class);'; 74 | } 75 | 76 | /** 77 | * @param array $lines 78 | * @return AppendUseCaseScriptInterface[] 79 | * @throws \Exception 80 | */ 81 | private function divideScripts(array $lines): array 82 | { 83 | $results = []; 84 | $lineCount = count($lines); 85 | for ($i = 0; $i < $lineCount; $i++) 86 | { 87 | $line = $lines[$i]; 88 | if ($this->startsWith($line, '//')) { 89 | $comment = $line; 90 | $bindInputPort = $lines[++$i]; 91 | $bindOutputPort = $lines[++$i]; 92 | array_push($results, new UseCaseSettingScript($comment, $bindInputPort, $bindOutputPort)); 93 | } else if ($this->startsWith($line, '$this->app->bind(')) { 94 | $bindInputPort = $line; 95 | $bindOutputPort = $lines[++$i]; 96 | array_push($results, new UseCaseSettingScript(null, $bindInputPort, $bindOutputPort)); 97 | } else { 98 | throw new \Exception(); 99 | } 100 | } 101 | 102 | return $results; 103 | } 104 | 105 | private function exists(array $scripts, UseCaseSettingScript $script): bool 106 | { 107 | $duplicates = array_filter($scripts, function ($s) use ($script) { return $s->getKey() === $script->getKey(); }); 108 | return !empty($duplicates); 109 | } 110 | 111 | private function startsWith(string $target, $word): bool 112 | { 113 | return strpos($target, $word) === 0; 114 | } 115 | } 116 | 117 | class UseCaseSetting 118 | { 119 | public $comment; 120 | public $bindInputPort; 121 | public $bindOutputPort; 122 | 123 | /** 124 | * UseCaseSetting constructor. 125 | * @param $comment 126 | * @param $bindInputPort 127 | * @param $bindOutputPort 128 | */ 129 | public function __construct($comment, $bindInputPort, $bindOutputPort) 130 | { 131 | $this->comment = $comment; 132 | $this->bindInputPort = $bindInputPort; 133 | $this->bindOutputPort = $bindOutputPort; 134 | } 135 | } -------------------------------------------------------------------------------- /src/UseCases/ClarcProvider/AppendUseCase/ClarcProviderAppendUseCaseOutputData.php: -------------------------------------------------------------------------------- 1 | clarcProviderCode = $clarcProviderCode; 21 | } 22 | 23 | /** 24 | * @return mixed 25 | */ 26 | public function getClarcProviderCode() 27 | { 28 | return $this->clarcProviderCode; 29 | } 30 | } -------------------------------------------------------------------------------- /src/UseCases/ClarcProvider/AppendUseCase/ClarcProviderAppendUseCaseOutputPortInterface.php: -------------------------------------------------------------------------------- 1 | commentLine = $commentLine; 22 | $this->bindInputPortLine = $bindInputPortLine; 23 | $this->bindOutputPortLine = $bindOutputPortLine; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | function getKey(): string 30 | { 31 | if (is_null($this->commentLine)) { 32 | return ''; 33 | } 34 | 35 | return $this->commentLine; 36 | } 37 | 38 | /** 39 | * @return string[] 40 | */ 41 | function getScripts(): array 42 | { 43 | if (is_null($this->commentLine)) { 44 | return [$this->bindInputPortLine, $this->bindOutputPortLine]; 45 | } 46 | 47 | return [$this->commentLine, $this->bindInputPortLine, $this->bindOutputPortLine]; 48 | } 49 | } -------------------------------------------------------------------------------- /tests/BasicCodeGenerate/BasicCodeGenerateTest.php: -------------------------------------------------------------------------------- 1 | controllerContents(), 35 | $actionName, 36 | $inputDataFields, 37 | $outputDataFields); 38 | $interactor->handle($inputData); 39 | $outputData = $presenter->getOutputData(); 40 | 41 | $projectPath = '..\\StrictLaraClean\\src'; 42 | $packagePath = $projectPath . '\\packages'; 43 | 44 | $directorySuffix = $controllerName . '\\' . $actionName; 45 | 46 | $controllerPath = $projectPath . '\\app\\Http\\Controllers'; 47 | if (!file_exists($controllerPath)) { 48 | mkdir($controllerPath, "0777", true); 49 | } 50 | $inputPortPath = $packagePath . '\\InputPorts\\' . $directorySuffix; 51 | if (!file_exists($inputPortPath)) { 52 | mkdir($inputPortPath, "0777", true); 53 | } 54 | $interactorPath = $packagePath . '\\Interactors\\' . $directorySuffix; 55 | if (!file_exists($interactorPath)) { 56 | mkdir($interactorPath, "0777", true); 57 | } 58 | $outputPortPath = $packagePath . '\\OutputPorts\\' . $directorySuffix; 59 | if (!file_exists($outputPortPath)) { 60 | mkdir($outputPortPath, "0777", true); 61 | } 62 | $presenterPath = $projectPath . '\\app\\Http\\Presenters\\' . $controllerName; 63 | if (!file_exists($presenterPath)) { 64 | mkdir($presenterPath, "0777", true); 65 | } 66 | 67 | $usecaseName = $controllerName . $actionName; 68 | 69 | file_put_contents($controllerPath . '\\' . $controllerName . 'Controller.php', $outputData->getControllerSourceFile()->getContents()); 70 | file_put_contents($inputPortPath . '\\' . $usecaseName . 'InputPortInterface.php', $outputData->getInputPortSourceFile()->getContents()); 71 | file_put_contents($inputPortPath . '\\' . $usecaseName . 'InputData.php', $outputData->getInputDataSourceFile()->getContents()); 72 | file_put_contents($interactorPath . '\\' . $usecaseName . 'Interactor.php', $outputData->getInteractorSourceFile()->getContents()); 73 | file_put_contents($outputPortPath . '\\' . $usecaseName . 'OutputPortInterface.php', $outputData->getOutputPortSourceFile()->getContents()); 74 | file_put_contents($outputPortPath . '\\' . $usecaseName . 'OutputData.php', $outputData->getOutputDataSourceFile()->getContents()); 75 | file_put_contents($presenterPath . '\\' . $usecaseName . 'Presenter.php', $outputData->getPresenterSourceFile()->getContents()); 76 | 77 | // echo $outputData->getControllerSourceFile()->getContents(); 78 | // echo $outputData->getInputPortSourceFile()->getContents(); 79 | // echo $outputData->getInteractorSourceFile()->getContents(); 80 | // echo $outputData->getInputDataSourceFile()->getContents(); 81 | // echo $outputData->getOutputPortSourceFile()->getContents(); 82 | // echo $outputData->getOutputDataSourceFile()->getContents(); 83 | // echo $outputData->getPresenterSourceFile()->getContents(); 84 | } 85 | 86 | private function controllerContents() 87 | { 88 | return 89 | 'outputData; 23 | } 24 | 25 | function output(ClarcObjectCreateOutputData $outputData) 26 | { 27 | $this->outputData = $outputData; 28 | } 29 | } -------------------------------------------------------------------------------- /tests/ClarcProviderAppendUseCaseInteractor/ClarcProviderAppendUseCaseInteractorTest.php: -------------------------------------------------------------------------------- 1 | handle($inputData); 32 | $outputdata = $presenter->outputData; 33 | 34 | file_put_contents($providerPath . '\\ClarcProvider.txt', $outputdata->getClarcProviderCode()); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/ClarcProviderAppendUseCaseInteractor/TestClarcProviderAppendUseCaseUseCasePresenter.php: -------------------------------------------------------------------------------- 1 | outputData = $outputData; 20 | } 21 | } -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |