[{"data":1,"prerenderedAt":700},["ShallowReactive",2],{"\u002Farticles\u002Fembrace-the-framework-why-working-with-laravel-beats-working-around-it":3,"article-series:embrace-the-framework":647},{"id":4,"title":5,"body":6,"description":630,"draft":631,"extension":632,"meta":633,"navigation":184,"path":634,"published":635,"seo":636,"seoDescription":637,"series":638,"seriesOrder":639,"stem":640,"tags":641,"updated":645,"__hash__":646},"articles\u002Farticles\u002Fembrace-the-framework-why-working-with-laravel-beats-working-around-it.md","Embrace the Framework: Why Working With Laravel Beats Working Around It",{"type":7,"value":8,"toc":622},"minimark",[9,13,16,19,22,25,28,31,34,39,42,45,48,51,55,58,61,64,67,70,95,98,102,105,108,111,114,117,120,123,126,134,141,144,148,151,154,375,378,381,384,476,479,482,553,556,574,577,580,584,587,590,593,596,599,603,606,609,612,615,618],[10,11,12],"p",{},"Laravel is a framework with a lot of opinions.",[10,14,15],{},"It gives us conventions for where code lives, how routes and controllers are structured, how models talk to a database, how validation works, and much more. It also gives us a large collection of small, well-considered tools for common problems.",[10,17,18],{},"Those opinions are not a cage. Laravel will not stop us from organising an application differently, introducing our own abstractions, or using plain PHP when it is the clearest solution. It gives us a strong default rather than insisting there is only one possible way to build.",[10,20,21],{},"That flexibility is useful, but it also means we need to make deliberate choices about when moving away from the framework's conventions genuinely helps the application.",[10,23,24],{},"Sometimes that can feel restrictive. When we are working on a feature, it is tempting to reach straight for a custom helper, a large service class, or a familiar bit of plain PHP. There is nothing inherently wrong with custom code, and Laravel certainly does not remove the need for it.",[10,26,27],{},"The problem comes when we start rebuilding things the framework already does well.",[10,29,30],{},"Over time, that can leave an application full of slightly different ways to solve the same problem. One developer uses arrays and loops, another uses a query scope, another builds a helper, and somebody else writes a service with several hundred lines of conditionals. All of those approaches may work, but the application becomes harder to understand because there is no consistent language for it.",[10,32,33],{},"This article is the beginning of a series about doing the opposite: embracing the framework.",[35,36,38],"h2",{"id":37},"what-i-mean-by-embracing-laravel","What I mean by embracing Laravel",[10,40,41],{},"Embracing Laravel does not mean blindly using every feature it offers.",[10,43,44],{},"It means learning the tools Laravel provides, understanding the problems they solve, and using them when they are a good fit. It means following conventions when they make an application easier for the next developer to work on. It also means extending Laravel in ways that feel natural when the application genuinely has its own domain language.",[10,46,47],{},"The goal is not to make code look clever. The goal is to make it easier to read, test, change, and maintain.",[10,49,50],{},"A good Laravel application often reads less like a set of instructions for the computer and more like a description of the business problem being solved.",[35,52,54],{"id":53},"the-cost-of-fighting-the-framework","The cost of fighting the framework",[10,56,57],{},"Most of us have seen a controller that starts small and slowly turns into the centre of an entire feature.",[10,59,60],{},"It might begin by fetching some orders, applying a few rules, calculating totals, sending notifications, and returning a response. A few months later, there are more conditions, more special cases, and more copied logic in other parts of the application.",[10,62,63],{},"The code may still work, but changing it starts to feel risky.",[10,65,66],{},"This is often not because the developer made a bad decision. Features grow, deadlines happen, and the simplest solution at the time is usually the one that gets written. The trouble is that custom logic tends to accumulate without providing a clear structure for where the next piece should go.",[10,68,69],{},"Laravel has structures for many of these situations already:",[71,72,73,77,80,83,86,89,92],"ul",{},[74,75,76],"li",{},"Form Requests can hold validation and authorisation rules.",[74,78,79],{},"Eloquent query scopes can describe reusable database queries.",[74,81,82],{},"Events and listeners can separate a business action from its side effects.",[74,84,85],{},"Jobs can move slow work out of a web request.",[74,87,88],{},"Policies can make permissions explicit.",[74,90,91],{},"Collections can make in-memory data transformations easier to understand.",[74,93,94],{},"Pipelines can break a complex process into small, ordered steps.",[10,96,97],{},"Using these tools does not guarantee good code, but it gives us shared places to put it.",[35,99,101],{"id":100},"shared-conventions-are-a-feature","Shared conventions are a feature",[10,103,104],{},"One of Laravel's biggest benefits is that developers already have a rough idea of where to look.",[10,106,107],{},"If an application uses a Form Request for validation, a policy for authorisation, and a custom collection for group behaviour, another Laravel developer can orient themselves quickly. They do not need to learn a new internal architecture before they can make a small change.",[10,109,110],{},"That is especially valuable as an application grows.",[10,112,113],{},"There is also a cost to being too inventive with an application's structure.",[10,115,116],{},"A developer may come up with a genuinely effective solution to a problem, perhaps even one that is more flexible or technically elegant than Laravel's usual approach. That does not automatically make it the best choice for the team.",[10,118,119],{},"If that solution is not documented, understood by others, or consistent with the rest of the application, every future developer has to spend time working out the idea before they can safely change it. The code relies on an assumption that someone has seen the pattern before, remembers why it exists, and knows when it should be used.",[10,121,122],{},"The cost of a custom solution is not always visible when it is first written. Even when it is technically sound, the team now owns the responsibility for explaining it, documenting it, testing its edge cases, and teaching new developers how and when to use it. Hiring becomes a little harder too, because the application depends on knowledge that exists only within the team.",[10,124,125],{},"Laravel conventions reduce that cost. Developers who already know Laravel can usually understand the structure, recognise familiar patterns, and become productive much faster. The framework's tools have also been used, tested, questioned, and improved across a much broader range of applications than most internal solutions ever will be.",[10,127,128,129,133],{},"A custom abstraction can still be excellent when it describes something unique about the business. A ",[130,131,132],"code",{},"SubscriptionCollection",", for example, may make perfect sense in an application that manages recurring billing. It can expose useful concepts such as active subscriptions, subscriptions at risk, or monthly recurring revenue.",[10,135,136,137,140],{},"A generic ",[130,138,139],{},"DataManager",", however, usually tells us very little. It may be doing important work, but the name does not help us understand what that work is or where it belongs.",[10,142,143],{},"Laravel's conventions give us a useful default. We can then introduce our own abstractions when they add meaningful language to the application and enough value to justify the ongoing cost of owning them.",[35,145,147],{"id":146},"a-small-example","A small example",[10,149,150],{},"Imagine we need to identify the orders that are ready to be sent to the warehouse.",[10,152,153],{},"A perfectly functional approach might be to use a loop and build an array as we go:",[155,156,161],"pre",{"className":157,"code":158,"language":159,"meta":160,"style":160},"language-php shiki shiki-themes github-light github-dark","$ordersReadyToShip = [];\n\nforeach ($orders as $order) {\n    $hasStockForEveryItem = true;\n\n    foreach ($order->items as $item) {\n        if (! $item->is_in_stock) {\n            $hasStockForEveryItem = false;\n            break;\n        }\n    }\n\n    if (\n        $order->is_paid\n        && $order->shipping_address !== null\n        && $hasStockForEveryItem\n    ) {\n        $ordersReadyToShip[] = $order;\n    }\n}\n","php","",[130,162,163,179,186,201,216,221,241,261,274,282,288,294,299,308,319,339,347,353,364,369],{"__ignoreMap":160},[164,165,168,172,176],"span",{"class":166,"line":167},"line",1,[164,169,171],{"class":170},"sVt8B","$ordersReadyToShip ",[164,173,175],{"class":174},"szBVR","=",[164,177,178],{"class":170}," [];\n",[164,180,182],{"class":166,"line":181},2,[164,183,185],{"emptyLinePlaceholder":184},true,"\n",[164,187,189,192,195,198],{"class":166,"line":188},3,[164,190,191],{"class":174},"foreach",[164,193,194],{"class":170}," ($orders ",[164,196,197],{"class":174},"as",[164,199,200],{"class":170}," $order) {\n",[164,202,204,207,209,213],{"class":166,"line":203},4,[164,205,206],{"class":170},"    $hasStockForEveryItem ",[164,208,175],{"class":174},[164,210,212],{"class":211},"sj4cs"," true",[164,214,215],{"class":170},";\n",[164,217,219],{"class":166,"line":218},5,[164,220,185],{"emptyLinePlaceholder":184},[164,222,224,227,230,233,236,238],{"class":166,"line":223},6,[164,225,226],{"class":174},"    foreach",[164,228,229],{"class":170}," ($order",[164,231,232],{"class":174},"->",[164,234,235],{"class":170},"items ",[164,237,197],{"class":174},[164,239,240],{"class":170}," $item) {\n",[164,242,244,247,250,253,256,258],{"class":166,"line":243},7,[164,245,246],{"class":174},"        if",[164,248,249],{"class":170}," (",[164,251,252],{"class":174},"!",[164,254,255],{"class":170}," $item",[164,257,232],{"class":174},[164,259,260],{"class":170},"is_in_stock) {\n",[164,262,264,267,269,272],{"class":166,"line":263},8,[164,265,266],{"class":170},"            $hasStockForEveryItem ",[164,268,175],{"class":174},[164,270,271],{"class":211}," false",[164,273,215],{"class":170},[164,275,277,280],{"class":166,"line":276},9,[164,278,279],{"class":174},"            break",[164,281,215],{"class":170},[164,283,285],{"class":166,"line":284},10,[164,286,287],{"class":170},"        }\n",[164,289,291],{"class":166,"line":290},11,[164,292,293],{"class":170},"    }\n",[164,295,297],{"class":166,"line":296},12,[164,298,185],{"emptyLinePlaceholder":184},[164,300,302,305],{"class":166,"line":301},13,[164,303,304],{"class":174},"    if",[164,306,307],{"class":170}," (\n",[164,309,311,314,316],{"class":166,"line":310},14,[164,312,313],{"class":170},"        $order",[164,315,232],{"class":174},[164,317,318],{"class":170},"is_paid\n",[164,320,322,325,328,330,333,336],{"class":166,"line":321},15,[164,323,324],{"class":174},"        &&",[164,326,327],{"class":170}," $order",[164,329,232],{"class":174},[164,331,332],{"class":170},"shipping_address ",[164,334,335],{"class":174},"!==",[164,337,338],{"class":211}," null\n",[164,340,342,344],{"class":166,"line":341},16,[164,343,324],{"class":174},[164,345,346],{"class":170}," $hasStockForEveryItem\n",[164,348,350],{"class":166,"line":349},17,[164,351,352],{"class":170},"    ) {\n",[164,354,356,359,361],{"class":166,"line":355},18,[164,357,358],{"class":170},"        $ordersReadyToShip[] ",[164,360,175],{"class":174},[164,362,363],{"class":170}," $order;\n",[164,365,367],{"class":166,"line":366},19,[164,368,293],{"class":170},[164,370,372],{"class":166,"line":371},20,[164,373,374],{"class":170},"}\n",[10,376,377],{},"There is nothing broken about this. It will produce the required result, and for a small one-off task it may be exactly what we need.",[10,379,380],{},"The trade-off is that we are manually managing the process: creating an array, looping through orders, looping through items, tracking state, and adding qualifying orders ourselves. As the conditions grow, the intent can become harder to pick out.",[10,382,383],{},"Laravel Collections let us describe the actual operation more directly:",[155,385,387],{"className":157,"code":386,"language":159,"meta":160,"style":160},"$ordersReadyToShip = $orders\n    ->filter(fn (Order $order) =>\n        $order->is_paid\n        && $order->shipping_address !== null\n        && $order->items->every(fn ($item) => $item->is_in_stock)\n    );\n",[130,388,389,398,421,429,443,471],{"__ignoreMap":160},[164,390,391,393,395],{"class":166,"line":167},[164,392,171],{"class":170},[164,394,175],{"class":174},[164,396,397],{"class":170}," $orders\n",[164,399,400,403,407,410,413,415,418],{"class":166,"line":181},[164,401,402],{"class":174},"    ->",[164,404,406],{"class":405},"sScJk","filter",[164,408,409],{"class":170},"(",[164,411,412],{"class":174},"fn",[164,414,249],{"class":170},[164,416,417],{"class":211},"Order",[164,419,420],{"class":170}," $order) =>\n",[164,422,423,425,427],{"class":166,"line":188},[164,424,313],{"class":170},[164,426,232],{"class":174},[164,428,318],{"class":170},[164,430,431,433,435,437,439,441],{"class":166,"line":203},[164,432,324],{"class":174},[164,434,327],{"class":170},[164,436,232],{"class":174},[164,438,332],{"class":170},[164,440,335],{"class":174},[164,442,338],{"class":211},[164,444,445,447,449,451,454,456,459,461,463,466,468],{"class":166,"line":218},[164,446,324],{"class":174},[164,448,327],{"class":170},[164,450,232],{"class":174},[164,452,453],{"class":170},"items",[164,455,232],{"class":174},[164,457,458],{"class":405},"every",[164,460,409],{"class":170},[164,462,412],{"class":174},[164,464,465],{"class":170}," ($item) => $item",[164,467,232],{"class":174},[164,469,470],{"class":170},"is_in_stock)\n",[164,472,473],{"class":166,"line":223},[164,474,475],{"class":170},"    );\n",[10,477,478],{},"The important improvement is not simply that this has fewer lines. It tells a clearer story: take the orders and filter them to the ones that are ready to ship.",[10,480,481],{},"The collection remains a Collection too, so further work stays expressive:",[155,483,485],{"className":157,"code":484,"language":159,"meta":160,"style":160},"$ordersReadyToShip\n    ->groupBy('warehouse_id')\n    ->each(fn (Collection $orders, int $warehouseId) =>\n        dispatch(new PrepareWarehousePickList($warehouseId, $orders))\n    );\n",[130,486,487,492,508,533,549],{"__ignoreMap":160},[164,488,489],{"class":166,"line":167},[164,490,491],{"class":170},"$ordersReadyToShip\n",[164,493,494,496,499,501,505],{"class":166,"line":181},[164,495,402],{"class":174},[164,497,498],{"class":405},"groupBy",[164,500,409],{"class":170},[164,502,504],{"class":503},"sZZnC","'warehouse_id'",[164,506,507],{"class":170},")\n",[164,509,510,512,515,517,519,521,524,527,530],{"class":166,"line":188},[164,511,402],{"class":174},[164,513,514],{"class":405},"each",[164,516,409],{"class":170},[164,518,412],{"class":174},[164,520,249],{"class":170},[164,522,523],{"class":211},"Collection",[164,525,526],{"class":170}," $orders, ",[164,528,529],{"class":174},"int",[164,531,532],{"class":170}," $warehouseId) =>\n",[164,534,535,538,540,543,546],{"class":166,"line":203},[164,536,537],{"class":405},"        dispatch",[164,539,409],{"class":170},[164,541,542],{"class":174},"new",[164,544,545],{"class":211}," PrepareWarehousePickList",[164,547,548],{"class":170},"($warehouseId, $orders))\n",[164,550,551],{"class":166,"line":218},[164,552,475],{"class":170},[10,554,555],{},"When 'ready to ship' becomes an important business concept used throughout the application, we can take it further:",[155,557,559],{"className":157,"code":558,"language":159,"meta":160,"style":160},"$orders->readyToShip();\n",[130,560,561],{"__ignoreMap":160},[164,562,563,566,568,571],{"class":166,"line":167},[164,564,565],{"class":170},"$orders",[164,567,232],{"class":174},[164,569,570],{"class":405},"readyToShip",[164,572,573],{"class":170},"();\n",[10,575,576],{},"That method might live on a custom collection. The controller no longer needs to understand the detailed rules. It asks a question in the same language the warehouse team would use.",[10,578,579],{},"The alternative example is not a mistake. It is working code. The question is whether it gives the next developer the clearest possible expression of the problem.",[35,581,583],{"id":582},"framework-features-should-remove-accidental-complexity","Framework features should remove accidental complexity",[10,585,586],{},"The business rules in an application are usually complex enough already.",[10,588,589],{},"We need to deal with pricing, stock, customers, permissions, reporting, notifications, and integrations. We should not also have to spend unnecessary energy on repeatedly solving framework-level problems such as validation, queues, caching, or transforming lists of data.",[10,591,592],{},"Laravel gives us a strong foundation for that work.",[10,594,595],{},"When we use the framework well, our code can focus more clearly on the decisions that are unique to the application. The framework handles the common plumbing, while our own code explains what makes this particular business different.",[10,597,598],{},"That is the real value in embracing the framework: not less thought, but more thought spent in the right place.",[35,600,602],{"id":601},"this-series","This series",[10,604,605],{},"The first practical part of this series will focus on Collections.",[10,607,608],{},"Collections are one of Laravel's most useful features because they help turn a messy group of data into a clear sequence of decisions. We will start with everyday examples: filtering, grouping, and summarising real application data.",[10,610,611],{},"From there, we will look at custom collections and how they can capture meaningful business rules. Finally, we will move into Laravel Pipelines, which are useful when a complex task needs to pass through a series of small, focused steps.",[10,613,614],{},"The aim throughout will be practical examples that are useful in real applications, not examples created purely to show off a method.",[10,616,617],{},"Laravel has a lot to offer once we stop treating it as something to work around. It is worth leaning into.",[619,620,621],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":160,"searchDepth":181,"depth":181,"links":623},[624,625,626,627,628,629],{"id":37,"depth":181,"text":38},{"id":53,"depth":181,"text":54},{"id":100,"depth":181,"text":101},{"id":146,"depth":181,"text":147},{"id":582,"depth":181,"text":583},{"id":601,"depth":181,"text":602},"Laravel gives us more than convenient helpers. This is an introduction to why leaning into its conventions and tools leads to clearer, more maintainable applications.",false,"md",{},"\u002Farticles\u002Fembrace-the-framework-why-working-with-laravel-beats-working-around-it","2026-07-23",{"title":5,"description":630},"Learn why embracing Laravel's conventions, collections, pipelines, and built-in tools creates clearer, more maintainable applications.","embrace-the-framework","1","articles\u002Fembrace-the-framework-why-working-with-laravel-beats-working-around-it",[642,159,643,644],"laravel","clean-code","architecture",null,"Ivub0wwmDQfZQa9WFXhXM4-qab-r5xmkoD70d-6I2Oo",[648,653,658,663,668,673,678,683,684,688,692,696],{"path":649,"title":650,"published":651,"series":638,"seriesOrder":652},"\u002Farticles\u002Fembrace-the-framework-custom-collections-for-subscriptions","Embrace the Framework: Put Subscription Business Rules in a Custom Collection","2026-07-28","3",{"path":654,"title":655,"published":656,"series":638,"seriesOrder":657},"\u002Farticles\u002Fembrace-the-framework-customer-eligibility-with-custom-collections","Embrace the Framework: Make Eligibility Rules Read Like Business Policy","2026-08-04","6",{"path":659,"title":660,"published":661,"series":638,"seriesOrder":662},"\u002Farticles\u002Fembrace-the-framework-operational-reports-with-collections","Embrace the Framework: Build Clear Operational Reports With Collections","2026-08-02","5",{"path":664,"title":665,"published":666,"series":638,"seriesOrder":667},"\u002Farticles\u002Fembrace-the-framework-order-fulfilment-with-collections","Embrace the Framework: Make Order Fulfilment Clearer With Collections","2026-07-25","2",{"path":669,"title":670,"published":671,"series":638,"seriesOrder":672},"\u002Farticles\u002Fembrace-the-framework-order-processing-pipeline-that-can-grow","Embrace the Framework: Build an Order Processing Pipeline That Can Grow","2026-08-06","7",{"path":674,"title":675,"published":676,"series":638,"seriesOrder":677},"\u002Farticles\u002Fembrace-the-framework-order-processing-with-pipelines","Embrace the Framework: Break Order Processing Into Steps With Pipelines","2026-07-31","4",{"path":679,"title":680,"published":681,"series":638,"seriesOrder":682},"\u002Farticles\u002Fembrace-the-framework-supplier-imports-with-collections-and-pipelines","Embrace the Framework: Combine Collections and Pipelines for Reliable Supplier Imports","2026-08-08","8",{"path":634,"title":5,"published":635,"series":638,"seriesOrder":639},{"path":685,"title":686,"published":687,"series":645,"seriesOrder":645},"\u002Farticles\u002Fsimple-search-helper","A Simple Laravel Search Helper","2023-08-06",{"path":689,"title":690,"published":691,"series":645,"seriesOrder":645},"\u002Farticles\u002Fsvg-tidy-local-svg-optimiser","SVG Tidy: Clean and Optimise SVGs Locally","2026-07-21",{"path":693,"title":694,"published":695,"series":645,"seriesOrder":645},"\u002Farticles\u002Ftools-i-use","Tools I Use","2024-05-26",{"path":697,"title":698,"published":699,"series":645,"seriesOrder":645},"\u002Farticles\u002Fwelcome","Welcome","2023-08-05",1785659753486]