diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..10c74aa --- /dev/null +++ b/.gitattributes @@ -0,0 +1,24 @@ +# Set default line ending behavior, in case people don't have core.autocrlf set +* text=auto + +# Explicitly declare text files you want to always be normalized and converted to native line endings on checkout +*.php text eol=lf +*.json text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf + +# Denote all files that are truly binary and should not be modified +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary + +# Composer files +/composer.lock -diff + +# Git attributes for better diff and merge handling +*.stub text eol=lf + diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..7d257b5 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,39 @@ +name: PHP Composer + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + # - name: Run test suite + # run: composer run-script test diff --git a/README.md b/README.md index f015832..50cab95 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ -# Kenernel243/Artisan +# Kernel243/Artisan -This package provides a set of new artisan commands for Laravel +[![Packagist Downloads](https://img.shields.io/packagist/dt/kernel243/artisan.svg?style=for-the-badge)](https://packagist.org/packages/kernel243/artisan) +[![Visits](https://badges.pufler.dev/visits/kernel243/artisan?style=for-the-badge)](https://packagist.org/packages/kernel243/artisan) + +This package provides a set of new artisan commands for Laravel. This package is based on another laravel package ``dannyvilla/artisan-commands``, my package adds other options which are not yet available in this one for example the use of a modular architecture with laravel ``nwidart/laravel-modules`` and many other options. +Visit the developer website: [developper.elongocrea.com](https://developper.elongocrea.com) + ## Installation Use the package manager [composer](https://getcomposer.org/) to install kernel243/artisan @@ -86,6 +91,18 @@ php artisan make:class App\Contracts\IClassable --kind=interface php artisan make:file folder.subfolder1.subfolder2.filename --ext=php ``` +### CRUD command +Generate a CRUD with model, repository, service, controller and Tailwind views +```bash +php artisan make:crud Product --fields="title:string,description:text,price:decimal,is_active:boolean" +``` + +### Resource CRUD command +Generate CRUD using a Resource class (Filament-inspired) +```bash +php artisan make:resource-crud Product --fields="title:string,description:text" +``` + ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/composer.json b/composer.json index 4879b3f..45ca221 100644 --- a/composer.json +++ b/composer.json @@ -3,11 +3,27 @@ "description": "This package provides a set of artisan commands for Laravel", "type": "library", "license": "MIT", + "keywords": [ + "laravel", + "artisan", + "commands", + "generator", + "repository", + "service", + "controller", + "resource", + "stubs" + ], "autoload": { "psr-4": { "Kernel243\\Artisan\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "Kernel243\\Artisan\\Tests\\": "tests/" + } + }, "extra": { "laravel": { "providers": [ @@ -21,9 +37,22 @@ "email": "merdielongo9@gmail.com" } ], - "minimum-stability": "dev", + "minimum-stability": "stable", + "prefer-stable": true, "require": { - "php": ">=7.4.0", - "laravel/framework": ">=5.8.0" + "php": "^8.0|^8.1|^8.2|^8.3", + "laravel/framework": "^8.0|^9.0|^10.0|^11.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0|^10.0", + "orchestra/testbench": "^6.0|^7.0|^8.0" + }, + "scripts": { + "test": "phpunit", + "analyse": "phpstan analyse" + }, + "config": { + "sort-packages": true, + "optimize-autoloader": true } } diff --git a/src/CommandServiceProvider.php b/src/CommandServiceProvider.php index 9338cf3..fb51f98 100644 --- a/src/CommandServiceProvider.php +++ b/src/CommandServiceProvider.php @@ -3,30 +3,28 @@ namespace Kernel243\Artisan; use Illuminate\Support\ServiceProvider; -use Kernel243\Artisan\Commands\Controller; -use Kernel243\Artisan\Commands\File; -use Kernel243\Artisan\Commands\Repository; -use Kernel243\Artisan\Commands\Resource; -use Kernel243\Artisan\Commands\Service; -use Kernel243\Artisan\Commands\View; - -class CommandServiceProvider extends ServiceProvider { +class CommandServiceProvider extends ServiceProvider +{ /** * Bootstrap the application services. * * @return void */ - public function boot() + public function boot(): void { if ($this->app->runningInConsole()) { $this->commands([ - Repository::class, - Service::class, - File::class, - View::class, - Resource::class, - Controller::class + Commands\Repository::class, + Commands\Service::class, + Commands\File::class, + Commands\View::class, + Commands\Resource::class, + Commands\Controller::class, + Commands\Lang::class, + Commands\ClassMakeCommand::class, + Commands\CrudMakeCommand::class, + Commands\ResourceMakeCommand::class, ]); } } @@ -36,9 +34,8 @@ public function boot() * * @return void */ - public function register() + public function register(): void { // } - } diff --git a/src/Commands/BaseCommand.php b/src/Commands/BaseCommand.php index f1bab0e..1454ca4 100644 --- a/src/Commands/BaseCommand.php +++ b/src/Commands/BaseCommand.php @@ -23,15 +23,15 @@ protected function moduleExists($module) /** * Checks if a folder exist and return canonicalized absolute pathname (sort version) * @param string $folder the path being checked. - * @return mixed returns the canonicalized absolute pathname on success otherwise FALSE is returned + * @return string|false returns the canonicalized absolute pathname on success otherwise FALSE is returned */ - protected function folderExist($folder) + protected function folderExist(string $folder) { // Get canonicalized absolute pathname $path = realpath($folder); // If it exists, check if it's a directory - return ($path !== false AND is_dir($path)) ? $path : false; + return ($path !== false && is_dir($path)) ? $path : false; } /** @@ -134,6 +134,74 @@ protected function replaceResourceNamespace($namespace, $stub) return str_replace('DummyNamespaceResource', ucfirst($namespace), $stub); } + /** + * Read a stub file by logical name from this package stubs directory. + */ + protected function getStubContent(string $name): string + { + $path = __DIR__ . '/stubs/' . str_replace('.', '/', $name) . '.stub'; + if (!file_exists($path)) { + throw new \RuntimeException('Stub not found: ' . $path); + } + return (string) file_get_contents($path); + } + + /** + * Write content to a file, creating parent directories when needed. + */ + protected function writeFile(string $path, string $content): void + { + $dir = dirname($path); + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + file_put_contents($path, $content); + } + + /** + * Ask whether a file should be replaced unless --force is provided. + */ + protected function shouldReplaceFile(string $path, string $question = 'Replace file? [y/n]'): bool + { + if (!file_exists($path)) { + return true; + } + if ($this->option('force')) { + return true; + } + do { + $input = strtolower($this->ask($question)); + } while ($input !== 'y' && $input !== 'n'); + return $input === 'y'; + } + + /** + * Simple success output helper. + */ + protected function displaySuccess(string $message, string $path): void + { + $this->info($message); + $this->line(' → ' . $path); + } + + /** + * Ensure a directory exists. + */ + protected function ensureDirectoryExists(string $directory): void + { + if (!file_exists($directory)) { + mkdir($directory, 0755, true); + } + } + + /** + * Basic filename validation for ClassMakeCommand. + */ + protected function isValidFilename(string $filename): bool + { + return (bool) preg_match('#^[A-Za-z0-9_\\\\/\.-]+$#', $filename); + } + /** * Check if a repository file exists. * @@ -144,11 +212,12 @@ protected function replaceResourceNamespace($namespace, $stub) protected function repositoryFileExists($repository, $module = null): bool { if (is_null($module)) { - return file_exists( base_path(lcfirst($repository).'.php')) || file_exists( base_path(lcfirst(str_replace('\\', '/', $repository)).'.php')); + $repositoryName = ucfirst(basename(str_replace('\\', '/', $repository))); + return file_exists(app_path('Repositories/'.$repositoryName.'.php')); } - $path = base_path('Modules/'.ucfirst($module).'/Repositories/'.lcfirst($repository).'.php'); - return file_exists($path) || file_exists('Modules/'.base_path(ucfirst($module).'/Repositories/'.lcfirst(str_replace('\\', '/', $repository)).'.php')); + $path = base_path('Modules/'.ucfirst($module).'/Repositories/'.ucfirst(basename(str_replace('\\', '/', $repository))).'.php'); + return file_exists($path); } /** @@ -167,7 +236,7 @@ protected function setModelAndNamespace(&$model, &$namespace, &$module) $module = ''; for ($i = 0; $i < count($exploded) - 1; $i++) { - if (!isEmpty($module)) { + if (!empty($module)) { $namespace .= $module.'\\'.$exploded[$i].'\\'.'Entities\\'; } else { $namespace .= $exploded[$i].'\\'; @@ -193,7 +262,7 @@ protected function setRepositoryAndNamespace(&$repository, &$namespace, &$module $module = ''; for ($i = 0; $i < count($exploded) - 1; $i++) { - if (!isEmpty($module)) { + if (!empty($module)) { $namespace .= $module.'\\'.$exploded[$i].'\\'.'Repositories\\'; } else { $namespace .= $exploded[$i].'\\'; @@ -213,11 +282,12 @@ protected function setRepositoryAndNamespace(&$repository, &$namespace, &$module protected function modelFileExists($model, $module = null): bool { if (is_null($module)) { - return file_exists( base_path(lcfirst($model).'.php')) || file_exists( base_path(lcfirst(str_replace('\\', '/', $model)).'.php')); + $modelName = ucfirst(basename(str_replace('\\', '/', $model))); + return file_exists(app_path('Models/'.$modelName.'.php')); } - $path = base_path('Modules/'.ucfirst($module).'/Entities/'.lcfirst($model).'.php'); - return file_exists($path) || file_exists('Modules/'.base_path(ucfirst($module).'/Entities/'.lcfirst(str_replace('\\', '/', $model)).'.php')); + $path = base_path('Modules/'.ucfirst($module).'/Entities/'.ucfirst(basename(str_replace('\\', '/', $model))).'.php'); + return file_exists($path); } diff --git a/src/Commands/ClassMakeCommand.php b/src/Commands/ClassMakeCommand.php new file mode 100644 index 0000000..1574c19 --- /dev/null +++ b/src/Commands/ClassMakeCommand.php @@ -0,0 +1,106 @@ +argument('filename'); + + if (!$this->isValidFilename($filename)) { + $this->error('The filename is not correct. Only alphanumeric characters, dots, underscores, backslashes, and hyphens are allowed.'); + return self::FAILURE; + } + + $kind = $this->getKind(); + if ($kind === null) { + return self::FAILURE; + } + + try { + $path = $this->buildFilePath($filename); + $question = "There is already a file with this name. Do you want to replace it? [y/n]"; + + if (!$this->shouldReplaceFile($path, $question)) { + return self::SUCCESS; + } + + $pathParts = $this->splitPath($filename); + $basePath = base_path(); + $this->createFoldersIfNecessary($pathParts, $basePath); + + $stub = $this->getStubContent($kind); + $className = $pathParts[count($pathParts) - 1]; + $namespace = $this->buildNamespace($pathParts); + + $stub = $this->replaceKindName($kind, $className, $stub); + $stub = $this->replaceNamespace($namespace, $stub); + + $this->writeFile($path, $stub); + $this->displaySuccess(ucfirst($kind) . ' created successfully!', $path); + return self::SUCCESS; + } catch (\Exception $e) { + $this->error("Error: {$e->getMessage()}"); + return self::FAILURE; + } + } + + protected function buildFilePath(string $filename): string + { + return base_path($filename . '.php'); + } + + protected function splitPath(string $filename): array + { + return str_contains($filename, '/') ? explode('/', $filename) : explode('\\', $filename); + } + + protected function buildNamespace(array $pathParts): string + { + $namespace = ''; + for ($i = 0; $i < count($pathParts) - 1; $i++) { + $namespace .= ucfirst($pathParts[$i]) . '\\'; + } + return Str::replaceLast('\\', '', $namespace); + } + + protected function replaceKindName(string $kind, string $name, string $stub): string + { + return str_replace('Dummy' . ucfirst($kind), ucfirst($name), $stub); + } + + protected function replaceNamespace(string $namespace, string $stub): string + { + if (!empty($namespace)) { + return str_replace('DummyNamespace', 'namespace ' . $namespace . ';', $stub); + } + return str_replace('DummyNamespace', '', $stub); + } + + protected function getKind(): ?string + { + $kind = $this->option('kind'); + if ($kind === null) { + return 'class'; + } + $validKinds = ['class', 'trait', 'interface']; + if (!in_array($kind, $validKinds, true)) { + $this->error('Invalid kind value. The kind must be one of: class, trait, interface'); + return null; + } + return $kind; + } +} diff --git a/src/Commands/Controller.php b/src/Commands/Controller.php index b6cf112..7096165 100644 --- a/src/Commands/Controller.php +++ b/src/Commands/Controller.php @@ -61,10 +61,13 @@ protected function putInFile($filename, $content, $module = null) { if (!is_null($module)) { $modulePath = base_path('Modules/'.$module.'/Http/Controllers'); - if (!is_dir($modulePath)) mkdir($modulePath); + if (!is_dir($modulePath)) { + mkdir($modulePath, 0755, true); + } } else { - if (!is_dir(app_path('/Http/Controllers'))) - mkdir(app_path('/Http/Controllers')); + if (!is_dir(app_path('/Http/Controllers'))) { + mkdir(app_path('/Http/Controllers'), 0755, true); + } } file_put_contents($filename, $content); diff --git a/src/Commands/CrudMakeCommand.php b/src/Commands/CrudMakeCommand.php new file mode 100644 index 0000000..99ddecf --- /dev/null +++ b/src/Commands/CrudMakeCommand.php @@ -0,0 +1,277 @@ + trim($parts[0]), 'type' => trim($parts[1])]; + } + } + return $parsedFields; + } + + public function handle(): int + { + $name = $this->argument('name'); + $fields = $this->parseFields($this->option('fields')); + if (empty($name)) { + $this->error('The name of the CRUD resource is required.'); + return self::FAILURE; + } + try { + $this->generateModel($name, $fields); + $this->generateRepository($name); + $this->generateService($name); + $this->generateController($name); + $this->generateViews($name, $fields); + $this->generateRoutes($name); + $this->generateTailwindConfig(); + $this->info('✓ CRUD generated successfully!'); + return self::SUCCESS; + } catch (\Exception $e) { + $this->error("Error: {$e->getMessage()}"); + return self::FAILURE; + } + } + + protected function generateModel(string $name, array $fields): void + { + $stub = $this->getStubContent('crud.model'); + $stub = str_replace('DummyModel', ucfirst($name), $stub); + $stub = str_replace('DummyTable', Str::snake(Str::plural($name)), $stub); + $fillableString = empty($fields) ? "// 'field1', 'field2'" : implode(",\n ", array_map(fn($f) => "'{$f['name']}'", $fields)); + $stub = str_replace('DummyFillable', $fillableString, $stub); + $path = app_path('Models/' . ucfirst($name) . '.php'); + if (!$this->shouldReplaceFile($path, "Model {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Models')); + $this->writeFile($path, $stub); + } + + protected function generateRepository(string $name): void + { + $modelName = ucfirst($name); + $stub = $this->getStubContent('repository'); + $stub = str_replace('DummyModelNamespace', 'App', $stub); + $stub = str_replace('DummyModel', $modelName, $stub); + $stub = str_replace('DummyProperty', lcfirst(Str::camel($modelName)), $stub); + $stub = str_replace('class DummyClass', 'class ' . $modelName . 'Repository', $stub); + $path = app_path('Repositories/' . $modelName . 'Repository.php'); + if (!$this->shouldReplaceFile($path, "Repository {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Repositories')); + $this->writeFile($path, $stub); + } + + protected function generateService(string $name): void + { + $modelName = ucfirst($name); + $stub = $this->getStubContent('crud.service'); + $stub = str_replace('DummyClass', $modelName . 'Service', $stub); + $stub = str_replace('DummyModel', $modelName, $stub); + $stub = str_replace('DummyRepository', $modelName . 'Repository', $stub); + $path = app_path('Services/' . $modelName . 'Service.php'); + if (!$this->shouldReplaceFile($path, "Service {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Services')); + $this->writeFile($path, $stub); + } + + protected function generateController(string $name): void + { + $modelName = ucfirst($name); + $variableName = lcfirst($name); + $pluralVariable = Str::plural($variableName); + $viewPath = Str::kebab(Str::plural($name)); + $stub = $this->getStubContent('crud.controller'); + $stub = str_replace(['DummyController','DummyService','DummyVariable','DummyPluralVariable','DummyViewPath'], + [$modelName.'Controller',$modelName.'Service',$variableName,$pluralVariable,$viewPath], $stub); + $path = app_path('Http/Controllers/' . $modelName . 'Controller.php'); + if (!$this->shouldReplaceFile($path, "Controller {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Http/Controllers')); + $this->writeFile($path, $stub); + } + + protected function generateViews(string $name, array $fields): void + { + $viewPath = Str::kebab(Str::plural($name)); + $this->generateIndexView($viewPath, $name, $fields); + $this->generateCreateView($viewPath, $name, $fields); + $this->generateEditView($viewPath, $name, $fields); + $this->generateShowView($viewPath, $name, $fields); + $this->generateLayout(); + } + + protected function generateIndexView(string $viewPath, string $name, array $fields): void + { + $stub = $this->getStubContent('crud.views.index'); + $stub = str_replace(['DummyResource','DummyResourceKebab','$items'], [Str::plural(ucfirst($name)), $viewPath, '$'.Str::plural(lcfirst($name))], $stub); + $path = resource_path('views/' . $viewPath . '/index.blade.php'); + if (!$this->shouldReplaceFile($path, 'Index view exists. Replace it? [y/n]')) return; + $this->ensureDirectoryExists(resource_path('views/' . $viewPath)); + $this->writeFile($path, $stub); + } + + protected function generateCreateView(string $viewPath, string $name, array $fields): void + { + $stub = $this->getStubContent('crud.views.create'); + $stub = str_replace(['DummyResource','DummyResourcePlural','DummyResourceKebab','DummyFormFields'], [ucfirst($name), Str::plural($name), $viewPath, $this->generateFormFields($fields)], $stub); + $path = resource_path('views/' . $viewPath . '/create.blade.php'); + if (!$this->shouldReplaceFile($path, 'Create view exists. Replace it? [y/n]')) return; + $this->writeFile($path, $stub); + } + + protected function generateEditView(string $viewPath, string $name, array $fields): void + { + $stub = $this->getStubContent('crud.views.edit'); + $stub = str_replace(['DummyResource','DummyResourcePlural','DummyResourceKebab','DummyVariable','DummyFormFields'], [ucfirst($name), Str::plural($name), $viewPath, lcfirst($name), $this->generateFormFields($fields, true, lcfirst($name))], $stub); + $path = resource_path('views/' . $viewPath . '/edit.blade.php'); + if (!$this->shouldReplaceFile($path, 'Edit view exists. Replace it? [y/n]')) return; + $this->writeFile($path, $stub); + } + + protected function generateShowView(string $viewPath, string $name, array $fields): void + { + $stub = $this->getStubContent('crud.views.show'); + $stub = str_replace(['DummyResource','DummyResourceKebab','DummyVariable','DummyDetailFields'], [ucfirst($name), $viewPath, lcfirst($name), $this->generateDetailFields($fields, lcfirst($name))], $stub); + $path = resource_path('views/' . $viewPath . '/show.blade.php'); + if (!$this->shouldReplaceFile($path, 'Show view exists. Replace it? [y/n]')) return; + $this->writeFile($path, $stub); + } + + protected function generateRoutes(string $name): void + { + $routesPath = base_path('routes/web.php'); + if (!file_exists($routesPath)) { $this->warn('⚠ routes/web.php not found. Routes not added.'); return; } + $resourceName = Str::plural(ucfirst($name)); + $controllerName = ucfirst($name) . 'Controller'; + $routePrefix = Str::kebab(Str::plural($name)); + $stub = $this->getStubContent('crud.routes'); + $stub = str_replace(['{{ $resourceName }}','{{ $controllerName }}','{{ $routePrefix }}'], [$resourceName,$controllerName,$routePrefix], $stub); + $routesContent = file_get_contents($routesPath); + if (str_contains($routesContent, $routePrefix)) { $this->line('⚠ Routes already exist in web.php. Skipping.'); return; } + $routesContent .= "\n" . $stub; + if (!$this->option('dry-run')) { + file_put_contents($routesPath, $routesContent); + $this->line('✓ Routes added to web.php'); + } else { + $this->line('[DRY RUN] Would add routes to web.php'); + } + } + + protected function generateLayout(): void + { + $path = resource_path('views/layouts/app.blade.php'); + if (file_exists($path)) return; + $stub = $this->getStubContent('crud.layout'); + if (!$this->shouldReplaceFile($path, 'Layout exists. Replace it? [y/n]')) return; + $this->ensureDirectoryExists(resource_path('views/layouts')); + $this->writeFile($path, $stub); + } + + protected function generateTailwindConfig(): void + { + $path = base_path('tailwind.config.js'); + if (file_exists($path)) return; + $stub = $this->getStubContent('tailwind.config'); + if (!$this->shouldReplaceFile($path, 'Tailwind config exists. Replace it? [y/n]')) return; + $this->writeFile($path, $stub); + } + + protected function generateFormFields(array $fields, bool $forEdit = false, string $variableName = 'item'): string + { + if (empty($fields)) { return ' {{-- Add your form fields here --}}'; } + $lines = []; + foreach ($fields as $field) { + $fieldName = $field['name']; + $fieldLabel = ucfirst(str_replace('_', ' ', $fieldName)); + $valueBinding = $forEdit ? "{{ old('{$fieldName}', \\$${variableName}->{$fieldName}) }}" : "{{ old('{$fieldName}') }}"; + $lines[] = $this->generateFieldByType($fieldName, $fieldLabel, $valueBinding, $field['type']); + } + return implode("\n", $lines); + } + + protected function generateFieldByType(string $fieldName, string $fieldLabel, string $valueBinding, string $type): string + { + $id = "field_{$fieldName}"; + if (in_array($type, ['text','string','varchar'])) { + return << + + + +HTML; + } elseif (in_array($type, ['textarea'])) { + return << + + + +HTML; + } elseif ($type === 'email') { + return << + + + +HTML; + } elseif (in_array($type, ['boolean','tinyint'])) { + return << + + +HTML; + } + return << + + + +HTML; + } + + protected function generateDetailFields(array $fields, string $variableName = 'item'): string + { + if (empty($fields)) { return ' {{-- Add detail fields here --}}'; } + $detailFields = []; + foreach ($fields as $field) { + $fieldName = $field['name']; + $fieldLabel = ucfirst(str_replace('_', ' ', $fieldName)); + $detailFields[] = << +
$fieldLabel:
+
{{\$$variableName->$fieldName}}
+ +HTML; + } + return implode("\n", $detailFields); + } +} diff --git a/src/Commands/Lang.php b/src/Commands/Lang.php new file mode 100644 index 0000000..0d42d65 --- /dev/null +++ b/src/Commands/Lang.php @@ -0,0 +1,105 @@ +hasArgument('name') ? $this->argument('name') : ''; + $locale = $this->option('locale') ?? 'en'; + + if ($this->option('json')) { + return $this->createJson($locale); + } + + if (empty($name)) { + $this->error('No filename is given. Use --json flag for JSON language files.'); + return self::FAILURE; + } + + if (!$this->nameIsCorrect($name)) { + $this->error('The given filename is not correct. Only alphanumeric characters are allowed.'); + return self::FAILURE; + } + + return $this->createLang($name, $locale); + } + + protected function createLang(string $name, string $locale): int + { + try { + $path = $this->getLangPath($locale, $name . '.php'); + $question = "There is already a locale file with this name. Do you want to replace it? [y/n]"; + + if (!$this->shouldReplaceFile($path, $question)) { + return self::SUCCESS; + } + + $this->ensureLocaleDirectoryExists($locale); + + $stub = $this->getStubContent('lang'); + $this->writeFile($path, $stub); + + $this->displaySuccess('Language file created successfully!', $path); + + return self::SUCCESS; + } catch (\Exception $e) { + $this->error("Error: {$e->getMessage()}"); + return self::FAILURE; + } + } + + protected function createJson(string $locale): int + { + try { + $path = $this->getLangPath($locale . '.json'); + $question = "There is already a locale file with this name. Do you want to replace it? [y/n]"; + + if (!$this->shouldReplaceFile($path, $question)) { + return self::SUCCESS; + } + + $content = "{\n \n}"; + $this->writeFile($path, $content); + + $this->displaySuccess('Language file created successfully!', $path); + + return self::SUCCESS; + } catch (\Exception $e) { + $this->error("Error: {$e->getMessage()}"); + return self::FAILURE; + } + } + + protected function getLangPath(string ...$parts): string + { + return resource_path('lang' . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $parts)); + } + + protected function ensureLocaleDirectoryExists(string $locale): void + { + $directory = resource_path('lang' . DIRECTORY_SEPARATOR . $locale); + + if (!file_exists($directory)) { + if (!mkdir($directory, 0755, true) && !is_dir($directory)) { + throw new \RuntimeException("Unable to create directory: {$directory}"); + } + } + } + + protected function nameIsCorrect(string $name): bool + { + return (bool) preg_match('#^[a-zA-Z][a-zA-Z0-9]+$#', $name); + } +} diff --git a/src/Commands/Repository.php b/src/Commands/Repository.php index 246a95c..c80f5e7 100644 --- a/src/Commands/Repository.php +++ b/src/Commands/Repository.php @@ -5,7 +5,6 @@ use Illuminate\Console\Command; use Illuminate\Support\Arr; use Illuminate\Support\Str; -use function PHPUnit\Framework\isEmpty; class Repository extends BaseCommand { @@ -61,13 +60,16 @@ protected function getEmptyStub() */ protected function putInFile($filename, $content, $module = null) { - if (!is_null($module)) { - $modulePath = base_path('Modules/'.$module.'/Repositories'); - if (!is_dir($modulePath)) mkdir($modulePath); - } else { - if (!is_dir(app_path('/Repositories'))) - mkdir(app_path('/Repositories')); - } + if (!is_null($module)) { + $modulePath = base_path('Modules/'.$module.'/Repositories'); + if (!is_dir($modulePath)) { + mkdir($modulePath, 0755, true); + } + } else { + if (!is_dir(app_path('/Repositories'))) { + mkdir(app_path('/Repositories'), 0755, true); + } + } file_put_contents($filename, $content); } diff --git a/src/Commands/Resource.php b/src/Commands/Resource.php index dc7811f..9940379 100644 --- a/src/Commands/Resource.php +++ b/src/Commands/Resource.php @@ -2,9 +2,7 @@ namespace Kernel243\Artisan\Commands; -use Illuminate\Console\Command; - -class Resource extends Command +class Resource extends BaseCommand { /** * The name and signature of the console command @@ -75,10 +73,13 @@ protected function putInFile($filename, $content, $module = null) { if (!is_null($module)) { $modulePath = base_path('Modules/'.ucfirst($module).'/Http/Resources'); - if (!is_dir($modulePath)) mkdir($modulePath); + if (!is_dir($modulePath)) { + mkdir($modulePath, 0755, true); + } } else { - if (!is_dir(app_path('/Http/Resources'))) - mkdir(app_path('/Http/Resources')); + if (!is_dir(app_path('/Http/Resources'))) { + mkdir(app_path('/Http/Resources'), 0755, true); + } } file_put_contents($filename, $content); @@ -91,39 +92,15 @@ protected function putInFile($filename, $content, $module = null) * @param $module * @return bool */ - protected function resourceFileExists($resource, $module = null) + protected function resourceFileExists($resource, $module = null): bool { if (is_null($module)) { - return file_exists(base_path(lcfirst($resource).'php')) || file_exists( base_path(lcfirst(str_replace('\\', '/', $resource)).'.php')); + $resourceName = ucfirst(basename(str_replace('\\', '/', $resource))); + return file_exists(app_path('Http/Resources/'.$resourceName.'.php')); } - $path = base_path('Modules/'.ucfirst($module).'/Http/Resources/'.lcfirst($resource).'.php'); - return file_exists($path) || file_exists('Modules/'.base_path(ucfirst($module).'/Entities/'.lcfirst(str_replace('\\', '/', $resource)).'.php')); - } - - /** - * Check if a module folder exists - * - * @param $module - * @return false|mixed - */ - protected function moduleExists($module) - { - return $this->folderExist('Modules/'.$module); - } - - /** - * Checks if a folder exist and return canonicalized absolute pathname (sort version) - * @param string $folder the path being checked. - * @return mixed returns the canonicalized absolute pathname on success otherwise FALSE is returned - */ - protected function folderExist($folder) - { - // Get canonicalized absolute pathname - $path = realpath($folder); - - // If it exists, check if it's a directory - return ($path !== false AND is_dir($path)) ? $path : false; + $path = base_path('Modules/'.ucfirst($module).'/Http/Resources/'.ucfirst(basename(str_replace('\\', '/', $resource))).'.php'); + return file_exists($path); } /** diff --git a/src/Commands/ResourceMakeCommand.php b/src/Commands/ResourceMakeCommand.php new file mode 100644 index 0000000..7f5653c --- /dev/null +++ b/src/Commands/ResourceMakeCommand.php @@ -0,0 +1,295 @@ +trim($parts[0]), 'type'=>trim($parts[1])]; } + } + return $parsed; + } + + public function handle(): int + { + $name = $this->argument('name'); + $fields = $this->parseFields($this->option('fields')); + if (empty($name)) { $this->error('The name of the CRUD resource is required.'); return self::FAILURE; } + try { + $this->generateBaseClasses(); + $this->generateModel($name, $fields); + $this->generateRepository($name); + $this->generateService($name); + $this->generateController($name); + $this->generateResource($name, $fields); + $this->generateDefaultViews($name, $fields); + $this->generateRoutes($name); + $this->generateTailwindConfig(); + $this->info('✓ Resource CRUD generated successfully!'); + return self::SUCCESS; + } catch (\Exception $e) { + $this->error("Error: {$e->getMessage()}"); + return self::FAILURE; + } + } + + protected function generateBaseClasses(): void + { + $this->generateBaseResource(); + $this->generateFormBuilder(); + $this->generateTableBuilder(); + } + + protected function generateBaseResource(): void + { + $path = app_path('Resources/Resource.php'); + if (file_exists($path)) return; + $stub = $this->getStubContent('resource.base'); + if (!$this->shouldReplaceFile($path, 'Base Resource exists. Replace it? [y/n]')) return; + $this->ensureDirectoryExists(app_path('Resources')); + $this->writeFile($path, $stub); + } + + protected function generateFormBuilder(): void + { + $path = app_path('Builders/Form.php'); + if (file_exists($path)) return; + $stub = $this->getStubContent('form.builder'); + if (!$this->shouldReplaceFile($path, 'Form builder exists. Replace it? [y/n]')) return; + $this->ensureDirectoryExists(app_path('Builders')); + $this->writeFile($path, $stub); + } + + protected function generateTableBuilder(): void + { + $path = app_path('Builders/Table.php'); + if (file_exists($path)) return; + $stub = $this->getStubContent('table.builder'); + if (!$this->shouldReplaceFile($path, 'Table builder exists. Replace it? [y/n]')) return; + $this->ensureDirectoryExists(app_path('Builders')); + $this->writeFile($path, $stub); + } + + protected function generateModel(string $name, array $fields): void + { + $stub = $this->getStubContent('crud.model'); + $stub = str_replace('DummyModel', ucfirst($name), $stub); + $stub = str_replace('DummyTable', Str::snake(Str::plural($name)), $stub); + $fillableString = empty($fields) ? "// 'field1', 'field2'" : implode(",\n ", array_map(fn($f)=>"'{$f['name']}'", $fields)); + $stub = str_replace('DummyFillable', $fillableString, $stub); + $path = app_path('Models/' . ucfirst($name) . '.php'); + if (!$this->shouldReplaceFile($path, "Model {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Models')); + $this->writeFile($path, $stub); + } + + protected function generateRepository(string $name): void + { + $modelName = ucfirst($name); + $stub = $this->getStubContent('repository'); + $stub = str_replace('DummyModelNamespace', 'App', $stub); + $stub = str_replace('DummyModel', $modelName, $stub); + $stub = str_replace('DummyProperty', lcfirst(Str::camel($modelName)), $stub); + $stub = str_replace('class DummyClass', 'class ' . $modelName . 'Repository', $stub); + $path = app_path('Repositories/' . $modelName . 'Repository.php'); + if (!$this->shouldReplaceFile($path, "Repository {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Repositories')); + $this->writeFile($path, $stub); + } + + protected function generateService(string $name): void + { + $modelName = ucfirst($name); + $stub = $this->getStubContent('crud.service'); + $stub = str_replace('DummyClass', $modelName . 'Service', $stub); + $stub = str_replace('DummyModel', $modelName, $stub); + $stub = str_replace('DummyRepository', $modelName . 'Repository', $stub); + $path = app_path('Services/' . $modelName . 'Service.php'); + if (!$this->shouldReplaceFile($path, "Service {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Services')); + $this->writeFile($path, $stub); + } + + protected function generateController(string $name): void + { + $modelName = ucfirst($name); + $variableName = lcfirst($name); + $pluralVariable = Str::plural($variableName); + $viewPath = 'resources.crud'; + $stub = $this->getStubContent('crud.controller.resource'); + $stub = str_replace(['DummyController','DummyService','DummyVariable','DummyPluralVariable','DummyViewPath','DummyResourceClass'], + [$modelName.'Controller',$modelName.'Service',$variableName,$pluralVariable,$viewPath,$modelName.'Resource'], $stub); + $path = app_path('Http/Controllers/' . $modelName . 'Controller.php'); + if (!$this->shouldReplaceFile($path, "Controller {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Http/Controllers')); + $this->writeFile($path, $stub); + } + + protected function generateResource(string $name, array $fields): void + { + $modelName = ucfirst($name); + $resourceName = Str::plural($modelName); + $routePrefix = Str::kebab(Str::plural($name)); + $stub = $this->getStubContent('crud.resource'); + $stub = str_replace(['DummyResourceName','DummyResourceClass','DummyModel','DummyResourcePlural','DummyResourceKebab'], + [$resourceName,$modelName.'Resource',$modelName,Str::plural($modelName),$routePrefix], $stub); + $formFields = $this->generateResourceFormFields($fields); + $stub = str_replace('DummyFormFields', $formFields, $stub); + $tableColumns = $this->generateResourceTableColumns($fields); + $stub = str_replace('DummyTableColumns', $tableColumns, $stub); + $validationRules = $this->generateValidationRules($fields); + $stub = str_replace('DummyValidationRules', $validationRules, $stub); + $path = app_path('Resources/' . $modelName . 'Resource.php'); + if (!$this->shouldReplaceFile($path, "Resource {$name} exists. Replace it? [y/n]")) return; + $this->ensureDirectoryExists(app_path('Resources')); + $this->writeFile($path, $stub); + } + + protected function generateDefaultViews(string $name, array $fields): void + { + $viewPath = 'resources.crud'; + $resourceName = Str::plural(ucfirst($name)); + $routePrefix = Str::kebab(Str::plural($name)); + $this->ensureDirectoryExists(resource_path('views/resources/crud')); + $this->generateDefaultFormView($name, $fields, $viewPath); + $this->generateDefaultIndexView($name, $fields, $viewPath, $resourceName, $routePrefix); + $this->generateDefaultCreateView($name, $viewPath, $resourceName, $routePrefix); + $this->generateDefaultEditView($name, $viewPath, $resourceName, $routePrefix); + $this->generateDefaultShowView($name, $fields, $viewPath, $resourceName, $routePrefix); + $this->generateLayout(); + } + + protected function generateDefaultFormView(string $name, array $fields, string $viewPath): void + { + $path = resource_path('views/' . $viewPath . '/_form.blade.php'); + if (!$this->shouldReplaceFile($path, '_form view exists. Replace it? [y/n]')) return; + $this->writeFile($path, $this->getStubContent('crud.views.default._form')); + } + + protected function generateDefaultIndexView(string $name, array $fields, string $viewPath, string $resourceName, string $routePrefix): void + { + $stub = $this->getStubContent('crud.views.default.index'); + $stub = str_replace(['{{ $resourceName }}','{{ $routePrefix }}'], [$resourceName,$routePrefix], $stub); + $path = resource_path('views/' . $viewPath . '/index.blade.php'); + if (!$this->shouldReplaceFile($path, 'Index view exists. Replace it? [y/n]')) return; + $this->writeFile($path, $stub); + } + + protected function generateDefaultCreateView(string $name, string $viewPath, string $resourceName, string $routePrefix): void + { + $stub = $this->getStubContent('crud.views.default.create'); + $stub = str_replace(['{{ $resourceName }}','{{ $routePrefix }}'], ['New ' . ucfirst($name), $routePrefix], $stub); + $path = resource_path('views/' . $viewPath . '/create.blade.php'); + if (!$this->shouldReplaceFile($path, 'Create view exists. Replace it? [y/n]')) return; + $this->writeFile($path, $stub); + } + + protected function generateDefaultEditView(string $name, string $viewPath, string $resourceName, string $routePrefix): void + { + $stub = $this->getStubContent('crud.views.default.edit'); + $stub = str_replace(['{{ $resourceName }}','{{ $routePrefix }}'], [ucfirst($name), $routePrefix], $stub); + $path = resource_path('views/' . $viewPath . '/edit.blade.php'); + if (!$this->shouldReplaceFile($path, 'Edit view exists. Replace it? [y/n]')) return; + $this->writeFile($path, $stub); + } + + protected function generateDefaultShowView(string $name, array $fields, string $viewPath, string $resourceName, string $routePrefix): void + { + $stub = $this->getStubContent('crud.views.default.show'); + $stub = str_replace(['{{ $resourceName }}','{{ $routePrefix }}'], [ucfirst($name), $routePrefix], $stub); + $path = resource_path('views/' . $viewPath . '/show.blade.php'); + if (!$this->shouldReplaceFile($path, 'Show view exists. Replace it? [y/n]')) return; + $this->writeFile($path, $stub); + } + + protected function generateRoutes(string $name): void + { + $routesPath = base_path('routes/web.php'); + if (!file_exists($routesPath)) { $this->warn('⚠ routes/web.php not found. Routes not added.'); return; } + $resourceName = Str::plural(ucfirst($name)); + $controllerName = ucfirst($name) . 'Controller'; + $routePrefix = Str::kebab(Str::plural($name)); + $stub = $this->getStubContent('crud.routes'); + $stub = str_replace(['{{ $resourceName }}','{{ $controllerName }}','{{ $routePrefix }}'], [$resourceName,$controllerName,$routePrefix], $stub); + $routesContent = file_get_contents($routesPath); + if (str_contains($routesContent, $routePrefix)) { $this->line('⚠ Routes already exist in web.php. Skipping.'); return; } + $routesContent .= "\n" . $stub; + if (!$this->option('dry-run')) { + file_put_contents($routesPath, $routesContent); + $this->line('✓ Routes added to web.php'); + } else { + $this->line('[DRY RUN] Would add routes to web.php'); + } + } + + protected function generateLayout(): void + { + $path = resource_path('views/layouts/app.blade.php'); + if (file_exists($path)) return; + $stub = $this->getStubContent('crud.layout'); + if (!$this->shouldReplaceFile($path, 'Layout exists. Replace it? [y/n]')) return; + $this->ensureDirectoryExists(resource_path('views/layouts')); + $this->writeFile($path, $stub); + } + + protected function generateResourceFormFields(array $fields): string + { + if (empty($fields)) { return " // Form::text('name', 'Name'),"; } + $lines = []; + foreach ($fields as $field) { + $fieldName = $field['name']; + $fieldLabel = ucfirst(str_replace('_', ' ', $fieldName)); + $type = $this->getFormFieldType($field['type']); + $lines[] = " Form::{$type}('{$fieldName}', '{$fieldLabel}'),"; + } + return implode("\n", $lines); + } + + protected function generateResourceTableColumns(array $fields): string + { + if (empty($fields)) { return " // Table::text('id', 'ID'),"; } + $columns = [" Table::text('id', 'ID'),"]; + foreach ($fields as $field) { + $fieldName = $field['name']; + $fieldLabel = ucfirst(str_replace('_', ' ', $fieldName)); + $type = $this->getTableColumnType($field['type']); + $columns[] = " Table::{$type}('{$fieldName}', '{$fieldLabel}'),"; + } + $columns[] = " Table::actions(),"; + return implode("\n", $columns); + } + + protected function generateValidationRules(array $fields): string + { + if (empty($fields)) { return " // 'field' => 'required',"; } + return implode("\n", array_map(fn($f)=>" '{$f['name']}' => 'required',", $fields)); + } + + protected function getFormFieldType(string $type): string + { + $map = ['string'=>'text','text'=>'textarea','textarea'=>'textarea','email'=>'email','boolean'=>'checkbox','tinyint'=>'checkbox']; + return $map[strtolower($type)] ?? 'text'; + } + + protected function getTableColumnType(string $type): string + { + $map = ['decimal'=>'number','integer'=>'number','bigint'=>'number','boolean'=>'boolean','tinyint'=>'boolean','datetime'=>'datetime','date'=>'datetime','timestamp'=>'datetime']; + return $map[strtolower($type)] ?? 'text'; + } +} diff --git a/src/Commands/Service.php b/src/Commands/Service.php index a4d2062..ce9c579 100644 --- a/src/Commands/Service.php +++ b/src/Commands/Service.php @@ -2,9 +2,7 @@ namespace Kernel243\Artisan\Commands; -use Illuminate\Console\Command; - -class Service extends Command +class Service extends BaseCommand { /** * The name and signature of the console command. @@ -62,13 +60,15 @@ protected function replaceClassName($name, $stub) */ protected function putInFile($filename, $content, $module = null) { - $this->info($module); if (!is_null($module)) { $modulePath = base_path('Modules/'.$module.'/Services'); - if (!is_dir($modulePath)) mkdir($modulePath); + if (!is_dir($modulePath)) { + mkdir($modulePath, 0755, true); + } } else { - if (!is_dir(app_path('/Services'))) - mkdir(app_path('/Services')); + if (!is_dir(app_path('/Services'))) { + mkdir(app_path('/Services'), 0755, true); + } } file_put_contents($filename, $content); @@ -86,39 +86,15 @@ protected function replaceNamespace($namespace, $stub) return str_replace('DummyNamespace', ucfirst($namespace), $stub); } - protected function serviceFileExist($service, $module = null) + protected function serviceFileExist($service, $module = null): bool { if (is_null($module)) { - return file_exists(base_path(lcfirst($service).'php')) || file_exists( base_path(lcfirst(str_replace('\\', '/', $service)).'.php')); + $serviceName = ucfirst(basename(str_replace('\\', '/', $service))); + return file_exists(app_path('Services/'.$serviceName.'.php')); } - $path = base_path('Modules/'.ucfirst($module).'/Services/'.lcfirst($service).'.php'); - return file_exists($path) || file_exists('Modules/'.base_path(ucfirst($module).'/Services/'.lcfirst(str_replace('\\', '/', $service)).'.php')); - } - - /** - * Check if a module folder exists - * - * @param $module - * @return false|mixed - */ - protected function moduleExists($module) - { - return $this->folderExist('Modules/'.$module); - } - - /** - * Checks if a folder exist and return canonicalized absolute pathname (sort version) - * @param string $folder the path being checked. - * @return mixed returns the canonicalized absolute pathname on success otherwise FALSE is returned - */ - protected function folderExist($folder) - { - // Get canonicalized absolute pathname - $path = realpath($folder); - - // If it exists, check if it's a directory - return ($path !== false AND is_dir($path)) ? $path : false; + $path = base_path('Modules/'.ucfirst($module).'/Services/'.ucfirst(basename(str_replace('\\', '/', $service))).'.php'); + return file_exists($path); } /** diff --git a/src/Commands/stubs/crud.controller.resource.stub b/src/Commands/stubs/crud.controller.resource.stub new file mode 100644 index 0000000..152e234 --- /dev/null +++ b/src/Commands/stubs/crud.controller.resource.stub @@ -0,0 +1,81 @@ +service = $service; + $this->resource = $resource; + } + + public function index(): View + { + $DummyPluralVariable = $this->service->getPaginated(); + $tableBuilder = app(\App\Builders\Table::class); + $tableColumns = $this->resource->table($tableBuilder)->getColumns(); + return view('DummyViewPath.index', compact('DummyPluralVariable', 'tableColumns')) + ->with(['resourceName' => $this->resource->getName(), 'routePrefix' => $this->resource->getRoutePrefix()]); + } + + public function create(): View + { + $formBuilder = app(\App\Builders\Form::class); + $formFields = $this->resource->form($formBuilder)->getFields(); + return view('DummyViewPath.create') + ->with(['resourceName' => $this->resource->getName(), 'routePrefix' => $this->resource->getRoutePrefix(), 'formFields' => $formFields]); + } + + public function store(Request $request): RedirectResponse + { + $validated = $request->validate($this->resource->rules()); + $this->service->create($validated); + return redirect()->route('DummyViewPath.index')->with('success', 'Item created successfully.'); + } + + public function show(int $id): View + { + $DummyVariable = $this->service->getById($id); + $formBuilder = app(\App\Builders\Form::class); + $formFields = $this->resource->form($formBuilder)->getFields(); + $detailFields = array_map(function ($field) { + return ['name' => $field['name'], 'label' => $field['label'], 'type' => $field['type'] ?? 'text']; + }, $formFields); + return view('DummyViewPath.show', compact('DummyVariable', 'detailFields')) + ->with(['resourceName' => $this->resource->getName(), 'routePrefix' => $this->resource->getRoutePrefix()]); + } + + public function edit(int $id): View + { + $DummyVariable = $this->service->getById($id); + $formBuilder = app(\App\Builders\Form::class); + $formFields = $this->resource->form($formBuilder)->getFields(); + return view('DummyViewPath.edit', compact('DummyVariable', 'formFields')) + ->with(['resourceName' => $this->resource->getName(), 'routePrefix' => $this->resource->getRoutePrefix()]); + } + + public function update(Request $request, int $id): RedirectResponse + { + $validated = $request->validate($this->resource->rules()); + $this->service->update($id, $validated); + return redirect()->route('DummyViewPath.index')->with('success', 'Item updated successfully.'); + } + + public function destroy(int $id): RedirectResponse + { + $this->service->delete($id); + return redirect()->route('DummyViewPath.index')->with('success', 'Item deleted successfully.'); + } +} + + diff --git a/src/Commands/stubs/crud.controller.stub b/src/Commands/stubs/crud.controller.stub new file mode 100644 index 0000000..85271b6 --- /dev/null +++ b/src/Commands/stubs/crud.controller.stub @@ -0,0 +1,67 @@ +service = $service; + } + + public function index(): View + { + $DummyPluralVariable = $this->service->getPaginated(); + return view('DummyViewPath.index', compact('DummyPluralVariable')); + } + + public function create(): View + { + return view('DummyViewPath.create'); + } + + public function store(Request $request): RedirectResponse + { + $validated = $request->validate([ + // Add your validation rules here + ]); + $this->service->create($validated); + return redirect()->route('DummyViewPath.index')->with('success', 'Item created successfully.'); + } + + public function show(int $id): View + { + $DummyVariable = $this->service->getById($id); + return view('DummyViewPath.show', compact('DummyVariable')); + } + + public function edit(int $id): View + { + $DummyVariable = $this->service->getById($id); + return view('DummyViewPath.edit', compact('DummyVariable')); + } + + public function update(Request $request, int $id): RedirectResponse + { + $validated = $request->validate([ + // Add your validation rules here + ]); + $this->service->update($id, $validated); + return redirect()->route('DummyViewPath.index')->with('success', 'Item updated successfully.'); + } + + public function destroy(int $id): RedirectResponse + { + $this->service->delete($id); + return redirect()->route('DummyViewPath.index')->with('success', 'Item deleted successfully.'); + } +} + + diff --git a/src/Commands/stubs/crud.layout.stub b/src/Commands/stubs/crud.layout.stub new file mode 100644 index 0000000..139024e --- /dev/null +++ b/src/Commands/stubs/crud.layout.stub @@ -0,0 +1,21 @@ + + + + + + @yield('title', 'App') + + + + +
+ @yield('content') +
+ + + + diff --git a/src/Commands/stubs/crud.model.stub b/src/Commands/stubs/crud.model.stub new file mode 100644 index 0000000..3f178a4 --- /dev/null +++ b/src/Commands/stubs/crud.model.stub @@ -0,0 +1,41 @@ + + */ + protected $fillable = [ + DummyFillable + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + // Add your casts here + ]; + } +} + + diff --git a/src/Commands/stubs/crud.routes.stub b/src/Commands/stubs/crud.routes.stub new file mode 100644 index 0000000..8b3c08b --- /dev/null +++ b/src/Commands/stubs/crud.routes.stub @@ -0,0 +1,6 @@ +// {{ $resourceName }} Routes (Generated by make:crud) +Route::resource('{{ $routePrefix }}', App\Http\Controllers\{{ $controllerName }}::class); +// DataTables route for index page +Route::get('{{ $routePrefix }}/datatable', [App\Http\Controllers\{{ $controllerName }}::class, 'datatable'])->name('{{ $routePrefix }}.datatable'); + + diff --git a/src/Commands/stubs/crud.views.create.stub b/src/Commands/stubs/crud.views.create.stub new file mode 100644 index 0000000..00ed51c --- /dev/null +++ b/src/Commands/stubs/crud.views.create.stub @@ -0,0 +1,30 @@ +@extends('layouts.app') + +@section('title', 'Create DummyResource') + +@section('content') +
+
+

Create DummyResource

+ @if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+ @csrf + DummyFormFields +
+ + Cancel +
+
+
+
+@endsection + + diff --git a/src/Commands/stubs/crud.views.default._form.stub b/src/Commands/stubs/crud.views.default._form.stub new file mode 100644 index 0000000..87140c8 --- /dev/null +++ b/src/Commands/stubs/crud.views.default._form.stub @@ -0,0 +1,10 @@ +@csrf +
+ {{-- Example field --}} +
+ + +
+
+ + diff --git a/src/Commands/stubs/crud.views.default.index.stub b/src/Commands/stubs/crud.views.default.index.stub new file mode 100644 index 0000000..ad50d1b --- /dev/null +++ b/src/Commands/stubs/crud.views.default.index.stub @@ -0,0 +1,36 @@ +@extends('layouts.app') + +@section('title', '{{ $resourceName }}') + +@section('content') +
+
+

{{ $resourceName }}

+ + Create New + +
+ + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + +
+ + + + + + + + {{-- Your rows here --}} + +
ID
+
+
+@endsection + +