[{"data":1,"prerenderedAt":725},["ShallowReactive",2],{"\u002Farticles\u002Fsimple-search-helper":3},{"id":4,"title":5,"body":6,"description":712,"draft":713,"extension":714,"meta":715,"navigation":41,"path":716,"published":717,"seo":718,"seoDescription":719,"stem":720,"tags":721,"updated":723,"__hash__":724},"articles\u002Farticles\u002Fsimple-search-helper.md","A Simple Laravel Search Helper",{"type":7,"value":8,"toc":710},"minimark",[9,13,706],[10,11,12],"p",{},"I wanted to share a simple search helper trait that I had to write to clean up some search code recently, in the hopes that it may be useful and also that it may help some newer laravel developers who are trying to understand how to write cleaner code.",[14,15,20],"pre",{"className":16,"code":17,"language":18,"meta":19,"style":19},"language-php shiki shiki-themes github-light github-dark","\u003C?php\n\nnamespace App\\Traits;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Support\\Arr;\n\n\u002F**\n * Used for simple api search\n *\n * Expected shape of  search would be\n *\n * search: {\n *      relation1: {\n *          relation2: {\n *              attr: 'value'\n *          }\n *      }\n * }\n *\n * handles multiple levels of nesting as long as the relations are loaded\n * eg. Model::with('relation1.relation2')->...\n *\u002F\ntrait NestedRelationSearch\n{\n\n    \u002F**\n     * used for simple controller search\n     *\n     * example:\n     * ...\n     * ->when(\n     *      request('search'),\n     *      fn (Builder $query) => $this->search($query)\n     * )\n     * ...\n     *\n     * @param Builder $query\n     * @return Builder\n     *\u002F\n    private function search(Builder $query): Builder\n    {\n        $search = Arr::dot(\n            json_decode(request('search'), true)\n        );\n\n        $search = collect($search)->filter(fn ($item) => $item != null);\n\n        foreach ($search as $attribute => $searchTerm) {\n            $query->when(\n                str_contains($attribute, '.'),\n                function (Builder $query) use ($attribute, $searchTerm) {\n                    $bits = explode('.', $attribute);\n                    $relationAttribute = array_pop($bits);\n                    $relationName = implode('.', $bits);\n\n                    $query->whereHas($relationName, function (Builder $query) use ($relationAttribute, $searchTerm) {\n                        $query->where($relationAttribute, 'LIKE', \"%{$searchTerm}%\");\n                    });\n                },\n                function (Builder $query) use ($attribute, $searchTerm) {\n                    $query->where($attribute, 'LIKE', \"%{$searchTerm}%\");\n                }\n            );\n        }\n\n        return $query;\n    }\n}\n","php","",[21,22,23,36,43,57,62,73,83,88,95,101,107,113,118,124,130,136,142,148,154,160,165,171,177,183,192,198,203,209,215,221,227,233,239,245,251,257,262,267,282,293,299,325,331,352,378,384,389,425,430,451,464,479,498,516,530,548,553,581,612,618,624,639,662,668,674,680,685,694,700],"code",{"__ignoreMap":19},[24,25,28,32],"span",{"class":26,"line":27},"line",1,[24,29,31],{"class":30},"szBVR","\u003C?",[24,33,35],{"class":34},"sj4cs","php\n",[24,37,39],{"class":26,"line":38},2,[24,40,42],{"emptyLinePlaceholder":41},true,"\n",[24,44,46,49,53],{"class":26,"line":45},3,[24,47,48],{"class":30},"namespace",[24,50,52],{"class":51},"sScJk"," App\\Traits",[24,54,56],{"class":55},"sVt8B",";\n",[24,58,60],{"class":26,"line":59},4,[24,61,42],{"emptyLinePlaceholder":41},[24,63,65,68,71],{"class":26,"line":64},5,[24,66,67],{"class":30},"use",[24,69,70],{"class":34}," Illuminate\\Database\\Eloquent\\Builder",[24,72,56],{"class":55},[24,74,76,78,81],{"class":26,"line":75},6,[24,77,67],{"class":30},[24,79,80],{"class":34}," Illuminate\\Support\\Arr",[24,82,56],{"class":55},[24,84,86],{"class":26,"line":85},7,[24,87,42],{"emptyLinePlaceholder":41},[24,89,91],{"class":26,"line":90},8,[24,92,94],{"class":93},"sJ8bj","\u002F**\n",[24,96,98],{"class":26,"line":97},9,[24,99,100],{"class":93}," * Used for simple api search\n",[24,102,104],{"class":26,"line":103},10,[24,105,106],{"class":93}," *\n",[24,108,110],{"class":26,"line":109},11,[24,111,112],{"class":93}," * Expected shape of  search would be\n",[24,114,116],{"class":26,"line":115},12,[24,117,106],{"class":93},[24,119,121],{"class":26,"line":120},13,[24,122,123],{"class":93}," * search: {\n",[24,125,127],{"class":26,"line":126},14,[24,128,129],{"class":93}," *      relation1: {\n",[24,131,133],{"class":26,"line":132},15,[24,134,135],{"class":93}," *          relation2: {\n",[24,137,139],{"class":26,"line":138},16,[24,140,141],{"class":93}," *              attr: 'value'\n",[24,143,145],{"class":26,"line":144},17,[24,146,147],{"class":93}," *          }\n",[24,149,151],{"class":26,"line":150},18,[24,152,153],{"class":93}," *      }\n",[24,155,157],{"class":26,"line":156},19,[24,158,159],{"class":93}," * }\n",[24,161,163],{"class":26,"line":162},20,[24,164,106],{"class":93},[24,166,168],{"class":26,"line":167},21,[24,169,170],{"class":93}," * handles multiple levels of nesting as long as the relations are loaded\n",[24,172,174],{"class":26,"line":173},22,[24,175,176],{"class":93}," * eg. Model::with('relation1.relation2')->...\n",[24,178,180],{"class":26,"line":179},23,[24,181,182],{"class":93}," *\u002F\n",[24,184,186,189],{"class":26,"line":185},24,[24,187,188],{"class":30},"trait",[24,190,191],{"class":51}," NestedRelationSearch\n",[24,193,195],{"class":26,"line":194},25,[24,196,197],{"class":55},"{\n",[24,199,201],{"class":26,"line":200},26,[24,202,42],{"emptyLinePlaceholder":41},[24,204,206],{"class":26,"line":205},27,[24,207,208],{"class":93},"    \u002F**\n",[24,210,212],{"class":26,"line":211},28,[24,213,214],{"class":93},"     * used for simple controller search\n",[24,216,218],{"class":26,"line":217},29,[24,219,220],{"class":93},"     *\n",[24,222,224],{"class":26,"line":223},30,[24,225,226],{"class":93},"     * example:\n",[24,228,230],{"class":26,"line":229},31,[24,231,232],{"class":93},"     * ...\n",[24,234,236],{"class":26,"line":235},32,[24,237,238],{"class":93},"     * ->when(\n",[24,240,242],{"class":26,"line":241},33,[24,243,244],{"class":93},"     *      request('search'),\n",[24,246,248],{"class":26,"line":247},34,[24,249,250],{"class":93},"     *      fn (Builder $query) => $this->search($query)\n",[24,252,254],{"class":26,"line":253},35,[24,255,256],{"class":93},"     * )\n",[24,258,260],{"class":26,"line":259},36,[24,261,232],{"class":93},[24,263,265],{"class":26,"line":264},37,[24,266,220],{"class":93},[24,268,270,273,276,279],{"class":26,"line":269},38,[24,271,272],{"class":93},"     * ",[24,274,275],{"class":30},"@param",[24,277,278],{"class":34}," Builder",[24,280,281],{"class":93}," $query\n",[24,283,285,287,290],{"class":26,"line":284},39,[24,286,272],{"class":93},[24,288,289],{"class":30},"@return",[24,291,292],{"class":34}," Builder\n",[24,294,296],{"class":26,"line":295},40,[24,297,298],{"class":93},"     *\u002F\n",[24,300,302,305,308,311,314,317,320,323],{"class":26,"line":301},41,[24,303,304],{"class":30},"    private",[24,306,307],{"class":30}," function",[24,309,310],{"class":51}," search",[24,312,313],{"class":55},"(",[24,315,316],{"class":34},"Builder",[24,318,319],{"class":55}," $query)",[24,321,322],{"class":30},":",[24,324,292],{"class":34},[24,326,328],{"class":26,"line":327},42,[24,329,330],{"class":55},"    {\n",[24,332,334,337,340,343,346,349],{"class":26,"line":333},43,[24,335,336],{"class":55},"        $search ",[24,338,339],{"class":30},"=",[24,341,342],{"class":34}," Arr",[24,344,345],{"class":30},"::",[24,347,348],{"class":51},"dot",[24,350,351],{"class":55},"(\n",[24,353,355,358,360,363,365,369,372,375],{"class":26,"line":354},44,[24,356,357],{"class":34},"            json_decode",[24,359,313],{"class":55},[24,361,362],{"class":51},"request",[24,364,313],{"class":55},[24,366,368],{"class":367},"sZZnC","'search'",[24,370,371],{"class":55},"), ",[24,373,374],{"class":34},"true",[24,376,377],{"class":55},")\n",[24,379,381],{"class":26,"line":380},45,[24,382,383],{"class":55},"        );\n",[24,385,387],{"class":26,"line":386},46,[24,388,42],{"emptyLinePlaceholder":41},[24,390,392,394,396,399,402,405,408,410,413,416,419,422],{"class":26,"line":391},47,[24,393,336],{"class":55},[24,395,339],{"class":30},[24,397,398],{"class":51}," collect",[24,400,401],{"class":55},"($search)",[24,403,404],{"class":30},"->",[24,406,407],{"class":51},"filter",[24,409,313],{"class":55},[24,411,412],{"class":30},"fn",[24,414,415],{"class":55}," ($item) => $item ",[24,417,418],{"class":30},"!=",[24,420,421],{"class":34}," null",[24,423,424],{"class":55},");\n",[24,426,428],{"class":26,"line":427},48,[24,429,42],{"emptyLinePlaceholder":41},[24,431,433,436,439,442,445,448],{"class":26,"line":432},49,[24,434,435],{"class":30},"        foreach",[24,437,438],{"class":55}," ($search ",[24,440,441],{"class":30},"as",[24,443,444],{"class":55}," $attribute ",[24,446,447],{"class":30},"=>",[24,449,450],{"class":55}," $searchTerm) {\n",[24,452,454,457,459,462],{"class":26,"line":453},50,[24,455,456],{"class":55},"            $query",[24,458,404],{"class":30},[24,460,461],{"class":51},"when",[24,463,351],{"class":55},[24,465,467,470,473,476],{"class":26,"line":466},51,[24,468,469],{"class":34},"                str_contains",[24,471,472],{"class":55},"($attribute, ",[24,474,475],{"class":367},"'.'",[24,477,478],{"class":55},"),\n",[24,480,482,485,488,490,493,495],{"class":26,"line":481},52,[24,483,484],{"class":30},"                function",[24,486,487],{"class":55}," (",[24,489,316],{"class":34},[24,491,492],{"class":55}," $query) ",[24,494,67],{"class":30},[24,496,497],{"class":55}," ($attribute, $searchTerm) {\n",[24,499,501,504,506,509,511,513],{"class":26,"line":500},53,[24,502,503],{"class":55},"                    $bits ",[24,505,339],{"class":30},[24,507,508],{"class":34}," explode",[24,510,313],{"class":55},[24,512,475],{"class":367},[24,514,515],{"class":55},", $attribute);\n",[24,517,519,522,524,527],{"class":26,"line":518},54,[24,520,521],{"class":55},"                    $relationAttribute ",[24,523,339],{"class":30},[24,525,526],{"class":34}," array_pop",[24,528,529],{"class":55},"($bits);\n",[24,531,533,536,538,541,543,545],{"class":26,"line":532},55,[24,534,535],{"class":55},"                    $relationName ",[24,537,339],{"class":30},[24,539,540],{"class":34}," implode",[24,542,313],{"class":55},[24,544,475],{"class":367},[24,546,547],{"class":55},", $bits);\n",[24,549,551],{"class":26,"line":550},56,[24,552,42],{"emptyLinePlaceholder":41},[24,554,556,559,561,564,567,570,572,574,576,578],{"class":26,"line":555},57,[24,557,558],{"class":55},"                    $query",[24,560,404],{"class":30},[24,562,563],{"class":51},"whereHas",[24,565,566],{"class":55},"($relationName, ",[24,568,569],{"class":30},"function",[24,571,487],{"class":55},[24,573,316],{"class":34},[24,575,492],{"class":55},[24,577,67],{"class":30},[24,579,580],{"class":55}," ($relationAttribute, $searchTerm) {\n",[24,582,584,587,589,592,595,598,601,604,607,610],{"class":26,"line":583},58,[24,585,586],{"class":55},"                        $query",[24,588,404],{"class":30},[24,590,591],{"class":51},"where",[24,593,594],{"class":55},"($relationAttribute, ",[24,596,597],{"class":367},"'LIKE'",[24,599,600],{"class":55},", ",[24,602,603],{"class":367},"\"%{",[24,605,606],{"class":55},"$searchTerm",[24,608,609],{"class":367},"}%\"",[24,611,424],{"class":55},[24,613,615],{"class":26,"line":614},59,[24,616,617],{"class":55},"                    });\n",[24,619,621],{"class":26,"line":620},60,[24,622,623],{"class":55},"                },\n",[24,625,627,629,631,633,635,637],{"class":26,"line":626},61,[24,628,484],{"class":30},[24,630,487],{"class":55},[24,632,316],{"class":34},[24,634,492],{"class":55},[24,636,67],{"class":30},[24,638,497],{"class":55},[24,640,642,644,646,648,650,652,654,656,658,660],{"class":26,"line":641},62,[24,643,558],{"class":55},[24,645,404],{"class":30},[24,647,591],{"class":51},[24,649,472],{"class":55},[24,651,597],{"class":367},[24,653,600],{"class":55},[24,655,603],{"class":367},[24,657,606],{"class":55},[24,659,609],{"class":367},[24,661,424],{"class":55},[24,663,665],{"class":26,"line":664},63,[24,666,667],{"class":55},"                }\n",[24,669,671],{"class":26,"line":670},64,[24,672,673],{"class":55},"            );\n",[24,675,677],{"class":26,"line":676},65,[24,678,679],{"class":55},"        }\n",[24,681,683],{"class":26,"line":682},66,[24,684,42],{"emptyLinePlaceholder":41},[24,686,688,691],{"class":26,"line":687},67,[24,689,690],{"class":30},"        return",[24,692,693],{"class":55}," $query;\n",[24,695,697],{"class":26,"line":696},68,[24,698,699],{"class":55},"    }\n",[24,701,703],{"class":26,"line":702},69,[24,704,705],{"class":55},"}\n",[707,708,709],"style",{},"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 pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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":19,"searchDepth":38,"depth":38,"links":711},[],"A simple example to demonstrate how to build a trait that you can reuse in your Laravel controllers to handle nested relation search automagically. I'll also explain why you would use this and not write the logic onto a model trait instead",false,"md",{},"\u002Farticles\u002Fsimple-search-helper","2023-08-06",{"title":5,"description":712},"Build a reusable Laravel trait for nested relation searches in controllers, with cleaner query logic and guidance on keeping it out of model traits.","articles\u002Fsimple-search-helper",[722,18],"laravel",null,"FLb-eCXd674Tfa66PUJ-iaW_XcL1a1WouBg9_VDAfOk",1784640747543]