Skip to content

Commit 38e862c

Browse files
committed
feat: add request injection
1 parent 252a248 commit 38e862c

File tree

8 files changed

+567
-21
lines changed

8 files changed

+567
-21
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ vendor/
99
.DS_Store
1010
composer.lock
1111
cliff.toml
12+
.CLAUDE.md
13+
CLAUDE.md

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ final class MyAction
102102
Or you can use more than one subfolder like this:.
103103

104104
```bash
105+
105106
php artisan make:action MyAction Folder1/Folder2
106107
```
107108
This will create a new action class in the `app/Actions/Folder1/Folder2/` directory with the name `MyAction.php`.
@@ -196,6 +197,78 @@ final class MyAction
196197

197198
```
198199

200+
- `--r` This flag generates a Laravel Request class and injects it into the action method.
201+
202+
For example, if you want to create an action class with Request injection, you can use:
203+
204+
```bash
205+
php artisan make:action MyAction --r
206+
```
207+
This will generate both an Action class and a Request class (`MyActionRequest`), and will result in:
208+
209+
```php
210+
<?php
211+
212+
declare(strict_types=1);
213+
214+
namespace App\Actions;
215+
216+
use App\Http\Requests\MyActionRequest;
217+
218+
final class MyAction
219+
{
220+
public static function handle(MyActionRequest $request): void
221+
{
222+
// This is where the action logic will be implemented.
223+
}
224+
}
225+
```
226+
227+
### Advanced Flag Combinations
228+
229+
You can combine multiple flags to create actions with different features. All possible combinations are supported:
230+
231+
**Two-flag combinations:**
232+
- `--tr` or `--rt`: Database transactions with Request injection
233+
- `--ur` or `--ru`: User injection with Request injection
234+
- `--tu` or `--ut`: Database transactions with User injection (as shown above)
235+
236+
**Three-flag combinations (all features):**
237+
- `--tur`, `--tru`, `--utr`, `--urt`, `--rtu`, `--rut`: All features combined
238+
239+
For example:
240+
```bash
241+
php artisan make:action CompleteAction --tur
242+
```
243+
244+
This will generate:
245+
```php
246+
<?php
247+
248+
declare(strict_types=1);
249+
250+
namespace App\Actions;
251+
252+
use Illuminate\Support\Facades\DB;
253+
use App\Models\User;
254+
use App\Http\Requests\CompleteActionRequest;
255+
256+
final class CompleteAction
257+
{
258+
public static function handle(User $user, CompleteActionRequest $request): void
259+
{
260+
DB::transaction(function () use ($request) {
261+
// Logic to be executed within the transaction
262+
});
263+
}
264+
}
265+
```
266+
267+
**Individual flags are also supported:**
268+
- `--t`: Only database transactions
269+
- `--u`: Only user injection
270+
- `--r`: Only request injection
271+
199272

200273
## Contributing
201274

src/Actions/GenerateRequest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Panchodp\LaravelAction\Actions;
6+
7+
use Illuminate\Support\Facades\Artisan;
8+
use InvalidArgumentException;
9+
use Throwable;
10+
11+
final class GenerateRequest
12+
{
13+
/**
14+
* Generate a Laravel Request class using Artisan command
15+
*
16+
* @param string $actionName The base name for the action (e.g., 'Limpiar')
17+
*
18+
* @throws InvalidArgumentException
19+
*/
20+
public static function handle(string $actionName): string
21+
{
22+
if (empty($actionName)) {
23+
throw new InvalidArgumentException('Action name cannot be empty.');
24+
}
25+
26+
// Validate action name format
27+
if (! preg_match('/^[A-Z][a-zA-Z0-9]*$/', $actionName)) {
28+
throw new InvalidArgumentException('Invalid action name format. Must start with uppercase letter and contain only alphanumeric characters.');
29+
}
30+
31+
$requestName = $actionName.'Request';
32+
33+
try {
34+
// Execute Laravel's make:request command
35+
$exitCode = Artisan::call('make:request', [
36+
'name' => $requestName,
37+
]);
38+
39+
if ($exitCode !== 0) {
40+
throw new InvalidArgumentException("Failed to generate Request class: {$requestName}");
41+
}
42+
43+
return $requestName;
44+
} catch (Throwable $e) {
45+
throw new InvalidArgumentException('Error generating Request class: '.$e->getMessage());
46+
}
47+
}
48+
}

src/Actions/PrepareStub.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99

1010
final class PrepareStub
1111
{
12-
public static function handle(bool $tuFlag, bool $tFlag, bool $uFlag, string $filename, string $namespace): string
12+
public static function handle(bool $tFlag, bool $uFlag, bool $rFlag, string $filename, string $namespace): string
1313
{
1414
try {
15-
$stubFile = $tFlag ? __DIR__.'/../stubs/action_transaction.stub' : __DIR__.'/../stubs/action.stub';
15+
// Select appropriate stub based on flags
16+
$stubFile = self::selectStubFile($tFlag, $rFlag);
1617

1718
// Security: Validate stub file exists before reading
1819
if (! file_exists($stubFile) || ! is_readable($stubFile)) {
@@ -25,6 +26,13 @@ public static function handle(bool $tuFlag, bool $tFlag, bool $uFlag, string $fi
2526
throw new RuntimeException("Failed to read stub file: {$stubFile}");
2627
}
2728

29+
// Handle Request class injection
30+
if ($rFlag) {
31+
$requestClass = $filename.'Request';
32+
$safeRequestClass = self::sanitizeForTemplate($requestClass);
33+
$stub = str_replace('{{ request_class }}', $safeRequestClass, $stub);
34+
}
35+
2836
if ($uFlag) {
2937
$stub = str_replace('{{ import_model }}', 'use App\Models\User;', $stub);
3038
$stub = str_replace('{{ user }}', 'User $user,', $stub);
@@ -53,6 +61,28 @@ public static function handle(bool $tuFlag, bool $tFlag, bool $uFlag, string $fi
5361

5462
}
5563

64+
/**
65+
* Select the appropriate stub file based on flags
66+
*/
67+
private static function selectStubFile(bool $tFlag, bool $rFlag): string
68+
{
69+
$baseDir = __DIR__.'/../stubs/';
70+
71+
if ($tFlag && $rFlag) {
72+
return $baseDir.'action_transaction_request.stub';
73+
}
74+
75+
if ($rFlag) {
76+
return $baseDir.'action_request.stub';
77+
}
78+
79+
if ($tFlag) {
80+
return $baseDir.'action_transaction.stub';
81+
}
82+
83+
return $baseDir.'action.stub';
84+
}
85+
5686
/**
5787
* Sanitize input for template usage to prevent template injection.
5888
*

src/Console/MakeActionCommand.php

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Console\Command;
88
use Illuminate\Support\Facades\File;
99
use Panchodp\LaravelAction\Actions\CreateDirectory;
10+
use Panchodp\LaravelAction\Actions\GenerateRequest;
1011
use Panchodp\LaravelAction\Actions\ObtainNamespace;
1112
use Panchodp\LaravelAction\Actions\PreparePath;
1213
use Panchodp\LaravelAction\Actions\PrepareStub;
@@ -20,9 +21,20 @@ final class MakeActionCommand extends Command
2021
{
2122
protected $signature = 'make:action {name} {subfolder?}
2223
{--t : Make a DB transaction action }
23-
{--tu : Make a DB transaction action }
24-
{--ut : Make a DB transaction action }
25-
{--u : Make a User injection}';
24+
{--u : Make a User injection}
25+
{--r : Generate a Request class and inject it into the action}
26+
{--tu : Make a DB transaction action with User injection}
27+
{--ut : Make a DB transaction action with User injection}
28+
{--tr : Make a DB transaction action with Request injection}
29+
{--rt : Make a DB transaction action with Request injection}
30+
{--ur : Make a User injection with Request injection}
31+
{--ru : Make a User injection with Request injection}
32+
{--tur : Make a DB transaction action with User and Request injection}
33+
{--tru : Make a DB transaction action with User and Request injection}
34+
{--utr : Make a DB transaction action with User and Request injection}
35+
{--urt : Make a DB transaction action with User and Request injection}
36+
{--rtu : Make a DB transaction action with User and Request injection}
37+
{--rut : Make a DB transaction action with User and Request injection}';
2638

2739
protected $description = 'Create a new action class';
2840

@@ -37,6 +49,12 @@ public function handle(): int
3749
$input = $this->processInputs();
3850
$config = $this->validateAndPrepareConfig($input);
3951
$this->createDirectoryStructure($config);
52+
53+
// Generate Request class if --r flag is present
54+
if ($config['rFlag'] ?? false) {
55+
$this->generateRequestFile($config);
56+
}
57+
4058
$this->generateActionFile($config);
4159
$this->displaySuccessMessages($config);
4260

@@ -50,6 +68,7 @@ public function handle(): int
5068

5169
/**
5270
* Process and sanitize command inputs.
71+
*
5372
* @return array<string, mixed>
5473
*/
5574
private function processInputs(): array
@@ -60,28 +79,38 @@ private function processInputs(): array
6079
$subfolder = $this->argument('subfolder');
6180
$subfolder = is_string($subfolder) ? mb_trim($subfolder, '/\\') : '';
6281

63-
$tuFlag = (bool) ($this->option('tu') || $this->option('ut'));
64-
$tFlag = (bool) ($this->option('t') || $tuFlag);
65-
$uFlag = (bool) ($this->option('u') || $tuFlag);
82+
// Check for combination flags first
83+
$turFlag = (bool) ($this->option('tur') || $this->option('tru') ||
84+
$this->option('utr') || $this->option('urt') ||
85+
$this->option('rtu') || $this->option('rut'));
86+
87+
$tuFlag = (bool) ($this->option('tu') || $this->option('ut') || $turFlag);
88+
$trFlag = (bool) ($this->option('tr') || $this->option('rt') || $turFlag);
89+
$urFlag = (bool) ($this->option('ur') || $this->option('ru') || $turFlag);
90+
91+
// Individual flags or derived from combinations
92+
$tFlag = (bool) ($this->option('t') || $tuFlag || $trFlag);
93+
$uFlag = (bool) ($this->option('u') || $tuFlag || $urFlag);
94+
$rFlag = (bool) ($this->option('r') || $trFlag || $urFlag);
6695

6796
return [
6897
'name' => $name,
6998
'subfolder' => $subfolder,
70-
'tuFlag' => $tuFlag,
7199
'tFlag' => $tFlag,
72100
'uFlag' => $uFlag,
101+
'rFlag' => $rFlag,
73102
];
74103
}
75104

76105
/**
77-
* @param array<string, mixed> $input
106+
* @param array<string, mixed> $input
78107
* @return array<string, mixed>
79108
*/
80109
private function validateAndPrepareConfig(array $input): array
81110
{
82111
$name = is_string($input['name']) ? $input['name'] : '';
83112
$subfolder = is_string($input['subfolder']) ? $input['subfolder'] : '';
84-
113+
85114
ValidateName::handle($name);
86115

87116
// Security: Validate configuration values
@@ -111,7 +140,7 @@ private function validateAndPrepareConfig(array $input): array
111140
}
112141

113142
/**
114-
* @param array<string, mixed> $config
143+
* @param array<string, mixed> $config
115144
*/
116145
private function createDirectoryStructure(array $config): void
117146
{
@@ -121,27 +150,38 @@ private function createDirectoryStructure(array $config): void
121150

122151
$path = is_string($config['path']) ? $config['path'] : '';
123152
$relativePath = is_string($config['relative_path']) ? $config['relative_path'] : '';
124-
153+
125154
CreateDirectory::handle($path, $permissions);
126155
$this->info("Directory {$relativePath} created successfully...");
127156
}
128157

129158
/**
130-
* @param array<string, mixed> $config
159+
* @param array<string, mixed> $config
160+
*/
161+
private function generateRequestFile(array $config): void
162+
{
163+
$filename = is_string($config['filename']) ? $config['filename'] : '';
164+
165+
$requestName = GenerateRequest::handle($filename);
166+
$this->info("Request {$requestName} created successfully...");
167+
}
168+
169+
/**
170+
* @param array<string, mixed> $config
131171
*/
132172
private function generateActionFile(array $config): void
133173
{
134-
$tuFlag = is_bool($config['tuFlag']) ? $config['tuFlag'] : false;
135174
$tFlag = is_bool($config['tFlag']) ? $config['tFlag'] : false;
136175
$uFlag = is_bool($config['uFlag']) ? $config['uFlag'] : false;
176+
$rFlag = is_bool($config['rFlag']) ? $config['rFlag'] : false;
137177
$filename = is_string($config['filename']) ? $config['filename'] : '';
138178
$namespace = is_string($config['namespace']) ? $config['namespace'] : '';
139179
$path = is_string($config['path']) ? $config['path'] : '';
140-
180+
141181
$stub = PrepareStub::handle(
142-
$tuFlag,
143182
$tFlag,
144183
$uFlag,
184+
$rFlag,
145185
$filename,
146186
$namespace
147187
);
@@ -150,15 +190,28 @@ private function generateActionFile(array $config): void
150190
}
151191

152192
/**
153-
* @param array<string, mixed> $config
193+
* @param array<string, mixed> $config
154194
*/
155195
private function displaySuccessMessages(array $config): void
156196
{
157197
$tFlag = is_bool($config['tFlag']) ? $config['tFlag'] : false;
198+
$uFlag = is_bool($config['uFlag']) ? $config['uFlag'] : false;
199+
$rFlag = is_bool($config['rFlag']) ? $config['rFlag'] : false;
158200
$filename = is_string($config['filename']) ? $config['filename'] : '';
159201
$relativePath = is_string($config['relative_path']) ? $config['relative_path'] : '';
160-
161-
$transaction = $tFlag ? ' with DB transaction' : '.';
162-
$this->info("Action {$filename} created successfully at app/{$relativePath} folder".$transaction);
202+
203+
$features = [];
204+
if ($tFlag) {
205+
$features[] = 'DB transaction';
206+
}
207+
if ($uFlag) {
208+
$features[] = 'User injection';
209+
}
210+
if ($rFlag) {
211+
$features[] = 'Request injection';
212+
}
213+
214+
$featuresText = empty($features) ? '.' : ' with '.implode(', ', $features).'.';
215+
$this->info("Action {$filename} created successfully at app/{$relativePath} folder{$featuresText}");
163216
}
164217
}

0 commit comments

Comments
 (0)