@@ -653,6 +653,134 @@ After executing those lines, your result should look similar to this::
653653 ...
654654 ]
655655
656+ .. _dto-projection :
657+
658+ Projecting Results Into DTOs
659+ ----------------------------
660+
661+ In 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.
665+
666+ The ``projectAs() `` method allows you to specify a DTO class that results will
667+ be hydrated into::
668+
669+ // Define a DTO class
670+ readonly class ArticleDto
671+ {
672+ public function __construct(
673+ public int $id,
674+ public string $title,
675+ public ?string $body = null,
676+ ) {
677+ }
678+ }
679+
680+ // Use projectAs() to hydrate results into DTOs
681+ $articles = $articlesTable->find()
682+ ->select(['id', 'title', 'body'])
683+ ->projectAs(ArticleDto::class)
684+ ->toArray();
685+
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+
689+ DTO Creation Methods
690+ ^^^^^^^^^^^^^^^^^^^^
691+
692+ CakePHP supports two approaches for creating DTOs:
693+
694+ **Reflection-based constructor mapping ** - CakePHP will use reflection to map
695+ database columns to constructor parameters::
696+
697+ readonly class ArticleDto
698+ {
699+ public function __construct(
700+ public int $id,
701+ public string $title,
702+ public ?AuthorDto $author = null,
703+ ) {
704+ }
705+ }
706+
707+ **Factory method pattern ** - If your DTO class has a ``createFromArray() ``
708+ static method, CakePHP will use that instead::
709+
710+ class ArticleDto
711+ {
712+ public int $id;
713+ public string $title;
714+
715+ public static function createFromArray(
716+ array $data,
717+ bool $ignoreMissing = false
718+ ): self {
719+ $dto = new self();
720+ $dto->id = $data['id'];
721+ $dto->title = $data['title'];
722+
723+ return $dto;
724+ }
725+ }
726+
727+ The factory method approach is approximately 2.5x faster than reflection-based
728+ hydration.
729+
730+ Nested Association DTOs
731+ ^^^^^^^^^^^^^^^^^^^^^^^
732+
733+ You can project associated data into nested DTOs. Use the ``#[CollectionOf] ``
734+ attribute to specify the type of elements in array properties::
735+
736+ use Cake\ORM\Attribute\CollectionOf;
737+
738+ readonly class ArticleDto
739+ {
740+ public function __construct(
741+ public int $id,
742+ public string $title,
743+ public ?AuthorDto $author = null,
744+ #[CollectionOf(CommentDto::class)]
745+ public array $comments = [],
746+ ) {
747+ }
748+ }
749+
750+ readonly class AuthorDto
751+ {
752+ public function __construct(
753+ public int $id,
754+ public string $name,
755+ ) {
756+ }
757+ }
758+
759+ readonly class CommentDto
760+ {
761+ public function __construct(
762+ public int $id,
763+ public string $body,
764+ ) {
765+ }
766+ }
767+
768+ // Fetch articles with associations projected into DTOs
769+ $articles = $articlesTable->find()
770+ ->contain(['Authors', 'Comments'])
771+ ->projectAs(ArticleDto::class)
772+ ->toArray();
773+
774+ .. note ::
775+
776+ DTO projection is applied as the final formatting step, after all other
777+ formatters and behaviors have processed the results. This ensures
778+ compatibility with existing behavior formatters while still providing the
779+ benefits of DTOs.
780+
781+ .. versionadded :: 5.3.0
782+ The ``projectAs() `` method and ``#[CollectionOf] `` attribute were added.
783+
656784.. _format-results :
657785
658786Adding Calculated Fields
0 commit comments