Skip to content

Commit aec3767

Browse files
Karim-AshrafKarim-Ashraf
authored andcommitted
Expand getting-started with design pattern usage examples
Add concrete service-repository, actions+DTO, lean, hand-picked patterns, custom presets, filters, enums and JSON response examples.
1 parent 089c823 commit aec3767

2 files changed

Lines changed: 314 additions & 19 deletions

File tree

docs/getting-started.md

Lines changed: 313 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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
146371
category_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

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Welcome to the LaraArchitect documentation. The [README](../README.md) is the pr
55
## Quick links
66

77
- [Getting started: from install to a working CRUD API](getting-started.md)
8+
- [Design pattern examples](getting-started.md#6-choosing-a-design-pattern-architecture) — service-repository, actions + DTO, lean, custom presets
89
- [Requirements & supported versions](../README.md#requirements)
910
- [Installation](../README.md#installation)
1011
- [The module generator](../README.md#the-module-generator) — architecture presets, field definitions, enum fields, query filters

0 commit comments

Comments
 (0)