@@ -659,9 +659,18 @@ Projecting Results Into DTOs
659659----------------------------
660660
661661In addition to fetching results as Entity objects or arrays, you can project
662- query results directly into Data Transfer Objects (DTOs). DTOs are useful when
663- you need a memory-efficient, read-only representation of your data, or when you
664- want to decouple your data layer from the ORM's Entity objects.
662+ query results directly into Data Transfer Objects (DTOs). DTOs offer several
663+ advantages:
664+
665+ - **Memory efficiency ** - DTOs consume approximately 3x less memory than Entity
666+ objects, making them ideal for large result sets.
667+ - **Type safety ** - DTOs provide strong typing and IDE autocompletion support,
668+ unlike plain arrays.
669+ - **Decoupled serialization ** - DTOs let you separate your API response
670+ structure from your database schema, making it easier to version APIs or
671+ expose only specific fields.
672+ - **Read-only data ** - Using ``readonly `` classes ensures data integrity and
673+ makes your intent clear.
665674
666675The ``projectAs() `` method allows you to specify a DTO class that results will
667676be hydrated into::
@@ -683,9 +692,6 @@ be hydrated into::
683692 ->projectAs(ArticleDto::class)
684693 ->toArray();
685694
686- DTOs typically consume about 3x less memory than Entity objects, making them
687- ideal for read-heavy operations or when processing large result sets.
688-
689695DTO Creation Methods
690696^^^^^^^^^^^^^^^^^^^^
691697
@@ -771,6 +777,51 @@ attribute to specify the type of elements in array properties::
771777 ->projectAs(ArticleDto::class)
772778 ->toArray();
773779
780+ Using DTOs for API Responses
781+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
782+
783+ DTOs are particularly useful for building API responses where you want to
784+ control the output structure independently from your database schema. You can
785+ define a DTO that represents your API contract and include custom serialization
786+ logic::
787+
788+ readonly class ArticleApiResponse
789+ {
790+ public function __construct(
791+ public int $id,
792+ public string $title,
793+ public string $slug,
794+ public string $authorName,
795+ public string $publishedAt,
796+ ) {
797+ }
798+
799+ public static function createFromArray(
800+ array $data,
801+ bool $ignoreMissing = false
802+ ): self {
803+ return new self(
804+ id: $data['id'],
805+ title: $data['title'],
806+ slug: Inflector::slug($data['title']),
807+ authorName: $data['author']['name'] ?? 'Unknown',
808+ publishedAt: $data['created']->format('c'),
809+ );
810+ }
811+ }
812+
813+ // In your controller
814+ $articles = $this->Articles->find()
815+ ->contain(['Authors'])
816+ ->projectAs(ArticleApiResponse::class)
817+ ->toArray();
818+
819+ return $this->response->withType('application/json')
820+ ->withStringBody(json_encode(['articles' => $articles]));
821+
822+ This approach keeps your API response format decoupled from your database
823+ schema, making it easier to evolve your API without changing your data model.
824+
774825.. note ::
775826
776827 DTO projection is applied as the final formatting step, after all other
0 commit comments