[{"data":1,"prerenderedAt":667},["ShallowReactive",2],{"page-\u002Fv1\u002Fadapters\u002Flaravel\u002Fpersistence-eloquent":3},{"id":4,"title":5,"body":6,"description":660,"extension":661,"meta":662,"navigation":143,"path":663,"seo":664,"stem":665,"__hash__":666},"content\u002Fv1\u002Fadapters\u002Flaravel\u002Fpersistence-eloquent.md","Laravel persistence (Eloquent)",{"type":7,"value":8,"toc":649},"minimark",[9,14,28,33,43,46,52,56,119,123,191,195,383,398,402,411,440,491,495,533,537,544,555,559,613,617,637,645],[10,11,13],"h1",{"id":12},"persistence-eloquent","Persistence (Eloquent)",[15,16,17,18,22,23,27],"p",{},"Eloquent is the default Laravel persistence driver. It belongs ",[19,20,21],"strong",{},"only"," in Infrastructure repositories (and related models\u002Ffactories). Application and Domain speak Entities. Examples use greenfield ",[24,25,26],"code",{},"Ordering",".",[29,30,32],"h2",{"id":31},"folders","Folders",[34,35,41],"pre",{"className":36,"code":38,"language":39,"meta":40},[37],"language-text","Ordering\u002FInfrastructure\u002FPersistence\u002FEloquent\u002F\n├── Models\u002FOrderModel.php\n├── Repositories\u002FOrderRepository.php\n├── Factories\u002F…          # optional\n└── Migrations\u002F…         # see Migrations page\n","text","",[24,42,38],{"__ignoreMap":40},[15,44,45],{},"Domain port:",[34,47,50],{"className":48,"code":49,"language":39,"meta":40},[37],"Ordering\u002FDomain\u002FPorts\u002FOrder\u002FOrderRepositoryInterface.php\n",[24,51,49],{"__ignoreMap":40},[29,53,55],{"id":54},"layer-responsibilities","Layer responsibilities",[57,58,59,75],"table",{},[60,61,62],"thead",{},[63,64,65,69,72],"tr",{},[66,67,68],"th",{},"Component",[66,70,71],{},"Layer",[66,73,74],{},"Purpose",[76,77,78,98,109],"tbody",{},[63,79,80,84,87],{},[81,82,83],"td",{},"Port (interface)",[81,85,86],{},"Domain",[81,88,89,90,93,94,97],{},"Contract (",[24,91,92],{},"findById",", ",[24,95,96],{},"save",", …)",[63,99,100,103,106],{},[81,101,102],{},"Implementation",[81,104,105],{},"Infrastructure",[81,107,108],{},"Eloquent \u002F SQL",[63,110,111,114,116],{},[81,112,113],{},"Entity mapping",[81,115,105],{},[81,117,118],{},"Model ↔ Entity",[29,120,122],{"id":121},"domain-port","Domain port",[34,124,128],{"className":125,"code":126,"language":127,"meta":40,"style":40},"language-php shiki shiki-themes github-light github-dark","namespace App\\Modules\\Ordering\\Domain\\Ports\\Order;\n\nuse App\\Modules\\Ordering\\Domain\\Entities\\OrderEntity;\n\ninterface OrderRepositoryInterface\n{\n    public function findById(int $id): ?OrderEntity;\n\n    public function save(OrderEntity $entity): void;\n}\n","php",[24,129,130,138,145,151,156,162,168,174,179,185],{"__ignoreMap":40},[131,132,135],"span",{"class":133,"line":134},"line",1,[131,136,137],{},"namespace App\\Modules\\Ordering\\Domain\\Ports\\Order;\n",[131,139,141],{"class":133,"line":140},2,[131,142,144],{"emptyLinePlaceholder":143},true,"\n",[131,146,148],{"class":133,"line":147},3,[131,149,150],{},"use App\\Modules\\Ordering\\Domain\\Entities\\OrderEntity;\n",[131,152,154],{"class":133,"line":153},4,[131,155,144],{"emptyLinePlaceholder":143},[131,157,159],{"class":133,"line":158},5,[131,160,161],{},"interface OrderRepositoryInterface\n",[131,163,165],{"class":133,"line":164},6,[131,166,167],{},"{\n",[131,169,171],{"class":133,"line":170},7,[131,172,173],{},"    public function findById(int $id): ?OrderEntity;\n",[131,175,177],{"class":133,"line":176},8,[131,178,144],{"emptyLinePlaceholder":143},[131,180,182],{"class":133,"line":181},9,[131,183,184],{},"    public function save(OrderEntity $entity): void;\n",[131,186,188],{"class":133,"line":187},10,[131,189,190],{},"}\n",[29,192,194],{"id":193},"infrastructure-adapter","Infrastructure adapter",[34,196,198],{"className":125,"code":197,"language":127,"meta":40,"style":40},"namespace App\\Modules\\Ordering\\Infrastructure\\Persistence\\Eloquent\\Repositories;\n\nuse App\\Modules\\Ordering\\Domain\\Entities\\OrderEntity;\nuse App\\Modules\\Ordering\\Domain\\Ports\\Order\\OrderRepositoryInterface;\nuse App\\Modules\\Ordering\\Infrastructure\\Persistence\\Eloquent\\Models\\OrderModel;\n\nclass OrderRepository implements OrderRepositoryInterface\n{\n    public function findById(int $id): ?OrderEntity\n    {\n        $model = OrderModel::find($id);\n\n        if (! $model) {\n            return null;\n        }\n\n        return new OrderEntity(\n            id: $model->getId(),\n            status: $model->getStatus(),\n            amount: $model->getAmount(),\n        );\n    }\n\n    public function save(OrderEntity $entity): void\n    {\n        OrderModel::updateOrCreate(\n            ['id' => $entity->id],\n            [\n                'status' => $entity->status,\n                'amount' => $entity->amount,\n            ],\n        );\n    }\n}\n",[24,199,200,205,209,213,218,223,227,232,236,241,246,252,257,263,269,275,280,286,292,298,304,310,316,321,327,332,338,344,350,356,362,368,373,378],{"__ignoreMap":40},[131,201,202],{"class":133,"line":134},[131,203,204],{},"namespace App\\Modules\\Ordering\\Infrastructure\\Persistence\\Eloquent\\Repositories;\n",[131,206,207],{"class":133,"line":140},[131,208,144],{"emptyLinePlaceholder":143},[131,210,211],{"class":133,"line":147},[131,212,150],{},[131,214,215],{"class":133,"line":153},[131,216,217],{},"use App\\Modules\\Ordering\\Domain\\Ports\\Order\\OrderRepositoryInterface;\n",[131,219,220],{"class":133,"line":158},[131,221,222],{},"use App\\Modules\\Ordering\\Infrastructure\\Persistence\\Eloquent\\Models\\OrderModel;\n",[131,224,225],{"class":133,"line":164},[131,226,144],{"emptyLinePlaceholder":143},[131,228,229],{"class":133,"line":170},[131,230,231],{},"class OrderRepository implements OrderRepositoryInterface\n",[131,233,234],{"class":133,"line":176},[131,235,167],{},[131,237,238],{"class":133,"line":181},[131,239,240],{},"    public function findById(int $id): ?OrderEntity\n",[131,242,243],{"class":133,"line":187},[131,244,245],{},"    {\n",[131,247,249],{"class":133,"line":248},11,[131,250,251],{},"        $model = OrderModel::find($id);\n",[131,253,255],{"class":133,"line":254},12,[131,256,144],{"emptyLinePlaceholder":143},[131,258,260],{"class":133,"line":259},13,[131,261,262],{},"        if (! $model) {\n",[131,264,266],{"class":133,"line":265},14,[131,267,268],{},"            return null;\n",[131,270,272],{"class":133,"line":271},15,[131,273,274],{},"        }\n",[131,276,278],{"class":133,"line":277},16,[131,279,144],{"emptyLinePlaceholder":143},[131,281,283],{"class":133,"line":282},17,[131,284,285],{},"        return new OrderEntity(\n",[131,287,289],{"class":133,"line":288},18,[131,290,291],{},"            id: $model->getId(),\n",[131,293,295],{"class":133,"line":294},19,[131,296,297],{},"            status: $model->getStatus(),\n",[131,299,301],{"class":133,"line":300},20,[131,302,303],{},"            amount: $model->getAmount(),\n",[131,305,307],{"class":133,"line":306},21,[131,308,309],{},"        );\n",[131,311,313],{"class":133,"line":312},22,[131,314,315],{},"    }\n",[131,317,319],{"class":133,"line":318},23,[131,320,144],{"emptyLinePlaceholder":143},[131,322,324],{"class":133,"line":323},24,[131,325,326],{},"    public function save(OrderEntity $entity): void\n",[131,328,330],{"class":133,"line":329},25,[131,331,245],{},[131,333,335],{"class":133,"line":334},26,[131,336,337],{},"        OrderModel::updateOrCreate(\n",[131,339,341],{"class":133,"line":340},27,[131,342,343],{},"            ['id' => $entity->id],\n",[131,345,347],{"class":133,"line":346},28,[131,348,349],{},"            [\n",[131,351,353],{"class":133,"line":352},29,[131,354,355],{},"                'status' => $entity->status,\n",[131,357,359],{"class":133,"line":358},30,[131,360,361],{},"                'amount' => $entity->amount,\n",[131,363,365],{"class":133,"line":364},31,[131,366,367],{},"            ],\n",[131,369,371],{"class":133,"line":370},32,[131,372,309],{},[131,374,376],{"class":133,"line":375},33,[131,377,315],{},[131,379,381],{"class":133,"line":380},34,[131,382,190],{},[15,384,385,386,389,390,393,394,397],{},"Read models via ",[19,387,388],{},"getters","; write via ",[19,391,392],{},"setters"," \u002F explicit attribute arrays. Direct ",[24,395,396],{},"$model->attribute"," access on Eloquent models is forbidden in host standards that enforce encapsulation.",[29,399,401],{"id":400},"eloquent-access-is-repository-only","Eloquent access is repository-only",[15,403,404,405,407,408,27],{},"The Repository (and its base class) is the ",[19,406,21],{}," place allowed to touch Eloquent models directly. Use Cases, Domain Services, and other Infrastructure services must obtain data ",[19,409,410],{},"through a Repository",[34,412,414],{"className":125,"code":413,"language":127,"meta":40,"style":40},"\u002F\u002F ❌ WRONG — Infrastructure helper reaching for the model statically\n$model = OrderModel::find($orderId);\n\n\u002F\u002F ✅ CORRECT — go through the Repository (Domain Port from Application)\n$entity = $this->orders->findById($orderId);\n",[24,415,416,421,426,430,435],{"__ignoreMap":40},[131,417,418],{"class":133,"line":134},[131,419,420],{},"\u002F\u002F ❌ WRONG — Infrastructure helper reaching for the model statically\n",[131,422,423],{"class":133,"line":140},[131,424,425],{},"$model = OrderModel::find($orderId);\n",[131,427,428],{"class":133,"line":147},[131,429,144],{"emptyLinePlaceholder":143},[131,431,432],{"class":133,"line":153},[131,433,434],{},"\u002F\u002F ✅ CORRECT — go through the Repository (Domain Port from Application)\n",[131,436,437],{"class":133,"line":158},[131,438,439],{},"$entity = $this->orders->findById($orderId);\n",[441,442,443,457,472],"ul",{},[444,445,446,449,450,453,454,27],"li",{},[19,447,448],{},"Domain \u002F Application"," always depend on the ",[19,451,452],{},"Port"," and receive ",[19,455,456],{},"Entities",[444,458,459,460,463,464,467,468,471],{},"Infrastructure services that genuinely need the persistence model (search index, reporting) may depend on the ",[19,461,462],{},"concrete Repository"," and a dedicated ",[24,465,466],{},"findModel()","-style method kept ",[19,469,470],{},"off"," the Domain Port.",[444,473,474,475,93,478,93,481,93,484,93,487,490],{},"Forbidden outside the Repository: ",[24,476,477],{},"Model::find()",[24,479,480],{},"Model::where()",[24,482,483],{},"Model::create()",[24,485,486],{},"Model::query()",[24,488,489],{},"Model::updateOrCreate()",", and similar.",[29,492,494],{"id":493},"why-manual-mapping-is-required","Why manual mapping is required",[57,496,497,507],{},[60,498,499],{},[63,500,501,504],{},[66,502,503],{},"Reason",[66,505,506],{},"Effect",[76,508,509,517,525],{},[63,510,511,514],{},[81,512,513],{},"Schema decoupling",[81,515,516],{},"Rename a column → touch Repository only",[63,518,519,522],{},[81,520,521],{},"Strict business rules",[81,523,524],{},"Use Cases operate on Entities, not rows",[63,526,527,530],{},[81,528,529],{},"Encapsulation",[81,531,532],{},"Sensitive columns never leak via Entity",[29,534,536],{"id":535},"model-pattern-laravel-host","Model pattern (Laravel host)",[15,538,539,540,543],{},"Eloquent models live under ",[24,541,542],{},"Infrastructure\u002FPersistence\u002FEloquent\u002FModels\u002F",". They are not Domain Entities. Prefer:",[441,545,546,549,552],{},[444,547,548],{},"Getters \u002F setters for every persisted field used by mapping",[444,550,551],{},"No business policy methods that belong on Entities",[444,553,554],{},"Factories colocated for tests",[29,556,558],{"id":557},"anti-patterns","Anti-patterns",[57,560,561,571],{},[60,562,563],{},[63,564,565,568],{},[66,566,567],{},"Anti-pattern",[66,569,570],{},"Fix",[76,572,573,583,597,605],{},[63,574,575,580],{},[81,576,577],{},[24,578,579],{},"OrderEntity extends Model",[81,581,582],{},"Plain Entity + Infrastructure Model",[63,584,585,591],{},[81,586,587,588],{},"Use Case calls ",[24,589,590],{},"OrderModel::query()",[81,592,593,594],{},"Inject ",[24,595,596],{},"OrderRepositoryInterface",[63,598,599,602],{},[81,600,601],{},"Repository returns paginator of Models",[81,603,604],{},"Map items to Entities",[63,606,607,610],{},[81,608,609],{},"Cross-module Eloquent relations for business joins",[81,611,612],{},"ACL \u002F Events",[29,614,616],{"id":615},"golden-line","Golden line",[618,619,620],"blockquote",{},[15,621,622,625,626,625,629,625,631,625,634],{},[19,623,624],{},"UI"," → ",[19,627,628],{},"UseCase",[19,630,452],{},[19,632,633],{},"Eloquent Repository",[19,635,636],{},"Domain Entity",[15,638,639,640,27],{},"Core reference: ",[641,642,644],"a",{"href":643},"\u002Fv1\u002Fcore\u002Fports-and-persistence","Ports & persistence",[646,647,648],"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":40,"searchDepth":140,"depth":140,"links":650},[651,652,653,654,655,656,657,658,659],{"id":31,"depth":140,"text":32},{"id":54,"depth":140,"text":55},{"id":121,"depth":140,"text":122},{"id":193,"depth":140,"text":194},{"id":400,"depth":140,"text":401},{"id":493,"depth":140,"text":494},{"id":535,"depth":140,"text":536},{"id":557,"depth":140,"text":558},{"id":615,"depth":140,"text":616},"Eloquent stays in Infrastructure — repositories map OrderEntity to OrderModel manually; repository-only Eloquent access.","md",{},"\u002Fv1\u002Fadapters\u002Flaravel\u002Fpersistence-eloquent",{"title":5,"description":660},"v1\u002Fadapters\u002Flaravel\u002Fpersistence-eloquent","GJuxwGML3xEd37c_Mp1XKl-4JRkLYE1q0fFR1lUF9Ao",1786241413716]