@@ -98,34 +98,259 @@ All responses use `ProductResource` and a consistent JSON envelope; validation e
9898
9999## 6. Choosing a design pattern (architecture)
100100
101- You have three ways to control what gets generated, from broadest to finest :
101+ You have three ways to control what gets generated:
102102
103- ** Per command** — pass ` --architecture ` (or ` -a ` ):
103+ | Approach | When to use | Example |
104+ | --- | --- | --- |
105+ | ` --architecture= ` | One module needs a different style | ` --architecture=actions ` |
106+ | Config / ` .env ` default | Whole project uses one style | ` LARA_ARCHITECT_ARCHITECTURE=actions ` |
107+ | ` --patterns= ` | Mix-and-match for this module only | ` --patterns=model,migration,service,controller ` |
108+
109+ Other useful flags: ` --force ` , ` --no-uuid ` , ` --no-soft-deletes ` , ` --dry-run ` .
110+
111+ ### 6.1 Service–Repository (default)
112+
113+ Best when you want a clear layer between HTTP, business rules, and data access.
114+
115+ ``` bash
116+ php artisan make:module Product \
117+ --architecture=service-repository \
118+ --fields=" name:string, price:decimal, sku:string:unique, status:enum"
119+ ```
120+
121+ ** How the pieces fit together**
122+
123+ ```
124+ Controller → ProductService → ProductRepository → Product (Eloquent)
125+ ↑ ↑
126+ StoreProductRequest prepareForCreate / created hooks
127+ ProductFilter (index)
128+ ```
129+
130+ ** Controller** injects the service:
131+
132+ ``` php
133+ public function __construct(
134+ private readonly ProductService $productService,
135+ ) {}
136+
137+ public function store(StoreProductRequest $request): JsonResponse
138+ {
139+ $product = $this->productService->create($request->validated());
140+
141+ return $this->respondCreated(new ProductResource($product));
142+ }
143+ ```
144+
145+ ** Extend the repository** with module-specific queries:
146+
147+ ``` php
148+ // app/Repositories/ProductRepository.php
149+ public function findBySku(string $sku): ?Product
150+ {
151+ return $this->findBy('sku', $sku);
152+ }
153+
154+ public function active(): Collection
155+ {
156+ return $this->getBy(['status' => ProductStatus::Active]);
157+ }
158+ ```
159+
160+ ** Extend the service** with business logic (still transactional):
161+
162+ ``` php
163+ // app/Services/ProductService.php
164+ protected function prepareForCreate(array $data): array
165+ {
166+ $data['sku'] = strtoupper($data['sku']);
167+
168+ return $data;
169+ }
170+
171+ protected function created(Model $model, array $data): void
172+ {
173+ // e.g. dispatch ProductCreated event, clear cache, ...
174+ }
175+
176+ public function archive(Product $product): Product
177+ {
178+ return $this->update($product, ['status' => ProductStatus::Archived]);
179+ }
180+ ```
181+
182+ ** Soft-delete helpers** (available on both repository and service):
183+
184+ ``` php
185+ $service->delete($product); // soft delete
186+ $service->restore($product); // restore one
187+ $service->restoreAll([1, 2, 3]); // restore by ids (or all trashed if empty)
188+ $service->forceDelete($product); // permanent
189+ $service->trashed(); // list soft-deleted
190+ $service->deleteMany([1, 2, 3]);
191+ $service->deleteAll();
192+ ```
193+
194+ ### 6.2 Actions + DTO
195+
196+ Best when each use-case should be a single, reusable class (ADR-style / single-responsibility).
104197
105198``` bash
106- php artisan make:module Order --architecture=actions --fields=" total:decimal, status:enum"
199+ php artisan make:module Order \
200+ --architecture=actions \
201+ --fields=" total:decimal, status:enum, notes:text:nullable"
107202```
108203
109- The ` actions ` preset skips the service/repository and instead generates ` CreateOrder ` / ` UpdateOrder ` / ` DeleteOrder ` action classes plus an ` OrderData ` DTO, and the controller dispatches those actions.
204+ ** What you get **
110205
111- ** As a project default** — set it once in ` config/lara-architect.php ` (or ` .env ` ):
206+ ```
207+ app/
208+ ├── Actions/Orders/CreateOrder.php
209+ ├── Actions/Orders/UpdateOrder.php
210+ ├── Actions/Orders/DeleteOrder.php
211+ ├── DTOs/OrderData.php
212+ ├── Enums/OrderStatus.php
213+ ├── Models/Order.php
214+ └── Http/Controllers/OrderController.php # calls CreateOrder::run(...), etc.
215+ ```
216+
217+ ** Controller** dispatches actions (no service injection):
218+
219+ ``` php
220+ public function store(StoreOrderRequest $request): JsonResponse
221+ {
222+ $order = CreateOrder::run(OrderData::fromRequest($request));
223+
224+ return $this->respondCreated(new OrderResource($order));
225+ }
226+
227+ public function update(UpdateOrderRequest $request, Order $order): JsonResponse
228+ {
229+ $order = UpdateOrder::run($order, OrderData::fromRequest($request));
230+
231+ return $this->respondSuccess(new OrderResource($order), 'Order updated successfully.');
232+ }
233+
234+ public function destroy(Order $order): JsonResponse
235+ {
236+ DeleteOrder::run($order);
237+
238+ return $this->respondDeleted();
239+ }
240+ ```
241+
242+ ** Add a custom action** for a non-CRUD use-case:
243+
244+ ``` php
245+ // app/Actions/Orders/MarkOrderPaid.php
246+ namespace App\Actions\Orders;
247+
248+ use App\Enums\OrderStatus;
249+ use App\Models\Order;
250+ use KarimAshraf\LaraArchitect\Actions\Action;
251+
252+ class MarkOrderPaid extends Action
253+ {
254+ protected function handle(Order $order): Order
255+ {
256+ $order->update(['status' => OrderStatus::Active]);
257+
258+ return $order->refresh();
259+ }
260+ }
261+
262+ // anywhere in the app:
263+ MarkOrderPaid::run($order);
264+ ```
265+
266+ ** DTO usage** outside the controller:
267+
268+ ``` php
269+ $data = OrderData::fromArray([
270+ 'total' => 99.5,
271+ 'status' => 'active',
272+ 'notes' => null,
273+ ]);
274+
275+ $data->toArray(); // snake_case keys, enums as values
276+ $data->toFilteredArray(); // nulls removed — good for partial updates
277+ ```
278+
279+ ### 6.3 Lean (Eloquent in the controller)
280+
281+ Best for tiny resources or admin prototypes where a full stack is overkill.
282+
283+ ``` bash
284+ php artisan make:module Tag \
285+ --architecture=lean \
286+ --fields=" name:string:unique"
287+ ```
288+
289+ Generates only: model, migration, form requests, resource, controller. The controller talks to Eloquent directly — no service, repository, or actions.
290+
291+ ### 6.4 Hand-picked patterns
292+
293+ Skip presets and compose your own stack for one module:
294+
295+ ``` bash
296+ # Model + service, no repository
297+ php artisan make:module Invoice \
298+ --patterns=model,migration,service,requests,resource,controller \
299+ --fields=" number:string:unique, total:decimal"
300+
301+ # Actions without DTO
302+ php artisan make:module Note \
303+ --patterns=model,migration,actions,requests,resource,controller \
304+ --fields=" body:text"
305+
306+ # Read-only resource (no write layer)
307+ php artisan make:module Report \
308+ --patterns=model,migration,resource,controller \
309+ --fields=" title:string"
310+ ```
311+
312+ List every registered pattern anytime:
313+
314+ ``` bash
315+ php artisan architect:patterns
316+ ```
317+
318+ ### 6.5 Project default and custom presets
319+
320+ ** Set a project-wide default** in ` config/lara-architect.php ` or ` .env ` :
321+
322+ ``` env
323+ LARA_ARCHITECT_ARCHITECTURE=actions
324+ ```
112325
113326``` php
114327'generation' => [
115- 'default_architecture' => env('LARA_ARCHITECT_ARCHITECTURE', 'actions '),
328+ 'default_architecture' => env('LARA_ARCHITECT_ARCHITECTURE', 'service-repository '),
116329],
117330```
118331
119- Now every ` make:module ` without ` --architecture ` uses it.
332+ ** Add your own preset ** — a named list of patterns — then use it like any built-in:
120333
121- ** Hand-picked patterns** — skip presets entirely with ` --patterns ` :
334+ ``` php
335+ // config/lara-architect.php
336+ 'architectures' => [
337+ 'service-repository' => [/* ... */],
338+ 'actions' => [/* ... */],
339+ 'lean' => [/* ... */],
340+
341+ // your team style:
342+ 'api-full' => [
343+ 'model', 'migration', 'factory', 'enum',
344+ 'repository', 'service', 'filter',
345+ 'requests', 'resource', 'controller',
346+ ],
347+ ],
348+ ```
122349
123350``` bash
124- php artisan make:module Tag --patterns=model,migration,resource,controller --fields=" name:string:unique"
351+ php artisan make:module Customer --architecture=api-full --fields=" name:string, email :string:unique"
125352```
126353
127- Other useful flags: ` --force ` (overwrite existing files), ` --no-uuid ` , ` --no-soft-deletes ` .
128-
129354## 7. Field definition reference
130355
131356```
@@ -146,17 +371,86 @@ published_at:datetime:nullable # date-range filters (published_at_f
146371category_id:foreignid # foreign key column
147372```
148373
149- ## 8. Beyond the generator
374+ ## 8. Patterns you use after generation
375+
376+ These work with generated modules and with hand-written code.
377+
378+ ### Query filters
379+
380+ ``` php
381+ // GET /api/products?search=desk&price_min=100&status=active
382+ public function index(ProductFilter $filter): AnonymousResourceCollection
383+ {
384+ return ProductResource::collection(
385+ $this->productService->filter($filter),
386+ );
387+ }
388+
389+ // or on the model / repository:
390+ Product::filter($filter)->paginate();
391+ $repository->filter($filter, perPage: 20);
392+ ```
393+
394+ Add a custom filter method — any public method on the filter maps from the query string (` ?featured=1 ` → ` featured() ` ):
395+
396+ ``` php
397+ // app/Http/Filters/ProductFilter.php
398+ public function featured(string $value): void
399+ {
400+ $this->builder->where('is_featured', filter_var($value, FILTER_VALIDATE_BOOLEAN));
401+ }
402+ ```
403+
404+ ### Enums (` EnumHelpers ` )
405+
406+ Generated enums use the package trait. Override any helper by redeclaring it:
407+
408+ ``` php
409+ namespace App\Enums;
410+
411+ use KarimAshraf\LaraArchitect\Enums\Concerns\EnumHelpers;
412+
413+ enum ProductStatus: string
414+ {
415+ use EnumHelpers;
150416
151- The generated classes extend the package's base classes, which you can also use directly in hand-written code:
417+ case Draft = 'draft';
418+ case Active = 'active';
419+ case Archived = 'archived';
152420
153- - ` BaseRepository ` / ` BaseService ` — CRUD plus soft-delete operations: ` restore() ` , ` restoreAll() ` , ` deleteAll() ` , ` deleteMany() ` , ` forceDelete() ` , ` trashed() `
154- - ` Action ` — single-purpose, transaction-wrapped classes invoked with ` MyAction::run(...) `
155- - ` QueryFilter ` + the ` Filterable ` model trait — request-driven filtering
156- - ` BaseData ` — DTOs hydrated from requests/arrays, with enum and nested-DTO support
157- - ` BaseFormRequest ` + ` RespondsWithJson ` — consistent JSON envelopes
421+ public function label(): string
422+ {
423+ return __("products.status.{$this->value}");
424+ }
425+ }
426+
427+ ProductStatus::values(); // ['draft', 'active', 'archived']
428+ ProductStatus::options(); // value => label (uses your label())
429+ ProductStatus::Active->is(ProductStatus::Active);
430+ ```
431+
432+ ### Form requests + JSON responses
433+
434+ ``` php
435+ class StoreProductRequest extends BaseFormRequest
436+ {
437+ public function rules(): array
438+ {
439+ return [
440+ 'name' => ['required', 'string', 'max:255'],
441+ 'status' => ['required', Rule::enum(ProductStatus::class)],
442+ ];
443+ }
444+ }
445+
446+ // In any controller using RespondsWithJson:
447+ return $this->respondCreated($resource);
448+ return $this->respondSuccess($resource, 'Updated.');
449+ return $this->respondDeleted();
450+ return $this->respondError('Something went wrong.', 422);
451+ ```
158452
159- See the [ README] ( ../README.md#runtime-building-blocks ) for examples of each .
453+ More detail: [ README — Runtime building blocks ] ( ../README.md#runtime-building-blocks ) .
160454
161455## 9. Customizing the generated code
162456
0 commit comments