Skip to content

Refactor Module Templates to use DTOs in Services and Controllers#444

Merged
armanist merged 3 commits intosoftberg:masterfrom
armanist:443-Implement-DTO-Pattern-for-Posts-Comments-and-Users-for-module-templates
Apr 2, 2026
Merged

Refactor Module Templates to use DTOs in Services and Controllers#444
armanist merged 3 commits intosoftberg:masterfrom
armanist:443-Implement-DTO-Pattern-for-Posts-Comments-and-Users-for-module-templates

Conversation

@armanist
Copy link
Copy Markdown
Member

@armanist armanist commented Apr 1, 2026

Closes #443

Summary by CodeRabbit

  • Refactor
    • Introduced structured data objects for signup, comments, and posts to standardize input handling across the app.
    • Services updated to accept those structured payloads.
    • Comment content is normalized (trimmed) before saving.
    • Image upload/removal flows made more consistent.
    • No user-facing API or behavior changes; existing features remain unchanged.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

Introduces PostDTO, CommentDTO, and UserDTO templates and updates controllers and services across DemoApi and DemoWeb templates to construct and pass DTOs (via factories/constructors) instead of assembling raw arrays; service signatures now accept DTOs and use DTO->toArray() when persisting.

Changes

Cohort / File(s) Summary
DTO Implementations
src/Module/Templates/DemoApi/src/DTOs/PostDTO.php.tpl, src/Module/Templates/DemoApi/src/DTOs/CommentDTO.php.tpl, src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl, src/Module/Templates/DemoWeb/src/DTOs/PostDTO.php.tpl, src/Module/Templates/DemoWeb/src/DTOs/CommentDTO.php.tpl, src/Module/Templates/DemoWeb/src/DTOs/UserDTO.php.tpl
Added typed DTO classes with fromRequest factories, getters, and toArray() serializers for Post, Comment, and User payloads.
Controller Changes
src/Module/Templates/DemoApi/src/Controllers/.../AuthController.php.tpl, src/Module/Templates/DemoApi/src/Controllers/.../CommentController.php.tpl, src/Module/Templates/DemoApi/src/Controllers/.../PostManagementController.php.tpl, src/Module/Templates/DemoWeb/src/Controllers/.../AuthController.php.tpl, src/Module/Templates/DemoWeb/src/Controllers/.../CommentController.php.tpl, src/Module/Templates/DemoWeb/src/Controllers/.../PostManagementController.php.tpl
Controllers now instantiate DTOs (factory or constructor), pass DTOs to services, and remove inline assembly/trimming of request arrays; added DTO imports.
Service Signature & Persistence Changes
src/Module/Templates/DemoApi/src/Services/CommentService.php.tpl, src/Module/Templates/DemoApi/src/Services/PostService.php.tpl, src/Module/Templates/DemoWeb/src/Services/CommentService.php.tpl, src/Module/Templates/DemoWeb/src/Services/PostService.php.tpl
Service methods addComment, addPost, and updatePost now accept DTO types; persistence uses array_merge(['uuid'=>...], $dto->toArray()) or $dto->toArray() and returns persisted model/array.
Auth Signup Adjustment
src/Module/Templates/DemoApi/src/Controllers/AuthController.php.tpl, src/Module/Templates/DemoWeb/src/Controllers/AuthController.php.tpl
Signup flow changed to create UserDTO::fromRequest(..., Role::EDITOR, uuid_ordered()) and call auth()->signup($userDto->toArray()) instead of mutating $request->all().

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Controller
  participant DTO as "DTO (Post/Comment/User)"
  participant Service
  participant ModelDB as "Model / DB"

  Client->>Controller: HTTP request (validated)
  Controller->>DTO: DTO::fromRequest(request, ..., ...) / new DTO(...)
  DTO-->>Controller: DTO instance
  Controller->>Service: addXxx(DTO) / updateXxx(uuid, DTO)
  Service->>ModelDB: create() or find(uuid)
  Service->>ModelDB: fill(array_merge(['uuid'=>...], DTO.toArray())) or fill(DTO.toArray())
  ModelDB-->>Service: persisted model
  Service-->>Controller: persisted model / array
  Controller-->>Client: JSON response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

"I hopped in code to tote a DTO,
carrots packed neat for service and flow,
controllers hand off, services receive,
tidy arrays now take their leave,
hop, patch, and ship — a joyful show!" 🥕🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main change: refactoring module templates to use DTOs in services and controllers, which directly matches the changeset content.
Linked Issues check ✅ Passed The PR implements all core DTO requirements from #443: creates UserDTO, PostDTO, and CommentDTO classes with appropriate properties and factory methods, updates controllers to instantiate DTOs, and modifies services to accept DTOs instead of arrays.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the DTO pattern for Posts, Comments, and Users across module templates. No unrelated modifications or out-of-scope refactoring detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.58%. Comparing base (3d207c4) to head (a569a1a).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##             master     #444   +/-   ##
=========================================
  Coverage     82.58%   82.58%           
  Complexity     2831     2831           
=========================================
  Files           243      243           
  Lines          7545     7545           
=========================================
  Hits           6231     6231           
  Misses         1314     1314           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/Module/Templates/DemoWeb/src/DTOs/PostDTO.php.tpl (1)

69-77: Inconsistent request data handling compared to CommentDTO.

PostDTO::fromRequest() uses $request->get('title', null, true) with raw=true, bypassing framework sanitization. Meanwhile, CommentDTO::fromRequest() applies trim() without the raw flag. This inconsistency may be intentional (e.g., posts allow HTML content) but should be documented or standardized.

If raw input is intentional for posts, consider adding a brief doc comment explaining why. If not, consider aligning the approach:

Option: Apply trim like CommentDTO
     public static function fromRequest(Request $request, ?string $userUuid = null, ?string $image = null): self
     {
         return new self(
-            $request->get('title', null, true),
-            $request->get('content', null, true),
+            trim($request->get('title', '')),
+            trim($request->get('content', '')),
             $userUuid,
             $image
         );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Module/Templates/DemoWeb/src/DTOs/PostDTO.php.tpl` around lines 69 - 77,
PostDTO::fromRequest currently calls $request->get('title', null, true) and
$request->get('content', null, true) which bypasses framework sanitization
unlike CommentDTO::fromRequest that trims input; either (1) document intent by
adding a brief docblock to PostDTO::fromRequest stating that raw=true is
intentional because posts may include HTML and must be stored raw, or (2)
standardize behavior by removing the raw=true and applying trim() (as in
CommentDTO) to title/content before constructing the DTO; locate the method
named fromRequest in class PostDTO and implement one of these two options
(docblock explaining raw input or change to trimmed/sanitized input) to resolve
the inconsistency.
src/Module/Templates/DemoWeb/src/Controllers/PostManagementController.php.tpl (1)

84-98: Verify intent: empty string vs null for image in create.

When no image is uploaded, $imageName is set to '' (empty string). Since PostDTO::toArray() only filters null values, 'image' => '' will be included and persisted to the database.

If the intent is to store no image, this works but stores an empty string rather than NULL. If storing NULL is preferred when no image exists, initialize $imageName to null instead:

Optional: Use null for no image
     public function create(Request $request)
     {
-        $imageName = '';
+        $imageName = null;
 
         if ($request->hasFile('image')) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Module/Templates/DemoWeb/src/Controllers/PostManagementController.php.tpl`
around lines 84 - 98, The create method sets $imageName = '' which causes
PostDTO::fromRequest (and later PostDTO::toArray()) to persist an empty string
for 'image' instead of NULL; change the initialization to null and ensure the
branch that assigns from $this->postService->saveImage still returns a string
(or null) so that when no file is uploaded $imageName remains null and the image
field is omitted/persisted as NULL; update references in create, the $imageName
variable, and validate saveImage behavior in PostService to return null when no
image is saved.
src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl (2)

93-103: fromRequest() does not populate the image property.

The factory method doesn't set image, so it defaults to ''. Since toArray() filters empty strings, image will never appear in the output array when using fromRequest(). If the auth service or model expects an image key, it will be missing.

Verify this is the intended behavior for signup flows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl` around lines 93 - 103,
The fromRequest factory in UserDTO currently doesn't set the image property, so
instances created via UserDTO::fromRequest(...) leave image as '' and toArray()
filters it out; update UserDTO::fromRequest to read and pass the image value
from the incoming Request (e.g., $request->get('image') or null/optional) into
the constructor so the image property is populated consistently with how
toArray() and the auth/model layers expect it; ensure the parameter/order
matches the UserDTO constructor signature and handle missing values (null or
empty) the same way other callers do.

144-157: Inconsistent toArray() filtering compared to PostDTO.

UserDTO::toArray() filters out both null and empty string ('') values, while PostDTO::toArray() only filters null. This inconsistency could cause subtle behavioral differences:

  • In UserDTO: empty role or image will be excluded from the array
  • In PostDTO: empty string values are retained

Consider aligning the filtering strategy across all DTOs for predictable behavior.

Option A: Filter only null (match PostDTO)
     public function toArray(): array
     {
         return array_filter([
             'uuid' => $this->uuid,
             'email' => $this->email,
             'password' => $this->password,
             'firstname' => $this->firstname,
             'lastname' => $this->lastname,
             'role' => $this->role,
             'image' => $this->image,
         ], function ($value) {
-            return $value !== null && $value !== '';
+            return $value !== null;
         });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl` around lines 144 -
157, UserDTO::toArray currently removes both null and empty string values (the
closure returns $value !== null && $value !== ''), causing behavior drift from
PostDTO::toArray which only filters nulls; update the closure in
UserDTO::toArray (in src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl) to
only exclude nulls (i.e., return $value !== null) so empty strings like role or
image remain consistent with PostDTO::toArray.
src/Module/Templates/DemoApi/src/DTOs/PostDTO.php.tpl (1)

69-77: Consider documenting the third parameter behavior.

The fromRequest() method passes true as the third argument to $request->get(). This likely enables HTML entity decoding or sanitization. A brief docblock note would clarify this behavior for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Module/Templates/DemoApi/src/DTOs/PostDTO.php.tpl` around lines 69 - 77,
The fromRequest() method passes true as the third argument to $request->get()
for the 'title' and 'content' fields; add a short docblock above fromRequest()
explaining what that third parameter does (e.g., enables HTML entity decoding or
sanitization/stripping depending on Request::get implementation), how it affects
the returned values for $title and $content, and any security considerations for
storing/displaying those fields; reference the function name fromRequest and the
parameters 'title' and 'content' so maintainers can quickly locate and
understand the behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Module/Templates/DemoApi/src/DTOs/CommentDTO.php.tpl`:
- Around line 61-64: The fromRequest factory (CommentDTO::fromRequest) calls
trim($request->get('content')) which can receive null and throw a TypeError;
update fromRequest to defensively handle missing content by normalizing to a
string (e.g., coalesce to '' or cast to string) before calling trim and then
pass the sanitized value into the constructor (new self($postUuid, $userUuid,
$sanitizedContent)); also add/update the method docblock to state that content
will be a string even when missing.

---

Nitpick comments:
In `@src/Module/Templates/DemoApi/src/DTOs/PostDTO.php.tpl`:
- Around line 69-77: The fromRequest() method passes true as the third argument
to $request->get() for the 'title' and 'content' fields; add a short docblock
above fromRequest() explaining what that third parameter does (e.g., enables
HTML entity decoding or sanitization/stripping depending on Request::get
implementation), how it affects the returned values for $title and $content, and
any security considerations for storing/displaying those fields; reference the
function name fromRequest and the parameters 'title' and 'content' so
maintainers can quickly locate and understand the behavior.

In `@src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl`:
- Around line 93-103: The fromRequest factory in UserDTO currently doesn't set
the image property, so instances created via UserDTO::fromRequest(...) leave
image as '' and toArray() filters it out; update UserDTO::fromRequest to read
and pass the image value from the incoming Request (e.g., $request->get('image')
or null/optional) into the constructor so the image property is populated
consistently with how toArray() and the auth/model layers expect it; ensure the
parameter/order matches the UserDTO constructor signature and handle missing
values (null or empty) the same way other callers do.
- Around line 144-157: UserDTO::toArray currently removes both null and empty
string values (the closure returns $value !== null && $value !== ''), causing
behavior drift from PostDTO::toArray which only filters nulls; update the
closure in UserDTO::toArray (in
src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl) to only exclude nulls
(i.e., return $value !== null) so empty strings like role or image remain
consistent with PostDTO::toArray.

In
`@src/Module/Templates/DemoWeb/src/Controllers/PostManagementController.php.tpl`:
- Around line 84-98: The create method sets $imageName = '' which causes
PostDTO::fromRequest (and later PostDTO::toArray()) to persist an empty string
for 'image' instead of NULL; change the initialization to null and ensure the
branch that assigns from $this->postService->saveImage still returns a string
(or null) so that when no file is uploaded $imageName remains null and the image
field is omitted/persisted as NULL; update references in create, the $imageName
variable, and validate saveImage behavior in PostService to return null when no
image is saved.

In `@src/Module/Templates/DemoWeb/src/DTOs/PostDTO.php.tpl`:
- Around line 69-77: PostDTO::fromRequest currently calls $request->get('title',
null, true) and $request->get('content', null, true) which bypasses framework
sanitization unlike CommentDTO::fromRequest that trims input; either (1)
document intent by adding a brief docblock to PostDTO::fromRequest stating that
raw=true is intentional because posts may include HTML and must be stored raw,
or (2) standardize behavior by removing the raw=true and applying trim() (as in
CommentDTO) to title/content before constructing the DTO; locate the method
named fromRequest in class PostDTO and implement one of these two options
(docblock explaining raw input or change to trimmed/sanitized input) to resolve
the inconsistency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5a90f30-b7e3-4fc3-a458-411e6d18d38e

📥 Commits

Reviewing files that changed from the base of the PR and between 3d207c4 and 78a1453.

📒 Files selected for processing (16)
  • src/Module/Templates/DemoApi/src/Controllers/AuthController.php.tpl
  • src/Module/Templates/DemoApi/src/Controllers/CommentController.php.tpl
  • src/Module/Templates/DemoApi/src/Controllers/PostManagementController.php.tpl
  • src/Module/Templates/DemoApi/src/DTOs/CommentDTO.php.tpl
  • src/Module/Templates/DemoApi/src/DTOs/PostDTO.php.tpl
  • src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl
  • src/Module/Templates/DemoApi/src/Services/CommentService.php.tpl
  • src/Module/Templates/DemoApi/src/Services/PostService.php.tpl
  • src/Module/Templates/DemoWeb/src/Controllers/AuthController.php.tpl
  • src/Module/Templates/DemoWeb/src/Controllers/CommentController.php.tpl
  • src/Module/Templates/DemoWeb/src/Controllers/PostManagementController.php.tpl
  • src/Module/Templates/DemoWeb/src/DTOs/CommentDTO.php.tpl
  • src/Module/Templates/DemoWeb/src/DTOs/PostDTO.php.tpl
  • src/Module/Templates/DemoWeb/src/DTOs/UserDTO.php.tpl
  • src/Module/Templates/DemoWeb/src/Services/CommentService.php.tpl
  • src/Module/Templates/DemoWeb/src/Services/PostService.php.tpl

@armanist armanist added enhancement New feature or request help wanted Extra attention is needed good first issue Good for newcomers labels Apr 1, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/Module/Templates/DemoWeb/src/DTOs/UserDTO.php.tpl (2)

105-138: Consider adding PHPDoc comments to getter methods for consistency.

The constructor and fromRequest methods have PHPDoc blocks, but the getter methods lack them. Adding @return annotations would improve consistency and IDE support.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Module/Templates/DemoWeb/src/DTOs/UserDTO.php.tpl` around lines 105 -
138, Add PHPDoc blocks with `@return` annotations to each getter (getEmail,
getPassword, getFirstname, getLastname, getRole, getUuid, getImage) to match the
existing constructor/fromRequest documentation style; for nullable return types
use `@return` string|null on getUuid and `@return` string on the others, placing the
docblock immediately above each method signature for IDE/typehint consistency.

93-103: Note: image field is not extracted from the request.

The fromRequest factory method doesn't extract or pass the image field from the request. This appears intentional since user signup likely doesn't include image upload, but worth confirming if image should ever be populated during user creation via this factory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Module/Templates/DemoWeb/src/DTOs/UserDTO.php.tpl` around lines 93 - 103,
The fromRequest factory currently omits the image field when constructing
UserDTO; update the fromRequest(Request $request, string $role, ?string $uuid =
null): self method to extract the image (e.g., $request->get('image') or
$request->file('image') as appropriate) and pass it into the UserDTO constructor
(or the Image-related parameter) so the DTO's image property is populated during
creation (adjust the constructor call/signature in UserDTO if needed).
src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl (1)

93-102: Consider decoupling DTO creation from raw request accessors for improved type safety.

While the Signup middleware validates the request before fromRequest() is called, the method still accepts untyped Request::get() values that return mixed. This creates a type contract mismatch: the constructor expects strict string parameters, but the factory receives mixed. For better type safety and clarity, consider accepting a validated array parameter instead, keeping transport parsing in the controller/middleware.

♻️ Suggested refactor
-    public static function fromRequest(Request $request, string $role, ?string $uuid = null): self
+    public static function fromValidated(array $data, string $role, ?string $uuid = null): self
     {
         return new self(
-            $request->get('email'),
-            $request->get('password'),
-            $request->get('firstname'),
-            $request->get('lastname'),
+            $data['email'],
+            $data['password'],
+            $data['firstname'],
+            $data['lastname'],
             $role,
             $uuid
         );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl` around lines 93 - 102,
The fromRequest factory currently reads untyped values via Request::get and
passes mixed into the UserDTO constructor; change fromRequest(Request $request,
string $role, ?string $uuid = null): self to accept a validated array (e.g.
array $validated) or a specific value-object so callers (Signup middleware /
controller) pass typed strings, then map those typed values into the
constructor; update the method signature and body of fromRequest and any call
sites in Signup middleware/controller to use the validated array keys (email,
password, firstname, lastname) instead of Request::get to restore strict string
typing for the constructor.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl`:
- Around line 93-102: The fromRequest factory currently reads untyped values via
Request::get and passes mixed into the UserDTO constructor; change
fromRequest(Request $request, string $role, ?string $uuid = null): self to
accept a validated array (e.g. array $validated) or a specific value-object so
callers (Signup middleware / controller) pass typed strings, then map those
typed values into the constructor; update the method signature and body of
fromRequest and any call sites in Signup middleware/controller to use the
validated array keys (email, password, firstname, lastname) instead of
Request::get to restore strict string typing for the constructor.

In `@src/Module/Templates/DemoWeb/src/DTOs/UserDTO.php.tpl`:
- Around line 105-138: Add PHPDoc blocks with `@return` annotations to each getter
(getEmail, getPassword, getFirstname, getLastname, getRole, getUuid, getImage)
to match the existing constructor/fromRequest documentation style; for nullable
return types use `@return` string|null on getUuid and `@return` string on the
others, placing the docblock immediately above each method signature for
IDE/typehint consistency.
- Around line 93-103: The fromRequest factory currently omits the image field
when constructing UserDTO; update the fromRequest(Request $request, string
$role, ?string $uuid = null): self method to extract the image (e.g.,
$request->get('image') or $request->file('image') as appropriate) and pass it
into the UserDTO constructor (or the Image-related parameter) so the DTO's image
property is populated during creation (adjust the constructor call/signature in
UserDTO if needed).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 457d7e3e-876b-4e59-a20e-a40a37187eba

📥 Commits

Reviewing files that changed from the base of the PR and between 2fdee83 and a569a1a.

📒 Files selected for processing (2)
  • src/Module/Templates/DemoApi/src/DTOs/UserDTO.php.tpl
  • src/Module/Templates/DemoWeb/src/DTOs/UserDTO.php.tpl

@armanist armanist merged commit 6e35193 into softberg:master Apr 2, 2026
7 checks passed
@armanist armanist deleted the 443-Implement-DTO-Pattern-for-Posts-Comments-and-Users-for-module-templates branch April 2, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement DTO Pattern for Posts, Comments and Users for module templates

2 participants