├── LICENSE.md
├── stubs
├── factory.stub
├── request.stub
├── seed.stub
├── service.stub
├── migrations.stub
├── AuthServiceProvider.stub
├── model.stub
├── dto.stub
├── resource.stub
├── controller.stub
├── provider.stub
└── policy.stub
├── config
├── code-generator.php
└── laravel-api-generator.php
├── src
├── EntitiesGenerator
│ ├── DTOGenerator.php
│ ├── Domain
│ │ └── Domain.php
│ ├── FactoryGenerator.php
│ ├── ModelGenerator.php
│ ├── PolicyGenerator.php
│ ├── RequestGenerator.php
│ ├── ResourceGenerator.php
│ ├── SeederGenerator.php
│ ├── ServiceGenerator.php
│ ├── ControllerGenerator.php
│ ├── MigrationGenerator.php
│ ├── Presentation
│ │ └── Presentation.php
│ ├── Infrastructure
│ │ └── Infrastucture.php
│ ├── AbstractGenerator.php
│ └── ModelGeneratorRefactored.php
├── Contracts
│ ├── ApiGenerationServiceInterface.php
│ └── GeneratorInterface.php
├── Exceptions
│ └── CodeGeneratorException.php
├── Support
│ ├── StubLoader.php
│ ├── FieldParser.php
│ └── JsonParser.php
├── ValueObjects
│ ├── RelationshipDefinition.php
│ ├── EntityDefinition.php
│ └── FieldDefinition.php
├── Providers
│ └── CodeGeneratorServiceProvider.php
├── Console
│ └── Commands
│ │ ├── MakeApiWithDiagram.php
│ │ ├── MakeApiCommand.php
│ │ ├── DeleteFullApi.php
│ │ ├── InstallPackageCommand.php
│ │ └── MakeApi.php
└── Services
│ └── ApiGenerationService.php
├── phpstan.neon
├── phpunit.xml
├── tests
├── TestCase.php
├── Feature
│ └── MakeApiCommandTest.php
└── Unit
│ └── ValueObjects
│ └── FieldDefinitionTest.php
├── deploy.bat
├── composer.json
├── .github
└── workflows
│ └── ci.yml
├── config.php
├── CONTRIBUTING.md
├── data.json
├── CHANGELOG.md
├── README_NEW.md
└── README.md
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stubs/factory.stub:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stubs/request.stub:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stubs/seed.stub:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stubs/service.stub:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stubs/migrations.stub:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/config/code-generator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stubs/AuthServiceProvider.stub:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/DTOGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/Domain/Domain.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/FactoryGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/ModelGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/PolicyGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/RequestGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/ResourceGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/SeederGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/ServiceGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/ControllerGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/MigrationGenerator.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/Presentation/Presentation.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/Infrastructure/Infrastucture.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stubs/model.stub:
--------------------------------------------------------------------------------
1 |
15 | */
16 | public function toArray(Request $request): array
17 | {
18 | return [
19 | {{fields}}
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 8
3 | paths:
4 | - src
5 | - tests
6 |
7 | ignoreErrors:
8 | - '#Call to an undefined method Illuminate\\Contracts\\Foundation\\Application::#'
9 | - '#Access to an undefined property Illuminate\\Contracts\\Foundation\\Application::#'
10 | - '#Undefined function#'
11 |
12 | checkMissingIterableValueType: false
13 | checkGenericClassInNonGenericObjectType: false
14 |
15 | excludePaths:
16 | - vendor/*
17 |
18 | reportUnmatchedIgnoredErrors: false
--------------------------------------------------------------------------------
/src/Contracts/ApiGenerationServiceInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | tests
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | src/
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Contracts/GeneratorInterface.php:
--------------------------------------------------------------------------------
1 | set('database.default', 'testing');
27 | $app['config']->set('database.connections.testing', [
28 | 'driver' => 'sqlite',
29 | 'database' => ':memory:',
30 | 'prefix' => '',
31 | ]);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/Exceptions/CodeGeneratorException.php:
--------------------------------------------------------------------------------
1 | artisan('make:fullapi', [
20 | 'name' => $name,
21 | '--fields' => $fields
22 | ])->assertSuccessful();
23 |
24 | // Assert
25 | $this->assertFileExists(app_path("Models/{$name}.php"));
26 | $this->assertFileExists(app_path("Http/Controllers/{$name}Controller.php"));
27 | $this->assertFileExists(app_path("Services/{$name}Service.php"));
28 | $this->assertFileExists(app_path("DTO/{$name}DTO.php"));
29 | }
30 |
31 | /** @test */
32 | public function it_requires_fields_option()
33 | {
34 | $this->artisan('make:fullapi', [
35 | 'name' => 'Post'
36 | ])->assertFailed();
37 | }
38 |
39 | /** @test */
40 | public function it_creates_valid_model_with_fillable()
41 | {
42 | // Arrange
43 | $name = 'Post';
44 | $fields = 'title:string,content:text';
45 |
46 | // Act
47 | $this->artisan('make:fullapi', [
48 | 'name' => $name,
49 | '--fields' => $fields
50 | ]);
51 |
52 | // Assert
53 | $modelPath = app_path("Models/{$name}.php");
54 | $this->assertStringContainsString(
55 | "protected \$fillable = ['title', 'content'];",
56 | file_get_contents($modelPath)
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Support/StubLoader.php:
--------------------------------------------------------------------------------
1 | getStubPath($stubName);
24 |
25 | if (!File::exists($stubPath)) {
26 | throw CodeGeneratorException::fileNotFound($stubPath);
27 | }
28 |
29 | $content = File::get($stubPath);
30 |
31 | return $this->replacePlaceholders($content, $replacements);
32 | }
33 |
34 | /**
35 | * Get the full path to a stub file.
36 | */
37 | private function getStubPath(string $stubName): string
38 | {
39 | $stubName = str_replace(self::STUB_EXTENSION, '', $stubName);
40 | return $this->stubsPath . DIRECTORY_SEPARATOR . $stubName . self::STUB_EXTENSION;
41 | }
42 |
43 | /**
44 | * Replace placeholders in stub content.
45 | */
46 | private function replacePlaceholders(string $content, array $replacements): string
47 | {
48 | foreach ($replacements as $placeholder => $value) {
49 | $content = str_replace("{{$placeholder}}", $value, $content);
50 | }
51 |
52 | return $content;
53 | }
54 |
55 | /**
56 | * Check if a stub exists.
57 | */
58 | public function exists(string $stubName): bool
59 | {
60 | return File::exists($this->getStubPath($stubName));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Support/FieldParser.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | public static function parseFieldsString(string $fields): array
18 | {
19 | if (empty(trim($fields))) {
20 | throw new InvalidArgumentException('Fields string cannot be empty');
21 | }
22 |
23 | $fieldsArray = [];
24 |
25 | foreach (explode(',', $fields) as $field) {
26 | $parts = explode(':', trim($field));
27 |
28 | if (count($parts) !== 2) {
29 | throw new InvalidArgumentException("Invalid field format: {$field}. Expected format: field:type");
30 | }
31 |
32 | $fieldName = trim($parts[0]);
33 | $fieldType = trim(strtolower($parts[1]));
34 |
35 | if (empty($fieldName) || empty($fieldType)) {
36 | throw new InvalidArgumentException("Field name and type cannot be empty: {$field}");
37 | }
38 |
39 | $fieldsArray[$fieldName] = $fieldType;
40 | }
41 |
42 | return $fieldsArray;
43 | }
44 |
45 | /**
46 | * Validate field type.
47 | */
48 | public static function isValidType(string $type): bool
49 | {
50 | $allowedTypes = [
51 | 'string', 'integer', 'int', 'boolean', 'bool', 'text', 'float',
52 | 'decimal', 'json', 'date', 'datetime', 'timestamp', 'time',
53 | 'uuid', 'UUID', 'bigint'
54 | ];
55 |
56 | return in_array($type, $allowedTypes, true);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/deploy.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | echo === Laravel API Generator - Deployment Script ===
3 | echo.
4 |
5 | REM Check if we're in the right directory
6 | if not exist "composer.json" (
7 | echo Error: composer.json not found. Make sure you're in the package root directory.
8 | pause
9 | exit /b 1
10 | )
11 |
12 | echo 1. Checking Git status...
13 | git status
14 |
15 | echo.
16 | echo 2. Adding all files...
17 | git add .
18 |
19 | echo.
20 | echo 3. Creating commit...
21 | git commit -m "feat: Major refactoring v3.0.0 - Clean architecture with Value Objects, Services, and improved generators
22 |
23 | - Implemented clean architecture with Value Objects (EntityDefinition, FieldDefinition, RelationshipDefinition)
24 | - Added professional Service Layer pattern with dependency injection
25 | - Created extensible generator system with AbstractGenerator
26 | - Improved JSON parsing with better error handling
27 | - Added comprehensive type safety with PHP 8.1+ features
28 | - Fixed model generation with proper relationships and inheritance
29 | - Enhanced stub system with better placeholder handling
30 | - Added professional error handling with custom exceptions
31 | - Improved documentation and contributing guidelines
32 | - Added GitHub Actions CI/CD pipeline
33 | - Updated PHPStan configuration to level 8
34 | - Added comprehensive testing structure"
35 |
36 | echo.
37 | echo 4. Creating version tag...
38 | git tag -a v3.0.0 -m "Release v3.0.0: Major refactoring with clean architecture"
39 |
40 | echo.
41 | echo 5. Pushing to repository...
42 | git push origin main
43 | git push origin --tags
44 |
45 | echo.
46 | echo 6. Deployment completed!
47 | echo.
48 | echo Next steps:
49 | echo - Check GitHub for the new tag: https://github.com/your-username/laravel-api-generator/tags
50 | echo - Check Packagist for the new version: https://packagist.org/packages/nameless/laravel-api-generator
51 | echo - The package should be available for installation with: composer require nameless/laravel-api-generator:^3.0
52 | echo.
53 | pause
54 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nameless/laravel-api-generator",
3 | "description": "A professional Laravel API generator that automatically creates complete API structures with clean architecture, type safety, and best practices",
4 | "version": "3.0.1",
5 | "type": "library",
6 | "license": "MIT",
7 | "keywords": [
8 | "laravel",
9 | "api",
10 | "generator",
11 | "crud",
12 | "clean-architecture",
13 | "dto",
14 | "service-layer",
15 | "artisan",
16 | "code-generator"
17 | ],
18 | "authors": [
19 | {
20 | "name": "Mbassi Loic Aron",
21 | "email": "loicmbassi5@email.com",
22 | "role": "Developer"
23 | }
24 | ],
25 | "require": {
26 | "php": "^8.1",
27 | "laravel/framework": "^10.0 || ^11.0",
28 | "dedoc/scramble": "^0.11.31"
29 | },
30 | "require-dev": {
31 | "phpunit/phpunit": "^10.0",
32 | "orchestra/testbench": "^8.0 || ^9.0",
33 | "phpstan/phpstan": "^2.1",
34 | "phpstan/phpstan-phpunit": "^2.0",
35 | "laravel/pint": "^1.0"
36 | },
37 | "autoload": {
38 | "psr-4": {
39 | "nameless\\CodeGenerator\\": "src/"
40 | }
41 | },
42 | "autoload-dev": {
43 | "psr-4": {
44 | "nameless\\CodeGenerator\\Tests\\": "tests/"
45 | }
46 | },
47 | "extra": {
48 | "laravel": {
49 | "providers": [
50 | "nameless\\CodeGenerator\\Providers\\CodeGeneratorServiceProvider"
51 | ]
52 | }
53 | },
54 | "config": {
55 | "optimize-autoloader": true,
56 | "preferred-install": "dist",
57 | "sort-packages": true
58 | },
59 | "scripts": {
60 | "test": "vendor/bin/phpunit",
61 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage",
62 | "analyse": "vendor/bin/phpstan analyse",
63 | "format": "vendor/bin/pint"
64 | },
65 | "minimum-stability": "dev",
66 | "prefer-stable": true
67 | }
68 |
--------------------------------------------------------------------------------
/stubs/controller.stub:
--------------------------------------------------------------------------------
1 | service->getAll();
27 | return {{modelName}}Resource::collection(${{pluralName}});
28 | }
29 |
30 | /**
31 | * Store a newly created resource in storage.
32 | */
33 | public function store({{modelName}}Request $request)
34 | {
35 | $dto = {{modelName}}DTO::fromRequest($request);
36 | ${{modelNameLower}} = $this->service->create($dto);
37 | return new {{modelName}}Resource(${{modelNameLower}});
38 | }
39 |
40 | /**
41 | * Display the specified resource.
42 | */
43 | public function show({{modelName}} ${{modelNameLower}})
44 | {
45 | return new {{modelName}}Resource(${{modelNameLower}});
46 | }
47 |
48 | /**
49 | * Update the specified resource in storage.
50 | */
51 | public function update({{modelName}}Request $request, {{modelName}} ${{modelNameLower}})
52 | {
53 | $dto = {{modelName}}DTO::fromRequest($request);
54 | $updated{{modelName}} = $this->service->update(${{modelNameLower}}, $dto);
55 | return new {{modelName}}Resource($updated{{modelName}});
56 | }
57 |
58 | /**
59 | * Remove the specified resource from storage.
60 | */
61 | public function destroy({{modelName}} ${{modelNameLower}})
62 | {
63 | $this->service->delete(${{modelNameLower}});
64 | return response(null, 204);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/ValueObjects/RelationshipDefinition.php:
--------------------------------------------------------------------------------
1 | validateType($type);
21 | $this->validateRelatedModel($relatedModel);
22 | $this->validateRole($role);
23 | }
24 |
25 | private function validateType(string $type): void
26 | {
27 | $allowedTypes = ['oneToOne', 'oneToMany', 'manyToOne', 'manyToMany'];
28 |
29 | if (!in_array($type, $allowedTypes, true)) {
30 | throw new InvalidArgumentException("Invalid relationship type: {$type}");
31 | }
32 | }
33 |
34 | private function validateRelatedModel(string $relatedModel): void
35 | {
36 | if (empty(trim($relatedModel))) {
37 | throw new InvalidArgumentException('Related model cannot be empty');
38 | }
39 | }
40 |
41 | private function validateRole(string $role): void
42 | {
43 | if (empty(trim($role))) {
44 | throw new InvalidArgumentException('Role cannot be empty');
45 | }
46 | }
47 |
48 | public function getEloquentMethod(): string
49 | {
50 | return match ($this->type) {
51 | 'oneToOne' => 'hasOne',
52 | 'oneToMany' => 'hasMany',
53 | 'manyToOne' => 'belongsTo',
54 | 'manyToMany' => 'belongsToMany',
55 | };
56 | }
57 |
58 | public function requiresForeignKey(): bool
59 | {
60 | return in_array($this->type, ['manyToOne', 'oneToOne'], true);
61 | }
62 |
63 | public function getForeignKeyName(): string
64 | {
65 | if ($this->foreignKey !== null) {
66 | return $this->foreignKey;
67 | }
68 |
69 | return Str::snake($this->role) . '_id';
70 | }
71 |
72 | public function getMethodName(): string
73 | {
74 | return Str::camel($this->role);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/stubs/provider.stub:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
24 | $this->commands([
25 | \nameless\CodeGenerator\Console\Commands\MakeApi::class, // Utiliser l'ancienne commande pour l'instant
26 | DeleteFullApi::class,
27 | MakeApiWithDiagram::class,
28 | InstallPackageCommand::class,
29 | ]);
30 | }
31 | }
32 |
33 | public function register(): void
34 | {
35 | $this->registerServices();
36 | $this->registerGenerators();
37 |
38 | // Register Scramble for API documentation
39 | $this->app->register(\Dedoc\Scramble\ScrambleServiceProvider::class);
40 | }
41 |
42 | private function registerServices(): void
43 | {
44 | // Register StubLoader
45 | $this->app->singleton(StubLoader::class, function () {
46 | return new StubLoader(__DIR__ . '/../../stubs');
47 | });
48 |
49 | // Register JsonParser
50 | $this->app->singleton(JsonParser::class);
51 |
52 | // Register main API generation service
53 | $this->app->singleton(ApiGenerationServiceInterface::class, function ($app) {
54 | return new ApiGenerationService(
55 | $app->make('code_generator.generators'),
56 | $app->make(JsonParser::class)
57 | );
58 | });
59 | }
60 |
61 | private function registerGenerators(): void
62 | {
63 | $this->app->singleton('code_generator.generators', function ($app) {
64 | return collect([
65 | $app->make(ModelGeneratorRefactored::class),
66 | // Add other generators here as they are created
67 | ]);
68 | });
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/AbstractGenerator.php:
--------------------------------------------------------------------------------
1 | generateContent($definition);
26 | $outputPath = $this->getOutputPath($definition);
27 |
28 | $this->ensureDirectoryExists($outputPath);
29 |
30 | if (!File::put($outputPath, $content)) {
31 | throw CodeGeneratorException::fileCreationFailed($outputPath);
32 | }
33 |
34 | return true;
35 | } catch (\Exception $e) {
36 | throw CodeGeneratorException::generationFailed($this->getType(), $e->getMessage());
37 | }
38 | }
39 |
40 | /**
41 | * Check if the generator supports the given entity definition.
42 | */
43 | public function supports(EntityDefinition $definition): bool
44 | {
45 | return true; // Default implementation supports all entities
46 | }
47 |
48 | /**
49 | * Generate the content for the file.
50 | */
51 | abstract protected function generateContent(EntityDefinition $definition): string;
52 |
53 | /**
54 | * Get the stub name for this generator.
55 | */
56 | abstract protected function getStubName(): string;
57 |
58 | /**
59 | * Get replacements for the stub.
60 | */
61 | abstract protected function getReplacements(EntityDefinition $definition): array;
62 |
63 | /**
64 | * Ensure the directory exists for the output path.
65 | */
66 | protected function ensureDirectoryExists(string $filePath): void
67 | {
68 | $directory = dirname($filePath);
69 |
70 | if (!File::isDirectory($directory)) {
71 | File::makeDirectory($directory, 0755, true);
72 | }
73 | }
74 |
75 | /**
76 | * Load and process stub with replacements.
77 | */
78 | protected function processStub(EntityDefinition $definition): string
79 | {
80 | $replacements = $this->getReplacements($definition);
81 | return $this->stubLoader->load($this->getStubName(), $replacements);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ main, develop ]
6 | pull_request:
7 | branches: [ main, develop ]
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | php-version: [8.1, 8.2, 8.3]
16 | laravel-version: [10.*, 11.*]
17 |
18 | name: PHP ${{ matrix.php-version }} - Laravel ${{ matrix.laravel-version }}
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 |
23 | - name: Setup PHP
24 | uses: shivammathur/setup-php@v2
25 | with:
26 | php-version: ${{ matrix.php-version }}
27 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
28 | coverage: xdebug
29 |
30 | - name: Cache Composer dependencies
31 | uses: actions/cache@v3
32 | with:
33 | path: ~/.composer/cache/files
34 | key: dependencies-laravel-${{ matrix.laravel-version }}-php-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}
35 |
36 | - name: Install dependencies
37 | run: |
38 | composer require "laravel/framework:${{ matrix.laravel-version }}" --no-interaction --no-update
39 | composer install --prefer-dist --no-interaction --no-suggest
40 |
41 | - name: Run tests
42 | run: vendor/bin/phpunit --coverage-clover=coverage.xml
43 |
44 | - name: Upload coverage to Codecov
45 | uses: codecov/codecov-action@v3
46 | with:
47 | file: ./coverage.xml
48 | fail_ci_if_error: true
49 |
50 | static-analysis:
51 | runs-on: ubuntu-latest
52 |
53 | name: Static Analysis
54 |
55 | steps:
56 | - uses: actions/checkout@v4
57 |
58 | - name: Setup PHP
59 | uses: shivammathur/setup-php@v2
60 | with:
61 | php-version: 8.2
62 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
63 |
64 | - name: Install dependencies
65 | run: composer install --prefer-dist --no-interaction --no-suggest
66 |
67 | - name: Run PHPStan
68 | run: vendor/bin/phpstan analyse --error-format=github
69 |
70 | code-style:
71 | runs-on: ubuntu-latest
72 |
73 | name: Code Style
74 |
75 | steps:
76 | - uses: actions/checkout@v4
77 |
78 | - name: Setup PHP
79 | uses: shivammathur/setup-php@v2
80 | with:
81 | php-version: 8.2
82 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
83 |
84 | - name: Install dependencies
85 | run: composer install --prefer-dist --no-interaction --no-suggest
86 |
87 | - name: Check code style
88 | run: vendor/bin/pint --test
89 |
--------------------------------------------------------------------------------
/config.php:
--------------------------------------------------------------------------------
1 |
5 | array (
6 | 'name' => 'Person',
7 | 'attributes' =>
8 | array (
9 | 0 =>
10 | array (
11 | 'name' => 'name',
12 | 'type' => 'string',
13 | ),
14 | 1 =>
15 | array (
16 | 'name' => 'phonenumber',
17 | 'type' => 'string',
18 | ),
19 | 2 =>
20 | array (
21 | 'name' => 'emailaddress',
22 | 'type' => 'string',
23 | ),
24 | 3 =>
25 | array (
26 | 'name' => 'address',
27 | 'type' => 'Address',
28 | ),
29 | ),
30 | ),
31 | 1 =>
32 | array (
33 | 'name' => 'Student',
34 | 'attributes' =>
35 | array (
36 | 0 =>
37 | array (
38 | 'name' => 'studentnumber',
39 | 'type' => 'integer',
40 | ),
41 | 1 =>
42 | array (
43 | 'name' => 'averagemark',
44 | 'type' => 'integer',
45 | ),
46 | ),
47 | ),
48 | 2 =>
49 | array (
50 | 'name' => 'Professor',
51 | 'attributes' =>
52 | array (
53 | 0 =>
54 | array (
55 | 'name' => 'name',
56 | 'type' => 'void',
57 | ),
58 | 1 =>
59 | array (
60 | 'name' => 'staffnumber',
61 | 'type' => 'integer',
62 | ),
63 | 2 =>
64 | array (
65 | 'name' => 'yearsofservice',
66 | 'type' => 'integer',
67 | ),
68 | 3 =>
69 | array (
70 | 'name' => 'numberofclasses',
71 | 'type' => 'integer',
72 | ),
73 | ),
74 | ),
75 | 3 =>
76 | array (
77 | 'name' => 'Address',
78 | 'attributes' =>
79 | array (
80 | 0 =>
81 | array (
82 | 'name' => 'street',
83 | 'type' => 'string',
84 | ),
85 | 1 =>
86 | array (
87 | 'name' => 'city',
88 | 'type' => 'string',
89 | ),
90 | 2 =>
91 | array (
92 | 'name' => 'state',
93 | 'type' => 'string',
94 | ),
95 | 3 =>
96 | array (
97 | 'name' => 'postalcode',
98 | 'type' => 'integer',
99 | ),
100 | 4 =>
101 | array (
102 | 'name' => 'country',
103 | 'type' => 'string',
104 | ),
105 | ),
106 | ),
107 | 4 =>
108 | array (
109 | 'name' => 'Adresses',
110 | 'attributes' =>
111 | array (
112 | 0 =>
113 | array (
114 | 'name' => 'city',
115 | 'type' => 'string',
116 | ),
117 | 1 =>
118 | array (
119 | 'name' => 'state',
120 | 'type' => 'string',
121 | ),
122 | 2 =>
123 | array (
124 | 'name' => 'postalcode',
125 | 'type' => 'integer',
126 | ),
127 | 3 =>
128 | array (
129 | 'name' => 'country',
130 | 'type' => 'string',
131 | ),
132 | ),
133 | ),
134 | );
135 |
--------------------------------------------------------------------------------
/src/Console/Commands/MakeApiWithDiagram.php:
--------------------------------------------------------------------------------
1 | error("Le fichier class_data est introuvable.");
21 | return;
22 | }
23 |
24 | $this->info("Lecture du fichier JSON...");
25 | $jsonData = file_get_contents($jsonFilePath);
26 | $this->classes = json_decode($jsonData, true);
27 |
28 | if (json_last_error() !== JSON_ERROR_NONE) {
29 | $this->error("Erreur de décodage JSON : " . json_last_error_msg());
30 | return;
31 | }
32 |
33 | $this->info("Extraction des données JSON...");
34 | $this->jsonExtractionToArray();
35 |
36 | $this->info("Génération des API avec Artisan...");
37 | $this->runFullApi();
38 | }
39 |
40 | /**
41 | * Extraire et formater les données JSON dans un tableau compatible.
42 | */
43 | public function jsonExtractionToArray()
44 | {
45 | $this->classes = array_map(function ($class) {
46 | return [
47 | 'name' => ucfirst($class['name']),
48 | 'attributes' => array_map(function ($attribute) {
49 | return [
50 | 'name' => $attribute['name'],
51 | 'type' => match (strtolower($attribute['type'])) {
52 | 'integer' => 'int',
53 | 'bigint' => 'int',
54 | 'str', 'text' => 'string',
55 | 'boolean' => 'bool',
56 | default => $attribute['type'],
57 | },
58 | ];
59 | }, $class['attributes']),
60 | ];
61 | }, $this->classes);
62 | }
63 |
64 | /**
65 | * Parcourir les classes et exécuter les commandes Artisan pour générer les API.
66 | */
67 | public function runFullApi()
68 | {
69 | foreach ($this->classes as $class) {
70 | $className = ucfirst($class['name']);
71 | $fields = [];
72 |
73 | foreach ($class['attributes'] as $attribute) {
74 | $fields[] = "{$attribute['name']}:{$attribute['type']}";
75 | }
76 |
77 | $fieldsString = implode(',', $fields);
78 | echo "Ici les parametres : ".$fieldsString. "\n";
79 |
80 | try {
81 | Artisan::call("make:fullapi {$className} --fields={$fieldsString}");
82 |
83 | $this->info("API pour la classe $className générée avec succès !");
84 | } catch (\Exception $e) {
85 | $this->error("Erreur lors de la génération de l'API pour la classe $className : " . $e->getMessage());
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Unit/ValueObjects/FieldDefinitionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('email', $field->name);
21 | $this->assertEquals('string', $field->type);
22 | $this->assertTrue($field->nullable);
23 | }
24 |
25 | public function test_throws_exception_for_empty_field_name(): void
26 | {
27 | $this->expectException(InvalidArgumentException::class);
28 | $this->expectExceptionMessage('Field name cannot be empty');
29 |
30 | new FieldDefinition(name: '', type: 'string');
31 | }
32 |
33 | public function test_throws_exception_for_invalid_field_name(): void
34 | {
35 | $this->expectException(InvalidArgumentException::class);
36 | $this->expectExceptionMessage('Invalid field name: 123invalid');
37 |
38 | new FieldDefinition(name: '123invalid', type: 'string');
39 | }
40 |
41 | public function test_throws_exception_for_unsupported_type(): void
42 | {
43 | $this->expectException(InvalidArgumentException::class);
44 | $this->expectExceptionMessage('Unsupported field type: unsupported');
45 |
46 | new FieldDefinition(name: 'field', type: 'unsupported');
47 | }
48 |
49 | public function test_gets_correct_database_type(): void
50 | {
51 | $stringField = new FieldDefinition('name', 'string');
52 | $this->assertEquals('string', $stringField->getDatabaseType());
53 |
54 | $intField = new FieldDefinition('age', 'integer');
55 | $this->assertEquals('integer', $intField->getDatabaseType());
56 |
57 | $boolField = new FieldDefinition('active', 'boolean');
58 | $this->assertEquals('boolean', $boolField->getDatabaseType());
59 | }
60 |
61 | public function test_gets_correct_php_type(): void
62 | {
63 | $stringField = new FieldDefinition('name', 'string');
64 | $this->assertEquals('string', $stringField->getPhpType());
65 |
66 | $intField = new FieldDefinition('age', 'integer');
67 | $this->assertEquals('int', $intField->getPhpType());
68 |
69 | $boolField = new FieldDefinition('active', 'boolean');
70 | $this->assertEquals('bool', $boolField->getPhpType());
71 | }
72 |
73 | public function test_gets_correct_validation_rule(): void
74 | {
75 | $stringField = new FieldDefinition('name', 'string');
76 | $this->assertEquals('sometimes|string|max:255', $stringField->getValidationRule());
77 |
78 | $requiredField = new FieldDefinition('email', 'string', false);
79 | $this->assertEquals('required|string|max:255', $requiredField->getValidationRule());
80 | }
81 |
82 | public function test_gets_correct_fake_value(): void
83 | {
84 | $stringField = new FieldDefinition('name', 'string');
85 | $this->assertEquals('fake()->word()', $stringField->getFakeValue());
86 |
87 | $intField = new FieldDefinition('age', 'integer');
88 | $this->assertEquals('fake()->randomNumber()', $intField->getFakeValue());
89 |
90 | $uuidField = new FieldDefinition('id', 'uuid');
91 | $this->assertEquals('fake()->uuid()', $uuidField->getFakeValue());
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/ValueObjects/EntityDefinition.php:
--------------------------------------------------------------------------------
1 | $fields
15 | * @param Collection $relationships
16 | */
17 | public function __construct(
18 | public string $name,
19 | public Collection $fields,
20 | public Collection $relationships,
21 | public ?string $parent = null,
22 | public array $options = []
23 | ) {
24 | $this->validateName($name);
25 | $this->validateFields($fields);
26 | $this->validateRelationships($relationships);
27 | }
28 |
29 | private function validateName(string $name): void
30 | {
31 | if (empty(trim($name))) {
32 | throw new InvalidArgumentException('Entity name cannot be empty');
33 | }
34 |
35 | if (!preg_match('/^[A-Z][a-zA-Z0-9]*$/', $name)) {
36 | throw new InvalidArgumentException("Invalid entity name: {$name}. Must start with uppercase letter.");
37 | }
38 | }
39 |
40 | private function validateFields(Collection $fields): void
41 | {
42 | $fieldNames = $fields->pluck('name')->toArray();
43 | $duplicates = array_filter(array_count_values($fieldNames), fn($count) => $count > 1);
44 |
45 | if (!empty($duplicates)) {
46 | throw new InvalidArgumentException('Duplicate field names found: ' . implode(', ', array_keys($duplicates)));
47 | }
48 | }
49 |
50 | private function validateRelationships(Collection $relationships): void
51 | {
52 | $relationshipRoles = $relationships->pluck('role')->toArray();
53 | $duplicates = array_filter(array_count_values($relationshipRoles), fn($count) => $count > 1);
54 |
55 | if (!empty($duplicates)) {
56 | throw new InvalidArgumentException('Duplicate relationship roles found: ' . implode(', ', array_keys($duplicates)));
57 | }
58 | }
59 |
60 | public function getTableName(): string
61 | {
62 | return Str::plural(Str::snake($this->name));
63 | }
64 |
65 | public function getPluralName(): string
66 | {
67 | return Str::plural(Str::lower($this->name));
68 | }
69 |
70 | public function getNameLower(): string
71 | {
72 | return Str::lower($this->name);
73 | }
74 |
75 | public function getNameCamel(): string
76 | {
77 | return Str::camel($this->name);
78 | }
79 |
80 | public function getFillableFields(): array
81 | {
82 | $fillable = $this->fields->pluck('name')->toArray();
83 |
84 | // Add foreign keys from relationships
85 | $foreignKeys = $this->relationships
86 | ->filter(fn(RelationshipDefinition $rel) => $rel->requiresForeignKey())
87 | ->map(fn(RelationshipDefinition $rel) => $rel->getForeignKeyName())
88 | ->toArray();
89 |
90 | return array_merge($fillable, $foreignKeys);
91 | }
92 |
93 | public function getFieldsArray(): array
94 | {
95 | return $this->fields->mapWithKeys(function (FieldDefinition $field) {
96 | return [$field->name => $field->type];
97 | })->toArray();
98 | }
99 |
100 | public function hasRelationships(): bool
101 | {
102 | return $this->relationships->isNotEmpty();
103 | }
104 |
105 | public function getRelationshipsByType(string $type): Collection
106 | {
107 | return $this->relationships->filter(
108 | fn(RelationshipDefinition $rel) => $rel->type === $type
109 | );
110 | }
111 |
112 | public function hasParent(): bool
113 | {
114 | return $this->parent !== null;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/ValueObjects/FieldDefinition.php:
--------------------------------------------------------------------------------
1 | validateName($name);
20 | $this->validateType($type);
21 | }
22 |
23 | private function validateName(string $name): void
24 | {
25 | if (empty(trim($name))) {
26 | throw new InvalidArgumentException('Field name cannot be empty');
27 | }
28 |
29 | if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $name)) {
30 | throw new InvalidArgumentException("Invalid field name: {$name}");
31 | }
32 | }
33 |
34 | private function validateType(string $type): void
35 | {
36 | $allowedTypes = [
37 | 'string', 'integer', 'int', 'boolean', 'bool', 'text', 'float',
38 | 'decimal', 'json', 'date', 'datetime', 'timestamp', 'time',
39 | 'uuid', 'UUID', 'bigint'
40 | ];
41 |
42 | if (!in_array($type, $allowedTypes, true)) {
43 | throw new InvalidArgumentException("Unsupported field type: {$type}");
44 | }
45 | }
46 |
47 | public function getDatabaseType(): string
48 | {
49 | return match ($this->type) {
50 | 'string' => 'string',
51 | 'integer', 'int' => 'integer',
52 | 'boolean', 'bool' => 'boolean',
53 | 'text' => 'text',
54 | 'float' => 'decimal',
55 | 'decimal' => 'decimal',
56 | 'json' => 'json',
57 | 'date', 'datetime', 'timestamp', 'time' => 'timestamp',
58 | 'uuid', 'UUID' => 'uuid',
59 | 'bigint' => 'bigInteger',
60 | default => 'string'
61 | };
62 | }
63 |
64 | public function getPhpType(): string
65 | {
66 | return match ($this->type) {
67 | 'string', 'text', 'uuid', 'UUID' => 'string',
68 | 'integer', 'int', 'bigint' => 'int',
69 | 'boolean', 'bool' => 'bool',
70 | 'float', 'decimal' => 'float',
71 | 'json' => 'array',
72 | 'date', 'datetime', 'timestamp', 'time' => '\DateTimeInterface',
73 | default => 'string'
74 | };
75 | }
76 |
77 | public function getValidationRule(): string
78 | {
79 | if (!empty($this->validationRules)) {
80 | return implode('|', $this->validationRules);
81 | }
82 |
83 | $rule = match ($this->type) {
84 | 'string' => 'string|max:255',
85 | 'integer', 'int', 'bigint' => 'integer',
86 | 'boolean', 'bool' => 'boolean',
87 | 'text' => 'string',
88 | 'uuid', 'UUID' => 'uuid',
89 | 'float', 'decimal' => 'numeric',
90 | 'json' => 'json',
91 | 'date', 'datetime', 'timestamp' => 'date',
92 | default => 'string'
93 | };
94 |
95 | return $this->nullable ? "sometimes|{$rule}" : "required|{$rule}";
96 | }
97 |
98 | public function getFakeValue(): string
99 | {
100 | return match ($this->type) {
101 | 'string' => "fake()->word()",
102 | 'integer', 'int', 'bigint' => "fake()->randomNumber()",
103 | 'boolean', 'bool' => "fake()->boolean()",
104 | 'text' => "fake()->sentence()",
105 | 'uuid', 'UUID' => "fake()->uuid()",
106 | 'float', 'decimal' => "fake()->randomFloat(2, 1, 1000)",
107 | 'json' => "json_encode(['key' => 'value'])",
108 | 'date', 'datetime', 'timestamp', 'time' => "fake()->dateTime()",
109 | default => "fake()->word()"
110 | };
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Console/Commands/MakeApiCommand.php:
--------------------------------------------------------------------------------
1 | argument('name');
32 |
33 | if (empty($name)) {
34 | return $this->handleJsonGeneration();
35 | }
36 |
37 | return $this->handleSingleEntityGeneration($name);
38 | } catch (CodeGeneratorException $e) {
39 | $this->error($e->getMessage());
40 | return self::FAILURE;
41 | } catch (\Exception $e) {
42 | $this->error("An unexpected error occurred: {$e->getMessage()}");
43 | return self::FAILURE;
44 | }
45 | }
46 |
47 | /**
48 | * Handle generation from JSON file.
49 | */
50 | private function handleJsonGeneration(): int
51 | {
52 | $this->warn("No entity name provided. Using JSON file for generation...");
53 |
54 | $jsonFilePath = base_path('class_data.json');
55 |
56 | if (!File::exists($jsonFilePath)) {
57 | $this->error("JSON file not found: {$jsonFilePath}");
58 | return self::FAILURE;
59 | }
60 |
61 | $jsonData = File::get($jsonFilePath);
62 |
63 | $this->info("Generating APIs from JSON data...");
64 | $this->apiGenerationService->generateFromJson($jsonData);
65 |
66 | $this->info("API generation completed successfully!");
67 | return self::SUCCESS;
68 | }
69 |
70 | /**
71 | * Handle generation for a single entity.
72 | */
73 | private function handleSingleEntityGeneration(string $name): int
74 | {
75 | $fieldsOption = $this->option('fields');
76 |
77 | if (!$fieldsOption) {
78 | $this->error('You must specify fields with the --fields option. Example: --fields="name:string,age:integer"');
79 | return self::FAILURE;
80 | }
81 |
82 | $fieldsArray = FieldParser::parseFieldsString($fieldsOption);
83 | $definition = $this->createEntityDefinition($name, $fieldsArray);
84 |
85 | $this->info("Generating complete API for: {$name}");
86 | $this->apiGenerationService->generateCompleteApi($definition);
87 |
88 | $this->info("API generation completed successfully!");
89 | return self::SUCCESS;
90 | }
91 |
92 | /**
93 | * Create EntityDefinition from parsed fields.
94 | */
95 | private function createEntityDefinition(string $name, array $fieldsArray): EntityDefinition
96 | {
97 | $fields = collect($fieldsArray)->map(function ($type, $fieldName) {
98 | return new FieldDefinition(
99 | name: $fieldName,
100 | type: $type
101 | );
102 | });
103 |
104 | return new EntityDefinition(
105 | name: ucfirst($name),
106 | fields: $fields,
107 | relationships: collect() // No relationships for single entity generation
108 | );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/EntitiesGenerator/ModelGeneratorRefactored.php:
--------------------------------------------------------------------------------
1 | name}.php");
27 | }
28 |
29 | /**
30 | * Generate the content for the file.
31 | */
32 | protected function generateContent(EntityDefinition $definition): string
33 | {
34 | return $this->processStub($definition);
35 | }
36 |
37 | /**
38 | * Get the stub name for this generator.
39 | */
40 | protected function getStubName(): string
41 | {
42 | return 'model';
43 | }
44 |
45 | /**
46 | * Get replacements for the stub.
47 | */
48 | protected function getReplacements(EntityDefinition $definition): array
49 | {
50 | return [
51 | 'modelName' => $definition->name,
52 | 'fillable' => $this->generateFillableArray($definition),
53 | 'relationships' => $this->generateRelationships($definition),
54 | 'parentClass' => $this->getParentClass($definition),
55 | 'imports' => $this->generateImports($definition),
56 | ];
57 | }
58 |
59 | /**
60 | * Generate fillable array string.
61 | */
62 | private function generateFillableArray(EntityDefinition $definition): string
63 | {
64 | $fillable = $definition->getFillableFields();
65 | $fillableString = "'" . implode("', '", $fillable) . "'";
66 |
67 | return "protected \$fillable = [{$fillableString}];";
68 | }
69 |
70 | /**
71 | * Generate relationship methods.
72 | */
73 | private function generateRelationships(EntityDefinition $definition): string
74 | {
75 | if (!$definition->hasRelationships()) {
76 | return '';
77 | }
78 |
79 | $methods = [];
80 |
81 | foreach ($definition->relationships as $relationship) {
82 | $methods[] = $this->generateRelationshipMethod($relationship);
83 | }
84 |
85 | return implode("\n\n", $methods);
86 | }
87 |
88 | /**
89 | * Generate a single relationship method.
90 | */
91 | private function generateRelationshipMethod(RelationshipDefinition $relationship): string
92 | {
93 | $methodName = $relationship->getMethodName();
94 | $eloquentMethod = $relationship->getEloquentMethod();
95 | $relatedModel = $relationship->relatedModel;
96 |
97 | return " public function {$methodName}()
98 | {
99 | return \$this->{$eloquentMethod}({$relatedModel}::class);
100 | }";
101 | }
102 |
103 | /**
104 | * Get parent class for inheritance.
105 | */
106 | private function getParentClass(EntityDefinition $definition): string
107 | {
108 | return $definition->hasParent() ? $definition->parent : 'Model';
109 | }
110 |
111 | /**
112 | * Generate imports based on relationships and parent class.
113 | */
114 | private function generateImports(EntityDefinition $definition): string
115 | {
116 | $imports = ['use Illuminate\Database\Eloquent\Model;'];
117 |
118 | if ($definition->hasParent()) {
119 | $imports[] = "use App\\Models\\{$definition->parent};";
120 | }
121 |
122 | // Add imports for related models
123 | $relatedModels = $definition->relationships
124 | ->pluck('relatedModel')
125 | ->unique()
126 | ->map(fn($model) => "use App\\Models\\{$model};")
127 | ->toArray();
128 |
129 | return implode("\n", array_merge($imports, $relatedModels));
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Support/JsonParser.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | public function parseJsonToEntities(string $jsonData): Collection
22 | {
23 | $data = json_decode($jsonData, true);
24 |
25 | if (json_last_error() !== JSON_ERROR_NONE) {
26 | throw CodeGeneratorException::invalidJsonData(json_last_error_msg());
27 | }
28 |
29 | // Handle different JSON formats
30 | $classes = $this->normalizeJsonData($data);
31 |
32 | return collect($classes)->map(function (array $classData) {
33 | return $this->createEntityDefinition($classData);
34 | });
35 | }
36 |
37 | /**
38 | * Normalize JSON data to consistent format.
39 | */
40 | private function normalizeJsonData(array $data): array
41 | {
42 | // Handle wrapped data format
43 | if (isset($data['data']) && is_array($data['data'])) {
44 | return [$data];
45 | }
46 |
47 | // Handle array of entities
48 | if (is_array($data) && !isset($data['name'])) {
49 | return $data;
50 | }
51 |
52 | // Handle single entity
53 | return [$data];
54 | }
55 |
56 | /**
57 | * Create EntityDefinition from array data.
58 | */
59 | private function createEntityDefinition(array $classData): EntityDefinition
60 | {
61 | $class = isset($classData['data']) ? $classData['data'] : $classData;
62 |
63 | $name = ucfirst($class['name']);
64 | $parent = isset($class['parent']) ? ucfirst($class['parent']) : null;
65 |
66 | $fields = $this->parseFields($class['attributes'] ?? []);
67 | $relationships = $this->parseRelationships($class);
68 |
69 | return new EntityDefinition(
70 | name: $name,
71 | fields: $fields,
72 | relationships: $relationships,
73 | parent: $parent
74 | );
75 | }
76 |
77 | /**
78 | * Parse fields from attributes array.
79 | *
80 | * @return Collection
81 | */
82 | private function parseFields(array $attributes): Collection
83 | {
84 | return collect($attributes)->map(function (array $attribute) {
85 | return new FieldDefinition(
86 | name: $attribute['name'],
87 | type: $this->normalizeType($attribute['_type'])
88 | );
89 | });
90 | }
91 |
92 | /**
93 | * Parse relationships from class data.
94 | *
95 | * @return Collection
96 | */
97 | private function parseRelationships(array $classData): Collection
98 | {
99 | $relationships = collect();
100 |
101 | $relationTypes = [
102 | 'oneToOneRelationships' => 'oneToOne',
103 | 'oneToManyRelationships' => 'oneToMany',
104 | 'manyToOneRelationships' => 'manyToOne',
105 | 'manyToManyRelationships' => 'manyToMany',
106 | ];
107 |
108 | foreach ($relationTypes as $key => $type) {
109 | if (isset($classData[$key]) && is_array($classData[$key])) {
110 | foreach ($classData[$key] as $relation) {
111 | $relationships->push(new RelationshipDefinition(
112 | type: $type,
113 | relatedModel: ucfirst($relation['comodel']),
114 | role: $relation['role']
115 | ));
116 | }
117 | }
118 | }
119 |
120 | return $relationships;
121 | }
122 |
123 | /**
124 | * Normalize type names.
125 | */
126 | private function normalizeType(string $type): string
127 | {
128 | return match (strtolower($type)) {
129 | 'integer', 'long' => 'int',
130 | 'bigint' => 'int',
131 | 'str', 'string', 'text', 'java.time.offsetdatetime', 'java.time.localdate' => 'string',
132 | 'boolean' => 'bool',
133 | 'java.math.bigdecimal' => 'float',
134 | 'java.util.map' => 'json',
135 | default => $type,
136 | };
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/Services/ApiGenerationService.php:
--------------------------------------------------------------------------------
1 | $generators
20 | */
21 | public function __construct(
22 | private readonly Collection $generators,
23 | private readonly JsonParser $jsonParser
24 | ) {}
25 |
26 | /**
27 | * Generate a complete API for the given entity.
28 | */
29 | public function generateCompleteApi(EntityDefinition $definition): bool
30 | {
31 | try {
32 | // Generate route first
33 | $this->generateApiRoute($definition);
34 |
35 | // Generate all components using registered generators
36 | foreach ($this->generators as $generator) {
37 | if ($generator->supports($definition)) {
38 | $generator->generate($definition);
39 | }
40 | }
41 |
42 | return true;
43 | } catch (\Exception $e) {
44 | throw CodeGeneratorException::generationFailed('API', $e->getMessage());
45 | }
46 | }
47 |
48 | /**
49 | * Generate APIs from JSON data.
50 | */
51 | public function generateFromJson(string $jsonData): bool
52 | {
53 | $entities = $this->jsonParser->parseJsonToEntities($jsonData);
54 |
55 | foreach ($entities as $entity) {
56 | $this->generateCompleteApi($entity);
57 | }
58 |
59 | return true;
60 | }
61 |
62 | /**
63 | * Delete a complete API for the given entity.
64 | */
65 | public function deleteCompleteApi(string $entityName): bool
66 | {
67 | // Implementation for deleting generated files
68 | // This would involve removing all generated files for the entity
69 |
70 | $filesToDelete = [
71 | app_path("Models/{$entityName}.php"),
72 | app_path("Http/Controllers/{$entityName}Controller.php"),
73 | app_path("Http/Requests/{$entityName}Request.php"),
74 | app_path("Http/Resources/{$entityName}Resource.php"),
75 | app_path("Services/{$entityName}Service.php"),
76 | app_path("DTO/{$entityName}DTO.php"),
77 | app_path("Policies/{$entityName}Policy.php"),
78 | database_path("factories/{$entityName}Factory.php"),
79 | database_path("seeders/{$entityName}Seeder.php"),
80 | ];
81 |
82 | foreach ($filesToDelete as $file) {
83 | if (File::exists($file)) {
84 | File::delete($file);
85 | }
86 | }
87 |
88 | // Remove migration files
89 | $tableName = Str::plural(Str::snake($entityName));
90 | $migrations = glob(database_path("migrations/*_create_{$tableName}_table.php"));
91 | foreach ($migrations as $migration) {
92 | File::delete($migration);
93 | }
94 |
95 | // Remove route from api.php
96 | $this->removeApiRoute($entityName);
97 |
98 | return true;
99 | }
100 |
101 | /**
102 | * Generate API route for the entity.
103 | */
104 | private function generateApiRoute(EntityDefinition $definition): void
105 | {
106 | $route = "Route::apiResource('{$definition->getPluralName()}', App\\Http\\Controllers\\{$definition->name}Controller::class);";
107 | $apiFilePath = base_path('routes/api.php');
108 | $phpHeader = "name}.php");
158 | }
159 |
160 | protected function generateContent(EntityDefinition $definition): string
161 | {
162 | return $this->processStub($definition);
163 | }
164 |
165 | protected function getStubName(): string
166 | {
167 | return 'custom';
168 | }
169 |
170 | protected function getReplacements(EntityDefinition $definition): array
171 | {
172 | return [
173 | 'className' => $definition->name,
174 | // ... other replacements
175 | ];
176 | }
177 | }
178 | ```
179 |
180 | ## Documentation
181 |
182 | - Update README.md for new features
183 | - Add PHPDoc comments to all public methods
184 | - Include code examples in documentation
185 | - Update CHANGELOG.md
186 |
187 | ## Testing
188 |
189 | Run the test suite:
190 |
191 | ```bash
192 | # Run all tests
193 | composer test
194 |
195 | # Run with coverage
196 | composer test-coverage
197 |
198 | # Run static analysis
199 | composer analyse
200 |
201 | # Format code
202 | composer format
203 | ```
204 |
205 | ## Questions?
206 |
207 | If you have questions about contributing, please:
208 |
209 | 1. Check existing issues and documentation
210 | 2. Create a new issue with the "question" label
211 | 3. Join our discussions
212 |
213 | Thank you for contributing!
214 |
--------------------------------------------------------------------------------
/config/laravel-api-generator.php:
--------------------------------------------------------------------------------
1 | __DIR__ . '/../stubs',
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Generated Files Configuration
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you can configure the paths and namespaces for generated files.
24 | | These paths are relative to the Laravel application root.
25 | |
26 | */
27 | 'paths' => [
28 | 'models' => 'app/Models',
29 | 'controllers' => 'app/Http/Controllers',
30 | 'requests' => 'app/Http/Requests',
31 | 'resources' => 'app/Http/Resources',
32 | 'services' => 'app/Services',
33 | 'dto' => 'app/DTO',
34 | 'policies' => 'app/Policies',
35 | 'factories' => 'database/factories',
36 | 'seeders' => 'database/seeders',
37 | 'migrations' => 'database/migrations',
38 | ],
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Namespaces Configuration
43 | |--------------------------------------------------------------------------
44 | |
45 | | Configure the namespaces for generated classes.
46 | |
47 | */
48 | 'namespaces' => [
49 | 'models' => 'App\\Models',
50 | 'controllers' => 'App\\Http\\Controllers',
51 | 'requests' => 'App\\Http\\Requests',
52 | 'resources' => 'App\\Http\\Resources',
53 | 'services' => 'App\\Services',
54 | 'dto' => 'App\\DTO',
55 | 'policies' => 'App\\Policies',
56 | ],
57 |
58 | /*
59 | |--------------------------------------------------------------------------
60 | | Default Field Types
61 | |--------------------------------------------------------------------------
62 | |
63 | | Define the default field types and their corresponding database types,
64 | | validation rules, and factory values.
65 | |
66 | */
67 | 'field_types' => [
68 | 'string' => [
69 | 'database' => 'string',
70 | 'php' => 'string',
71 | 'validation' => 'string|max:255',
72 | 'factory' => 'fake()->word()',
73 | ],
74 | 'text' => [
75 | 'database' => 'text',
76 | 'php' => 'string',
77 | 'validation' => 'string',
78 | 'factory' => 'fake()->sentence()',
79 | ],
80 | 'integer' => [
81 | 'database' => 'integer',
82 | 'php' => 'int',
83 | 'validation' => 'integer',
84 | 'factory' => 'fake()->randomNumber()',
85 | ],
86 | 'boolean' => [
87 | 'database' => 'boolean',
88 | 'php' => 'bool',
89 | 'validation' => 'boolean',
90 | 'factory' => 'fake()->boolean()',
91 | ],
92 | 'float' => [
93 | 'database' => 'decimal',
94 | 'php' => 'float',
95 | 'validation' => 'numeric',
96 | 'factory' => 'fake()->randomFloat(2, 1, 1000)',
97 | ],
98 | 'json' => [
99 | 'database' => 'json',
100 | 'php' => 'array',
101 | 'validation' => 'json',
102 | 'factory' => 'json_encode([\'key\' => \'value\'])',
103 | ],
104 | 'date' => [
105 | 'database' => 'date',
106 | 'php' => '\DateTimeInterface',
107 | 'validation' => 'date',
108 | 'factory' => 'fake()->date()',
109 | ],
110 | 'datetime' => [
111 | 'database' => 'datetime',
112 | 'php' => '\DateTimeInterface',
113 | 'validation' => 'date',
114 | 'factory' => 'fake()->dateTime()',
115 | ],
116 | 'timestamp' => [
117 | 'database' => 'timestamp',
118 | 'php' => '\DateTimeInterface',
119 | 'validation' => 'date',
120 | 'factory' => 'fake()->dateTime()',
121 | ],
122 | 'uuid' => [
123 | 'database' => 'uuid',
124 | 'php' => 'string',
125 | 'validation' => 'uuid',
126 | 'factory' => 'fake()->uuid()',
127 | ],
128 | ],
129 |
130 | /*
131 | |--------------------------------------------------------------------------
132 | | Generator Configuration
133 | |--------------------------------------------------------------------------
134 | |
135 | | Configure which generators should be enabled and their order of execution.
136 | |
137 | */
138 | 'generators' => [
139 | 'model' => true,
140 | 'migration' => true,
141 | 'controller' => true,
142 | 'request' => true,
143 | 'resource' => true,
144 | 'service' => true,
145 | 'dto' => true,
146 | 'policy' => true,
147 | 'factory' => true,
148 | 'seeder' => true,
149 | ],
150 |
151 | /*
152 | |--------------------------------------------------------------------------
153 | | API Route Configuration
154 | |--------------------------------------------------------------------------
155 | |
156 | | Configure how API routes should be generated.
157 | |
158 | */
159 | 'routes' => [
160 | 'file' => 'routes/api.php',
161 | 'prefix' => 'api',
162 | 'middleware' => ['api'],
163 | ],
164 | ];
165 |
--------------------------------------------------------------------------------
/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "person",
4 | "type": "class",
5 | "attributes": [
6 | {
7 | "visibility": "public",
8 | "name": "name",
9 | "type": "str"
10 | },
11 | {
12 | "visibility": "public",
13 | "name": "phonenumber",
14 | "type": "str"
15 | },
16 | {
17 | "visibility": "public",
18 | "name": "emailaddress",
19 | "type": "str"
20 | },
21 | {
22 | "visibility": "private",
23 | "type": "Address",
24 | "name": "address"
25 | }
26 | ],
27 | "methods": [
28 | {
29 | "visibility": "public",
30 | "name": "purchaseparkingpass",
31 | "type": "void",
32 | "args": []
33 | }
34 | ],
35 | "aggregations": [],
36 | "compositions": [],
37 | "import_list": true
38 | },
39 | {
40 | "name": "student",
41 | "type": "class",
42 | "attributes": [
43 | {
44 | "visibility": "public",
45 | "name": "studentnumber",
46 | "type": "int"
47 | },
48 | {
49 | "visibility": "public",
50 | "name": "averagemark",
51 | "type": "int"
52 | }
53 | ],
54 | "methods": [
55 | {
56 | "visibility": "public",
57 | "name": "iseligibletoenroll",
58 | "type": "bool",
59 | "args": [
60 | {
61 | "type": "Object",
62 | "name": "str"
63 | }
64 | ]
65 | },
66 | {
67 | "visibility": "public",
68 | "name": "getseminarstaken",
69 | "type": "int",
70 | "args": [
71 | {
72 | "type": "string",
73 | "name": "papa"
74 | }
75 | ]
76 | }
77 | ],
78 | "aggregations": [],
79 | "compositions": [],
80 | "parent": "person",
81 | "import_list": true
82 | },
83 | {
84 | "name": "professor",
85 | "type": "class",
86 | "attributes": [
87 | {
88 | "visibility": "public",
89 | "name": "name",
90 | "type": "void"
91 | },
92 | {
93 | "visibility": "protected",
94 | "name": "staffnumber",
95 | "type": "int"
96 | },
97 | {
98 | "visibility": "private",
99 | "name": "yearsofservice",
100 | "type": "int"
101 | },
102 | {
103 | "visibility": "public",
104 | "name": "numberofclasses",
105 | "type": "int"
106 | }
107 | ],
108 | "methods": [],
109 | "aggregations": [],
110 | "compositions": [
111 | {
112 | "visibility": "private",
113 | "type": "string",
114 | "name": "adressess"
115 | }
116 | ],
117 | "parent": "person",
118 | "import_list": true
119 | },
120 | {
121 | "name": "address",
122 | "type": "class",
123 | "attributes": [
124 | {
125 | "visibility": "public",
126 | "name": "street",
127 | "type": "str"
128 | },
129 | {
130 | "visibility": "public",
131 | "name": "city",
132 | "type": "str"
133 | },
134 | {
135 | "visibility": "public",
136 | "name": "state",
137 | "type": "str"
138 | },
139 | {
140 | "visibility": "public",
141 | "name": "postalcode",
142 | "type": "int"
143 | },
144 | {
145 | "visibility": "public",
146 | "name": "country",
147 | "type": "str"
148 | }
149 | ],
150 | "methods": [
151 | {
152 | "visibility": "private",
153 | "name": "validate",
154 | "type": "bool",
155 | "args": []
156 | },
157 | {
158 | "visibility": "public",
159 | "name": "outputaslabel",
160 | "type": "str",
161 | "args": []
162 | }
163 | ],
164 | "aggregations": [],
165 | "compositions": []
166 | },
167 | {
168 | "name": "adresses",
169 | "type": "class",
170 | "attributes": [
171 | {
172 | "visibility": "public",
173 | "name": "city",
174 | "type": "str"
175 | },
176 | {
177 | "visibility": "public",
178 | "name": "state",
179 | "type": "str"
180 | },
181 | {
182 | "visibility": "public",
183 | "name": "postalcode",
184 | "type": "int"
185 | },
186 | {
187 | "visibility": "public",
188 | "name": "country",
189 | "type": "str"
190 | }
191 | ],
192 | "methods": [
193 | {
194 | "visibility": "private",
195 | "name": "validate",
196 | "type": "bool",
197 | "args": []
198 | },
199 | {
200 | "visibility": "public",
201 | "name": "outputaslabel",
202 | "type": "str",
203 | "args": []
204 | }
205 | ],
206 | "aggregations": [],
207 | "compositions": []
208 | }
209 | ]
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `laravel-api-generator` will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [3.0.1] - 2025-06-28
9 |
10 | ### 📚 Documentation & Polish
11 |
12 | ### Updated
13 | - **Documentation Improvements**
14 | - Completely updated README.md with modern features and architecture examples
15 | - Added comprehensive usage examples with new syntax
16 | - Improved installation and quick start guides
17 | - Added architecture overview with Service Layer and DTO examples
18 | - Enhanced field types documentation
19 | - Added configuration and testing sections
20 |
21 | ### Fixed
22 | - Minor documentation formatting and consistency issues
23 | - Updated examples to reflect current v3.0+ architecture
24 |
25 | ### Added
26 | - Better code examples showing modern PHP 8.1+ features
27 | - Enhanced Quick Start section with single entity and bulk generation examples
28 | - Comprehensive field types and relationship documentation
29 |
30 | ## [3.0.0] - 2025-06-28
31 |
32 | ### 🚀 Major Refactoring - Clean Architecture Implementation
33 |
34 | This is a major release that completely refactors the package architecture for better maintainability, extensibility, and professionalism.
35 |
36 | ### Added
37 | - **Clean Architecture Implementation**
38 | - Value Objects for domain modeling (EntityDefinition, FieldDefinition, RelationshipDefinition)
39 | - Service Layer pattern with proper dependency injection
40 | - Contracts/Interfaces for better testability
41 | - Professional error handling with custom exceptions
42 |
43 | - **Enhanced Generator System**
44 | - AbstractGenerator base class for extensibility
45 | - Improved stub system with better placeholder handling
46 | - Support for complex relationships and inheritance
47 | - Type-safe field definitions and validation
48 |
49 | - **Developer Experience**
50 | - Comprehensive PHPDoc comments
51 | - PHPStan level 8 static analysis
52 | - GitHub Actions CI/CD pipeline
53 | - Professional contributing guidelines
54 | - Comprehensive test structure
55 |
56 | - **New Features**
57 | - JSON parser with robust error handling
58 | - Field parser with validation
59 | - Stub loader system
60 | - Professional configuration system
61 |
62 | ### Changed
63 | - **Architecture**: Complete rewrite using clean architecture principles
64 | - **Type Safety**: Full PHP 8.1+ type declarations with readonly properties
65 | - **Error Handling**: Professional exception handling throughout
66 | - **Code Quality**: SOLID principles compliance
67 | - **Documentation**: Complete rewrite with professional formatting
68 |
69 | ### Fixed
70 | - **Model Generation**: Fixed issues with relationships and inheritance
71 | - **JSON Parsing**: Better handling of different JSON formats
72 | - **Stub Processing**: Resolved placeholder replacement issues
73 | - **Field Types**: Improved type mapping and validation
74 |
75 | ### Technical Improvements
76 | - **PHP 8.1+ Features**: Constructor property promotion, readonly classes, match expressions
77 | - **Dependency Injection**: Proper DI container usage throughout
78 | - **Static Analysis**: PHPStan level 8 compliance
79 | - **Code Style**: Laravel Pint formatting
80 | - **Testing**: Comprehensive test structure
81 |
82 | ### Breaking Changes
83 | ⚠️ **This is a major version with breaking changes**
84 | - Namespace changes for better organization
85 | - Service provider restructuring
86 | - Command signature improvements
87 | - Configuration format updates
88 | - 🏗️ **Complete Architecture Refactoring**
89 | - Value Objects for type-safe domain modeling
90 | - Service Layer pattern implementation
91 | - Dependency Injection container integration
92 | - Clean Architecture principles
93 | - SOLID principles compliance
94 |
95 | - 🔧 **New Features**
96 | - Professional DTO generation with readonly classes
97 | - Enhanced JSON parsing with relationship support
98 | - Configurable field types and validation rules
99 | - Extensible generator system
100 | - Custom exceptions and error handling
101 |
102 | - 📁 **Improved Project Structure**
103 | - Contracts/Interfaces for better testability
104 | - Support classes for utilities
105 | - Organized generators by responsibility
106 | - Professional documentation
107 |
108 | - 🚀 **Enhanced Code Generation**
109 | - Type-safe PHP 8.1+ code generation
110 | - Improved stub system with better templating
111 | - Relationship handling (One-to-One, One-to-Many, Many-to-Many)
112 | - Foreign key management
113 | - Fillable properties automation
114 |
115 | - 🧪 **Quality Improvements**
116 | - PHPStan level 8 compliance
117 | - Comprehensive test structure
118 | - GitHub Actions CI/CD pipeline
119 | - Code style with Laravel Pint
120 | - Professional documentation
121 |
122 | ### Changed
123 | - **Breaking**: Refactored entire codebase for clean architecture
124 | - **Breaking**: Updated minimum PHP version to 8.1
125 | - **Breaking**: New namespace structure
126 | - Improved error messages and validation
127 | - Enhanced JSON data parsing logic
128 | - Better relationship detection and handling
129 |
130 | ### Fixed
131 | - Model generation with proper inheritance handling
132 | - Duplicate import statements
133 | - Foreign key generation issues
134 | - Stub placeholder processing
135 | - Route generation and management
136 |
137 | ### Removed
138 | - Legacy code patterns
139 | - Outdated stub formats
140 | - Unnecessary dependencies
141 |
142 | ## [2.0.6] - Previous Version
143 | - Legacy implementation with basic functionality
144 |
145 | ---
146 |
147 | ## Migration Guide from 2.x to 3.0
148 |
149 | ### What's Changed
150 | 1. **PHP Version**: Minimum PHP 8.1 required
151 | 2. **Architecture**: Complete refactoring to clean architecture
152 | 3. **Type Safety**: Full type declarations throughout
153 | 4. **Better Error Handling**: Custom exceptions with clear messages
154 |
155 | ### How to Upgrade
156 | 1. Update your PHP version to 8.1+
157 | 2. Update the package: `composer update nameless/laravel-api-generator`
158 | 3. Clear your cache: `php artisan cache:clear`
159 | 4. Re-generate your APIs to benefit from new features
160 |
161 | The package maintains backward compatibility for the main commands:
162 | - `php artisan make:fullapi EntityName --fields="field:type"`
163 | - `php artisan make:fullapi` (JSON mode)
164 |
--------------------------------------------------------------------------------
/src/Console/Commands/DeleteFullApi.php:
--------------------------------------------------------------------------------
1 | argument('name');
19 | if (empty($name)) {
20 | $this->warn("Aucun nom fourni. Utilisation du nom par défaut du fichier JSON.");
21 | $jsonFilePath = base_path('class_data.json');
22 | if (!file_exists($jsonFilePath)) {
23 | $this->error("Le fichier class_data.json est introuvable.");
24 | return;
25 | }
26 |
27 | $this->info("Lecture du fichier JSON...");
28 | $jsonData = file_get_contents($jsonFilePath);
29 | $this->classes = json_decode($jsonData, true);
30 |
31 | if (json_last_error() !== JSON_ERROR_NONE) {
32 | $this->error("Erreur de décodage JSON : " . json_last_error_msg());
33 | return;
34 | }
35 |
36 | $this->info("Extraction des données JSON...");
37 | $this->jsonExtractionToArray();
38 |
39 | $this->info("Delete API with diagram...");
40 | $this->runDeleteApiWithDiagram();
41 | return;
42 | }
43 | $pluralName = Str::plural(Str::snake($name));
44 | $className = Str::studly($name);
45 |
46 | $this->info("Suppression des fichiers pour : {$name}");
47 |
48 | // Supprimer le modèle
49 | $this->deleteFile(app_path("Models/{$className}.php"), "Modèle");
50 |
51 | // Supprimer les migrations
52 | $this->deleteFilesByPattern(database_path('migrations'), "*_create_{$pluralName}_table.php", "Migration");
53 |
54 | // Supprimer le service
55 | $this->deleteFile(app_path("Services/{$className}Service.php"), "Service");
56 |
57 | // Supprimer la policy
58 | $this->deleteFile(app_path("Policies/{$className}Policy.php"), "Policy");
59 | $this->removeFromAuthServiceProvider($className);
60 |
61 | // Supprimer le contrôleur
62 | $this->deleteFile(app_path("Http/Controllers/{$className}Controller.php"), "Contrôleur");
63 |
64 | // Supprimer la resource
65 | $this->deleteFile(app_path("Http/Resources/{$className}Resource.php"), "Resource");
66 |
67 | // Supprimer la requête
68 | $this->deleteFile(app_path("Http/Requests/{$className}Request.php"), "Requête");
69 |
70 | // Supprimer le seeder
71 | $this->deleteFile(database_path("seeders/{$className}Seeder.php"), "Seeder");
72 |
73 | // Supprimer le factory
74 | $this->deleteFile(database_path("factories/{$className}Factory.php"), "Factory");
75 |
76 | // Supprimer le DTO
77 | $this->deleteFile(app_path("DTO/{$className}DTO.php"), "DTO");
78 |
79 | $this->info("Tous les fichiers associés à {$name} ont été supprimés.");
80 | }
81 |
82 | /**
83 | * Extraire et formater les données JSON dans un tableau compatible.
84 | */
85 | public function jsonExtractionToArray()
86 | {
87 | $this->classes = array_map(function ($class) {
88 | return [
89 | 'name' => ucfirst($class['name']),
90 | 'attributes' => array_map(function ($attribute) {
91 | return [
92 | 'name' => $attribute['name'],
93 | '_type' => match (strtolower($attribute['_type'])) {
94 | 'integer' => 'int',
95 | 'bigint' => 'int',
96 | 'str', 'text' => 'string',
97 | 'boolean' => 'bool',
98 | default => $attribute['_type'],
99 | },
100 | ];
101 | }, $class['attributes']),
102 | ];
103 | }, $this->classes);
104 | }
105 |
106 | /**
107 | * Parcourir les classes et exécuter les commandes Artisan pour générer les API.
108 | */
109 | public function runDeleteApiWithDiagram()
110 | {
111 | foreach ($this->classes as $class) {
112 | $className = ucfirst($class['name']);
113 |
114 | echo "Ici les parametres : ".$className. "\n";
115 |
116 | try {
117 | Artisan::call("delete:fullapi {$className}");
118 |
119 | $this->info("API pour la classe $className générée avec succès !");
120 | } catch (\Exception $e) {
121 | $this->error("Erreur lors de la génération de l'API pour la classe $className : " . $e->getMessage());
122 | }
123 | }
124 | }
125 |
126 | private function deleteFile($filePath, $type)
127 | {
128 | if (File::exists($filePath)) {
129 | File::delete($filePath);
130 | $this->info("{$type} supprimé : {$filePath}");
131 | } else {
132 | $this->warn("{$type} introuvable : {$filePath}");
133 | }
134 | }
135 |
136 | private function deleteFilesByPattern($directory, $pattern, $type)
137 | {
138 | $files = File::glob("{$directory}/{$pattern}");
139 | if ($files) {
140 | foreach ($files as $file) {
141 | File::delete($file);
142 | $this->info("{$type} supprimé : {$file}");
143 | }
144 | } else {
145 | $this->warn("Aucun fichier {$type} correspondant au motif : {$pattern}");
146 | }
147 | }
148 |
149 | private function removeFromAuthServiceProvider($className)
150 | {
151 | $providerPath = app_path('Providers/AuthServiceProvider.php');
152 |
153 | if (!file_exists($providerPath)) {
154 | $this->warn("AuthServiceProvider n'existe pas.");
155 | return;
156 | }
157 | $content = file_get_contents($providerPath);
158 |
159 | // Supprimer les imports
160 | $content = preg_replace("/use App\\\\Models\\\\{$className};\n/", '', $content);
161 | $content = preg_replace("/use App\\\\Policies\\\\{$className}Policy;\n/", '', $content);
162 |
163 | // Supprimer le mapping de la policy
164 | $content = preg_replace("/\s*{$className}::class => {$className}Policy::class,/", '', $content);
165 |
166 | file_put_contents($providerPath, $content);
167 | $this->info("Policy supprimée de AuthServiceProvider");
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/README_NEW.md:
--------------------------------------------------------------------------------
1 | # Laravel API Generator
2 |
3 | [](https://packagist.org/packages/nameless/laravel-api-generator)
4 | [](https://packagist.org/packages/nameless/laravel-api-generator)
5 | [](https://packagist.org/packages/nameless/laravel-api-generator)
6 |
7 | **Laravel API Generator** is a professional, enterprise-grade Laravel package that generates complete API structures following best practices and clean architecture principles.
8 |
9 | ## 🚀 Features
10 |
11 | ### ✨ Complete API Generation
12 | - **Models** with proper relationships and fillable properties
13 | - **RESTful Controllers** with full CRUD operations
14 | - **Service Layer** implementation for business logic
15 | - **Data Transfer Objects (DTOs)** for type-safe data handling
16 | - **Form Request Validations** with intelligent rules
17 | - **API Resources** for consistent response formatting
18 | - **Policies** for authorization
19 | - **Database Factories** with realistic fake data
20 | - **Seeders** for test data generation
21 | - **Migrations** with proper foreign keys and constraints
22 |
23 | ### 🏗️ Architecture & Design Patterns
24 | - **Clean Architecture** with separated concerns
25 | - **Repository Pattern** with service layer
26 | - **Value Objects** for domain modeling
27 | - **Dependency Injection** throughout
28 | - **SOLID Principles** compliance
29 | - **Type Safety** with PHP 8.1+ features
30 |
31 | ### 🔧 Advanced Features
32 | - **JSON Schema Support** for bulk generation
33 | - **Relationship Management** (One-to-One, One-to-Many, Many-to-Many)
34 | - **Inheritance Support** for model hierarchies
35 | - **Custom Field Types** with validation rules
36 | - **Extensible Generator System**
37 | - **Professional Error Handling**
38 |
39 | ## 📦 Installation
40 |
41 | ```bash
42 | composer require nameless/laravel-api-generator
43 | ```
44 |
45 | The package automatically registers its service provider.
46 |
47 | ## 🎯 Quick Start
48 |
49 | ### Single Entity Generation
50 |
51 | Generate a complete API for a single entity:
52 |
53 | ```bash
54 | php artisan make:fullapi User --fields="name:string,email:string,age:integer,is_active:boolean"
55 | ```
56 |
57 | This creates:
58 | - `app/Models/User.php`
59 | - `app/Http/Controllers/UserController.php`
60 | - `app/Http/Requests/UserRequest.php`
61 | - `app/Http/Resources/UserResource.php`
62 | - `app/Services/UserService.php`
63 | - `app/DTO/UserDTO.php`
64 | - `app/Policies/UserPolicy.php`
65 | - `database/factories/UserFactory.php`
66 | - `database/seeders/UserSeeder.php`
67 | - `database/migrations/xxxx_create_users_table.php`
68 | - API routes in `routes/api.php`
69 |
70 | ### Bulk Generation from JSON
71 |
72 | Create a `class_data.json` file in your project root:
73 |
74 | ```json
75 | [
76 | {
77 | "name": "User",
78 | "attributes": [
79 | {"name": "name", "_type": "string"},
80 | {"name": "email", "_type": "string"},
81 | {"name": "email_verified_at", "_type": "timestamp"}
82 | ],
83 | "oneToManyRelationships": [
84 | {"role": "posts", "comodel": "Post"}
85 | ]
86 | },
87 | {
88 | "name": "Post",
89 | "attributes": [
90 | {"name": "title", "_type": "string"},
91 | {"name": "content", "_type": "text"},
92 | {"name": "published_at", "_type": "timestamp"}
93 | ],
94 | "manyToOneRelationships": [
95 | {"role": "user", "comodel": "User"}
96 | ]
97 | }
98 | ]
99 | ```
100 |
101 | Then run:
102 |
103 | ```bash
104 | php artisan make:fullapi
105 | ```
106 |
107 | ## 🏗️ Architecture Overview
108 |
109 | ### Service Layer Pattern
110 |
111 | The generated code follows the Service Layer pattern for better organization:
112 |
113 | ```php
114 | class UserController extends Controller
115 | {
116 | public function __construct(
117 | private readonly UserService $service
118 | ) {}
119 |
120 | public function store(UserRequest $request)
121 | {
122 | $dto = UserDTO::fromRequest($request);
123 | $user = $this->service->create($dto);
124 | return new UserResource($user);
125 | }
126 | }
127 | ```
128 |
129 | ### Data Transfer Objects
130 |
131 | Type-safe data handling with DTOs:
132 |
133 | ```php
134 | readonly class UserDTO
135 | {
136 | public function __construct(
137 | public ?string $name,
138 | public ?string $email,
139 | public ?int $age,
140 | public ?bool $is_active,
141 | ) {}
142 |
143 | public static function fromRequest(Request $request): self
144 | {
145 | return new self(
146 | name: $request->get('name'),
147 | email: $request->get('email'),
148 | age: $request->get('age'),
149 | is_active: $request->get('is_active'),
150 | );
151 | }
152 | }
153 | ```
154 |
155 | ## 🛠️ Advanced Usage
156 |
157 | ### Custom Field Types
158 |
159 | Supported field types:
160 | - `string` - VARCHAR(255)
161 | - `text` - TEXT
162 | - `integer`/`int` - INTEGER
163 | - `bigint` - BIG INTEGER
164 | - `boolean`/`bool` - BOOLEAN
165 | - `float`/`decimal` - DECIMAL
166 | - `json` - JSON
167 | - `date` - DATE
168 | - `datetime` - DATETIME
169 | - `timestamp` - TIMESTAMP
170 | - `uuid` - UUID
171 |
172 | ### Relationship Types
173 |
174 | The generator supports all Laravel relationship types:
175 |
176 | - **One-to-One**: `oneToOneRelationships`
177 | - **One-to-Many**: `oneToManyRelationships`
178 | - **Many-to-One**: `manyToOneRelationships`
179 | - **Many-to-Many**: `manyToManyRelationships`
180 |
181 | ### Model Inheritance
182 |
183 | Support for model inheritance:
184 |
185 | ```json
186 | {
187 | "name": "AdminUser",
188 | "parent": "User",
189 | "attributes": [
190 | {"name": "permissions", "_type": "json"}
191 | ]
192 | }
193 | ```
194 |
195 | ## 🔧 Configuration
196 |
197 | ### Custom Stubs
198 |
199 | You can customize the generated code by publishing and modifying the stubs:
200 |
201 | ```bash
202 | php artisan vendor:publish --tag=laravel-api-generator-stubs
203 | ```
204 |
205 | ### Service Registration
206 |
207 | The package automatically registers all generators and services through dependency injection.
208 |
209 | ## 🧪 Testing
210 |
211 | ```bash
212 | composer test
213 | ```
214 |
215 | ## 📖 API Documentation
216 |
217 | The package integrates with [Scramble](https://github.com/dedoc/scramble) for automatic API documentation generation.
218 |
219 | ## 🤝 Contributing
220 |
221 | Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
222 |
223 | ## 🔒 Security
224 |
225 | If you discover any security-related issues, please email the maintainer instead of using the issue tracker.
226 |
227 | ## 📄 License
228 |
229 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
230 |
231 | ## 🏆 Credits
232 |
233 | - **Author**: Mbassi Loic Aron
234 | - **Email**: loicmbassi5@email.com
235 |
236 | ## 📚 Changelog
237 |
238 | Please see [CHANGELOG.md](CHANGELOG.md) for more information on what has changed recently.
239 |
240 | ---
241 |
242 | ## 💡 Why Choose Laravel API Generator?
243 |
244 | ✅ **Professional Architecture** - Built with enterprise-grade patterns
245 | ✅ **Type Safety** - Full PHP 8.1+ type declarations
246 | ✅ **Clean Code** - SOLID principles and clean architecture
247 | ✅ **Extensible** - Easy to extend with custom generators
248 | ✅ **Well Tested** - Comprehensive test suite
249 | ✅ **Documentation** - Complete API documentation generation
250 | ✅ **Best Practices** - Follows Laravel and PHP best practices
251 |
252 | Transform your Laravel development workflow with professional API generation!
253 |
--------------------------------------------------------------------------------
/src/Console/Commands/InstallPackageCommand.php:
--------------------------------------------------------------------------------
1 | info('Installing API Generator Package...');
26 | // $this->installBreeze();
27 | // Demander si l'utilisateur veut un starter kit d'authentification
28 | $wantsAuth = $this->confirm('Would you like to install an authentication starter kit?', true);
29 |
30 | if ($wantsAuth) {
31 | $authChoice = $this->choice(
32 | 'Which authentication starter kit would you prefer?',
33 | [
34 | 'breeze' => 'Laravel Breeze (Lightweight, minimal)',
35 | 'ui' => 'Laravel UI (Traditional Bootstrap)',
36 | 'none' => 'No authentication starter kit'
37 | ],
38 | 'breeze'
39 | );
40 |
41 | if ($authChoice === 'breeze') {
42 | $this->installBreezeWithOptions();
43 | } elseif ($authChoice === 'ui') {
44 | $this->installLaravelUI();
45 | }
46 | }else{
47 | $this->info('No authentication starter kit installed.');
48 | $this->call('install:api');
49 |
50 | }
51 |
52 | $this->publishScrambleConfig();
53 |
54 | // Ajouter le provider de Scramble dans config/app.php si pas déjà présent
55 | $this->registerScrambleProvider();
56 |
57 | // Publier les autres fichiers de configuration de votre package si nécessaire
58 | $this->publishPackageConfig();
59 | //installation des
60 |
61 | $this->info('Installation completed! 🎉');
62 |
63 | // $this->call('install:api');
64 |
65 | $this->showPostInstallationMessage();
66 | } finally {
67 | self::$isRunning = false;
68 | }
69 |
70 |
71 |
72 | }
73 |
74 | protected function publishScrambleConfig()
75 | {
76 | $this->info('Publishing Scramble configuration...');
77 | $this->call('vendor:publish', [
78 | '--provider' => 'Dedoc\Scramble\ScrambleServiceProvider',
79 | '--force' => true
80 | ]);
81 | }
82 |
83 | protected function registerScrambleProvider()
84 | {
85 | $this->info('Registering Scramble Service Provider...');
86 |
87 | $config_app = config_path('app.php');
88 | $provider = \Dedoc\Scramble\ScrambleServiceProvider::class;
89 |
90 | if (File::exists($config_app)) {
91 | $contents = File::get($config_app);
92 |
93 | if (!str_contains($contents, $provider)) {
94 | $providers = str_replace(
95 | 'providers\' => [',
96 | 'providers\' => [' . PHP_EOL . ' ' . $provider . '::class,',
97 | $contents
98 | );
99 |
100 | File::put($config_app, $providers);
101 | $this->info('Scramble Service Provider registered successfully.');
102 | } else {
103 | $this->info('Scramble Service Provider already registered.');
104 | }
105 | }
106 | }
107 |
108 | protected function publishPackageConfig()
109 | {
110 | $this->info('Publishing API Generator configuration...');
111 | //Execute this commande : php artisan install:api
112 |
113 | $this->call('vendor:publish', [
114 | '--provider' => 'nameless\CodeGenerator\Providers\CodeGeneratorServiceProvider',
115 | '--tag' => 'config',
116 | '--force' => true
117 | ]);
118 | }
119 |
120 | protected function showPostInstallationMessage()
121 | {
122 | $this->info('');
123 | $this->info('🚀 API Generator has been installed successfully!');
124 | $this->info('');
125 | $this->info('Next steps:');
126 | $this->info('1. Review the configuration in config/scramble.php');
127 | $this->info('2. Review the configuration in config/api-generator.php');
128 | $this->info('3. Start generating your API with: php artisan api:generate {name}');
129 | $this->info('');
130 | $this->info('Documentation: https://github.com/Nameless0l/laravel-api-generator');
131 | }
132 |
133 | protected function installBreeze()
134 | {
135 | $this->info('Installing Laravel Breeze API...');
136 |
137 | // Installation de Breeze via Composer
138 | $process = new Process(['composer', 'require', 'laravel/breeze', '--dev']);
139 | $process->setTimeout(null);
140 |
141 | $process->run(function ($type, $buffer) {
142 | $this->output->write($buffer);
143 | });
144 |
145 | if ($process->isSuccessful()) {
146 | $this->info('Laravel Breeze installed successfully!');
147 |
148 | // Clear configuration cache
149 | $this->call('config:clear');
150 |
151 | // Clear and rebuild cached Composer autoload files
152 | $composerDump = new Process(['composer', 'dump-autoload']);
153 | $composerDump->run();
154 |
155 | // Try installing Breeze with error handling
156 | try {
157 | $this->call('breeze:install', [
158 | 'stack' => 'api',
159 | '--api' => true,
160 | '--pest' => false
161 | ]);
162 | } catch (\Exception $e) {
163 | $this->warn('Could not run breeze:install automatically. Please run the following commands manually:');
164 | $this->info('php artisan breeze:install api --api');
165 | $this->info('php artisan migrate');
166 | return;
167 | }
168 |
169 | // Run migrations if Breeze installation was successful
170 | $this->call('migrate');
171 |
172 | $this->info('Laravel Breeze API configuration completed!');
173 | } else {
174 | $this->error('Failed to install Laravel Breeze');
175 | $this->error($process->getErrorOutput());
176 | }
177 | }
178 |
179 | protected function installBreezeWithOptions()
180 | {
181 | $this->info('Installing Laravel Breeze...');
182 |
183 | // Installation de Breeze via Composer
184 | $process = new Process(['composer', 'require', 'laravel/breeze', '--dev']);
185 | $process->setTimeout(null);
186 | $process->run(function ($type, $buffer) {
187 | $this->output->write($buffer);
188 | });
189 |
190 | if ($process->isSuccessful()) {
191 | $this->info('Laravel Breeze installed successfully!');
192 |
193 | $this->info('Laravel Breeze configuration completed!');
194 | } else {
195 | $this->error('Failed to install Laravel Breeze');
196 | $this->error($process->getErrorOutput());
197 | }
198 | }
199 | protected function installLaravelUI()
200 | {
201 | $this->info('Installing Laravel UI...');
202 |
203 | $process = new Process(['composer', 'require', 'laravel/ui', '--dev']);
204 | $process->setTimeout(null);
205 | $process->run(function ($type, $buffer) {
206 | $this->output->write($buffer);
207 | });
208 |
209 | if ($process->isSuccessful()) {
210 | $frontend = $this->choice(
211 | 'Which frontend would you like to use?',
212 | [
213 | 'bootstrap' => 'Bootstrap',
214 | 'vue' => 'Vue.js',
215 | 'react' => 'React'
216 | ],
217 | 'bootstrap'
218 | );
219 |
220 | $this->call('ui', [$frontend, '--auth']);
221 |
222 | $this->info('Installing and building frontend dependencies...');
223 | $this->runProcess(['npm', 'install']);
224 | $this->runProcess(['npm', 'run', 'dev']);
225 |
226 | if ($this->confirm('Would you like to run migrations now?', true)) {
227 | $this->call('migrate');
228 | }
229 |
230 | $this->info('Laravel UI installed successfully!');
231 | }
232 | }
233 |
234 | protected function runProcess(array $command)
235 | {
236 | $process = new Process($command);
237 | $process->setTimeout(null);
238 | $process->run(function ($type, $buffer) {
239 | $this->output->write($buffer);
240 | });
241 | return $process->isSuccessful();
242 | }
243 |
244 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel API Generator
2 |
3 | [](https://packagist.org/packages/nameless/laravel-api-generator)
4 | [](https://packagist.org/packages/nameless/laravel-api-generator)
5 | [](https://packagist.org/packages/nameless/laravel-api-generator)
6 |
7 | **Laravel API Generator** is a professional, enterprise-grade Laravel package that generates complete API structures following best practices and clean architecture principles.
8 |
9 | ---
10 |
11 | ## 🚀 Features
12 |
13 | ### ✨ Complete API Generation
14 | - **Models** with proper relationships and fillable properties
15 | - **RESTful Controllers** with full CRUD operations
16 | - **Service Layer** implementation for business logic
17 | - **Data Transfer Objects (DTOs)** for type-safe data handling
18 | - **Form Request Validations** with intelligent rules
19 | - **API Resources** for consistent response formatting
20 | - **Policies** for authorization
21 | - **Database Factories** with realistic fake data
22 | - **Seeders** for test data generation
23 | - **Migrations** with proper foreign keys and constraints
24 |
25 | ### 🏗️ Architecture & Design Patterns
26 | - **Clean Architecture** with separated concerns
27 | - **Repository Pattern** with service layer
28 | - **Value Objects** for domain modeling
29 | - **Dependency Injection** throughout
30 | - **SOLID Principles** compliance
31 | - **Type Safety** with PHP 8.1+ features
32 |
33 | ### 🔧 Advanced Features
34 | - **JSON Schema Support** for bulk generation
35 | - **Relationship Management** (One-to-One, One-to-Many, Many-to-Many)
36 | - **Inheritance Support** for model hierarchies
37 | - **Custom Field Types** with validation rules
38 | - **Extensible Generator System**
39 | - **Professional Error Handling**
40 | - **Delete generated API structures** with a single command
41 |
42 | ---
43 |
44 | ## 📦 Installation
45 |
46 | You can install the package via Composer:
47 |
48 | ```bash
49 | composer require nameless/laravel-api-generator
50 | ```
51 |
52 | The package automatically registers its service provider.
53 |
54 | ---
55 |
56 | ## 🎯 Quick Start
57 |
58 | ### Single Entity Generation
59 |
60 | Generate a complete API for a single entity:
61 |
62 | ```bash
63 | php artisan make:fullapi User --fields="name:string,email:string,age:integer,is_active:boolean"
64 | ```
65 |
66 | This creates:
67 |
68 | - `app/Models/User.php`
69 | - `app/Http/Controllers/UserController.php`
70 | - `app/Http/Requests/UserRequest.php`
71 | - `app/Http/Resources/UserResource.php`
72 | - `app/Services/UserService.php`
73 | - `app/DTO/UserDTO.php`
74 | - `app/Policies/UserPolicy.php`
75 | - `database/factories/UserFactory.php`
76 | - `database/seeders/UserSeeder.php`
77 | - `database/migrations/xxxx_create_users_table.php`
78 | - API routes in `routes/api.php`
79 |
80 | ### Bulk Generation from JSON
81 |
82 | Create a `class_data.json` file in your project root:
83 |
84 | ```json
85 | [
86 | {
87 | "name": "User",
88 | "attributes": [
89 | {"name": "name", "_type": "string"},
90 | {"name": "email", "_type": "string"},
91 | {"name": "email_verified_at", "_type": "timestamp"}
92 | ],
93 | "oneToManyRelationships": [
94 | {"role": "posts", "comodel": "Post"}
95 | ]
96 | },
97 | {
98 | "name": "Post",
99 | "attributes": [
100 | {"name": "title", "_type": "string"},
101 | {"name": "content", "_type": "text"},
102 | {"name": "published_at", "_type": "timestamp"}
103 | ],
104 | "manyToOneRelationships": [
105 | {"role": "user", "comodel": "User"}
106 | ]
107 | }
108 | ]
109 | ```
110 |
111 | Then run:
112 |
113 | ```bash
114 | php artisan make:fullapi
115 | ```
116 |
117 | ### Delete Generated API
118 |
119 | Remove all generated files for an entity:
120 |
121 | ---
122 |
123 | ## 🏗️ Architecture Overview
124 |
125 | ### Service Layer Pattern
126 |
127 | The generated code follows the Service Layer pattern for better organization:
128 |
129 | ```php
130 | class UserController extends Controller
131 | {
132 | public function __construct(
133 | private readonly UserService $service
134 | ) {}
135 |
136 | public function store(UserRequest $request)
137 | {
138 | $dto = UserDTO::fromRequest($request);
139 | $user = $this->service->create($dto);
140 | return new UserResource($user);
141 | }
142 | }
143 | ```
144 |
145 | ### Data Transfer Objects
146 |
147 | Type-safe data handling with DTOs:
148 |
149 | ```php
150 | readonly class UserDTO
151 | {
152 | public function __construct(
153 | public ?string $name,
154 | public ?string $email,
155 | public ?int $age,
156 | public ?bool $is_active,
157 | ) {}
158 |
159 | public static function fromRequest(Request $request): self
160 | {
161 | return new self(
162 | name: $request->get('name'),
163 | email: $request->get('email'),
164 | age: $request->get('age'),
165 | is_active: $request->get('is_active'),
166 | );
167 | }
168 | }
169 | ```
170 |
171 | ## 🛠️ Advanced Usage
172 |
173 | ### Custom Field Types
174 |
175 | Supported field types:
176 |
177 | - `string` - VARCHAR(255)
178 | - `text` - TEXT
179 | - `integer`/`int` - INTEGER
180 | - `bigint` - BIG INTEGER
181 | - `boolean`/`bool` - BOOLEAN
182 | - `float`/`decimal` - DECIMAL
183 | - `json` - JSON
184 | - `date` - DATE
185 | - `datetime` - DATETIME
186 | - `timestamp` - TIMESTAMP
187 | - `uuid` - UUID
188 |
189 | ### Relationship Types
190 |
191 | The generator supports all Laravel relationship types:
192 |
193 | - **One-to-One**: `oneToOneRelationships`
194 | - **One-to-Many**: `oneToManyRelationships`
195 | - **Many-to-One**: `manyToOneRelationships`
196 | - **Many-to-Many**: `manyToManyRelationships`
197 |
198 | ### Model Inheritance
199 |
200 | Support for model inheritance:
201 |
202 | ```json
203 | {
204 | "name": "AdminUser",
205 | "parent": "User",
206 | "attributes": [
207 | {"name": "permissions", "_type": "json"}
208 | ]
209 | }
210 | ```
211 |
212 | ### Generated File Structure
213 |
214 | This command generates:
215 |
216 | - **Models** (`App\Models`)
217 | - **Controllers** (`App\Http\Controllers`)
218 | - **Services** (`App\Services`)
219 | - **DTOs** (`App\DTO`)
220 | - **Policies** (`App\Policies`)
221 | - **Requests** (`App\Http\Requests`)
222 | - **Resources** (`App\Http\Resources`)
223 | - **Factories** (`Database\Factories`)
224 | - **Migrations** (`Database\Migrations`)
225 | - **Seeders** (`Database\Seeders`)
226 |
227 | ### Delete API Structure
228 |
229 | To remove the generated API structure, you can use:
230 |
231 | ```bash
232 | php artisan delete:fullapi
233 | ```
234 |
235 | This will remove all the generated files from the API structure.
236 |
237 | To delete a specific model's API structure, use:
238 |
239 | ```bash
240 | php artisan delete:fullapi ModelName
241 | ```
242 |
243 | For example:
244 |
245 | ```bash
246 | php artisan delete:fullapi Post
247 | ```
248 |
249 | This will delete all the generated files related to the Post model, including controllers, services, DTOs, policies, resources, factories, seeders, and migrations.
250 |
251 | ---
252 |
253 | ## 🔧 Configuration
254 |
255 | ### Custom Stubs
256 |
257 | You can customize the generated code by publishing and modifying the stubs:
258 |
259 | ```bash
260 | php artisan vendor:publish --tag=laravel-api-generator-stubs
261 | ```
262 |
263 | ### Service Registration
264 |
265 | The package automatically registers all generators and services through dependency injection.
266 |
267 | ---
268 |
269 | ## 🧪 Testing
270 |
271 | ```bash
272 | composer test
273 | ```
274 |
275 | Run static analysis:
276 |
277 | ```bash
278 | composer analyse
279 | ```
280 |
281 | Format code:
282 |
283 | ```bash
284 | composer format
285 | ```
286 |
287 | ---
288 |
289 | ## 📖 API Documentation
290 |
291 | The package integrates with [Scramble](https://github.com/dedoc/scramble) for automatic API documentation generation.
292 |
293 | After generating your APIs, visit `/docs/api` to see the generated documentation.
294 |
295 | ---
296 |
297 | ## Generated Structure
298 |
299 | ### Modern Controller Example
300 |
301 | ```php
302 | service = $service;
321 | }
322 |
323 | public function index()
324 | {
325 | $posts = $this->service->getAll();
326 | return PostResource::collection($posts);
327 | }
328 |
329 | public function store(PostRequest $request)
330 | {
331 | $dto = PostDTO::fromRequest($request);
332 | $post = $this->service->create($dto);
333 | return new PostResource($post);
334 | }
335 |
336 | public function show(Post $post)
337 | {
338 | return new PostResource($post);
339 | }
340 |
341 | public function update(PostRequest $request, Post $post)
342 | {
343 | $dto = PostDTO::fromRequest($request);
344 | $updatedPost = $this->service->update($post, $dto);
345 | return new PostResource($updatedPost);
346 | }
347 |
348 | public function destroy(Post $post)
349 | {
350 | $this->service->delete($post);
351 | return response(null, 204);
352 | }
353 | }
354 | ```
355 |
356 | ### Service
357 |
358 | ```php
359 | update((array) $dto);
386 | return $post;
387 | }
388 |
389 | public function delete(Post $post)
390 | {
391 | return $post->delete();
392 | }
393 | }
394 | ```
395 |
396 | ### DTO
397 |
398 | ```php
399 | get('title'),
417 | content: $request->get('content'),
418 | published: $request->get('published'),
419 | );
420 | }
421 | }
422 | ```
423 |
424 | ### Model
425 |
426 | ```php
427 | */
439 | use HasFactory;
440 | }
441 | ```
442 |
443 | ### Resource
444 |
445 | ```php
446 | $this->title,
459 | 'content' => $this->content,
460 | 'published' => $this->published,
461 | ];
462 | }
463 | }
464 | ```
465 |
466 | ### Request
467 |
468 | ```php
469 | 'string|max:255',
486 | 'content' => 'string',
487 | 'published' => 'boolean',
488 | ];
489 | }
490 | }
491 | ```
492 |
493 | ### Factory
494 |
495 | ```php
496 | fake()->word(),
508 | 'content' => fake()->sentence(),
509 | 'published' => fake()->boolean(),
510 | ];
511 | }
512 | }
513 | ```
514 |
515 | ### Seeder
516 |
517 | ```php
518 | create();
529 | }
530 | }
531 | ```
532 |
533 | ### Policy
534 |
535 | ```php
536 | id();
601 | $table->string('title')->nullable();
602 | $table->text('content')->nullable();
603 | $table->boolean('published')->nullable();
604 | $table->timestamps();
605 | });
606 | }
607 |
608 | public function down(): void
609 | {
610 | Schema::dropIfExists('posts');
611 | }
612 | };
613 | ```
614 |
615 | ---
616 |
617 | ## 🤝 Contributing
618 |
619 | Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
620 |
621 | 1. Fork the repository
622 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
623 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
624 | 4. Push to the branch (`git push origin feature/amazing-feature`)
625 | 5. Open a Pull Request
626 |
627 | ---
628 |
629 | ## 📚 Testing
630 |
631 | Run the test suite:
632 |
633 | ```bash
634 | composer test
635 | ```
636 |
637 | Run static analysis:
638 |
639 | ```bash
640 | composer analyse
641 | ```
642 |
643 | Format code:
644 |
645 | ```bash
646 | composer format
647 | ```
648 |
649 | ---
650 |
651 | ## 🚀 Local Development
652 |
653 | 1. Clone this repository:
654 |
655 | ```bash
656 | git clone https://github.com/Nameless0l/laravel-api-generator.git
657 | ```
658 |
659 | 2. Install dependencies:
660 |
661 | ```bash
662 | composer install
663 | ```
664 |
665 | 3. Run tests:
666 |
667 | ```bash
668 | ./vendor/bin/phpunit
669 | ```
670 |
671 | ### Testing in a Laravel Project
672 |
673 | 1. In your Laravel project's `composer.json`, add:
674 |
675 | ```json
676 | {
677 | "repositories": [
678 | {
679 | "type": "path",
680 | "url": "../laravel-api-generator",
681 | "options": {
682 | "symlink": true
683 | }
684 | }
685 | ],
686 | "require": {
687 | "nameless/laravel-api-generator": "@dev"
688 | }
689 | }
690 | ```
691 |
692 | 2. Run:
693 |
694 | ```bash
695 | composer update
696 | ```
697 |
698 | ---
699 |
700 | ## 🔒 Security
701 |
702 | If you discover any security-related issues, please email [loicmbassi5@gmail.com](mailto:loicmbassi5@gmail.com) instead of using the issue tracker.
703 |
704 | ---
705 |
706 | ## 🏆 Credits
707 |
708 | - **Author**: [Mbassi Loïc Aron](https://github.com/Nameless0l)
709 | - **Email**: loicmbassi5@gmail.com
710 |
711 | ---
712 |
713 | ## 📚 Changelog
714 |
715 | Please see [CHANGELOG.md](CHANGELOG.md) for more information on what has changed recently.
716 |
717 | ---
718 |
719 | ## 💡 Why Choose Laravel API Generator?
720 |
721 | ✅ **Professional Architecture** - Built with enterprise-grade patterns
722 | ✅ **Type Safety** - Full PHP 8.1+ type declarations
723 | ✅ **Clean Code** - SOLID principles and clean architecture
724 | ✅ **Extensible** - Easy to extend with custom generators
725 | ✅ **Well Tested** - Comprehensive test suite
726 | ✅ **Documentation** - Complete API documentation generation
727 | ✅ **Best Practices** - Follows Laravel and PHP best practices
728 |
729 | Transform your Laravel development workflow with professional API generation!
730 |
731 | ---
732 |
733 | ## 📄 License
734 |
735 | This package is open-source and distributed under the MIT License. See the [LICENSE](LICENSE.md) file for more details.
--------------------------------------------------------------------------------
/src/Console/Commands/MakeApi.php:
--------------------------------------------------------------------------------
1 | argument('name');
22 |
23 | if (empty($this->argument('name'))) {
24 | $this->callDiagramsMethods();
25 | return;
26 | }
27 | $fields = $this->option('fields');
28 | $this->nameLower = strtolower($name);
29 | if (!$fields) {
30 | $this->error('Vous devez spécifier des champs avec l\'option --fields="champ1:type1,champ2:type2"');
31 | return;
32 | }
33 | $this->callDefaulttMethods($name, $fields);
34 | }
35 |
36 | private function callDefaulttMethods($name, $fields, ?array $classData = null)
37 | {
38 |
39 | $fieldsArray = $this->parseFields($fields);
40 | $pluralName = Str::plural(Str::lower($name));
41 | $this->nameLower = Str::lower($name);
42 |
43 | info("Création des fichiers pour l'API: {$name}");
44 | $route = "Route::apiResource('{$pluralName}', App\Http\Controllers\\{$name}Controller::class);";
45 | $apiFilePath = base_path('routes/api.php');
46 | $phpHeader = "updateModel($name, $fieldsArray, $classData);
63 | $this->info("Modèle mis à jour avec les fillable et les relations.");
64 |
65 | // Ajouter les champs et les clés étrangères dans la migration
66 | $this->updateMigration($name, $fieldsArray, $classData);
67 |
68 | // Créer les migrations pour les tables pivots (ManyToMany)
69 | $this->createPivotMigrations($name, $classData);
70 |
71 | // Créer le service
72 | $this->createService($name, $this->nameLower);
73 | $this->info("Service créé.");
74 |
75 | // Créer la policy
76 | Artisan::call("make:policy {$name}Policy --model={$name}");
77 | $this->updatePolicy($name);
78 | $this->info("Policy créée et configurée.");
79 |
80 | // Créer le contrôleur avec CRUD
81 | Artisan::call("make:controller {$name}Controller");
82 | $this->info("Contrôleur API créé.");
83 |
84 | // Créer la resource
85 | Artisan::call("make:resource {$name}Resource");
86 | $this->info("Resource créée.");
87 |
88 | $this->updateResource($name, $fieldsArray);
89 | // Créer la requête (FormRequest)
90 | Artisan::call("make:request {$name}Request");
91 | $this->updateRequest($name, $fieldsArray);
92 | $this->info("Requête créée.");
93 |
94 | // Créer le seeder
95 | Artisan::call("make:seeder {$name}Seeder");
96 | $this->updateSeeder($name, $fieldsArray);
97 | $this->info("Seeder créé.");
98 |
99 | // Créer un DTO
100 | $this->createDTO($name, $fieldsArray);
101 | $this->info("DTO créé.");
102 |
103 | // Ajouter les méthodes CRUD au contrôleur
104 | $this->addCrudToController($name, $this->nameLower, $pluralName);
105 |
106 | // Mettre à jour AuthServiceProvider
107 | $this->updateAuthServiceProvider($name);
108 |
109 | $this->info("API complète créée pour : {$name} !");
110 | $this->updateFactory($name, $fieldsArray);
111 | }
112 | private function callDiagramsMethods()
113 | {
114 | $this->warn("Aucun nom fourni. Utilisation par défaut du fichier JSON...");
115 | $jsonFilePath = base_path('class_data.json');
116 | if (!file_exists($jsonFilePath)) {
117 | $this->error("Le fichier class_data.json est introuvable.");
118 | return;
119 | }
120 |
121 | $this->info("Lecture du fichier JSON...");
122 | $jsonData = file_get_contents($jsonFilePath);
123 | // Le JSON peut être un tableau d'objets ou un seul objet avec une clé "data"
124 | $rawClasses = json_decode($jsonData, true);
125 |
126 | if (json_last_error() !== JSON_ERROR_NONE) {
127 | $this->error("Erreur de décodage JSON : " . json_last_error_msg());
128 | return;
129 | }
130 |
131 | // S'assurer que nous travaillons avec un tableau
132 | if (isset($rawClasses['data']) && is_array($rawClasses['data'])) {
133 | $this->classes = [$rawClasses];
134 | } else {
135 | $this->classes = $rawClasses;
136 | }
137 |
138 | $this->info("Extraction des données JSON...");
139 | $this->jsonExtractionToArray();
140 |
141 | $this->info("Génération des API avec Artisan...");
142 | $this->runFullApiWithDiagram();
143 | return;
144 | }
145 |
146 | /**
147 | * Extraire et formater les données JSON dans un tableau compatible.
148 | */
149 | public function jsonExtractionToArray()
150 | {
151 | $this->classes = array_map(function ($classData) {
152 | // Gérer les deux formats JSON (avec ou sans l'enveloppe 'data')
153 | $class = isset($classData['data']) ? $classData['data'] : $classData;
154 |
155 | return [
156 | 'name' => ucfirst($class['name']),
157 | 'parent' => isset($class['parent']) ? ucfirst($class['parent']) : null,
158 | 'attributes' => array_map(function ($attribute) {
159 | return [
160 | 'name' => $attribute['name'],
161 | '_type' => match (strtolower($attribute['_type'])) {
162 | 'integer', 'long', 'int' => 'int',
163 | 'bigint' => 'int',
164 | 'str', 'string', 'text', 'java.time.offsetdatetime', 'java.time.localdate' => 'string',
165 | 'boolean' => 'bool',
166 | 'java.math.bigdecimal' => 'float',
167 | 'java.util.map' => 'json',
168 | default => strtolower($attribute['_type']),
169 | },
170 | ];
171 | }, $class['attributes'] ?? []),
172 | 'oneToOneRelationships' => $class['oneToOneRelationships'] ?? [],
173 | 'manyToOneRelationships' => $class['manyToOneRelationships'] ?? [],
174 | 'oneToManyRelationships' => $class['oneToManyRelationships'] ?? [],
175 | 'manyToManyRelationships' => $class['manyToManyRelationships'] ?? [],
176 | ];
177 | }, $this->classes);
178 | }
179 |
180 | /**
181 | * Parcourir les classes et exécuter les commandes Artisan pour générer les API.
182 | */
183 | public function runFullApiWithDiagram()
184 | {
185 | foreach ($this->classes as $class) {
186 | $className = ucfirst($class['name']);
187 | $fields = [];
188 |
189 | foreach ($class['attributes'] as $attribute) {
190 | $fields[] = "{$attribute['name']}:{$attribute['_type']}";
191 | }
192 |
193 | $fieldsString = implode(',', $fields);
194 |
195 | try {
196 | // Passer le tableau complet de la classe, qui inclut maintenant les relations
197 | $this->callDefaulttMethods($className, $fieldsString, $class);
198 | $this->info("API pour la classe $className générée avec succès !");
199 | } catch (\Exception $e) {
200 | $this->error("Erreur lors de la génération de l'API pour la classe $className : " . $e->getMessage());
201 | }
202 | }
203 | }
204 |
205 | private function updateFactory($name, $fieldsArray)
206 | {
207 | $factoryPath = database_path("factories/{$name}Factory.php");
208 |
209 | if (!file_exists($factoryPath)) {
210 | $this->error("Le fichier Factory pour {$name} n'existe pas.");
211 | return;
212 | }
213 |
214 | $factoryFile = file_get_contents($factoryPath);
215 |
216 | $fields = [];
217 | foreach ($fieldsArray as $field => $type) {
218 | $value = match ($type) {
219 | 'string' => "fake()->word()",
220 | 'integer', 'int' => "fake()->randomNumber()",
221 | 'boolean', 'bool' => "fake()->boolean()",
222 | 'text' => "fake()->sentence()",
223 | 'uuid', 'UUID' => "fake()->uuid()",
224 | 'float' => "fake()->randomFloat(2, 1, 1000)",
225 | 'json' => "json_encode(['key' => 'value'])",
226 | 'date', 'datetime', 'timestamp', 'time' => "fake()->dateTime()",
227 | default => "fake()->word()"
228 | };
229 | $fields[] = "'{$field}' => {$value}";
230 | }
231 |
232 | $fieldsContent = implode(",\n ", $fields);
233 |
234 | $factoryFile = preg_replace(
235 | "/return \[.*?\];/s",
236 | "return [\n {$fieldsContent}\n ];",
237 | $factoryFile
238 | );
239 |
240 | file_put_contents($factoryPath, $factoryFile);
241 |
242 | $this->info("Factory mis à jour : {$factoryPath}");
243 | }
244 |
245 | private function parseFields($fields)
246 | {
247 | $fieldsArray = [];
248 | foreach (explode(',', $fields) as $field) {
249 | $parts = explode(':', $field);
250 | if(count($parts) == 2){
251 | $fieldsArray[$parts[0]] = strtolower($parts[1]);
252 | }
253 | }
254 | return $fieldsArray;
255 | }
256 |
257 | private function updateMigration($name, $fieldsArray, ?array $classData = null)
258 | {
259 | $pluralName = Str::plural(Str::snake($name));
260 | $migrations = glob(database_path("migrations/*_create_{$pluralName}_table.php"));
261 |
262 | if (empty($migrations)) {
263 | $this->error("Impossible de trouver une migration pour {$name}.");
264 | return;
265 | }
266 |
267 | $migrationPath = $migrations[0];
268 | $migrationFile = file_get_contents($migrationPath);
269 |
270 | $fieldLines = '';
271 | foreach ($fieldsArray as $field => $type) {
272 | $type = match ($type) {
273 | 'string' => 'string',
274 | 'integer', 'int' => 'integer',
275 | 'boolean', 'bool' => 'boolean',
276 | 'text' => 'text',
277 | 'float' => 'decimal',
278 | 'json' => 'json',
279 | 'date', 'datetime', 'timestamp', 'time' => 'timestamp',
280 | 'uuid', 'UUID' => 'uuid',
281 | default => 'string'
282 | };
283 | if($type === 'decimal'){
284 | $fieldLines .= "\$table->{$type}('{$field}', 8, 2)->nullable();\n ";
285 | } else {
286 | $fieldLines .= "\$table->{$type}('{$field}')->nullable();\n ";
287 | }
288 | }
289 |
290 | $foreignKeyLines = '';
291 | if ($classData) {
292 | // Les relations Many-to-One et One-to-One (côté "enfant") impliquent une clé étrangère
293 | $relations = array_merge($classData['manyToOneRelationships'], $classData['oneToOneRelationships']);
294 | foreach ($relations as $relation) {
295 | $foreignKeyColumn = Str::snake($relation['role']) . '_id';
296 | // Assumer que le nom de la table est le pluriel du nom du modèle lié
297 | $relatedTable = Str::plural(Str::snake($relation['comodel']));
298 | $foreignKeyLines .= "\$table->foreignId('{$foreignKeyColumn}')->nullable()->constrained('{$relatedTable}')->onDelete('set null');\n ";
299 | }
300 | }
301 |
302 | // Injecter les champs dans la migration
303 | $pattern = '/\$table->id\(\);(.*?)\$table->timestamps\(\);/s';
304 | $replacement = "\$table->id();\n {$fieldLines}{$foreignKeyLines}\$table->timestamps();";
305 |
306 | if(preg_match($pattern, $migrationFile)) {
307 | $migrationFile = preg_replace($pattern, $replacement, $migrationFile);
308 | } else {
309 | $pattern = '/Schema::create\([\'"]' . preg_quote($pluralName, '/') . '[\'"],\s*function\s*\(Blueprint\s*\$table\)\s*\{.*?id\(\);/s';
310 | $replacement = "Schema::create('{$pluralName}', function (Blueprint \$table) {\n \$table->id();\n {$fieldLines}{$foreignKeyLines}";
311 | $migrationFile = preg_replace($pattern, $replacement, $migrationFile);
312 | }
313 |
314 | file_put_contents($migrationPath, $migrationFile);
315 | $this->info("Migration mise à jour : {$migrationPath}");
316 | }
317 |
318 | private function createPivotMigrations($name, ?array $classData = null)
319 | {
320 | if (!$classData || empty($classData['manyToManyRelationships'])) {
321 | return;
322 | }
323 |
324 | $modelNameSingular = Str::snake($name);
325 |
326 | foreach ($classData['manyToManyRelationships'] as $relation) {
327 | $relatedModel = ucfirst($relation['comodel']);
328 | $relatedModelSingular = Str::snake($relatedModel);
329 |
330 | $tableParts = [$modelNameSingular, $relatedModelSingular];
331 | sort($tableParts);
332 | $pivotTableName = implode('_', $tableParts);
333 |
334 | $existingMigrations = File::glob(database_path("migrations/*_create_{$pivotTableName}_table.php"));
335 | if (!empty($existingMigrations)) {
336 | $this->warn("La migration de la table pivot '{$pivotTableName}' existe déjà. Ignorée.");
337 | continue;
338 | }
339 |
340 | $migrationName = "create_{$pivotTableName}_table";
341 | Artisan::call('make:migration', ['name' => $migrationName]);
342 | $this->info("Migration de la table pivot créée : {$migrationName}");
343 |
344 | $migrationFile = last(File::glob(database_path("migrations/*_{$migrationName}.php")));
345 |
346 | if ($migrationFile) {
347 | $upMethodContent = "
348 | Schema::create('{$pivotTableName}', function (Blueprint \$table) {
349 | \$table->primary(['{$tableParts[0]}_id', '{$tableParts[1]}_id']);
350 | \$table->foreignId('{$tableParts[0]}_id')->constrained('" . Str::plural($tableParts[0]) . "')->onDelete('cascade');
351 | \$table->foreignId('{$tableParts[1]}_id')->constrained('" . Str::plural($tableParts[1]) . "')->onDelete('cascade');
352 | \$table->timestamps();
353 | });";
354 |
355 | $migrationContent = file_get_contents($migrationFile);
356 | $migrationContent = preg_replace(
357 | '/(public function up\(\): void\s*{)/s',
358 | "$1" . $upMethodContent,
359 | $migrationContent
360 | );
361 |
362 | file_put_contents($migrationFile, $migrationContent);
363 | $this->info("Migration de la table pivot mise à jour : {$migrationFile}");
364 | }
365 | }
366 | }
367 |
368 |
369 | private function updateRequest($name, $fieldsArray)
370 | {
371 | $requestPath = app_path("Http/Requests/{$name}Request.php");
372 | $requestFile = file_get_contents($requestPath);
373 |
374 | // Ajouter la méthode authorize qui retourne true
375 | $requestFile = str_replace(
376 | "public function authorize(): bool\n {\n return false;\n }",
377 | "public function authorize(): bool\n {\n return true;\n }",
378 | $requestFile
379 | );
380 |
381 | $rules = '';
382 | foreach ($fieldsArray as $field => $type) {
383 | $rule = match ($type) {
384 | 'string' => 'string|max:255',
385 | 'integer', 'int' => 'integer',
386 | 'boolean', 'bool' => 'boolean',
387 | 'text' => 'string',
388 | 'uuid', 'UUID' => 'uuid',
389 | 'float' => 'numeric',
390 | 'json' => 'json',
391 | 'date', 'datetime', 'timestamp' => 'date',
392 | default => 'sometimes|string'
393 | };
394 | // CORRECTION : Pas de guillemets dans le match, on les ajoute ici
395 | $rules .= "'{$field}' => 'sometimes|{$rule}',\n ";
396 | }
397 |
398 | $requestFile = preg_replace(
399 | "/public function rules\(\).*?\{.*?\n.*?\}/s",
400 | "public function rules(): array\n {\n return [\n {$rules}\n ];\n }",
401 | $requestFile
402 | );
403 |
404 | file_put_contents($requestPath, $requestFile);
405 | }
406 |
407 | private function addCrudToController($name, $nameLower, $pluralName)
408 | {
409 | $controllerPath = app_path("Http/Controllers/{$name}Controller.php");
410 |
411 | if (!file_exists($controllerPath)) {
412 | $this->error("Le contrôleur {$name}Controller n'existe pas.");
413 | return;
414 | }
415 |
416 | // Lire le contenu original
417 | $content = file_get_contents($controllerPath);
418 |
419 | // Ajouter l'import de Controller si pas déjà présent
420 | if (strpos($content, 'use App\Http\Controllers\Controller;') === false) {
421 | $content = str_replace(
422 | 'namespace App\Http\Controllers;',
423 | "namespace App\Http\Controllers;\n\nuse App\Http\Controllers\Controller;",
424 | $content
425 | );
426 | }
427 |
428 | // Remplacer la déclaration de classe
429 | $content = preg_replace(
430 | '/class\s+' . $name . 'Controller\s*{/',
431 | 'class ' . $name . 'Controller extends Controller {',
432 | $content
433 | );
434 |
435 | $methods = <<service = \$service;
442 | }
443 |
444 | public function index()
445 | {
446 | \${$pluralName} = \$this->service->getAll();
447 | return {$name}Resource::collection(\${$pluralName});
448 | }
449 |
450 | public function store({$name}Request \$request)
451 | {
452 | \$dto = {$name}DTO::fromRequest(\$request);
453 | \${$nameLower} = \$this->service->create(\$dto);
454 | return new {$name}Resource(\${$nameLower});
455 | }
456 |
457 | public function show({$name} \${$nameLower})
458 | {
459 | return new {$name}Resource(\${$nameLower});
460 | }
461 |
462 | public function update({$name}Request \$request, {$name} \${$nameLower})
463 | {
464 | \$dto = {$name}DTO::fromRequest(\$request);
465 | \$updated{$name} = \$this->service->update(\${$nameLower}, \$dto);
466 | return new {$name}Resource(\$updated{$name});
467 | }
468 |
469 | public function destroy({$name} \${$nameLower})
470 | {
471 | \$this->service->delete(\${$nameLower});
472 | return response(null, 204);
473 | }
474 |
475 | EOD;
476 |
477 |
478 | // Ajouter les imports nécessaires
479 | $content = str_replace(
480 | 'use Illuminate\Http\Request;',
481 | "use App\\Http\\Requests\\{$name}Request;\nuse App\\Models\\{$name};\nuse App\\Http\\Resources\\{$name}Resource;\nuse App\\Services\\{$name}Service;\nuse App\\DTO\\{$name}DTO;\nuse Illuminate\\Http\\Response;",
482 | $content
483 | );
484 |
485 | // Vider la classe avant d'ajouter les nouvelles méthodes
486 | $pattern = '/class ' . $name . 'Controller extends Controller\s*{[^}]*}/';
487 | $replacement = 'class ' . $name . 'Controller extends Controller {' . $methods . '}';
488 | $content = preg_replace($pattern, $replacement, $content, 1);
489 |
490 |
491 | file_put_contents($controllerPath, $content);
492 |
493 | $this->info("CRUD ajouté au contrôleur : {$controllerPath}");
494 | }
495 |
496 | private function updateSeeder($name, $fieldsArray)
497 | {
498 | $seederPath = database_path("seeders/{$name}Seeder.php");
499 | $seederFile = file_get_contents($seederPath);
500 |
501 | $factoryFields = [];
502 | foreach ($fieldsArray as $field => $type) {
503 | $value = match ($type) {
504 | 'string' => "fake()->word()",
505 | 'integer' => "fake()->randomNumber()",
506 | 'boolean' => "fake()->boolean()",
507 | 'uuid','UUID'=>"fake()->uuid()",
508 | 'bigint' => "fake()->randomNumber()",
509 | 'date', 'datetime', 'timestamp', 'time' => "fake()->dateTime()",
510 | 'text' => "fake()->paragraph()",
511 | default => "fake()->word()"
512 | };
513 | $factoryFields[] = "'{$field}' => {$value}";
514 | }
515 |
516 | $factoryContent = implode(",\n ", $factoryFields);
517 | $seederFile = preg_replace(
518 | "/public function run\(\): void\s*{[^}]*}/s",
519 | "public function run(): void\n {\n \\App\\Models\\{$name}::factory(10)->create();\n }",
520 | $seederFile
521 | );
522 |
523 | file_put_contents($seederPath, $seederFile);
524 | }
525 |
526 | private function createDTO($name, $fieldsArray)
527 | {
528 | $dtoPath = app_path("DTO/{$name}DTO.php");
529 | if (!file_exists(app_path('DTO'))) {
530 | mkdir(app_path('DTO'), 0755, true);
531 | }
532 |
533 | $attributes = '';
534 |
535 | foreach ($fieldsArray as $field => $type) {
536 | if ($type == 'text') {
537 | $type = 'string';
538 | }
539 | if ($type == 'boolean') {
540 | $type = 'bool';
541 | }
542 | if ($type == 'integer' || $type == 'bigint') {
543 | $type = 'int';
544 | }
545 | if ($type == 'float'){
546 | $type = 'float';
547 | }
548 | if($type == 'json'){
549 | $type = 'array';
550 | }
551 | if ($type == 'date' || $type == 'datetime' || $type == 'timestamp' || $type == 'time') {
552 | $type = '\DateTimeInterface';
553 | }
554 | if($type == 'uuid' || $type == 'UUID'){
555 | $type = 'string';
556 | }
557 |
558 | $attributes .= "public ?{$type} \${$field},
559 | ";
560 | }
561 | $atributsFromRequest = '';
562 | foreach ($fieldsArray as $field => $type) {
563 | if ($type == 'date' || $type == 'datetime' || $type == 'timestamp' || $type == 'time') {
564 | $atributsFromRequest .= "$field: \$request->filled('{$field}') ? \Carbon\Carbon::parse(\$request->get('{$field}')) : null,\n ";
565 | } else {
566 | $atributsFromRequest .= "$field: \$request->get('{$field}'),\n ";
567 | }
568 | }
569 | $attributes = rtrim($attributes, ",
570 | ");
571 | $atributsFromRequest = rtrim($atributsFromRequest, ",
572 | ");
573 |
574 | $content = << \$value !== null);
626 | return {$name}::create(\$data);
627 | }
628 |
629 | public function find(\$id)
630 | {
631 | return {$name}::findOrFail(\$id);
632 | }
633 |
634 | public function update({$name} \${$nameLower}, {$name}DTO \$dto)
635 | {
636 | \$data = array_filter((array) \$dto, fn(\$value) => \$value !== null);
637 | \${$nameLower}->update(\$data);
638 | return \${$nameLower};
639 | }
640 |
641 | public function delete({$name} \${$nameLower})
642 | {
643 | return \${$nameLower}->delete();
644 | }
645 | }
646 | EOD;
647 |
648 | file_put_contents($servicePath, $content);
649 | }
650 |
651 | private function updatePolicy($name)
652 | {
653 | $policyPath = app_path("Policies/{$name}Policy.php");
654 |
655 | if (!file_exists($policyPath)) {
656 | $this->error("Le fichier Policy pour {$name} n'existe pas.");
657 | return;
658 | }
659 |
660 | $policyContent = file_get_contents($policyPath);
661 |
662 | // Rendre toutes les méthodes true
663 | $policyContent = preg_replace('/(return\s+)(false|Response::deny\(\));/m', '$1true;', $policyContent);
664 |
665 |
666 | file_put_contents($policyPath, $policyContent);
667 | }
668 |
669 | private function updateAuthServiceProvider($name)
670 | {
671 | $providerPath = app_path('Providers/AuthServiceProvider.php');
672 |
673 | if (!file_exists($providerPath)) {
674 | $this->createAuthServiceProvider();
675 | }
676 |
677 | $content = file_get_contents($providerPath);
678 |
679 | // Éviter les doublons d'imports et de mappings
680 | $modelImport = "use App\\Models\\{$name};";
681 | $policyImport = "use App\\Policies\\{$name}Policy;";
682 | $mapping = " {$name}::class => {$name}Policy::class,";
683 |
684 | if (strpos($content, $modelImport) === false) {
685 | $content = preg_replace('/(namespace App\\\\Providers;)/', "$1\n{$modelImport}", $content, 1);
686 | }
687 | if (strpos($content, $policyImport) === false) {
688 | $content = preg_replace('/(namespace App\\\\Providers;)/', "$1\n{$policyImport}", $content, 1);
689 | }
690 |
691 | if (strpos($content, $mapping) === false) {
692 | $content = preg_replace(
693 | '/protected \$policies = \[/',
694 | "protected \$policies = [\n{$mapping}",
695 | $content,
696 | 1
697 | );
698 | }
699 |
700 | file_put_contents($providerPath, $content);
701 | }
702 |
703 | private function createAuthServiceProvider()
704 | {
705 | $providerPath = app_path('Providers/AuthServiceProvider.php');
706 | $content = <<
720 | */
721 | protected \$policies = [];
722 |
723 | /**
724 | * Register any authentication / authorization services.
725 | */
726 | public function boot(): void
727 | {
728 | \$this->registerPolicies();
729 | }
730 | }
731 | EOD;
732 |
733 | if (!file_exists(app_path('Providers'))) {
734 | mkdir(app_path('Providers'), 0755, true);
735 | }
736 |
737 | file_put_contents($providerPath, $content);
738 | $this->info('AuthServiceProvider créé.');
739 | }
740 |
741 | private function updateModel($name, $fieldsArray, ?array $classData = null)
742 | {
743 | $modelPath = app_path("Models/{$name}.php");
744 |
745 | if (!file_exists($modelPath)) {
746 | $this->error("Le modèle {$name} n'existe pas.");
747 | return;
748 | }
749 |
750 | // Préparer les données pour le template
751 | $fillableFields = array_keys($fieldsArray);
752 |
753 | // Ajouter les clés étrangères
754 | if ($classData) {
755 | $relations = array_merge(
756 | $classData['manyToOneRelationships'] ?? [],
757 | $classData['oneToOneRelationships'] ?? []
758 | );
759 | foreach ($relations as $relation) {
760 | $fillableFields[] = Str::snake($relation['role']) . '_id';
761 | }
762 | }
763 |
764 | $fillableString = "'" . implode("', '", $fillableFields) . "'";
765 | $fillableProperty = "protected \$fillable = [{$fillableString}];";
766 |
767 | // Générer les relations
768 | $relationshipMethods = '';
769 | $imports = '';
770 | $parentClass = 'Model';
771 |
772 | if ($classData) {
773 | // Ne pas traiter l'héritage pour l'instant car dans ce JSON c'est des relations
774 | // L'héritage sera traité différemment plus tard
775 |
776 | $relations = [
777 | 'oneToOne' => $classData['oneToOneRelationships'] ?? [],
778 | 'oneToMany' => $classData['oneToManyRelationships'] ?? [],
779 | 'manyToOne' => $classData['manyToOneRelationships'] ?? [],
780 | 'manyToMany' => $classData['manyToManyRelationships'] ?? []
781 | ];
782 |
783 | $relatedModels = [];
784 |
785 | foreach ($relations['oneToOne'] as $rel) {
786 | $methodName = Str::camel($rel['role']);
787 | $relatedModel = ucfirst($rel['comodel']);
788 | $relatedModels[$relatedModel] = true;
789 | $relationshipMethods .= "\n public function {$methodName}()\n {\n return \$this->hasOne({$relatedModel}::class);\n }\n";
790 | }
791 |
792 | foreach ($relations['oneToMany'] as $rel) {
793 | $methodName = Str::camel($rel['role']);
794 | $relatedModel = ucfirst($rel['comodel']);
795 | $relatedModels[$relatedModel] = true;
796 | $relationshipMethods .= "\n public function {$methodName}()\n {\n return \$this->hasMany({$relatedModel}::class);\n }\n";
797 | }
798 |
799 | foreach ($relations['manyToOne'] as $rel) {
800 | $methodName = Str::camel($rel['role']);
801 | $relatedModel = ucfirst($rel['comodel']);
802 | $relatedModels[$relatedModel] = true;
803 | $relationshipMethods .= "\n public function {$methodName}()\n {\n return \$this->belongsTo({$relatedModel}::class);\n }\n";
804 | }
805 |
806 | foreach ($relations['manyToMany'] as $rel) {
807 | $methodName = Str::camel($rel['role']);
808 | $relatedModel = ucfirst($rel['comodel']);
809 | $relatedModels[$relatedModel] = true;
810 | $relationshipMethods .= "\n public function {$methodName}()\n {\n return \$this->belongsToMany({$relatedModel}::class);\n }\n";
811 | }
812 |
813 | // Ajouter les imports pour les modèles liés
814 | $uniqueRelatedModels = array_unique(array_keys($relatedModels));
815 | foreach ($uniqueRelatedModels as $relatedModel) {
816 | if ($relatedModel !== $name && $relatedModel !== $parentClass) { // Éviter l'auto-import et le parent
817 | $imports .= "\nuse App\\Models\\{$relatedModel};";
818 | }
819 | }
820 | }
821 |
822 | // Charger le stub et remplacer les placeholders
823 | $stubPath = base_path(self::BASE_STUB_PATH . 'model.stub');
824 | if (!file_exists($stubPath)) {
825 | $this->error("Stub model.stub introuvable: {$stubPath}");
826 | return;
827 | }
828 |
829 | $modelContent = file_get_contents($stubPath);
830 | $modelContent = str_replace('{{modelName}}', $name, $modelContent);
831 | $modelContent = str_replace('{{parentClass}}', $parentClass, $modelContent);
832 | $modelContent = str_replace('{{fillable}}', $fillableProperty, $modelContent);
833 | $modelContent = str_replace('{{relationships}}', $relationshipMethods, $modelContent);
834 | $modelContent = str_replace('{{imports}}', $imports, $modelContent);
835 |
836 | file_put_contents($modelPath, $modelContent);
837 | }
838 |
839 | private function updateResource($name, $fieldsArray)
840 | {
841 | $resourcePath = app_path("Http/Resources/{$name}Resource.php");
842 | if (!file_exists(app_path('Http/Resources'))) {
843 | mkdir(app_path('Http/Resources'), 0755, true);
844 | }
845 |
846 | $fieldsCode = '';
847 | foreach ($fieldsArray as $field => $type) {
848 | $fieldsCode .= " '{$field}' => \$this->{$field},\n";
849 | }
850 |
851 | $stub = file_get_contents(base_path(MakeApi::BASE_STUB_PATH . 'resource.stub'));
852 | $template = str_replace(
853 | ['{{modelName}}', '{{fields}}'],
854 | [$name, rtrim($fieldsCode)],
855 | $stub
856 | );
857 | file_put_contents($resourcePath, $template);
858 | }
859 | }
--------------------------------------------------------------------------------