Skip to content

Commit 9a69d75

Browse files
authored
Merge pull request #2 from YorCreative/feature/assembler-di--fromclassname--documentation
🚀 New Assembler Features (DI and from<ClassName>) and Documentation
2 parents b0f3c09 + d9d9b6a commit 9a69d75

File tree

9 files changed

+808
-232
lines changed

9 files changed

+808
-232
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
/vendor
66
clover.xml
77
.DS_Store
8+
/laravel-argonaut-dto

README.md

Lines changed: 192 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
<br />
2-
<br />
31
<div align="center">
42
<a href="https://github.com/YorCreative">
53
<img src="content/Laravel-Argonaut-DTO.png" alt="Logo" width="245" height="200">
@@ -17,7 +15,8 @@
1715
<a href="https://github.com/YorCreative/Laravel-Argonaut-DTO/actions/workflows/phpunit-tests.yml"><img alt="PHPUnit" src="https://github.com/YorCreative/Laravel-Argonaut-DTO/actions/workflows/phpunit-tests.yml/badge.svg"></a>
1816
</div>
1917

20-
Laravel Argonaut DTO is a lightweight, highly composable package for transforming arrays, objects, or collections into structured DTOs (Data Transfer Objects), with built-in support for:
18+
Laravel Argonaut DTO is a lightweight, highly composable package for transforming arrays, objects, or collections into
19+
structured DTOs (Data Transfer Objects), with built-in support for:
2120

2221
- 🧱 Deep nested transformation and casting
2322
- 🔁 Type-safe data conversion
@@ -74,6 +73,7 @@ This defines a strongly typed DTO with both validation rules and simple type cas
7473
Assemblers are responsible for mapping raw inputs (arrays or objects) into your DTOs.
7574

7675
```php
76+
// static usage example
7777
class UserDTOAssembler extends ArgonautAssembler
7878
{
7979
public static function toUserDTO(object $input): UserDTO
@@ -84,9 +84,26 @@ class UserDTOAssembler extends ArgonautAssembler
8484
]);
8585
}
8686
}
87+
88+
// instance usage example
89+
class UserDTOAssembler extends ArgonautAssembler
90+
{
91+
public function __construct(protected UserFormattingService $formattingService)
92+
{
93+
//
94+
}
95+
96+
public static function toUserDTO(object $input): UserDTO
97+
{
98+
return new UserDTO([
99+
'username' => $formatingService->userName($input->display_name),
100+
'email' => $formatingService->email($input->email),
101+
]);
102+
}
103+
}
87104
```
88105

89-
> Assembler method names must follow the format `to<ClassName>`, and are resolved automatically using `class_basename`.
106+
> Assembler method names must follow the format `to<ClassName>` or `from<ClassName>`, and are resolved automatically using `class_basename`.
90107
91108
---
92109

@@ -95,22 +112,38 @@ class UserDTOAssembler extends ArgonautAssembler
95112
Use the assembler to transform raw data into structured, casted DTO instances.
96113

97114
```php
115+
// static usage example
98116
$dto = UserDTOAssembler::assemble([
99117
'display_name' => 'jdoe',
100118
'email' => 'jdoe@example.com',
101119
], UserDTO::class);
120+
121+
// instance usage example
122+
$dto = $userDTOAssemblerInstance->assembleInstance([
123+
'display_name' => 'jdoe',
124+
'email' => 'jdoe@example.com',
125+
], UserDTO::class);
102126
```
103127

104128
You can also batch transform arrays or collections:
105129

106130
```php
131+
// static usage
107132
UserDTOAssembler::fromArray($userArray, UserDTO::class);
108133
UserDTOAssembler::fromCollection($userCollection, UserDTO::class);
134+
135+
// instance usage
136+
UserDTOAssembler::fromArray($userArray, UserDTO::class, $userDTOAssemblerInstance);
137+
UserDTOAssembler::fromCollection($userCollection, UserDTO::class, $userDTOAssemblerInstance);
138+
139+
// or using the assembler instance's static methods
140+
$userDTOAssemblerInstance::fromArray($userArray, UserDTO::class, $userDTOAssemblerInstance);
141+
$userDTOAssemblerInstance::fromCollection($userCollection, UserDTO::class, $userDTOAssemblerInstance);
109142
```
110143

111144
---
112145

113-
## 🧪 Real-World Example: Product + Features + Reviews
146+
## 🧪 Real-World Static Usage Example: Product + Features + Reviews
114147

115148
This example demonstrates nested relationships and complex type casting in action.
116149

@@ -173,11 +206,155 @@ class ProductDTOAssembler extends ArgonautAssembler
173206
}
174207
```
175208

209+
## 🎯 Advanced: Dependency Injection in Assemblers
210+
211+
ArgonautAssembler offers enhanced flexibility for your Assembler logic by supporting dependency injection. This allows
212+
you to leverage services or custom logic, whether defined in static or non-static methods, during the DTO assembly
213+
process. This is particularly powerful when integrating with Laravel's service container.
214+
215+
This feature enables you to:
216+
217+
- **Integrate Application Services:** Easily inject your existing application services (e.g., a custom formatting
218+
utility, a validation service) directly into your assembler methods.
219+
- **Decouple Complex Logic:** Keep your assembler methods focused on the core task of data mapping by delegating more
220+
complex operations or external data fetching/processing to injected dependencies.
221+
- **Improve Testability:** By injecting dependencies, you can more easily mock them in your unit tests, leading to more
222+
robust and isolated tests for your assemblers.
223+
224+
### How Dependency Injection Works
225+
226+
`ArgonautAssembler` supports dependency injection in non-static transformation methods (e.g., `toUserDTO` or
227+
`fromUserDTO`) by leveraging Laravel’s service container. When you call `ArgonautAssembler::assemble()`,
228+
`fromCollection()`, `fromArray()`, or `assembleInstance()` with an instance of the assembler, the transformation method
229+
is invoked on that instance. Laravel’s container automatically resolves and injects any dependencies declared in the
230+
method’s signature.
231+
232+
- **Static Methods:** Static transformation methods (e.g., `public static function toUserDTO($input)`) do not support
233+
dependency injection, as they are called statically without an instance.
234+
- **Instance Methods:** Non-static transformation methods (e.g., `public function toUserDTO($input)`) are called on an
235+
assembler instance, allowing Laravel to inject dependencies into the method.
236+
237+
### Example: Using Dependency Injection
238+
239+
Below is an example of an assembler with a non-static transformation method that uses dependency injection to format a
240+
user’s name via an injected service.
241+
242+
```php
243+
<?php
244+
245+
namespace App\Assemblers;
246+
247+
use App\DTOs\UserDTO;
248+
use App\Services\UserFormattingService;
249+
use YorCreative\LaravelArgonautDTO\ArgonautAssembler;
250+
251+
class UserAssembler extends ArgonautAssembler
252+
{
253+
public function __construct(protected UserFormattingService $formattingService)
254+
{
255+
//
256+
}
257+
258+
/**
259+
* Transform input data into a UserDTO with dependency injection.
260+
*
261+
* @param object $input Input data (e.g., from a model or array cast to object).
262+
* @param UserFormattingService $formatter Injected service for formatting user data.
263+
* @return UserDTO
264+
*/
265+
public function toUserDTO(object $input): UserDTO
266+
{
267+
return new UserDTO([
268+
'full_name' => $formattingService->formatName($input->first_name, $input->last_name),
269+
'email' => $input->email,
270+
'created_at' => $input->created_at,
271+
]);
272+
}
273+
}
274+
```
275+
276+
### Registering the Assembler
277+
278+
```php
279+
// ServiceProvider
280+
<?php
281+
282+
namespace App\Providers;
283+
284+
use Illuminate\Support\ServiceProvider;
285+
286+
class YourServiceProvider extends ServiceProvider
287+
{
288+
public function register()
289+
{
290+
$this->app->bind(FormattingServiceInterface::class, function($app) {
291+
return new FormattingService();
292+
})
293+
$this->app->bind(YourArgonautAssembler::clas, function ($app) {
294+
return new YourArgonautAssembler($app->get(FormattingServiceInterface::class));
295+
});
296+
}
297+
298+
public function provides()
299+
{
300+
return [
301+
YourArgonautAssembler::class,
302+
FormattingServiceInterface::class
303+
]
304+
}
305+
}
306+
```
307+
308+
#### Using the Assembler
309+
310+
To use the assembler with dependency injection, you need to provide an instance of the assembler to the `assemble`
311+
method or related methods (`fromCollection`, `fromArray`, or `assembleInstance`). Laravel’s container will resolve the
312+
dependencies when the method is invoked.
313+
314+
```php
315+
<?php
316+
317+
use App\Assemblers\UserAssembler;
318+
use App\DTOs\UserDTO;
319+
320+
// Example input (e.g., a model or object)
321+
$input = (object) [
322+
'first_name' => 'John',
323+
'last_name' => 'Doe',
324+
'email' => 'john.doe@example.com',
325+
'created_at' => now(),
326+
];
327+
328+
// Creating an assembler instance
329+
$formattingService = new UserFormattingService();
330+
$assembler = new UserAssembler($formattingService);
331+
// or using the container instance
332+
$assembler = resolve(YourArgonautAssembler::class);
333+
334+
// Pass the $assembler instance
335+
$userDTO = UserAssembler::assemble($input, UserDTO::class, $assembler);
336+
// Or use the instance method
337+
$userDTO = $assembler->assembleInstance($input, UserDTO::class);
338+
339+
// Transform a collection passing the $assembler instance
340+
$array = [$input, $input];
341+
$collection = collect($array);
342+
$userDTOs = UserAssembler::fromCollection($collection, UserDTO::class, $assembler);
343+
$userDTOs = $assembler::fromArray($array, UserDTO::class, $assembler)
344+
```
345+
346+
In this example:
347+
348+
- The `toUserDTO` method requires a `UserFormattingService` dependency.
349+
- The assembler instance (`$assembler`) is passed to `assemble`, `fromArray` or `fromCollection`, ensuring the
350+
non-static `toUserDTO` method is invoked on the instance.
351+
176352
---
177353

178-
## 🎯 DTOs with Prioritized Attributes and Custom Setters
354+
## Advanced: 🎯 DTOs with Prioritized Attributes and Custom Setters
179355

180-
ArgonautDTO allows you to prioritize the assignment of specific fields using `$prioritizedAttributes`, which is critical for cases where one field influences others.
356+
ArgonautDTO allows you to prioritize the assignment of specific fields using `$prioritizedAttributes`, which is critical
357+
for cases where one field influences others.
181358

182359
```php
183360
class UserDTO extends ArgonautDTO
@@ -237,13 +414,13 @@ protected array $casts = [
237414
];
238415
```
239416

240-
| Cast Type | Example | Description |
241-
|------------------------|---------------------------------------------------|--------------------------------------|
242-
| Scalar | `'string'`, `'int'`, etc. | Native PHP type cast |
243-
| Single DTO | `ProfileDTO::class` | Cast an array to a DTO instance |
244-
| Array of DTOs | `[RoleDTO::class]` | Cast to array of DTOs |
245-
| Collection of DTOs | `Collection::class . ':' . CommentDTO::class` | Cast to a Laravel Collection |
246-
| Date casting | `Carbon::class` | Cast to Carbon/DateTime instance |
417+
| Cast Type | Example | Description |
418+
|--------------------|-----------------------------------------------|----------------------------------|
419+
| Scalar | `'string'`, `'int'`, etc. | Native PHP type cast |
420+
| Single DTO | `ProfileDTO::class` | Cast an array to a DTO instance |
421+
| Array of DTOs | `[RoleDTO::class]` | Cast to array of DTOs |
422+
| Collection of DTOs | `Collection::class . ':' . CommentDTO::class` | Cast to a Laravel Collection |
423+
| Date casting | `Carbon::class` | Cast to Carbon/DateTime instance |
247424

248425
---
249426

@@ -301,4 +478,4 @@ composer test
301478

302479
## 📃 License
303480

304-
This package is open-sourced software licensed under the [MIT license](LICENSE).
481+
This package is open-sourced software licensed under the [MIT license](LICENSE).

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"scripts": {
4848
"lint": "vendor/bin/pint",
4949
"test": "vendor/bin/phpunit",
50-
"coverage": "vendor/bin/phpunit --coverage-text"
50+
"coverage": "vendor/bin/phpunit --coverage-text",
51+
"coverage-html": "vendor/bin/phpunit --coverage-html laravel-argonaut-dto"
5152
}
5253
}

0 commit comments

Comments
 (0)