├── .gitignore ├── LICENSE ├── composer.json ├── phpunit.xml ├── readme.md ├── src ├── Commands │ ├── Custom │ │ └── MakeModuleRepo.php │ ├── Graphql │ │ ├── MakeModuleGraphqlMutation.php │ │ ├── MakeModuleGraphqlQuery.php │ │ └── MakeModuleGraphqlType.php │ ├── Laravel │ │ ├── MakeModuleController.php │ │ ├── MakeModuleEvent.php │ │ ├── MakeModuleException.php │ │ ├── MakeModuleJob.php │ │ ├── MakeModuleListener.php │ │ ├── MakeModuleMigration.php │ │ ├── MakeModuleModel.php │ │ ├── MakeModuleNotification.php │ │ ├── MakeModulePolicy.php │ │ ├── MakeModuleRequest.php │ │ ├── MakeModuleResource.php │ │ ├── MakeModuleRule.php │ │ └── MakeModuleSeeder.php │ └── MakeModule.php ├── Exceptions │ └── File │ │ ├── FileIsNotWritableException.php │ │ └── FileNotExistException.php ├── Helpers │ ├── ComposerParser.php │ ├── File.php │ └── ModuleHandler.php ├── LaravelModuleCreatorServiceProvider.php ├── Stubs │ ├── Graphql │ │ ├── mutation.stub │ │ ├── query.stub │ │ └── type.stub │ ├── Providers │ │ └── ServiceProvider.stub │ ├── Repo.stub │ ├── Routes.stub │ └── composer.stub └── Traits │ ├── GraphqlCommand.php │ └── RequireModule.php └── tests └── Unit ├── ComposerParserTest.php ├── FileTest.php └── ModuleHandlerTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea/ 3 | composer.lock 4 | .phpunit.result.cache 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Hesam Mousavi 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 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hesammousavi/laravel-module-creator", 3 | "description": "a tool for build your laravel modules", 4 | "license": "MIT", 5 | "homepage": "https://github.com/Hesammousavi/laravel-module-creator", 6 | "authors": [ 7 | { 8 | "name": "Hesam Mousavi", 9 | "email": "hesammoousavi@gmail.com" 10 | } 11 | ], 12 | "keywords": ["Laravel Module" , "Laravel" , "Module Creator"], 13 | "scripts": { 14 | "test": "./vendor/bin/phpunit" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Hesammousavi\\LaravelModuleCreator\\": "src/" 19 | } 20 | }, 21 | "autoload-dev": { 22 | "psr-4": { 23 | "Tests\\": "tests/" 24 | } 25 | }, 26 | "extra": { 27 | "laravel": { 28 | "providers": [ 29 | "Hesammousavi\\LaravelModuleCreator\\LaravelModuleCreatorServiceProvider" 30 | ] 31 | } 32 | }, 33 | "require": { 34 | "illuminate/support": "^9.0 || ^10.0 || ^11.0 || ^12.0 ", 35 | "ext-json": "*" 36 | }, 37 | "require-dev": { 38 | "roave/security-advisories": "dev-latest", 39 | "phpunit/phpunit": "^9.5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/Unit 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Laravel package for Create Laravel Modules from a template 2 | 3 | # Requirements 4 | Laravel 9 or later 5 | PHP 8.0 or later 6 | 7 | # Install 8 | 9 | You can install the package via composer: 10 | ```bash 11 | composer require hesammousavi/laravel-module-creator 12 | ``` 13 | 14 | # Usage 15 | 16 | first you must create your module 17 | 18 | ```bash 19 | php artisan m:make Roocket/User 20 | ``` 21 | 22 | finally, run this code : 23 | 24 | ``` 25 | composer update 26 | ``` 27 | 28 | beautifully done 29 | 30 | 31 | # Your Module Commands 32 | 33 | you have different commands to do anything with your module 34 | ```bash 35 | php artisan 36 | ``` 37 | 38 | you can see these commands for your usage 39 | 40 | ``` 41 | module 42 | m:make create a new module to develop project 43 | m:make:controller Create a new controller class 44 | m:make:request Create a new request class 45 | m:make:resource Create a new resource class 46 | m:make:graphql-mutation Create a new Graphql Mutation class 47 | m:make:graphql-query Create a new Graphql Query class 48 | m:make:graphql-type Create a new Graphql Type class 49 | m:make:migration Create a new migration file 50 | m:make:model Create a new Eloquent model class 51 | m:make:repo Create a new repo class 52 | m:make:seeder Create a new Seeder class 53 | m:make:rule Create a new Rule Validation class 54 | m:make:event Create a new Event class 55 | m:make:listener Create a new Listener class 56 | ``` 57 | 58 | 59 | you can build model for your module like 60 | 61 | ```bash 62 | php artisan m:make:model 63 | php artisan m:make:model Roocket/user User 64 | ``` 65 | -------------------------------------------------------------------------------- /src/Commands/Custom/MakeModuleRepo.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 54 | 55 | return base_path("modules/{$this->argument('module')}/src/Database/Repo") . '/' . str_replace('\\', '/', $name) . '.php'; 56 | } 57 | 58 | protected function rootNamespace() 59 | { 60 | return str_replace('/', '\\', $this->argument('module')) . '\Database\Repo'; 61 | } 62 | 63 | protected function getStub() 64 | { 65 | return __DIR__ . '/../../Stubs/Repo.stub'; 66 | } 67 | 68 | /** 69 | * Get the console command arguments. 70 | * 71 | * @return array 72 | */ 73 | protected function getArguments() 74 | { 75 | return [ 76 | ['module', InputArgument::REQUIRED, 'the name of the module'], 77 | ['name', InputArgument::REQUIRED, 'The name of the class'], 78 | ]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Commands/Graphql/MakeModuleGraphqlMutation.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 51 | 52 | return base_path("modules/{$this->argument('module')}/src/Graphql/Mutations") . '/' . str_replace('\\', '/', $name) . '.php'; 53 | } 54 | 55 | 56 | protected function rootNamespace() 57 | { 58 | return str_replace('/', '\\', $this->argument('module')) . '\Graphql\Mutations'; 59 | } 60 | 61 | protected function getStub() 62 | { 63 | return __DIR__ . '/../../Stubs/Graphql/mutation.stub'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Commands/Graphql/MakeModuleGraphqlQuery.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 52 | 53 | return base_path("modules/{$this->argument('module')}/src/Graphql/Queries") . '/' . str_replace('\\', '/', $name) . '.php'; 54 | } 55 | 56 | 57 | protected function rootNamespace() 58 | { 59 | return str_replace('/', '\\', $this->argument('module')) . '\Graphql\Queries'; 60 | } 61 | 62 | protected function getStub() 63 | { 64 | return __DIR__ . '/../../Stubs/Graphql/query.stub'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Commands/Graphql/MakeModuleGraphqlType.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 51 | 52 | return base_path("modules/{$this->argument('module')}/src/Graphql/Types") . '/' . str_replace('\\', '/', $name) . '.php'; 53 | } 54 | 55 | 56 | protected function rootNamespace() 57 | { 58 | return str_replace('/', '\\', $this->argument('module')) . '\Graphql\Types'; 59 | } 60 | 61 | protected function getStub() 62 | { 63 | return __DIR__ . '/../../Stubs/Graphql/type.stub'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleController.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 54 | 55 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 56 | } 57 | 58 | 59 | protected function rootNamespace() 60 | { 61 | return str_replace('/', '\\', $this->argument('module')); 62 | } 63 | 64 | /** 65 | * Generate the form requests for the given model and classes. 66 | * 67 | * @param string $modelClass 68 | * @param string $storeRequestClass 69 | * @param string $updateRequestClass 70 | * @return array 71 | */ 72 | protected function generateFormRequests($modelClass, $storeRequestClass, $updateRequestClass) 73 | { 74 | $storeRequestClass = 'Store'.class_basename($modelClass).'Request'; 75 | 76 | $this->call('m:make:request', [ 77 | 'module' => $this->argument('module'), 78 | 'name' => $storeRequestClass, 79 | ]); 80 | 81 | $updateRequestClass = 'Update'.class_basename($modelClass).'Request'; 82 | 83 | $this->call('m:make:request', [ 84 | 'module' => $this->argument('module'), 85 | 'name' => $storeRequestClass, 86 | ]); 87 | 88 | return [$storeRequestClass, $updateRequestClass]; 89 | } 90 | 91 | /** 92 | * Get the console command arguments. 93 | * 94 | * @return array 95 | */ 96 | protected function getArguments() 97 | { 98 | return [ 99 | ['module', InputArgument::REQUIRED, 'the name of the module'], 100 | ['name', InputArgument::REQUIRED, 'The name of the class'], 101 | ]; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleEvent.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 53 | 54 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 55 | } 56 | 57 | 58 | protected function rootNamespace() 59 | { 60 | return str_replace('/', '\\', $this->argument('module')); 61 | } 62 | 63 | /** 64 | * Get the console command arguments. 65 | * 66 | * @return array 67 | */ 68 | protected function getArguments() 69 | { 70 | return [ 71 | ['module', InputArgument::REQUIRED, 'the name of the module'], 72 | ['name', InputArgument::REQUIRED, 'The name of the class'], 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleException.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 54 | 55 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 56 | } 57 | 58 | 59 | protected function rootNamespace() 60 | { 61 | return str_replace('/', '\\', $this->argument('module')); 62 | } 63 | 64 | /** 65 | * Get the console command arguments. 66 | * 67 | * @return array 68 | */ 69 | protected function getArguments() 70 | { 71 | return [ 72 | ['module', InputArgument::REQUIRED, 'the name of the module'], 73 | ['name', InputArgument::REQUIRED, 'The name of the class'], 74 | ]; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleJob.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 54 | 55 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 56 | } 57 | 58 | 59 | protected function rootNamespace() 60 | { 61 | return str_replace('/', '\\', $this->argument('module')); 62 | } 63 | 64 | /** 65 | * Get the console command arguments. 66 | * 67 | * @return array 68 | */ 69 | protected function getArguments() 70 | { 71 | return [ 72 | ['module', InputArgument::REQUIRED, 'the name of the module'], 73 | ['name', InputArgument::REQUIRED, 'The name of the class'], 74 | ]; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleListener.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 53 | 54 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 55 | } 56 | 57 | 58 | protected function rootNamespace() 59 | { 60 | return str_replace('/', '\\', $this->argument('module')); 61 | } 62 | 63 | /** 64 | * Get the console command arguments. 65 | * 66 | * @return array 67 | */ 68 | protected function getArguments() 69 | { 70 | return [ 71 | ['module', InputArgument::REQUIRED, 'the name of the module'], 72 | ['name', InputArgument::REQUIRED, 'The name of the class'], 73 | ]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleMigration.php: -------------------------------------------------------------------------------- 1 | composer = $composer; 58 | $this->file = $filesystem; 59 | } 60 | 61 | public function handle() 62 | { 63 | $this->checkModuleExists(); 64 | 65 | // It's possible for the developer to specify the tables to modify in this 66 | // schema operation. The developer may also specify if this table needs 67 | // to be freshly created so we can create the appropriate migrations. 68 | $name = Str::snake(trim($this->input->getArgument('name'))); 69 | 70 | $table = $this->input->getOption('table'); 71 | 72 | $create = $this->input->getOption('create') ?: false; 73 | 74 | // If no table was given as an option but a create option is given then we 75 | // will use the "create" option as the table name. This allows the devs 76 | // to pass a table name into this option as a short-cut for creating. 77 | if (! $table && is_string($create)) { 78 | $table = $create; 79 | 80 | $create = true; 81 | } 82 | 83 | // Next, we will attempt to guess the table name if this the migration has 84 | // "create" in the name. This will allow us to provide a convenient way 85 | // of creating migrations that create new tables for the application. 86 | if (! $table) { 87 | [$table, $create] = TableGuesser::guess($name); 88 | } 89 | 90 | // Now we are ready to write the migration out to disk. Once we've written 91 | // the migration out, we will dump-autoload for the entire framework to 92 | // make sure that the migrations are registered by the class loaders. 93 | $this->writeMigration($name, $table, $create); 94 | 95 | $this->composer->dumpAutoloads(); 96 | } 97 | 98 | /** 99 | * Write the migration file to disk. 100 | * 101 | * @param string $name 102 | * @param string $table 103 | * @param bool $create 104 | * @print string 105 | */ 106 | protected function writeMigration($name, $table, $create) 107 | { 108 | $creator = new MigrationCreator($this->file , ''); 109 | 110 | $file = $creator->create( 111 | $name, $this->getMigrationPath(), $table, $create 112 | ); 113 | 114 | $this->line("Created Migration: {$file}"); 115 | } 116 | 117 | protected function getMigrationPath() 118 | { 119 | if (! is_null($targetPath = $this->input->getOption('path'))) { 120 | return $this->laravel->basePath().'/'.$targetPath; 121 | } 122 | 123 | return base_path("modules/{$this->argument('module')}/src/Database/Migrations"); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleModel.php: -------------------------------------------------------------------------------- 1 | argument('name')))); 53 | 54 | if ($this->option('pivot')) { 55 | $table = Str::singular($table); 56 | } 57 | 58 | $this->call(MakeModuleMigration::class, [ 59 | 'module' => $this->argument('module'), 60 | 'name' => "create_{$table}_table", 61 | '--create' => $table, 62 | ]); 63 | } 64 | 65 | /** 66 | * Create a controller for the model. 67 | * 68 | * @return void 69 | */ 70 | protected function createController() 71 | { 72 | $controller = Str::studly(class_basename($this->argument('name'))); 73 | 74 | $modelName = $this->qualifyClass($this->getNameInput()); 75 | 76 | $this->call(MakeModuleController::class, array_filter([ 77 | 'name' => "{$controller}Controller", 78 | 'module' => $this->argument('module'), 79 | '--model' => $this->option('resource') || $this->option('api') ? $modelName : null, 80 | '--api' => $this->option('api'), 81 | '--requests' => $this->option('requests') || $this->option('all'), 82 | ])); 83 | } 84 | 85 | /** 86 | * Get the destination class path. 87 | * 88 | * @param string $name 89 | * @return string 90 | */ 91 | protected function getPath($name) 92 | { 93 | $name = Str::replaceFirst($this->rootNamespace(), '', $name); 94 | 95 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 96 | } 97 | 98 | 99 | protected function rootNamespace() 100 | { 101 | return str_replace('/', '\\', $this->argument('module')); 102 | } 103 | 104 | /** 105 | * Get the console command arguments. 106 | * 107 | * @return array 108 | */ 109 | protected function getArguments() 110 | { 111 | return [ 112 | ['module', InputArgument::REQUIRED, 'the name of the module'], 113 | ['name', InputArgument::REQUIRED, 'The name of the class'], 114 | ]; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleNotification.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 54 | 55 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 56 | } 57 | 58 | 59 | protected function rootNamespace() 60 | { 61 | return str_replace('/', '\\', $this->argument('module')); 62 | } 63 | 64 | /** 65 | * Get the console command arguments. 66 | * 67 | * @return array 68 | */ 69 | protected function getArguments() 70 | { 71 | return [ 72 | ['module', InputArgument::REQUIRED, 'the name of the module'], 73 | ['name', InputArgument::REQUIRED, 'The name of the class'], 74 | ]; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModulePolicy.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 48 | 49 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 50 | } 51 | 52 | 53 | protected function rootNamespace() 54 | { 55 | return str_replace('/', '\\', $this->argument('module')); 56 | } 57 | 58 | /** 59 | * Get the console command arguments. 60 | * 61 | * @return array 62 | */ 63 | protected function getArguments() 64 | { 65 | return [ 66 | ['module', InputArgument::REQUIRED, 'the name of the module'], 67 | ['name', InputArgument::REQUIRED, 'The name of the class'], 68 | ]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleRequest.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 49 | 50 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 51 | } 52 | 53 | 54 | protected function rootNamespace() 55 | { 56 | return str_replace('/', '\\', $this->argument('module')); 57 | } 58 | 59 | /** 60 | * Generate the form requests for the given model and classes. 61 | * 62 | * @param string $modelClass 63 | * @param string $storeRequestClass 64 | * @param string $updateRequestClass 65 | * @return array 66 | */ 67 | protected function generateFormRequests($modelClass, $storeRequestClass, $updateRequestClass) 68 | { 69 | $storeRequestClass = 'Store'.class_basename($modelClass).'Request'; 70 | 71 | $this->call('m:make:request', [ 72 | 'module' => $this->argument('module'), 73 | 'name' => $storeRequestClass, 74 | ]); 75 | 76 | $updateRequestClass = 'Update'.class_basename($modelClass).'Request'; 77 | 78 | $this->call('m:make:request', [ 79 | 'module' => $this->argument('module'), 80 | 'name' => $storeRequestClass, 81 | ]); 82 | 83 | return [$storeRequestClass, $updateRequestClass]; 84 | } 85 | 86 | /** 87 | * Get the console command arguments. 88 | * 89 | * @return array 90 | */ 91 | protected function getArguments() 92 | { 93 | return [ 94 | ['module', InputArgument::REQUIRED, 'the name of the module'], 95 | ['name', InputArgument::REQUIRED, 'The name of the class'], 96 | ]; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleResource.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 49 | 50 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 51 | } 52 | 53 | 54 | protected function rootNamespace() 55 | { 56 | return str_replace('/', '\\', $this->argument('module')); 57 | } 58 | 59 | 60 | /** 61 | * Get the console command arguments. 62 | * 63 | * @return array 64 | */ 65 | protected function getArguments() 66 | { 67 | return [ 68 | ['module', InputArgument::REQUIRED, 'the name of the module'], 69 | ['name', InputArgument::REQUIRED, 'The name of the class'], 70 | ]; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleRule.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 49 | 50 | return base_path("modules/{$this->argument('module')}/src") . '/' . str_replace('\\', '/', $name) . '.php'; 51 | } 52 | 53 | 54 | protected function rootNamespace() 55 | { 56 | return str_replace('/', '\\', $this->argument('module')); 57 | } 58 | 59 | /** 60 | * Get the console command arguments. 61 | * 62 | * @return array 63 | */ 64 | protected function getArguments() 65 | { 66 | return [ 67 | ['module', InputArgument::REQUIRED, 'the name of the module'], 68 | ['name', InputArgument::REQUIRED, 'The name of the class'], 69 | ]; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Commands/Laravel/MakeModuleSeeder.php: -------------------------------------------------------------------------------- 1 | rootNamespace(), '', $name); 52 | 53 | return base_path("modules/{$this->argument('module')}/src/Database/Seeders") . '/' . str_replace('\\', '/', $name) . '.php'; 54 | } 55 | 56 | 57 | protected function rootNamespace() 58 | { 59 | return str_replace('/', '\\', $this->argument('module')) . '\Database\Seeders'; 60 | } 61 | 62 | /** 63 | * Get the console command arguments. 64 | * 65 | * @return array 66 | */ 67 | protected function getArguments() 68 | { 69 | return [ 70 | ['module', InputArgument::REQUIRED, 'the name of the module'], 71 | ['name', InputArgument::REQUIRED, 'The name of the class'], 72 | ]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Commands/MakeModule.php: -------------------------------------------------------------------------------- 1 | moduleName = $this->argument('name'); 33 | 34 | $files = [ 35 | 'ServiceProvider.php' => [ 36 | 'name' => '{name}ServiceProvider', 37 | 'namespace' => 'Providers', 38 | 'stub' => __DIR__ . '/../Stubs/Providers/ServiceProvider.stub', 39 | 'rootPath' => "/src/Providers/", 40 | 'extensions' => 'php' 41 | ], 42 | 'routes.php' => [ 43 | 'name' => 'routes', 44 | 'stub' => __DIR__ . '/../Stubs/Routes.stub', 45 | 'rootPath' => "/src/Routes/", 46 | 'extensions' => 'php' 47 | ], 48 | 'composer.json' => [ 49 | 'name' => 'composer', 50 | 'stub' => __DIR__ . '/../Stubs/composer.stub', 51 | 'rootPath' => "/", 52 | 'extensions' => 'json', 53 | ] 54 | ]; 55 | 56 | foreach ($files as $file => $meta) { 57 | $this->currentFile = $meta; 58 | parent::handle(); 59 | } 60 | 61 | (new ModuleHandler($this->moduleName))->add(); 62 | } 63 | 64 | public function getNameInput() 65 | { 66 | $name = substr( $this->argument('name'), strrpos( $this->argument('name'), '/') + 1); 67 | return str_replace('{name}' , $name , $this->currentFile['name']); 68 | } 69 | 70 | /** 71 | * Build the class with the given name. 72 | * 73 | * @param string $name 74 | * @return string 75 | * 76 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 77 | */ 78 | protected function buildClass($name) 79 | { 80 | $stub = $this->files->get($this->getStub()); 81 | 82 | return $this->replaceNamespace($stub, $name) 83 | ->replaceModuleNamesapce($stub , $name) 84 | ->replaceClass($stub, $name); 85 | } 86 | 87 | 88 | /** 89 | * Replace the class name for the given stub. 90 | * 91 | * @param string $stub 92 | * @param string $name 93 | * @return string 94 | */ 95 | protected function replaceClass($stub, $name) 96 | { 97 | $class = substr( $name, strrpos( $name, '\\') + 1); 98 | 99 | return str_replace(['DummyClass', '{{ class }}', '{{class}}'], $class, $stub); 100 | } 101 | 102 | /** 103 | * Replace the class name for the given stub. 104 | * 105 | * @param string $stub 106 | * @param string $name 107 | * @return $this 108 | */ 109 | protected function replaceModuleNamesapce(&$stub, $name) 110 | { 111 | $namespace = str_replace('\\' , '\\\\', $this->rootNamespace()); 112 | $namespaceName = Str::lower(str_replace('\\', '/' , $this->rootNamespace())); 113 | $moduleName = Arr::last(explode('\\', $namespace)); 114 | 115 | $searches = [ 116 | ['{{ moduleNamespace }}' , '{{ moduleNamespaceName }}' , '{{ moduleName }}'] , 117 | ['{{moduleNamespace}}' , '{{moduleNamespaceName}}' , '{{moduleName}}'] 118 | ]; 119 | 120 | foreach ($searches as $search ) { 121 | $stub = str_replace($search, [ $namespace , $namespaceName , $moduleName ], $stub); 122 | } 123 | 124 | return $this; 125 | } 126 | 127 | 128 | /** 129 | * Get the full namespace for a given class, without the class name. 130 | * 131 | * @param string $name 132 | * @return string 133 | */ 134 | protected function getNamespace($name) 135 | { 136 | $rootNamespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); 137 | 138 | if(isset($this->currentFile['namespace'])) 139 | return "$rootNamespace\\{$this->currentFile['namespace']}"; 140 | 141 | return $rootNamespace; 142 | } 143 | 144 | 145 | /** 146 | * Get the destination class path. 147 | * 148 | * @param string $name 149 | * @return string 150 | */ 151 | protected function getPath($name) 152 | { 153 | return "{$this->getModulePath()}/{$this->getNameInput()}.{$this->currentFile['extensions']}"; 154 | } 155 | 156 | protected function getStub() 157 | { 158 | return $this->currentFile['stub']; 159 | } 160 | 161 | protected function rootNamespace() 162 | { 163 | return str_replace('/' , '\\' , $this->moduleName); 164 | } 165 | 166 | protected function getModulePath() 167 | { 168 | return base_path("modules/{$this->moduleName}/{$this->currentFile['rootPath']}"); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Exceptions/File/FileIsNotWritableException.php: -------------------------------------------------------------------------------- 1 | getFilePath()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Exceptions/File/FileNotExistException.php: -------------------------------------------------------------------------------- 1 | getFilePath()); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Helpers/ComposerParser.php: -------------------------------------------------------------------------------- 1 | file = new File($composerFilePath); 12 | } 13 | 14 | public function getOption(string $key) 15 | { 16 | if(!array_key_exists($key, $this->decodeFile())) return null; 17 | 18 | return $this->decodeFile()[$key]; 19 | } 20 | 21 | public function setOption(string $key, $value): void 22 | { 23 | $composerContent = $this->decodeFile(); 24 | 25 | $composerContent[$key] = $value; 26 | 27 | $this->file->write( 28 | $this->encodeFile($composerContent) 29 | ); 30 | } 31 | 32 | private function encodeFile($content): string 33 | { 34 | return json_encode($content, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); 35 | } 36 | 37 | private function decodeFile() 38 | { 39 | return json_decode($this->file->read(), true); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Helpers/File.php: -------------------------------------------------------------------------------- 1 | filePath = $filePath; 18 | 19 | $this->checkFileExistence(); 20 | } 21 | 22 | public function getFilePath(): string 23 | { 24 | return $this->filePath; 25 | } 26 | 27 | public function read(): string 28 | { 29 | $content = $this->readFile(); 30 | 31 | return $content; 32 | } 33 | 34 | /** 35 | * @throws FileIsNotWritableException 36 | */ 37 | public function write(string $content): void 38 | { 39 | $this->checkIsFileWritable(); 40 | $this->writeFile($content); 41 | } 42 | 43 | private function readFile(): string 44 | { 45 | return file_get_contents($this->filePath); 46 | } 47 | 48 | private function writeFile(string $content): void 49 | { 50 | file_put_contents($this->filePath, $content); 51 | } 52 | 53 | /** 54 | * @throws FileIsNotWritableException 55 | */ 56 | private function checkIsFileWritable(): void 57 | { 58 | if (!is_writable($this->filePath)) throw new FileIsNotWritableException($this); 59 | } 60 | 61 | /** 62 | * @throws FileNotExistException 63 | */ 64 | private function checkFileExistence(): void 65 | { 66 | if (!file_exists($this->filePath)) throw new FileNotExistException($this); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Helpers/ModuleHandler.php: -------------------------------------------------------------------------------- 1 | moduleName = $moduleName; 13 | $this->composerFilePath = $composerFilePath; 14 | } 15 | 16 | public function add(): void 17 | { 18 | $this->addRequire(); 19 | $this->addRepository(); 20 | } 21 | 22 | private function addRequire() 23 | { 24 | $composerParser = new ComposerParser($this->composerFilePath); 25 | $require = $composerParser->getOption('require'); 26 | $require[strtolower($this->moduleName)] = 'dev-main'; 27 | $composerParser->setOption('require', $require); 28 | } 29 | 30 | private function addRepository() 31 | { 32 | $composerParser = new ComposerParser($this->composerFilePath); 33 | $repositories = $composerParser->getOption('repositories'); 34 | if (!is_null($repositories)) { 35 | $repositories[] = $this->createRepository(); 36 | } else { 37 | $repositories = [$this->createRepository()]; 38 | } 39 | $composerParser->setOption('repositories', $repositories); 40 | } 41 | 42 | private function createRepository(): array 43 | { 44 | return [ 45 | 'type' => 'path', 46 | 'url' => "modules/$this->moduleName", 47 | ]; 48 | } 49 | } -------------------------------------------------------------------------------- /src/LaravelModuleCreatorServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerCommands(); 29 | } 30 | 31 | public function registerCommands() 32 | { 33 | $commands = [ 34 | MakeModule::class, 35 | MakeModuleMigration::class, 36 | MakeModuleModel::class, 37 | MakeModuleSeeder::class, 38 | MakeModuleController::class, 39 | MakeModuleRepo::class, 40 | MakeModuleResource::class, 41 | MakeModuleRequest::class, 42 | MakeModulePolicy::class, 43 | MakeModuleRule::class, 44 | MakeModuleEvent::class, 45 | MakeModuleListener::class, 46 | MakeModuleJob::class, 47 | MakeModuleNotification::class, 48 | MakeModuleException::class 49 | ]; 50 | 51 | if(class_exists("Rebing\GraphQL\Support\Facades\GraphQL")) { 52 | $commands = array_merge($commands, [ 53 | MakeModuleGraphqlType::class, 54 | MakeModuleGraphqlMutation::class, 55 | MakeModuleGraphqlQuery::class 56 | ]); 57 | } 58 | 59 | $this->commands($commands); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/Stubs/Graphql/mutation.stub: -------------------------------------------------------------------------------- 1 | 'DummyGraphqlName', 17 | 'description' => 'A mutation' 18 | ]; 19 | 20 | public function type(): Type 21 | { 22 | return Type::listOf(Type::string()); 23 | } 24 | 25 | public function args(): array 26 | { 27 | return [ 28 | 29 | ]; 30 | } 31 | 32 | public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) 33 | { 34 | $fields = $getSelectFields(); 35 | $select = $fields->getSelect(); 36 | $with = $fields->getRelations(); 37 | 38 | return []; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Stubs/Graphql/query.stub: -------------------------------------------------------------------------------- 1 | 'DummyGraphqlName', 17 | 'description' => 'A query' 18 | ]; 19 | 20 | public function type(): Type 21 | { 22 | return Type::listOf(Type::string()); 23 | } 24 | 25 | public function args(): array 26 | { 27 | return [ 28 | 29 | ]; 30 | } 31 | 32 | public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) 33 | { 34 | /** @var SelectFields $fields */ 35 | $fields = $getSelectFields(); 36 | $select = $fields->getSelect(); 37 | $with = $fields->getRelations(); 38 | 39 | return [ 40 | 'The DummyGraphqlName works', 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Stubs/Graphql/type.stub: -------------------------------------------------------------------------------- 1 | 'DummyGraphqlName', 13 | 'description' => 'A type' 14 | ]; 15 | 16 | public function fields(): array 17 | { 18 | return [ 19 | 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Stubs/Providers/ServiceProvider.stub: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__."/../Database/Migrations"); 9 | $this->loadRoutesFrom(__DIR__.'/../Routes/routes.php'); 10 | } 11 | 12 | public function boot() 13 | { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Stubs/Repo.stub: -------------------------------------------------------------------------------- 1 | replaceGraphqlName($stub); 14 | } 15 | 16 | protected function replaceGraphqlName(string $stub): string 17 | { 18 | $graphqlName = $this->getNameInput(); 19 | $graphqlName = \Safe\preg_replace('/Type$/', '', $graphqlName); 20 | 21 | return str_replace( 22 | 'DummyGraphqlName', 23 | $graphqlName, 24 | $stub 25 | ); 26 | } 27 | 28 | /** 29 | * Get the console command arguments. 30 | * 31 | * @return array 32 | */ 33 | protected function getArguments() 34 | { 35 | return [ 36 | ['module', InputArgument::REQUIRED, 'the name of the module'], 37 | ['name', InputArgument::REQUIRED, 'The name of the class'], 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Traits/RequireModule.php: -------------------------------------------------------------------------------- 1 | isDirectory(base_path("modules/{$this->argument('module')}"))) 10 | throw new \Exception("{$this->argument('module')} module not found"); 11 | } 12 | 13 | public function handle() 14 | { 15 | $this->checkModuleExists(); 16 | 17 | parent::handle(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Unit/ComposerParserTest.php: -------------------------------------------------------------------------------- 1 | copyComposerFile(); 17 | } 18 | 19 | private function copyComposerFile(): void 20 | { 21 | copy('composer.json', self::composerFileName); 22 | } 23 | 24 | public function testAddingOption(): void 25 | { 26 | $composerParser = new ComposerParser(self::composerFileName); 27 | $require = $composerParser->getOption('require'); 28 | $require['test/test'] = 'dev-main'; 29 | $composerParser->setOption( 30 | 'require', 31 | $require 32 | ); 33 | 34 | $this->assertTrue( 35 | !is_null($composerParser->getOption('require')) 36 | && !is_null($composerParser->getOption('require')['illuminate/support']) 37 | && $composerParser->getOption('require')['test/test'] == 'dev-main' 38 | ); 39 | } 40 | 41 | public function testSettingOptionSimple(): void 42 | { 43 | $composerParser = new ComposerParser(self::composerFileName); 44 | $composerParser->setOption('name', 'test/test'); 45 | $name = $composerParser->getOption('name'); 46 | 47 | $this->assertEquals('test/test', $name); 48 | } 49 | 50 | public function testSettingOptionArray(): void 51 | { 52 | $composerParser = new ComposerParser(self::composerFileName); 53 | $keywords = ['keyOne', 'keyTwo', 'keyThree']; 54 | $composerParser->setOption('keywords', $keywords); 55 | $resultKeywords = $composerParser->getOption('keywords'); 56 | 57 | $this->assertEquals($keywords[0], $resultKeywords[0]); 58 | $this->assertEquals($keywords[1], $resultKeywords[1]); 59 | $this->assertEquals($keywords[2], $resultKeywords[2]); 60 | } 61 | 62 | public function testSettingOptionObject(): void 63 | { 64 | $composerParser = new ComposerParser(self::composerFileName); 65 | $composerParser->setOption('require', [ 66 | 'test/test' => 'dev-main', 67 | ]); 68 | $require = $composerParser->getOption('require'); 69 | 70 | $this->assertTrue(!is_null($require['test/test']) && $require['test/test'] == 'dev-main'); 71 | } 72 | 73 | public function testSettingOptionComplex(): void 74 | { 75 | $composerParser = new ComposerParser(self::composerFileName); 76 | $composerParser->setOption('extra', [ 77 | 'test' => [ 78 | 'providers' => [ 79 | 'one', 80 | 'two', 81 | 'three', 82 | ] 83 | ], 84 | ]); 85 | $extra = $composerParser->getOption('extra'); 86 | 87 | $this->assertTrue($extra['test'] === ['providers' => ['one', 'two', 'three']]); 88 | } 89 | 90 | public function testGettingOption(): void 91 | { 92 | $composerParser = new ComposerParser(self::composerFileName); 93 | $name = $composerParser->getOption('name'); 94 | 95 | $this->assertEquals('hesammousavi/laravel-module-creator', $name); 96 | } 97 | 98 | public function tearDown(): void 99 | { 100 | parent::tearDown(); 101 | 102 | $this->deleteComposerFile(); 103 | } 104 | 105 | private function deleteComposerFile() 106 | { 107 | unlink(self::composerFileName); 108 | } 109 | } -------------------------------------------------------------------------------- /tests/Unit/FileTest.php: -------------------------------------------------------------------------------- 1 | createTestFile(); 18 | } 19 | 20 | private function createTestFile() 21 | { 22 | $file = fopen(self::testFileName, 'w'); 23 | fwrite($file, self::testFileContent); 24 | fclose($file); 25 | } 26 | 27 | public function testExceptionOnNotExistingFile() 28 | { 29 | $this->expectException(FileNotExistException::class); 30 | 31 | new File('dummy_name'); 32 | } 33 | 34 | public function testReadFile() 35 | { 36 | $file = new File(self::testFileName); 37 | $content = $file->read(); 38 | 39 | $this->assertTrue($content === self::testFileContent); 40 | } 41 | 42 | public function testWriteFile() 43 | { 44 | $file = new File(self::testFileName); 45 | $content = $file->read(); 46 | 47 | $this->assertTrue($content === self::testFileContent); 48 | 49 | $newFileContent = 'new content'; 50 | $file->write($newFileContent); 51 | $newContent = $file->read(); 52 | 53 | $this->assertTrue($newContent == $newFileContent); 54 | } 55 | 56 | protected function tearDown(): void 57 | { 58 | parent::tearDown(); 59 | 60 | $this->deleteTestFile(); 61 | } 62 | 63 | private function deleteTestFile() 64 | { 65 | unlink(self::testFileName); 66 | } 67 | } -------------------------------------------------------------------------------- /tests/Unit/ModuleHandlerTest.php: -------------------------------------------------------------------------------- 1 | copyComposerFile(); 19 | } 20 | 21 | private function copyComposerFile(): void 22 | { 23 | copy('composer.json', self::composerFileName); 24 | } 25 | 26 | public function testAddingModule() 27 | { 28 | $moduleHandler = new ModuleHandler('Roocket/User', self::composerFileName); 29 | $moduleHandler->add(); 30 | 31 | $composerParser = new ComposerParser(self::composerFileName); 32 | $require = $composerParser->getOption('require'); 33 | $repositories = $composerParser->getOption('repositories'); 34 | 35 | $this->assertTrue(!is_null($require['roocket/user']) && $require['roocket/user'] == 'dev-main'); 36 | $this->assertTrue(!is_null($repositories)); 37 | 38 | foreach ($repositories as $repository){ 39 | if($repository['url'] == 'modules/Roocket/User'){ 40 | return; 41 | } 42 | } 43 | throw new Exception('repository does not exist in composer file'); 44 | } 45 | 46 | public function tearDown(): void 47 | { 48 | parent::tearDown(); 49 | 50 | $this->deleteComposerFile(); 51 | } 52 | 53 | private function deleteComposerFile() 54 | { 55 | unlink(self::composerFileName); 56 | } 57 | } --------------------------------------------------------------------------------