Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ php artisan vendor:publish --tag=essentials-config
- [Fake Sleep](#-fake-sleep)
- [Artisan Commands](#-artisan-commands)
- [make:action](#makeaction)
- [make:dto](#makedto)
- [essentials:pint](#essentialspint)
- [essentials:rector](#essentialsrector)

Expand Down Expand Up @@ -162,6 +163,34 @@ final readonly class CreateUserAction

Actions help organize business logic in dedicated classes, promoting single responsibility and cleaner controllers.

#### `make:dto`

Quickly generates dto classes in your Laravel application:

```bash
php artisan make:dto CreateUserDTO
```

This creates a clean DTO class at `app/DTOs/CreateUserDTO.php`:

```php
<?php

declare(strict_types=1);

namespace App\DTOs;

final readonly class CreateUserDTO
{
public function __construct()
{
//
}
}
```

DTOs (Data Transfer Objects) encapsulate and transport data between layers—keeping business logic elsewhere—so controllers stay lean and single-purpose.

#### `essentials:pint`

Laravel Pint is included by default in every Laravel project and is a great tool to keep your code clean and consistent. But it is configured very minimally by default. This command will publish a configuration file for Pint that includes the following:
Expand Down
102 changes: 102 additions & 0 deletions src/Commands/MakeDtoCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace NunoMaduro\Essentials\Commands;

use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;

final class MakeDtoCommand extends GeneratorCommand
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $name = 'make:dto';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Data Transfer Object class';

/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Dto';

/**
* Execute the console command.
*
* @return bool|int|null
*/
public function handle()
{
// Check if the dto already exists
if ($this->alreadyExists($this->getNameInput())) {
$this->error($this->type.' already exists!');

return 1;
}

return parent::handle();
}

/**
* Get the name input
**/
protected function getNameInput(): string
{
/** @var string $name */
$name = $this->argument('name');

return Str::of(mb_trim($name))
->replaceEnd('.php', '')
->replaceEnd('DTO', '')
->append('DTO')
->toString();
}

/**
* Get the stub file for the generator
**/
protected function getStub(): string
{
return $this->resolveStubPath('/stubs/dto.stub');
}

/**
* Get default namespace for the class
**/
protected function getDefaultNamespace($rootNamespace): string
{
return $rootNamespace.'\DTOs';
}

/**
* Get the destination class path
**/
protected function getPath($name): string
{
$name = Str::replaceFirst($this->rootNamespace(), '', $name);

return app_path(str_replace('\\', '/', $name).'.php');
}

/**
* Resolve the fully qualified path to the stub.
**/
private function resolveStubPath(string $stub): string
{
$basePath = $this->laravel->basePath(mb_trim($stub, '/'));

return file_exists($basePath)
? $basePath
: __DIR__.'/../../'.$stub;
}
}
1 change: 1 addition & 0 deletions src/EssentialsServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ final class EssentialsServiceProvider extends BaseServiceProvider
Commands\EssentialsRectorCommand::class,
Commands\EssentialsPintCommand::class,
Commands\MakeActionCommand::class,
Commands\MakeDtoCommand::class,
];

/**
Expand Down
13 changes: 13 additions & 0 deletions stubs/dto.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace {{ namespace }};

final readonly class {{ class }}
{
public function __construct()
{
//
}
}
96 changes: 96 additions & 0 deletions tests/Commands/MakeDtoCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;

beforeEach(function (): void {
$dtoPath = app_path('DTOs');

if (File::isDirectory($dtoPath)) {
File::deleteDirectory($dtoPath);
}

$stubsPath = base_path('stubs');
if (File::exists($stubsPath)) {
File::deleteDirectory($stubsPath);
}
});

afterEach(function (): void {
$dtoPath = app_path('DTOs');

if (File::isDirectory($dtoPath)) {
File::deleteDirectory($dtoPath);
}

$stubsPath = base_path('stubs');
if (File::exists($stubsPath)) {
File::deleteDirectory($stubsPath);
}
});

it('creates a new dto file', function (): void {
$name = 'UserDTO';
$exitCode = Artisan::call('make:dto', ['name' => $name]);

expect($exitCode)->toBe(0);

$expectedPath = app_path('DTOs/UserDTO.php');
expect(File::exists($expectedPath))->toBeTrue();

$content = File::get($expectedPath);

expect($content)
->toContain('namespace App\DTOs')
->toContain('class UserDTO')
->toContain('public function __construct()');
});

it('Should fail when the DTO file already exists', function (): void {
$name = 'UserDTO';
Artisan::call('make:dto', ['name' => $name]);
$exitCode = Artisan::call('make:dto', ['name' => $name]);

expect($exitCode)->toBe(1);
});

it('Add suffix "DTO" to the dto name if not provided', function (string $dtoName): void {
$exitCode = Artisan::call('make:dto', ['name' => $dtoName]);

expect($exitCode)->toBe(0);

$expectedPath = app_path('DTOs/UserDTO.php');
expect(File::exists($expectedPath))->toBeTrue();

$content = File::get($expectedPath);

expect($content)
->toContain('namespace App\DTOs;')
->toContain('class UserDTO')
->toContain('public function __construct()');
})->with([
'User',
'User.php',
]);

it('uses published stub when available', function (): void {
$this->artisan('vendor:publish', ['--tag' => 'essentials-stubs'])
->assertSuccessful();

$publishedStubPath = base_path('stubs/dto.stub');
$originalContent = File::get($publishedStubPath);

File::put($publishedStubPath, $originalContent."\n// this is user modified stub");

$dtoName = 'TestPublishedStubDTO';
$this->artisan('make:dto', ['name' => $dtoName])
->assertSuccessful();

$expectedPath = app_path('DTOs/TestPublishedStubDTO.php');
expect(File::exists($expectedPath))->toBeTrue()
->and(File::get($expectedPath))->toContain(
'// this is user modified stub'
);
});