[{"data":1,"prerenderedAt":614},["ShallowReactive",2],{"page-\u002Fv1\u002Fcore\u002Fports-and-persistence":3},{"id":4,"title":5,"body":6,"description":607,"extension":608,"meta":609,"navigation":71,"path":610,"seo":611,"stem":612,"__hash__":613},"content\u002Fv1\u002Fcore\u002Fports-and-persistence.md","Ports and persistence",{"type":7,"value":8,"toc":598},"minimark",[9,13,25,30,222,226,234,245,251,262,266,270,306,326,329,333,393,399,403,502,506,517,539,543,589,594],[10,11,5],"h1",{"id":12},"ports-and-persistence",[14,15,16,20,21,24],"p",{},[17,18,19],"strong",{},"Purpose:"," persist and load Domain data ",[17,22,23],{},"without"," teaching Use Cases about an ORM.",[26,27,29],"h2",{"id":28},"component-map-one-feature","Component map (one feature)",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-mermaid shiki shiki-themes github-light github-dark","flowchart TB\n    subgraph UI[\"UI\"]\n        C[Controller]\n    end\n\n    subgraph Application[\"Application\"]\n        DTO[CreateXDTO]\n        UC[CreateXUseCase]\n    end\n\n    subgraph Domain[\"Domain\"]\n        ENT[XEntity]\n        IF[XRepositoryInterface]\n    end\n\n    subgraph Infrastructure[\"Infrastructure\"]\n        REPO[Persistence repository]\n        MODEL[ORM \u002F table record]\n        MAP[toEntity \u002F toRecord mappers]\n    end\n\n    DB[(Database)]\n\n    C --> DTO\n    C --> UC\n    UC --> ENT\n    UC --> IF\n    IF -.implements.-> REPO\n    REPO --> MAP\n    MAP --> MODEL\n    MODEL --> DB\n","mermaid","",[38,39,40,48,54,60,66,73,79,85,91,96,101,107,113,119,124,129,135,141,147,153,158,163,169,174,180,186,192,198,204,210,216],"code",{"__ignoreMap":36},[41,42,45],"span",{"class":43,"line":44},"line",1,[41,46,47],{},"flowchart TB\n",[41,49,51],{"class":43,"line":50},2,[41,52,53],{},"    subgraph UI[\"UI\"]\n",[41,55,57],{"class":43,"line":56},3,[41,58,59],{},"        C[Controller]\n",[41,61,63],{"class":43,"line":62},4,[41,64,65],{},"    end\n",[41,67,69],{"class":43,"line":68},5,[41,70,72],{"emptyLinePlaceholder":71},true,"\n",[41,74,76],{"class":43,"line":75},6,[41,77,78],{},"    subgraph Application[\"Application\"]\n",[41,80,82],{"class":43,"line":81},7,[41,83,84],{},"        DTO[CreateXDTO]\n",[41,86,88],{"class":43,"line":87},8,[41,89,90],{},"        UC[CreateXUseCase]\n",[41,92,94],{"class":43,"line":93},9,[41,95,65],{},[41,97,99],{"class":43,"line":98},10,[41,100,72],{"emptyLinePlaceholder":71},[41,102,104],{"class":43,"line":103},11,[41,105,106],{},"    subgraph Domain[\"Domain\"]\n",[41,108,110],{"class":43,"line":109},12,[41,111,112],{},"        ENT[XEntity]\n",[41,114,116],{"class":43,"line":115},13,[41,117,118],{},"        IF[XRepositoryInterface]\n",[41,120,122],{"class":43,"line":121},14,[41,123,65],{},[41,125,127],{"class":43,"line":126},15,[41,128,72],{"emptyLinePlaceholder":71},[41,130,132],{"class":43,"line":131},16,[41,133,134],{},"    subgraph Infrastructure[\"Infrastructure\"]\n",[41,136,138],{"class":43,"line":137},17,[41,139,140],{},"        REPO[Persistence repository]\n",[41,142,144],{"class":43,"line":143},18,[41,145,146],{},"        MODEL[ORM \u002F table record]\n",[41,148,150],{"class":43,"line":149},19,[41,151,152],{},"        MAP[toEntity \u002F toRecord mappers]\n",[41,154,156],{"class":43,"line":155},20,[41,157,65],{},[41,159,161],{"class":43,"line":160},21,[41,162,72],{"emptyLinePlaceholder":71},[41,164,166],{"class":43,"line":165},22,[41,167,168],{},"    DB[(Database)]\n",[41,170,172],{"class":43,"line":171},23,[41,173,72],{"emptyLinePlaceholder":71},[41,175,177],{"class":43,"line":176},24,[41,178,179],{},"    C --> DTO\n",[41,181,183],{"class":43,"line":182},25,[41,184,185],{},"    C --> UC\n",[41,187,189],{"class":43,"line":188},26,[41,190,191],{},"    UC --> ENT\n",[41,193,195],{"class":43,"line":194},27,[41,196,197],{},"    UC --> IF\n",[41,199,201],{"class":43,"line":200},28,[41,202,203],{},"    IF -.implements.-> REPO\n",[41,205,207],{"class":43,"line":206},29,[41,208,209],{},"    REPO --> MAP\n",[41,211,213],{"class":43,"line":212},30,[41,214,215],{},"    MAP --> MODEL\n",[41,217,219],{"class":43,"line":218},31,[41,220,221],{},"    MODEL --> DB\n",[26,223,225],{"id":224},"typical-folders","Typical folders",[31,227,232],{"className":228,"code":230,"language":231,"meta":36},[229],"language-text","Application\u002FDTO\u002F{Capability}\u002FCreateXDTO.php\nApplication\u002FUseCases\u002F{Capability}\u002FCreateXUseCase.php\nDomain\u002FEntities\u002FXEntity.php\nDomain\u002FPorts\u002F{Capability}\u002FXRepositoryInterface.php\nInfrastructure\u002FPersistence\u002F...\u002FXRecord.php          # host-specific\nInfrastructure\u002FPersistence\u002F...\u002FXRepository.php      # implements port\n","text",[38,233,230],{"__ignoreMap":36},[14,235,236,237,240,241,244],{},"Core requires: ",[17,238,239],{},"manual mapping"," and ",[17,242,243],{},"no ORM types in Domain\u002FApplication",".",[31,246,249],{"className":247,"code":248,"language":231,"meta":36},[229],"Domain\u002FPorts\u002F{Capability}\u002FXRepositoryInterface.php\nInfrastructure\u002FPersistence\u002F...\u002FXRepository.php   # implements port\nInfrastructure\u002FPersistence\u002F...\u002FXRecord.php       # host-specific row type\n",[38,250,248],{"__ignoreMap":36},[14,252,253,254,257,258,261],{},"Map ",[17,255,256],{},"Entity ↔ record"," inside the adapter. Application never sees the record type. Persistence access is ",[17,259,260],{},"repository-only","; Use Cases depend on the port and receive Domain Entities.",[263,264],"host-examples",{"topic":265},"persistence",[26,267,269],{"id":268},"port-sketch","Port sketch",[31,271,275],{"className":272,"code":273,"language":274,"meta":36,"style":36},"language-php shiki shiki-themes github-light github-dark","interface OrderRepositoryInterface\n{\n    public function save(OrderEntity $order): OrderEntity;\n\n    public function findByCode(string $code): ?OrderEntity;\n}\n","php",[38,276,277,282,287,292,296,301],{"__ignoreMap":36},[41,278,279],{"class":43,"line":44},[41,280,281],{},"interface OrderRepositoryInterface\n",[41,283,284],{"class":43,"line":50},[41,285,286],{},"{\n",[41,288,289],{"class":43,"line":56},[41,290,291],{},"    public function save(OrderEntity $order): OrderEntity;\n",[41,293,294],{"class":43,"line":62},[41,295,72],{"emptyLinePlaceholder":71},[41,297,298],{"class":43,"line":68},[41,299,300],{},"    public function findByCode(string $code): ?OrderEntity;\n",[41,302,303],{"class":43,"line":75},[41,304,305],{},"}\n",[31,307,309],{"className":272,"code":308,"language":274,"meta":36,"style":36},"public function __construct(\n    private OrderRepositoryInterface $orders,\n) {}\n",[38,310,311,316,321],{"__ignoreMap":36},[41,312,313],{"class":43,"line":44},[41,314,315],{},"public function __construct(\n",[41,317,318],{"class":43,"line":50},[41,319,320],{},"    private OrderRepositoryInterface $orders,\n",[41,322,323],{"class":43,"line":56},[41,324,325],{},") {}\n",[14,327,328],{},"The composition root binds the port to a concrete adapter.",[26,330,332],{"id":331},"mapping-rules","Mapping rules",[334,335,336,352],"table",{},[337,338,339],"thead",{},[340,341,342,346,349],"tr",{},[343,344,345],"th",{},"Direction",[343,347,348],{},"Where",[343,350,351],{},"Input → Output",[353,354,355,370,382],"tbody",{},[340,356,357,361,364],{},[358,359,360],"td",{},"Write",[358,362,363],{},"Repository adapter",[358,365,366,369],{},[38,367,368],{},"XEntity"," → record attributes → DB",[340,371,372,375,377],{},[358,373,374],{},"Read",[358,376,363],{},[358,378,379,380],{},"DB → record → ",[38,381,368],{},[340,383,384,387,390],{},[358,385,386],{},"HTTP out",[358,388,389],{},"UI",[358,391,392],{},"Entity fields → JSON \u002F resource",[14,394,395,398],{},[17,396,397],{},"Never"," return an ORM model (or query builder) from the repository to the Use Case.",[26,400,402],{"id":401},"sequence-create-then-return","Sequence — create then return",[31,404,406],{"className":33,"code":405,"language":35,"meta":36,"style":36},"sequenceDiagram\n    autonumber\n    participant C as Controller\n    participant UC as CreateXUseCase\n    participant IF as XRepositoryInterface\n    participant R as Persistence repository\n    participant DB as DB\n\n    C->>UC: __invoke(CreateXDTO)\n    UC->>UC: new XEntity(...from DTO)\n    UC->>IF: save(entity)\n    IF->>R: save(entity)\n    R->>R: toRecord(entity)\n    R->>DB: INSERT\n    DB-->>R: id\n    R->>R: toEntity(record)\n    R-->>IF: XEntity\n    IF-->>UC: XEntity\n    UC-->>C: XEntity\n",[38,407,408,413,418,423,428,433,438,443,447,452,457,462,467,472,477,482,487,492,497],{"__ignoreMap":36},[41,409,410],{"class":43,"line":44},[41,411,412],{},"sequenceDiagram\n",[41,414,415],{"class":43,"line":50},[41,416,417],{},"    autonumber\n",[41,419,420],{"class":43,"line":56},[41,421,422],{},"    participant C as Controller\n",[41,424,425],{"class":43,"line":62},[41,426,427],{},"    participant UC as CreateXUseCase\n",[41,429,430],{"class":43,"line":68},[41,431,432],{},"    participant IF as XRepositoryInterface\n",[41,434,435],{"class":43,"line":75},[41,436,437],{},"    participant R as Persistence repository\n",[41,439,440],{"class":43,"line":81},[41,441,442],{},"    participant DB as DB\n",[41,444,445],{"class":43,"line":87},[41,446,72],{"emptyLinePlaceholder":71},[41,448,449],{"class":43,"line":93},[41,450,451],{},"    C->>UC: __invoke(CreateXDTO)\n",[41,453,454],{"class":43,"line":98},[41,455,456],{},"    UC->>UC: new XEntity(...from DTO)\n",[41,458,459],{"class":43,"line":103},[41,460,461],{},"    UC->>IF: save(entity)\n",[41,463,464],{"class":43,"line":109},[41,465,466],{},"    IF->>R: save(entity)\n",[41,468,469],{"class":43,"line":115},[41,470,471],{},"    R->>R: toRecord(entity)\n",[41,473,474],{"class":43,"line":121},[41,475,476],{},"    R->>DB: INSERT\n",[41,478,479],{"class":43,"line":126},[41,480,481],{},"    DB-->>R: id\n",[41,483,484],{"class":43,"line":131},[41,485,486],{},"    R->>R: toEntity(record)\n",[41,488,489],{"class":43,"line":137},[41,490,491],{},"    R-->>IF: XEntity\n",[41,493,494],{"class":43,"line":143},[41,495,496],{},"    IF-->>UC: XEntity\n",[41,498,499],{"class":43,"line":149},[41,500,501],{},"    UC-->>C: XEntity\n",[26,503,505],{"id":504},"nested-helpers-still-use-ports","Nested helpers still use ports",[14,507,508,509,512,513,516],{},"When ",[38,510,511],{},"PlaceOrderUseCase"," delegates to ",[38,514,515],{},"PrepareOrderLines",":",[518,519,520,524,527],"ul",{},[521,522,523],"li",{},"Helpers use the same Domain ports.",[521,525,526],{},"Helpers must not become UI entry points.",[521,528,529,530,533,534,244],{},"Peer calls (Warehouse, Directory) are ",[17,531,532],{},"ACL ports",", not this module’s repositories — see ",[535,536,538],"a",{"href":537},"\u002Fv1\u002Fcore\u002Fcross-module-acl","cross-module ACL",[26,540,542],{"id":541},"anti-patterns","Anti-patterns",[334,544,545,555],{},[337,546,547],{},[340,548,549,552],{},[343,550,551],{},"Anti-pattern",[343,553,554],{},"Fix",[353,556,557,565,573,581],{},[340,558,559,562],{},[358,560,561],{},"Entity extends ORM model",[358,563,564],{},"Entity = plain PHP; record only in Infrastructure",[340,566,567,570],{},[358,568,569],{},"Repository returns paginator of ORM models",[358,571,572],{},"Map items to Entities (or a Domain page DTO)",[340,574,575,578],{},[358,576,577],{},"Use Case builds SQL strings",[358,579,580],{},"Belong in the repository adapter",[340,582,583,586],{},[358,584,585],{},"Sharing ORM relations across modules",[358,587,588],{},"Use ACL \u002F Events; no cross-module business joins via foreign models",[14,590,591,592,244],{},"Next: ",[535,593,538],{"href":537},[595,596,597],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":36,"searchDepth":50,"depth":50,"links":599},[600,601,602,603,604,605,606],{"id":28,"depth":50,"text":29},{"id":224,"depth":50,"text":225},{"id":268,"depth":50,"text":269},{"id":331,"depth":50,"text":332},{"id":401,"depth":50,"text":402},{"id":504,"depth":50,"text":505},{"id":541,"depth":50,"text":542},"Repository ports, manual Entity mapping, and keeping ORM out of Domain and Application.","md",{},"\u002Fv1\u002Fcore\u002Fports-and-persistence",{"title":5,"description":607},"v1\u002Fcore\u002Fports-and-persistence","vsd_siaB2IgjaCOoaTCki-F9Vj4ZLSrnpyy14LToP5k",1786241411786]