[{"data":1,"prerenderedAt":1789},["ShallowReactive",2],{"\u002Farticles\u002Flaravel-action-pattern":3},{"id":4,"title":5,"author":6,"body":7,"category":1774,"date":1775,"description":1776,"extension":1777,"lastModified":6,"meta":1778,"navigation":78,"path":1779,"seo":1780,"status":1781,"stem":1782,"tags":1783,"__hash__":1788},"articles\u002Farticles\u002Flaravel-action-pattern.md","Laravel Action Pattern",null,{"type":8,"value":9,"toc":1764},"minimark",[10,19,31,36,46,304,307,311,654,657,702,706,717,791,808,828,832,839,956,959,963,966,1124,1127,1131,1134,1361,1373,1377,1380,1696,1700,1738,1743,1760],[11,12,13,14,18],"p",{},"Your ",[15,16,17],"code",{},"ArticleController::store()"," validates input, generates a slug, creates the record, and syncs tags. The same logic shows up again in a queued import job, then in an Artisan command. When the rules change, you hunt down all three.",[11,20,21,22,26,27,30],{},"The fix: extract each operation into an ",[23,24,25],"strong",{},"Action class"," — a small object with one ",[15,28,29],{},"handle()"," method, resolved from the container and callable from controllers, jobs, and commands.",[32,33,35],"h2",{"id":34},"what-is-an-action","What Is an Action?",[11,37,38,39,42,43,45],{},"A plain PHP object that performs ",[23,40,41],{},"one business operation",". No base class, no interface. One ",[15,44,29],{}," entry point.",[47,48,54],"pre",{"className":49,"code":50,"filename":51,"language":52,"meta":53,"style":53},"language-php shiki shiki-themes github-dark-high-contrast","namespace App\\Actions;\n\nuse App\\Models\\Article;\nuse Illuminate\\Validation\\ValidationException;\n\nfinal readonly class PublishArticle\n{\n    public function handle(Article $article): void\n    {\n        if ($article->status !== 'draft') {\n            throw ValidationException::withMessages([\n                'status' => [__('Only draft articles can be published.')],\n            ]);\n        }\n\n        $article->update([\n            'status' => 'published',\n            'published_at' => now(),\n        ]);\n    }\n}\n","app\u002FActions\u002FPublishArticle.php","php","",[15,55,56,73,80,92,102,107,122,128,156,162,187,205,228,234,240,245,258,272,286,292,298],{"__ignoreMap":53},[57,58,61,65,69],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"sWyjQ","namespace",[57,66,68],{"class":67},"s_sBn"," App\\Actions",[57,70,72],{"class":71},"sMAXC",";\n",[57,74,76],{"class":59,"line":75},2,[57,77,79],{"emptyLinePlaceholder":78},true,"\n",[57,81,83,86,90],{"class":59,"line":82},3,[57,84,85],{"class":63},"use",[57,87,89],{"class":88},"sCcAr"," App\\Models\\Article",[57,91,72],{"class":71},[57,93,95,97,100],{"class":59,"line":94},4,[57,96,85],{"class":63},[57,98,99],{"class":88}," Illuminate\\Validation\\ValidationException",[57,101,72],{"class":71},[57,103,105],{"class":59,"line":104},5,[57,106,79],{"emptyLinePlaceholder":78},[57,108,110,113,116,119],{"class":59,"line":109},6,[57,111,112],{"class":63},"final",[57,114,115],{"class":63}," readonly",[57,117,118],{"class":63}," class",[57,120,121],{"class":67}," PublishArticle\n",[57,123,125],{"class":59,"line":124},7,[57,126,127],{"class":71},"{\n",[57,129,131,134,137,141,144,147,150,153],{"class":59,"line":130},8,[57,132,133],{"class":63},"    public",[57,135,136],{"class":63}," function",[57,138,140],{"class":139},"sj_SG"," handle",[57,142,143],{"class":71},"(",[57,145,146],{"class":88},"Article",[57,148,149],{"class":71}," $article)",[57,151,152],{"class":63},":",[57,154,155],{"class":63}," void\n",[57,157,159],{"class":59,"line":158},9,[57,160,161],{"class":71},"    {\n",[57,163,165,168,171,174,177,180,184],{"class":59,"line":164},10,[57,166,167],{"class":63},"        if",[57,169,170],{"class":71}," ($article",[57,172,173],{"class":63},"->",[57,175,176],{"class":71},"status ",[57,178,179],{"class":63},"!==",[57,181,183],{"class":182},"sTRMh"," 'draft'",[57,185,186],{"class":71},") {\n",[57,188,190,193,196,199,202],{"class":59,"line":189},11,[57,191,192],{"class":63},"            throw",[57,194,195],{"class":88}," ValidationException",[57,197,198],{"class":63},"::",[57,200,201],{"class":139},"withMessages",[57,203,204],{"class":71},"([\n",[57,206,208,211,214,217,220,222,225],{"class":59,"line":207},12,[57,209,210],{"class":182},"                'status'",[57,212,213],{"class":63}," =>",[57,215,216],{"class":71}," [",[57,218,219],{"class":139},"__",[57,221,143],{"class":71},[57,223,224],{"class":182},"'Only draft articles can be published.'",[57,226,227],{"class":71},")],\n",[57,229,231],{"class":59,"line":230},13,[57,232,233],{"class":71},"            ]);\n",[57,235,237],{"class":59,"line":236},14,[57,238,239],{"class":71},"        }\n",[57,241,243],{"class":59,"line":242},15,[57,244,79],{"emptyLinePlaceholder":78},[57,246,248,251,253,256],{"class":59,"line":247},16,[57,249,250],{"class":71},"        $article",[57,252,173],{"class":63},[57,254,255],{"class":139},"update",[57,257,204],{"class":71},[57,259,261,264,266,269],{"class":59,"line":260},17,[57,262,263],{"class":182},"            'status'",[57,265,213],{"class":63},[57,267,268],{"class":182}," 'published'",[57,270,271],{"class":71},",\n",[57,273,275,278,280,283],{"class":59,"line":274},18,[57,276,277],{"class":182},"            'published_at'",[57,279,213],{"class":63},[57,281,282],{"class":139}," now",[57,284,285],{"class":71},"(),\n",[57,287,289],{"class":59,"line":288},19,[57,290,291],{"class":71},"        ]);\n",[57,293,295],{"class":59,"line":294},20,[57,296,297],{"class":71},"    }\n",[57,299,301],{"class":59,"line":300},21,[57,302,303],{"class":71},"}\n",[11,305,306],{},"That is the whole pattern. Laravel resolves it from the container automatically — no service provider registration, no binding.",[32,308,310],{"id":309},"anatomy-of-an-action","Anatomy of an Action",[47,312,315],{"className":49,"code":313,"filename":314,"language":52,"meta":53,"style":53},"namespace App\\Actions;\n\nuse App\\Models\\Article;\nuse App\\Models\\User;\nuse Illuminate\\Support\\Facades\\DB;\nuse Illuminate\\Support\\Str;\n\nfinal readonly class CreateArticle\n{\n    \u002F**\n     * @param  array{title: string, body: string, tags?: string[]}  $data\n     *\u002F\n    public function handle(User $user, array $data): Article\n    {\n        return DB::transaction(function () use ($user, $data): Article {\n            $article = $user->articles()->create([\n                'title' => $data['title'],\n                'slug' => Str::slug($data['title']),\n                'body' => $data['body'],\n                'status' => 'draft',\n            ]);\n\n            if (! empty($data['tags'])) {\n                $article->syncTags($data['tags']);\n            }\n\n            return $article;\n        });\n    }\n}\n","app\u002FActions\u002FCreateArticle.php",[15,316,317,325,329,337,346,355,364,368,379,383,389,409,414,441,445,479,505,521,544,558,568,572,577,600,618,624,629,638,644,649],{"__ignoreMap":53},[57,318,319,321,323],{"class":59,"line":60},[57,320,64],{"class":63},[57,322,68],{"class":67},[57,324,72],{"class":71},[57,326,327],{"class":59,"line":75},[57,328,79],{"emptyLinePlaceholder":78},[57,330,331,333,335],{"class":59,"line":82},[57,332,85],{"class":63},[57,334,89],{"class":88},[57,336,72],{"class":71},[57,338,339,341,344],{"class":59,"line":94},[57,340,85],{"class":63},[57,342,343],{"class":88}," App\\Models\\User",[57,345,72],{"class":71},[57,347,348,350,353],{"class":59,"line":104},[57,349,85],{"class":63},[57,351,352],{"class":88}," Illuminate\\Support\\Facades\\DB",[57,354,72],{"class":71},[57,356,357,359,362],{"class":59,"line":109},[57,358,85],{"class":63},[57,360,361],{"class":88}," Illuminate\\Support\\Str",[57,363,72],{"class":71},[57,365,366],{"class":59,"line":124},[57,367,79],{"emptyLinePlaceholder":78},[57,369,370,372,374,376],{"class":59,"line":130},[57,371,112],{"class":63},[57,373,115],{"class":63},[57,375,118],{"class":63},[57,377,378],{"class":67}," CreateArticle\n",[57,380,381],{"class":59,"line":158},[57,382,127],{"class":71},[57,384,385],{"class":59,"line":164},[57,386,388],{"class":387},"sQrFR","    \u002F**\n",[57,390,391,394,397,400,403,406],{"class":59,"line":189},[57,392,393],{"class":387},"     * ",[57,395,396],{"class":63},"@param",[57,398,399],{"class":63},"  array",[57,401,402],{"class":387},"{",[57,404,405],{"class":88},"title",[57,407,408],{"class":387},": string, body: string, tags?: string[]}  $data\n",[57,410,411],{"class":59,"line":207},[57,412,413],{"class":387},"     *\u002F\n",[57,415,416,418,420,422,424,427,430,433,436,438],{"class":59,"line":230},[57,417,133],{"class":63},[57,419,136],{"class":63},[57,421,140],{"class":139},[57,423,143],{"class":71},[57,425,426],{"class":88},"User",[57,428,429],{"class":71}," $user, ",[57,431,432],{"class":63},"array",[57,434,435],{"class":71}," $data)",[57,437,152],{"class":63},[57,439,440],{"class":88}," Article\n",[57,442,443],{"class":59,"line":236},[57,444,161],{"class":71},[57,446,447,450,453,455,458,460,463,466,468,471,473,476],{"class":59,"line":242},[57,448,449],{"class":63},"        return",[57,451,452],{"class":88}," DB",[57,454,198],{"class":63},[57,456,457],{"class":139},"transaction",[57,459,143],{"class":71},[57,461,462],{"class":63},"function",[57,464,465],{"class":71}," () ",[57,467,85],{"class":63},[57,469,470],{"class":71}," ($user, $data)",[57,472,152],{"class":63},[57,474,475],{"class":88}," Article",[57,477,478],{"class":71}," {\n",[57,480,481,484,487,490,492,495,498,500,503],{"class":59,"line":247},[57,482,483],{"class":71},"            $article ",[57,485,486],{"class":63},"=",[57,488,489],{"class":71}," $user",[57,491,173],{"class":63},[57,493,494],{"class":139},"articles",[57,496,497],{"class":71},"()",[57,499,173],{"class":63},[57,501,502],{"class":139},"create",[57,504,204],{"class":71},[57,506,507,510,512,515,518],{"class":59,"line":260},[57,508,509],{"class":182},"                'title'",[57,511,213],{"class":63},[57,513,514],{"class":71}," $data[",[57,516,517],{"class":182},"'title'",[57,519,520],{"class":71},"],\n",[57,522,523,526,528,531,533,536,539,541],{"class":59,"line":274},[57,524,525],{"class":182},"                'slug'",[57,527,213],{"class":63},[57,529,530],{"class":88}," Str",[57,532,198],{"class":63},[57,534,535],{"class":139},"slug",[57,537,538],{"class":71},"($data[",[57,540,517],{"class":182},[57,542,543],{"class":71},"]),\n",[57,545,546,549,551,553,556],{"class":59,"line":288},[57,547,548],{"class":182},"                'body'",[57,550,213],{"class":63},[57,552,514],{"class":71},[57,554,555],{"class":182},"'body'",[57,557,520],{"class":71},[57,559,560,562,564,566],{"class":59,"line":294},[57,561,210],{"class":182},[57,563,213],{"class":63},[57,565,183],{"class":182},[57,567,271],{"class":71},[57,569,570],{"class":59,"line":300},[57,571,233],{"class":71},[57,573,575],{"class":59,"line":574},22,[57,576,79],{"emptyLinePlaceholder":78},[57,578,580,583,586,589,592,594,597],{"class":59,"line":579},23,[57,581,582],{"class":63},"            if",[57,584,585],{"class":71}," (",[57,587,588],{"class":63},"!",[57,590,591],{"class":88}," empty",[57,593,538],{"class":71},[57,595,596],{"class":182},"'tags'",[57,598,599],{"class":71},"])) {\n",[57,601,603,606,608,611,613,615],{"class":59,"line":602},24,[57,604,605],{"class":71},"                $article",[57,607,173],{"class":63},[57,609,610],{"class":139},"syncTags",[57,612,538],{"class":71},[57,614,596],{"class":182},[57,616,617],{"class":71},"]);\n",[57,619,621],{"class":59,"line":620},25,[57,622,623],{"class":71},"            }\n",[57,625,627],{"class":59,"line":626},26,[57,628,79],{"emptyLinePlaceholder":78},[57,630,632,635],{"class":59,"line":631},27,[57,633,634],{"class":63},"            return",[57,636,637],{"class":71}," $article;\n",[57,639,641],{"class":59,"line":640},28,[57,642,643],{"class":71},"        });\n",[57,645,647],{"class":59,"line":646},29,[57,648,297],{"class":71},[57,650,652],{"class":59,"line":651},30,[57,653,303],{"class":71},[11,655,656],{},"Conventions to follow:",[658,659,660,669,678,688,696],"ul",{},[661,662,663,668],"li",{},[23,664,665],{},[15,666,667],{},"final readonly class"," — the class cannot be extended, and its collaborators stay immutable after construction.",[661,670,671,677],{},[23,672,673,674,676],{},"One ",[15,675,29],{}," method"," — the single entry point every caller uses.",[661,679,680,683,684,687],{},[23,681,682],{},"Constructor injection"," for collaborators (a payment gateway, a file uploader). If the Action has no dependencies, skip the constructor entirely — see ",[15,685,686],{},"PublishArticle"," above.",[661,689,690,695],{},[23,691,692],{},[15,693,694],{},"DB::transaction()"," for any operation that touches multiple records, so it stays atomic.",[661,697,698,701],{},[23,699,700],{},"Return what the caller needs"," — the created model, a bool, or void.",[32,703,705],{"id":704},"naming-and-location","Naming and Location",[11,707,708,709,712,713,716],{},"Actions live in ",[15,710,711],{},"app\u002FActions",", named for the operation, ",[23,714,715],{},"no suffix",".",[718,719,720,733],"table",{},[721,722,723],"thead",{},[724,725,726,730],"tr",{},[727,728,729],"th",{},"Convention",[727,731,732],{},"Example",[734,735,736,748,764,781],"tbody",{},[724,737,738,744],{},[739,740,741],"td",{},[15,742,743],{},"app\u002FActions\u002F{Name}.php",[739,745,746],{},[15,747,314],{},[724,749,750,753],{},[739,751,752],{},"Verb + noun",[739,754,755,758,759,758,761],{},[15,756,757],{},"CreateArticle",", ",[15,760,686],{},[15,762,763],{},"ArchiveArticle",[724,765,766,773],{},[739,767,768,769,772],{},"No ",[15,770,771],{},"Action"," suffix",[739,774,775,777,778],{},[15,776,757],{},", not ",[15,779,780],{},"CreateArticleAction",[724,782,783,786],{},[739,784,785],{},"Entry point",[739,787,788],{},[15,789,790],{},"$createArticle->handle(...)",[11,792,793,794,797,798,807],{},"The ",[15,795,796],{},"make:action"," command ships with ",[799,800,806],"a",{"href":801,"rel":802,"target":805},"https:\u002F\u002Fgithub.com\u002Fnunomaduro\u002Fessentials",[803,804],"noopener","noreferrer","_blank","nunomaduro\u002Fessentials",", not Laravel itself. With the package installed:",[47,809,813],{"className":810,"code":811,"language":812,"meta":53,"style":53},"language-bash shiki shiki-themes github-dark-high-contrast","php artisan make:action \"CreateArticle\"\n","bash",[15,814,815],{"__ignoreMap":53},[57,816,817,819,822,825],{"class":59,"line":60},[57,818,52],{"class":67},[57,820,821],{"class":182}," artisan",[57,823,824],{"class":182}," make:action",[57,826,827],{"class":182}," \"CreateArticle\"\n",[32,829,831],{"id":830},"calling-actions-from-controllers","Calling Actions from Controllers",[11,833,834,835,838],{},"Controllers should only coordinate: receive the request, call the Action, return a response. Inject the Action into the ",[23,836,837],{},"method",", not the constructor.",[47,840,843],{"className":49,"code":841,"filename":842,"language":52,"meta":53,"style":53},"final class ArticleController extends Controller\n{\n    public function store(StoreArticleRequest $request, CreateArticle $createArticle): RedirectResponse\n    {\n        $article = $createArticle->handle($request->user(), $request->validated());\n\n        return to_route('articles.show', $article);\n    }\n}\n","app\u002FHttp\u002FControllers\u002FArticleController.php",[15,844,845,860,864,891,895,929,933,948,952],{"__ignoreMap":53},[57,846,847,849,851,854,857],{"class":59,"line":60},[57,848,112],{"class":63},[57,850,118],{"class":63},[57,852,853],{"class":67}," ArticleController",[57,855,856],{"class":63}," extends",[57,858,859],{"class":88}," Controller\n",[57,861,862],{"class":59,"line":75},[57,863,127],{"class":71},[57,865,866,868,870,873,875,878,881,883,886,888],{"class":59,"line":82},[57,867,133],{"class":63},[57,869,136],{"class":63},[57,871,872],{"class":139}," store",[57,874,143],{"class":71},[57,876,877],{"class":88},"StoreArticleRequest",[57,879,880],{"class":71}," $request, ",[57,882,757],{"class":88},[57,884,885],{"class":71}," $createArticle)",[57,887,152],{"class":63},[57,889,890],{"class":88}," RedirectResponse\n",[57,892,893],{"class":59,"line":94},[57,894,161],{"class":71},[57,896,897,900,902,905,907,910,913,915,918,921,923,926],{"class":59,"line":104},[57,898,899],{"class":71},"        $article ",[57,901,486],{"class":63},[57,903,904],{"class":71}," $createArticle",[57,906,173],{"class":63},[57,908,909],{"class":139},"handle",[57,911,912],{"class":71},"($request",[57,914,173],{"class":63},[57,916,917],{"class":139},"user",[57,919,920],{"class":71},"(), $request",[57,922,173],{"class":63},[57,924,925],{"class":139},"validated",[57,927,928],{"class":71},"());\n",[57,930,931],{"class":59,"line":109},[57,932,79],{"emptyLinePlaceholder":78},[57,934,935,937,940,942,945],{"class":59,"line":124},[57,936,449],{"class":63},[57,938,939],{"class":139}," to_route",[57,941,143],{"class":71},[57,943,944],{"class":182},"'articles.show'",[57,946,947],{"class":71},", $article);\n",[57,949,950],{"class":59,"line":130},[57,951,297],{"class":71},[57,953,954],{"class":59,"line":158},[57,955,303],{"class":71},[11,957,958],{},"Two lines in the body: call the Action, redirect. Validation lives in the Form Request, business logic in the Action — the controller only coordinates.",[32,960,962],{"id":961},"why-method-injection","Why Method Injection",[11,964,965],{},"Inject Actions on the method that uses them, never through the controller's constructor.",[47,967,969],{"className":49,"code":968,"filename":842,"language":52,"meta":53,"style":53},"\u002F\u002F ❌ Constructor — every Action constructed for every request, even unused ones\nfinal class ArticleController extends Controller\n{\n    public function __construct(\n        private CreateArticle $createArticle,\n        private UpdateArticle $updateArticle,\n        private DeleteArticle $deleteArticle,\n    ) {}\n}\n\n\u002F\u002F ✅ Method — each endpoint pulls only what it needs\npublic function store(StoreArticleRequest $request, CreateArticle $createArticle): RedirectResponse\n{\n    $article = $createArticle->handle($request->user(), $request->validated());\n    return to_route('articles.show', $article);\n}\n",[15,970,971,976,988,992,1004,1015,1025,1035,1040,1044,1048,1053,1076,1080,1107,1120],{"__ignoreMap":53},[57,972,973],{"class":59,"line":60},[57,974,975],{"class":387},"\u002F\u002F ❌ Constructor — every Action constructed for every request, even unused ones\n",[57,977,978,980,982,984,986],{"class":59,"line":75},[57,979,112],{"class":63},[57,981,118],{"class":63},[57,983,853],{"class":67},[57,985,856],{"class":63},[57,987,859],{"class":88},[57,989,990],{"class":59,"line":82},[57,991,127],{"class":71},[57,993,994,996,998,1001],{"class":59,"line":94},[57,995,133],{"class":63},[57,997,136],{"class":63},[57,999,1000],{"class":88}," __construct",[57,1002,1003],{"class":71},"(\n",[57,1005,1006,1009,1012],{"class":59,"line":104},[57,1007,1008],{"class":63},"        private",[57,1010,1011],{"class":88}," CreateArticle",[57,1013,1014],{"class":71}," $createArticle,\n",[57,1016,1017,1019,1022],{"class":59,"line":109},[57,1018,1008],{"class":63},[57,1020,1021],{"class":88}," UpdateArticle",[57,1023,1024],{"class":71}," $updateArticle,\n",[57,1026,1027,1029,1032],{"class":59,"line":124},[57,1028,1008],{"class":63},[57,1030,1031],{"class":88}," DeleteArticle",[57,1033,1034],{"class":71}," $deleteArticle,\n",[57,1036,1037],{"class":59,"line":130},[57,1038,1039],{"class":71},"    ) {}\n",[57,1041,1042],{"class":59,"line":158},[57,1043,303],{"class":71},[57,1045,1046],{"class":59,"line":164},[57,1047,79],{"emptyLinePlaceholder":78},[57,1049,1050],{"class":59,"line":189},[57,1051,1052],{"class":387},"\u002F\u002F ✅ Method — each endpoint pulls only what it needs\n",[57,1054,1055,1058,1060,1062,1064,1066,1068,1070,1072,1074],{"class":59,"line":207},[57,1056,1057],{"class":63},"public",[57,1059,136],{"class":63},[57,1061,872],{"class":139},[57,1063,143],{"class":71},[57,1065,877],{"class":88},[57,1067,880],{"class":71},[57,1069,757],{"class":88},[57,1071,885],{"class":71},[57,1073,152],{"class":63},[57,1075,890],{"class":88},[57,1077,1078],{"class":59,"line":230},[57,1079,127],{"class":71},[57,1081,1082,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105],{"class":59,"line":236},[57,1083,1084],{"class":71},"    $article ",[57,1086,486],{"class":63},[57,1088,904],{"class":71},[57,1090,173],{"class":63},[57,1092,909],{"class":139},[57,1094,912],{"class":71},[57,1096,173],{"class":63},[57,1098,917],{"class":139},[57,1100,920],{"class":71},[57,1102,173],{"class":63},[57,1104,925],{"class":139},[57,1106,928],{"class":71},[57,1108,1109,1112,1114,1116,1118],{"class":59,"line":242},[57,1110,1111],{"class":63},"    return",[57,1113,939],{"class":139},[57,1115,143],{"class":71},[57,1117,944],{"class":182},[57,1119,947],{"class":71},[57,1121,1122],{"class":59,"line":247},[57,1123,303],{"class":71},[11,1125,1126],{},"Adding a new endpoint never touches the constructor, and each method reads as a self-contained list of its dependencies.",[32,1128,1130],{"id":1129},"invoke-from-anywhere","Invoke from Anywhere",[11,1132,1133],{},"Because Actions resolve from the container, the same class runs from a controller, a job, or a command — one implementation, every caller.",[47,1135,1138],{"className":49,"code":1136,"filename":1137,"language":52,"meta":53,"style":53},"namespace App\\Jobs;\n\nuse App\\Actions\\CreateArticle;\nuse App\\Models\\User;\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\n\nfinal class ImportArticle implements ShouldQueue\n{\n    use Dispatchable, Queueable;\n\n    \u002F**\n     * @param  array{title: string, body: string, tags?: string[]}  $payload\n     *\u002F\n    public function __construct(\n        public readonly User $user,\n        public readonly array $payload,\n    ) {}\n\n    public function handle(CreateArticle $createArticle): void\n    {\n        $createArticle->handle($this->user, $this->payload);\n    }\n}\n","app\u002FJobs\u002FImportArticle.php",[15,1139,1140,1149,1153,1162,1170,1179,1188,1197,1201,1216,1220,1235,1239,1243,1258,1262,1272,1285,1297,1301,1305,1323,1327,1353,1357],{"__ignoreMap":53},[57,1141,1142,1144,1147],{"class":59,"line":60},[57,1143,64],{"class":63},[57,1145,1146],{"class":67}," App\\Jobs",[57,1148,72],{"class":71},[57,1150,1151],{"class":59,"line":75},[57,1152,79],{"emptyLinePlaceholder":78},[57,1154,1155,1157,1160],{"class":59,"line":82},[57,1156,85],{"class":63},[57,1158,1159],{"class":88}," App\\Actions\\CreateArticle",[57,1161,72],{"class":71},[57,1163,1164,1166,1168],{"class":59,"line":94},[57,1165,85],{"class":63},[57,1167,343],{"class":88},[57,1169,72],{"class":71},[57,1171,1172,1174,1177],{"class":59,"line":104},[57,1173,85],{"class":63},[57,1175,1176],{"class":88}," Illuminate\\Bus\\Queueable",[57,1178,72],{"class":71},[57,1180,1181,1183,1186],{"class":59,"line":109},[57,1182,85],{"class":63},[57,1184,1185],{"class":88}," Illuminate\\Contracts\\Queue\\ShouldQueue",[57,1187,72],{"class":71},[57,1189,1190,1192,1195],{"class":59,"line":124},[57,1191,85],{"class":63},[57,1193,1194],{"class":88}," Illuminate\\Foundation\\Bus\\Dispatchable",[57,1196,72],{"class":71},[57,1198,1199],{"class":59,"line":130},[57,1200,79],{"emptyLinePlaceholder":78},[57,1202,1203,1205,1207,1210,1213],{"class":59,"line":158},[57,1204,112],{"class":63},[57,1206,118],{"class":63},[57,1208,1209],{"class":67}," ImportArticle",[57,1211,1212],{"class":63}," implements",[57,1214,1215],{"class":88}," ShouldQueue\n",[57,1217,1218],{"class":59,"line":164},[57,1219,127],{"class":71},[57,1221,1222,1225,1228,1230,1233],{"class":59,"line":189},[57,1223,1224],{"class":63},"    use",[57,1226,1227],{"class":88}," Dispatchable",[57,1229,758],{"class":71},[57,1231,1232],{"class":88},"Queueable",[57,1234,72],{"class":71},[57,1236,1237],{"class":59,"line":207},[57,1238,79],{"emptyLinePlaceholder":78},[57,1240,1241],{"class":59,"line":230},[57,1242,388],{"class":387},[57,1244,1245,1247,1249,1251,1253,1255],{"class":59,"line":236},[57,1246,393],{"class":387},[57,1248,396],{"class":63},[57,1250,399],{"class":63},[57,1252,402],{"class":387},[57,1254,405],{"class":88},[57,1256,1257],{"class":387},": string, body: string, tags?: string[]}  $payload\n",[57,1259,1260],{"class":59,"line":242},[57,1261,413],{"class":387},[57,1263,1264,1266,1268,1270],{"class":59,"line":247},[57,1265,133],{"class":63},[57,1267,136],{"class":63},[57,1269,1000],{"class":88},[57,1271,1003],{"class":71},[57,1273,1274,1277,1279,1282],{"class":59,"line":260},[57,1275,1276],{"class":63},"        public",[57,1278,115],{"class":63},[57,1280,1281],{"class":88}," User",[57,1283,1284],{"class":71}," $user,\n",[57,1286,1287,1289,1291,1294],{"class":59,"line":274},[57,1288,1276],{"class":63},[57,1290,115],{"class":63},[57,1292,1293],{"class":63}," array",[57,1295,1296],{"class":71}," $payload,\n",[57,1298,1299],{"class":59,"line":288},[57,1300,1039],{"class":71},[57,1302,1303],{"class":59,"line":294},[57,1304,79],{"emptyLinePlaceholder":78},[57,1306,1307,1309,1311,1313,1315,1317,1319,1321],{"class":59,"line":300},[57,1308,133],{"class":63},[57,1310,136],{"class":63},[57,1312,140],{"class":139},[57,1314,143],{"class":71},[57,1316,757],{"class":88},[57,1318,885],{"class":71},[57,1320,152],{"class":63},[57,1322,155],{"class":63},[57,1324,1325],{"class":59,"line":574},[57,1326,161],{"class":71},[57,1328,1329,1332,1334,1336,1338,1341,1343,1346,1348,1350],{"class":59,"line":579},[57,1330,1331],{"class":71},"        $createArticle",[57,1333,173],{"class":63},[57,1335,909],{"class":139},[57,1337,143],{"class":71},[57,1339,1340],{"class":88},"$this",[57,1342,173],{"class":63},[57,1344,1345],{"class":71},"user, ",[57,1347,1340],{"class":88},[57,1349,173],{"class":63},[57,1351,1352],{"class":71},"payload);\n",[57,1354,1355],{"class":59,"line":602},[57,1356,297],{"class":71},[57,1358,1359],{"class":59,"line":620},[57,1360,303],{"class":71},[11,1362,1363,1364,1368,1369,1372],{},"The job does not know ",[1365,1366,1367],"em",{},"how"," an article is created, only ",[1365,1370,1371],{},"which"," Action to call. Change the implementation, and every caller picks up the change.",[32,1374,1376],{"id":1375},"before-and-after","Before and After",[11,1378,1379],{},"Suppose you now need to log every article creation to an audit table. Add one line to the Action, and every caller starts logging.",[47,1381,1383],{"className":49,"code":1382,"language":52,"meta":53,"style":53},"\u002F\u002F ❌ Before — logic in the controller; the job and command duplicate it\npublic function store(Request $request)\n{\n    $data = $request->validate(['title' => ['required'], 'body' => ['required']]);\n\n    $article = $request->user()->articles()->create([\n        'title' => $data['title'],\n        'slug' => Str::slug($data['title']),\n        'body' => $data['body'],\n        'status' => 'draft',\n    ]);\n    Activity::log($request->user(), 'article.created'); \u002F\u002F controller only\n\n    return to_route('articles.show', $article);\n}\n\n\u002F\u002F ✅ After — the Action owns the operation, so the audit log ships everywhere\npublic function handle(User $user, array $data): Article\n{\n    return DB::transaction(function () use ($user, $data): Article {\n        \u002F\u002F ...create article, sync tags...\n\n        Activity::log($user, 'article.created');\n\n        return $article;\n    });\n}\n",[15,1384,1385,1390,1406,1410,1451,1455,1481,1494,1513,1526,1537,1542,1570,1574,1586,1590,1594,1599,1621,1625,1651,1656,1660,1677,1681,1687,1692],{"__ignoreMap":53},[57,1386,1387],{"class":59,"line":60},[57,1388,1389],{"class":387},"\u002F\u002F ❌ Before — logic in the controller; the job and command duplicate it\n",[57,1391,1392,1394,1396,1398,1400,1403],{"class":59,"line":75},[57,1393,1057],{"class":63},[57,1395,136],{"class":63},[57,1397,872],{"class":139},[57,1399,143],{"class":71},[57,1401,1402],{"class":88},"Request",[57,1404,1405],{"class":71}," $request)\n",[57,1407,1408],{"class":59,"line":82},[57,1409,127],{"class":71},[57,1411,1412,1415,1417,1420,1422,1425,1428,1430,1432,1434,1437,1440,1442,1444,1446,1448],{"class":59,"line":94},[57,1413,1414],{"class":71},"    $data ",[57,1416,486],{"class":63},[57,1418,1419],{"class":71}," $request",[57,1421,173],{"class":63},[57,1423,1424],{"class":139},"validate",[57,1426,1427],{"class":71},"([",[57,1429,517],{"class":182},[57,1431,213],{"class":63},[57,1433,216],{"class":71},[57,1435,1436],{"class":182},"'required'",[57,1438,1439],{"class":71},"], ",[57,1441,555],{"class":182},[57,1443,213],{"class":63},[57,1445,216],{"class":71},[57,1447,1436],{"class":182},[57,1449,1450],{"class":71},"]]);\n",[57,1452,1453],{"class":59,"line":104},[57,1454,79],{"emptyLinePlaceholder":78},[57,1456,1457,1459,1461,1463,1465,1467,1469,1471,1473,1475,1477,1479],{"class":59,"line":109},[57,1458,1084],{"class":71},[57,1460,486],{"class":63},[57,1462,1419],{"class":71},[57,1464,173],{"class":63},[57,1466,917],{"class":139},[57,1468,497],{"class":71},[57,1470,173],{"class":63},[57,1472,494],{"class":139},[57,1474,497],{"class":71},[57,1476,173],{"class":63},[57,1478,502],{"class":139},[57,1480,204],{"class":71},[57,1482,1483,1486,1488,1490,1492],{"class":59,"line":124},[57,1484,1485],{"class":182},"        'title'",[57,1487,213],{"class":63},[57,1489,514],{"class":71},[57,1491,517],{"class":182},[57,1493,520],{"class":71},[57,1495,1496,1499,1501,1503,1505,1507,1509,1511],{"class":59,"line":130},[57,1497,1498],{"class":182},"        'slug'",[57,1500,213],{"class":63},[57,1502,530],{"class":88},[57,1504,198],{"class":63},[57,1506,535],{"class":139},[57,1508,538],{"class":71},[57,1510,517],{"class":182},[57,1512,543],{"class":71},[57,1514,1515,1518,1520,1522,1524],{"class":59,"line":158},[57,1516,1517],{"class":182},"        'body'",[57,1519,213],{"class":63},[57,1521,514],{"class":71},[57,1523,555],{"class":182},[57,1525,520],{"class":71},[57,1527,1528,1531,1533,1535],{"class":59,"line":164},[57,1529,1530],{"class":182},"        'status'",[57,1532,213],{"class":63},[57,1534,183],{"class":182},[57,1536,271],{"class":71},[57,1538,1539],{"class":59,"line":189},[57,1540,1541],{"class":71},"    ]);\n",[57,1543,1544,1547,1549,1552,1554,1556,1558,1561,1564,1567],{"class":59,"line":207},[57,1545,1546],{"class":88},"    Activity",[57,1548,198],{"class":63},[57,1550,1551],{"class":139},"log",[57,1553,912],{"class":71},[57,1555,173],{"class":63},[57,1557,917],{"class":139},[57,1559,1560],{"class":71},"(), ",[57,1562,1563],{"class":182},"'article.created'",[57,1565,1566],{"class":71},"); ",[57,1568,1569],{"class":387},"\u002F\u002F controller only\n",[57,1571,1572],{"class":59,"line":230},[57,1573,79],{"emptyLinePlaceholder":78},[57,1575,1576,1578,1580,1582,1584],{"class":59,"line":236},[57,1577,1111],{"class":63},[57,1579,939],{"class":139},[57,1581,143],{"class":71},[57,1583,944],{"class":182},[57,1585,947],{"class":71},[57,1587,1588],{"class":59,"line":242},[57,1589,303],{"class":71},[57,1591,1592],{"class":59,"line":247},[57,1593,79],{"emptyLinePlaceholder":78},[57,1595,1596],{"class":59,"line":260},[57,1597,1598],{"class":387},"\u002F\u002F ✅ After — the Action owns the operation, so the audit log ships everywhere\n",[57,1600,1601,1603,1605,1607,1609,1611,1613,1615,1617,1619],{"class":59,"line":274},[57,1602,1057],{"class":63},[57,1604,136],{"class":63},[57,1606,140],{"class":139},[57,1608,143],{"class":71},[57,1610,426],{"class":88},[57,1612,429],{"class":71},[57,1614,432],{"class":63},[57,1616,435],{"class":71},[57,1618,152],{"class":63},[57,1620,440],{"class":88},[57,1622,1623],{"class":59,"line":288},[57,1624,127],{"class":71},[57,1626,1627,1629,1631,1633,1635,1637,1639,1641,1643,1645,1647,1649],{"class":59,"line":294},[57,1628,1111],{"class":63},[57,1630,452],{"class":88},[57,1632,198],{"class":63},[57,1634,457],{"class":139},[57,1636,143],{"class":71},[57,1638,462],{"class":63},[57,1640,465],{"class":71},[57,1642,85],{"class":63},[57,1644,470],{"class":71},[57,1646,152],{"class":63},[57,1648,475],{"class":88},[57,1650,478],{"class":71},[57,1652,1653],{"class":59,"line":300},[57,1654,1655],{"class":387},"        \u002F\u002F ...create article, sync tags...\n",[57,1657,1658],{"class":59,"line":574},[57,1659,79],{"emptyLinePlaceholder":78},[57,1661,1662,1665,1667,1669,1672,1674],{"class":59,"line":579},[57,1663,1664],{"class":88},"        Activity",[57,1666,198],{"class":63},[57,1668,1551],{"class":139},[57,1670,1671],{"class":71},"($user, ",[57,1673,1563],{"class":182},[57,1675,1676],{"class":71},");\n",[57,1678,1679],{"class":59,"line":602},[57,1680,79],{"emptyLinePlaceholder":78},[57,1682,1683,1685],{"class":59,"line":620},[57,1684,449],{"class":63},[57,1686,637],{"class":71},[57,1688,1689],{"class":59,"line":626},[57,1690,1691],{"class":71},"    });\n",[57,1693,1694],{"class":59,"line":631},[57,1695,303],{"class":71},[32,1697,1699],{"id":1698},"summary","Summary",[658,1701,1702,1711,1719,1728,1735],{},[661,1703,1704,1705,1707,1708,1710],{},"An ",[23,1706,771],{}," is a small, single-purpose object with one ",[15,1709,29],{}," method that owns a single operation.",[661,1712,1713,1714,1716,1717,716],{},"Keep them in ",[15,1715,711],{},", named for what they do, with ",[23,1718,715],{},[661,1720,1721,1722,1725,1726,716],{},"Make them ",[15,1723,1724],{},"final readonly",", inject collaborators through the constructor, and wrap multi-model work in ",[15,1727,694],{},[661,1729,1730,1731,1734],{},"Call them from controller ",[23,1732,1733],{},"methods",", never from constructors.",[661,1736,1737],{},"The same Action runs from controllers, jobs, and commands — that reuse is the payoff.",[11,1739,1740],{},[23,1741,1742],{},"References:",[658,1744,1745,1752],{},[661,1746,1747],{},[799,1748,1751],{"href":1749,"rel":1750,"target":805},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fcontainer#method-injection",[803,804],"Laravel Service Container — Method Injection",[661,1753,1754],{},[799,1755,1757,1758],{"href":801,"rel":1756,"target":805},[803,804],"nunomaduro\u002Fessentials — provides ",[15,1759,796],{},[1761,1762,1763],"style",{},"html pre.shiki code .sWyjQ, html code.shiki .sWyjQ{--shiki-default:#FF9492}html pre.shiki code .s_sBn, html code.shiki .s_sBn{--shiki-default:#FFB757}html pre.shiki code .sMAXC, html code.shiki .sMAXC{--shiki-default:#F0F3F6}html pre.shiki code .sCcAr, html code.shiki .sCcAr{--shiki-default:#91CBFF}html pre.shiki code .sj_SG, html code.shiki .sj_SG{--shiki-default:#DBB7FF}html pre.shiki code .sTRMh, html code.shiki .sTRMh{--shiki-default:#ADDCFF}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 pre.shiki code .sQrFR, html code.shiki .sQrFR{--shiki-default:#BDC4CC}",{"title":53,"searchDepth":75,"depth":75,"links":1765},[1766,1767,1768,1769,1770,1771,1772,1773],{"id":34,"depth":75,"text":35},{"id":309,"depth":75,"text":310},{"id":704,"depth":75,"text":705},{"id":830,"depth":75,"text":831},{"id":961,"depth":75,"text":962},{"id":1129,"depth":75,"text":1130},{"id":1375,"depth":75,"text":1376},{"id":1698,"depth":75,"text":1699},"Laravel","2026-07-04","Pull business logic out of controllers and models into small, single-purpose Action classes with one handle() method — one class per operation, called from anywhere.","md",{},"\u002Farticles\u002Flaravel-action-pattern",{"title":5,"description":1776},"published","articles\u002Flaravel-action-pattern",[1774,1784,1785,1786,1787],"PHP","Backend","Architecture","Action Pattern","5gnZG37dNh8zShJ93pyyRnUfls6EH5t2yMEAI8qJVNE",1783159016331]