Skip to content

Commit 54be50c

Browse files
committed
Add usability improvements
- Add --config-path, --output-path, --namespace CLI options - Add --force option to force regeneration - Add error handling when config file not found - Add dto:init command to create starter configuration - Improve dry-run output message
1 parent 3b9f4b8 commit 54be50c

File tree

5 files changed

+366
-9
lines changed

5 files changed

+366
-9
lines changed

src/DtoServiceProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public function boot(): void
1717
if ($this->app->runningInConsole()) {
1818
$this->commands([
1919
GenerateDtoCommand::class,
20+
InitDtoCommand::class,
2021
]);
2122

2223
$this->publishes([

src/GenerateDtoCommand.php

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ class GenerateDtoCommand extends Command
1818
/**
1919
* @var string
2020
*/
21-
protected $signature = 'dto:generate {--dry-run : Preview changes without writing files}';
21+
protected $signature = 'dto:generate
22+
{--dry-run : Preview changes without writing files}
23+
{--force : Force regeneration of all DTOs}
24+
{--config-path= : Path to DTO config files}
25+
{--output-path= : Path for generated DTOs}
26+
{--namespace= : Namespace for generated DTOs}';
2227

2328
/**
2429
* @var string
@@ -27,9 +32,9 @@ class GenerateDtoCommand extends Command
2732

2833
public function handle(): int
2934
{
30-
$configPath = config('dto.config_path', config_path());
31-
$outputPath = config('dto.output_path', app_path('Dto'));
32-
$namespace = config('dto.namespace', 'App\\Dto');
35+
$configPath = $this->option('config-path') ?? config('dto.config_path', config_path());
36+
$outputPath = $this->option('output-path') ?? config('dto.output_path', app_path('Dto'));
37+
$namespace = $this->option('namespace') ?? config('dto.namespace', 'App\\Dto');
3338

3439
// Ensure paths end with /
3540
if (!str_ends_with($configPath, '/')) {
@@ -39,29 +44,55 @@ public function handle(): int
3944
$outputPath .= '/';
4045
}
4146

47+
// Validate config path exists
48+
if (!is_dir($configPath)) {
49+
$this->error("Config path does not exist: {$configPath}");
50+
51+
return self::FAILURE;
52+
}
53+
54+
// Check if any config files exist
55+
$engine = $this->detectEngine($configPath);
56+
if ($engine === null) {
57+
$this->error("No DTO configuration files found in: {$configPath}");
58+
$this->line('');
59+
$this->line('Expected one of:');
60+
$this->line(' - dtos.php, dtos.xml, dtos.yml, dtos.yaml');
61+
$this->line(' - dto.xml, dto.yml, dto.yaml');
62+
$this->line(' - dto/ subdirectory with config files');
63+
$this->line('');
64+
$this->line('Run "php artisan dto:init" to create a starter configuration.');
65+
66+
return self::FAILURE;
67+
}
68+
4269
$config = new ArrayConfig([
4370
'namespace' => $namespace,
4471
'dryRun' => $this->option('dry-run'),
4572
'verbose' => $this->getOutput()->isVerbose(),
4673
]);
4774

48-
$engine = $this->detectEngine($configPath);
4975
$builder = new Builder($engine, $config);
5076
$renderer = new TwigRenderer(null, $config);
5177
$io = new LaravelConsoleIo($this);
5278

5379
$generator = new Generator($builder, $renderer, $io, $config);
54-
$generator->generate($configPath, $outputPath, [
80+
$result = $generator->generate($configPath, $outputPath, [
5581
'dryRun' => $this->option('dry-run'),
5682
'verbose' => $this->getOutput()->isVerbose(),
83+
'force' => $this->option('force'),
5784
]);
5885

59-
$this->info('DTOs generated successfully.');
86+
if ($this->option('dry-run')) {
87+
$this->info('Dry run complete. No files were written.');
88+
} else {
89+
$this->info('DTOs generated successfully.');
90+
}
6091

6192
return self::SUCCESS;
6293
}
6394

64-
private function detectEngine(string $configPath): PhpEngine|XmlEngine|YamlEngine
95+
private function detectEngine(string $configPath): PhpEngine|XmlEngine|YamlEngine|null
6596
{
6697
$sep = str_ends_with($configPath, '/') ? '' : '/';
6798

@@ -98,6 +129,6 @@ private function detectEngine(string $configPath): PhpEngine|XmlEngine|YamlEngin
98129
}
99130
}
100131

101-
return new PhpEngine();
132+
return null;
102133
}
103134
}

src/InitDtoCommand.php

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpCollective\LaravelDto;
6+
7+
use Illuminate\Console\Command;
8+
9+
class InitDtoCommand extends Command
10+
{
11+
/**
12+
* @var string
13+
*/
14+
protected $signature = 'dto:init
15+
{--format=php : Config format (php, xml, yaml)}
16+
{--force : Overwrite existing config file}';
17+
18+
/**
19+
* @var string
20+
*/
21+
protected $description = 'Create a starter DTO configuration file';
22+
23+
public function handle(): int
24+
{
25+
$format = strtolower($this->option('format'));
26+
$configPath = config('dto.config_path', config_path());
27+
28+
if (!in_array($format, ['php', 'xml', 'yaml', 'yml'], true)) {
29+
$this->error("Invalid format: {$format}. Use php, xml, or yaml.");
30+
31+
return self::FAILURE;
32+
}
33+
34+
if ($format === 'yml') {
35+
$format = 'yaml';
36+
}
37+
38+
$filename = $format === 'php' ? 'dtos.php' : 'dto.' . $format;
39+
$filePath = rtrim($configPath, '/') . '/' . $filename;
40+
41+
if (file_exists($filePath) && !$this->option('force')) {
42+
$this->error("Config file already exists: {$filePath}");
43+
$this->line('Use --force to overwrite.');
44+
45+
return self::FAILURE;
46+
}
47+
48+
$content = $this->getConfigContent($format);
49+
50+
if (!is_dir(dirname($filePath))) {
51+
mkdir(dirname($filePath), 0755, true);
52+
}
53+
54+
file_put_contents($filePath, $content);
55+
56+
$this->info("Created DTO configuration: {$filePath}");
57+
$this->line('');
58+
$this->line('Next steps:');
59+
$this->line(' 1. Edit the configuration to define your DTOs');
60+
$this->line(' 2. Run "php artisan dto:generate" to generate the DTO classes');
61+
62+
return self::SUCCESS;
63+
}
64+
65+
private function getConfigContent(string $format): string
66+
{
67+
return match ($format) {
68+
'php' => $this->getPhpConfig(),
69+
'xml' => $this->getXmlConfig(),
70+
'yaml' => $this->getYamlConfig(),
71+
};
72+
}
73+
74+
private function getPhpConfig(): string
75+
{
76+
return <<<'PHP'
77+
<?php
78+
79+
/**
80+
* DTO Configuration
81+
*
82+
* Define your Data Transfer Objects here.
83+
*
84+
* @see https://github.com/php-collective/dto
85+
*/
86+
return [
87+
// Example DTO definition
88+
[
89+
'name' => 'User',
90+
'fields' => [
91+
[
92+
'name' => 'id',
93+
'type' => 'int',
94+
],
95+
[
96+
'name' => 'email',
97+
'type' => 'string',
98+
],
99+
[
100+
'name' => 'name',
101+
'type' => 'string',
102+
'nullable' => true,
103+
],
104+
[
105+
'name' => 'createdAt',
106+
'type' => '\DateTimeInterface',
107+
'nullable' => true,
108+
],
109+
],
110+
],
111+
112+
// Add more DTOs here...
113+
];
114+
PHP;
115+
}
116+
117+
private function getXmlConfig(): string
118+
{
119+
return <<<'XML'
120+
<?xml version="1.0" encoding="UTF-8"?>
121+
<!--
122+
DTO Configuration
123+
124+
Define your Data Transfer Objects here.
125+
126+
@see https://github.com/php-collective/dto
127+
-->
128+
<dtos xmlns="php-collective-dto">
129+
<!-- Example DTO definition -->
130+
<dto name="User">
131+
<field name="id" type="int"/>
132+
<field name="email" type="string"/>
133+
<field name="name" type="string" nullable="true"/>
134+
<field name="createdAt" type="\DateTimeInterface" nullable="true"/>
135+
</dto>
136+
137+
<!-- Add more DTOs here... -->
138+
</dtos>
139+
XML;
140+
}
141+
142+
private function getYamlConfig(): string
143+
{
144+
return <<<'YAML'
145+
# DTO Configuration
146+
#
147+
# Define your Data Transfer Objects here.
148+
#
149+
# @see https://github.com/php-collective/dto
150+
151+
# Example DTO definition
152+
User:
153+
fields:
154+
id:
155+
type: int
156+
email:
157+
type: string
158+
name:
159+
type: string
160+
nullable: true
161+
createdAt:
162+
type: \DateTimeInterface
163+
nullable: true
164+
165+
# Add more DTOs here...
166+
YAML;
167+
}
168+
}

tests/GenerateDtoCommandTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ public function testCommandExists(): void
7070
$commands = Artisan::all();
7171

7272
$this->assertArrayHasKey('dto:generate', $commands);
73+
$this->assertArrayHasKey('dto:init', $commands);
74+
}
75+
76+
public function testCommandFailsWhenNoConfigFound(): void
77+
{
78+
$this->artisan('dto:generate')
79+
->assertFailed()
80+
->expectsOutputToContain('No DTO configuration files found');
7381
}
7482

7583
public function testCommandWithDryRun(): void
@@ -114,4 +122,28 @@ public function testCommandGeneratesDto(): void
114122
$this->assertStringContainsString('namespace TestApp\\Dto', $content);
115123
$this->assertStringContainsString('class UserDto', $content);
116124
}
125+
126+
public function testCommandWithCustomOptions(): void
127+
{
128+
$configContent = <<<'XML'
129+
<?xml version="1.0" encoding="UTF-8"?>
130+
<dtos xmlns="php-collective-dto">
131+
<dto name="Product">
132+
<field name="id" type="int"/>
133+
</dto>
134+
</dtos>
135+
XML;
136+
file_put_contents($this->tempDir . '/config/dto.xml', $configContent);
137+
138+
$this->artisan('dto:generate', [
139+
'--config-path' => $this->tempDir . '/config',
140+
'--output-path' => $this->tempDir . '/output',
141+
'--namespace' => 'Custom\\Dto',
142+
])->assertSuccessful();
143+
144+
$this->assertFileExists($this->tempDir . '/output/Dto/ProductDto.php');
145+
146+
$content = file_get_contents($this->tempDir . '/output/Dto/ProductDto.php');
147+
$this->assertStringContainsString('namespace Custom\\Dto', $content);
148+
}
117149
}

0 commit comments

Comments
 (0)