@@ -28,7 +28,7 @@ A Laravel package that provides an elegant way to generate and use Actions and D
2828## Features
2929
3030- 🚀 ** Simple Command Generation** : Generate Actions and DTOs with simple Artisan commands
31- - 🔒 ** Type Safety** : Built with PHP 8.2 + readonly classes for immutable data structures
31+ - 🔒 ** Type Safety** : Built with PHP 8.3 + readonly classes for immutable data structures
3232- 🏗️ ** Clean Architecture** : Promotes separation of concerns and clean code practices
3333- 🔄 ** Automatic Data Mapping** : Seamless conversion between arrays, Form Requests, and Models
3434- ✅ ** Attribute-Based Validation** : Use PHP attributes for declarative validation rules
@@ -41,8 +41,8 @@ A Laravel package that provides an elegant way to generate and use Actions and D
4141
4242## Requirements
4343
44- - PHP 8.2 or higher
45- - Laravel 11 .0 or higher
44+ - PHP 8.3 or higher
45+ - Laravel 12 .0 or 13.0
4646
4747## Installation
4848
@@ -326,14 +326,10 @@ readonly class CreateUserData extends DataTransferObject
326326}
327327
328328// Validate using attributes
329- try {
330- $user = CreateUserData::resolve($data);
331- $user->validateAttributes();
332- // DTO is valid
333- } catch (\InvalidArgumentException $e) {
334- // Handle validation errors
335- echo $e->getMessage();
336- }
329+ $user = CreateUserData::resolve($data)->validateAttributes();
330+
331+ // If invalid, validateAttributes() throws Illuminate\Validation\ValidationException
332+ // with Laravel-standard per-field errors.
337333```
338334
339335** Available validation attributes:**
@@ -364,6 +360,29 @@ $user
364360 ->validateAttributes(); // Combine with attribute validation
365361```
366362
363+ #### Custom Validation Attributes
364+
365+ You can create your own PHP attributes by implementing ` Holiq\ActionData\Contracts\Validator ` :
366+
367+ ``` php
368+ use Attribute;
369+ use Holiq\ActionData\Contracts\Validator;
370+
371+ #[Attribute(Attribute::TARGET_PROPERTY)]
372+ class Positive implements Validator
373+ {
374+ public function validate(mixed $value, string $property): bool
375+ {
376+ return is_numeric($value) && $value > 0;
377+ }
378+
379+ public function getErrorMessage(string $property): string
380+ {
381+ return "'{$property}' must be a positive number.";
382+ }
383+ }
384+ ```
385+
367386### Nested DTOs
368387
369388Laravel Action Data automatically resolves nested DTOs and arrays of DTOs:
@@ -389,7 +408,7 @@ readonly class UserData extends DataTransferObject
389408 ) {}
390409}
391410
392- // Automatically resolves nested structure
411+ // Option 1: Using resolver (auto maps nested array → DTO)
393412$user = UserData::resolve([
394413 'name' => 'John Doe',
395414 'email' => 'john@example.com',
@@ -400,6 +419,17 @@ $user = UserData::resolve([
400419 ],
401420]);
402421
422+ // Option 2: Manual instantiation (must pass AddressData object)
423+ $user = new UserData(
424+ name: 'John Doe',
425+ email: 'john@example.com',
426+ address: new AddressData(
427+ street: '123 Main St',
428+ city: 'Anytown',
429+ country: 'USA',
430+ ),
431+ );
432+
403433// Access nested data
404434echo $user->address->street; // "123 Main St"
405435```
@@ -624,28 +654,24 @@ class UserController extends Controller
624654{
625655 public function store(CreateUserRequest $request): JsonResponse
626656 {
627- try {
628- // Resolve and validate DTO
629- $userData = CreateUserData::resolve($request->validated())
630- ->validateAttributes();
631-
632- // Execute action with validated DTO
633- $user = CreateUserAction::resolve()->execute($userData);
634-
635- return response()->json([
636- 'message' => 'User created successfully',
637- 'data' => $user
638- ], 201);
639- } catch (\InvalidArgumentException $e) {
640- return response()->json([
641- 'message' => 'Validation failed',
642- 'errors' => $e->getMessage()
643- ], 422);
644- }
657+ // Resolve and validate DTO
658+ $userData = CreateUserData::resolve($request->validated())
659+ ->validateAttributes();
660+
661+ // Execute action with validated DTO
662+ $user = CreateUserAction::resolve()->execute($userData);
663+
664+ return response()->json([
665+ 'message' => 'User created successfully',
666+ 'data' => $user
667+ ], 201);
645668 }
646669}
647670```
648671
672+ When validation fails, Laravel will automatically convert the
673+ ` ValidationException ` to a standard ` 422 Unprocessable Entity ` response.
674+
649675#### Nested DTOs Example: Order Management
650676
651677``` php
@@ -783,6 +809,7 @@ Creates a DTO instance from various data sources.
783809** ` toArrayForCreate(): array ` ** - Excludes properties from ` toExcludedPropertiesOnCreate() `
784810** ` toArrayForUpdate(): array ` ** - Excludes properties from ` toExcludedPropertiesOnUpdate() `
785811** ` toJson(int $options = 0): string ` ** - Converts to JSON string
812+ ** ` toCamelJson(int $options = 0): string ` ** - Converts to camelCase JSON string
786813
787814#### Validation
788815
@@ -797,6 +824,12 @@ Validates using PHP attributes.
797824** ` has(string $property): bool ` ** - Checks if property exists
798825** ` get(string $property, mixed $default = null): mixed ` ** - Gets property value
799826
827+ #### Immutable Update Helpers
828+
829+ ** ` with(mixed ...$values): static ` ** - Returns a cloned DTO with named-argument overrides
830+ ** ` withArray(array $overrides): static ` ** - Returns a cloned DTO with snake_case/camelCase key overrides
831+ ** ` without(string ...$properties): static ` ** - Returns a cloned DTO with nullable properties set to null
832+
800833#### Utility Methods
801834
802835** ` clone(): static ` ** - Creates a clone
0 commit comments