Cherry-picking across repos and the three bugs that came with it
The Starting Point
I'm working on a multi-tenant Laravel platform with a modular architecture — separate modules for accounts, data imports, notifications, and a customer portal. The wrinkle: I maintain two repos for two different deployments, and features get built on one then cherry-picked to the other.
Earlier today I'd shipped a big data import feature (that's a whole separate post). This session was about cleaning up the aftermath — fixing bugs that surfaced in production after that deploy — and then cherry-picking a parent-child account feature from the other repo.
What Actually Happened
Bug 1: The buttons that did nothing
This one was embarrassing. I'd just built an entire data import page — forms, AJAX lookups, CSV upload, the works. Shipped it. Went to test it properly and... none of the buttons worked. The add form wouldn't open. The CSV upload modal was dead. Everything looked right in the HTML but zero JavaScript was executing.
The problem? Blade directive mismatch. The layout uses @stack('scripts') to render page-level JavaScript, but in the new view I'd written @section('scripts'). They look almost identical but they're completely incompatible — @section writes to a section, @stack reads from a stack. The JS was being registered in a section that nobody was reading.
The fix was two lines:
-@section('scripts')
+@push('scripts')
<script>
// 700 lines of JavaScript that were silently going nowhere
</script>
-@endsection
+@endpush
I've hit this before and I'll hit it again. There's no error, no warning, nothing in the console. The page just renders without your scripts. It's the kind of thing you only catch by actually clicking buttons in the browser.
Bug 2: toArray() killed my models
A notifications index page has a column showing which site each record belongs to. After the data import deploy, it was throwing errors when you clicked a site link — or worse, silently showing broken links.
Here's what happened. I was building a map of reference-to-site so the view could link each record to its site:
$refSiteMap = $refRecords->mapWithKeys(
fn($r) => [$r->reference => $r->site]
)->toArray();
See it? toArray() on a Laravel collection converts everything — including the Site model values — into plain arrays. So in the view, $matchedSite->id and $matchedSite->name were failing because $matchedSite wasn't a Site model anymore, it was an array.
The fix: swap ->toArray() for ->all(). The all() method gives you a plain PHP array of the collection's items but doesn't recursively convert the values. The Site models stay as models.
-$refSiteMap = ...->toArray();
+$refSiteMap = ...->all();
One character's difference in the method name. Completely different behaviour.
Bug 3: Orphaned records and a missing null check
The notifications page also had a route generation error. When you'd display a site link for a record, the code assumed the site always existed:
<a href="{{ route('sites.show', $matchedSite) }}">
But some reference records are orphaned — the site they pointed to had been deleted, but the mapping record survived. So $matchedSite was a model with null fields, and the route helper choked trying to generate a URL.
Added a null check on the site ID:
-@if($matchedSite)
+@if($matchedSite && $matchedSite->id)
Bonus: the cherry-pick merge conflict that wasn't
Earlier in the day (on the other repo), the big data import cherry-pick had introduced a duplicate method. The cherry-pick brought across a lookupRecord() method that already existed from a previous merge. The error log was clear:
Call to undefined method SiteController::lookupRecord()
Wait — that's "undefined", not "duplicate"? The cherry-pick had actually created a duplicate method definition which caused a PHP parse error, making the entire controller fail to load. So every method on that controller was "undefined". The fix was just deleting the duplicate 31-line method.
The parent-child feature
With the bugs sorted, the last task was cherry-picking the parent-child account support for the customer portal. This was the big feature: when a customer logs in and they're linked to a parent account, they should see data aggregated from all child accounts too.
The key change was making accountIds() recursive:
private function accountIds(): array
{
$directIds = $this->customer()->accounts()
->pluck('accounts.id')->toArray();
$allIds = $directIds;
$parentIds = $directIds;
while (!empty($parentIds)) {
$childIds = Account::whereIn('parent_id', $parentIds)
->pluck('id')->toArray();
$allIds = array_merge($allIds, $childIds);
$parentIds = $childIds;
}
return array_unique($allIds);
}
Once that method returns all account IDs in the hierarchy, every downstream query — sites, contacts, cases, contracts, quotes — automatically aggregates across the full tree. The account detail page also got an "Accounts" tab showing parent, siblings, and children with coloured icons and badges.
This cherry-picked cleanly from the other repo — no conflicts, no fixups needed. A nice change of pace.
What Worked
Six commits in this session, covering the whole spectrum:
- 3 bug fixes:
@pushvs@section,toArray()vsall(), null site check - 1 merge cleanup: duplicate method removal from a cherry-pick
- 1 feature: parent-child account hierarchy in the customer portal
- 1 infrastructure task: provider lookup page and onboarding improvements
Everything was built on one repo, cherry-picked to the other, and pushed to both remotes. The multi-repo workflow is a bit of overhead but it means both deployments stay in sync.
What I Learned
@sectionand@pushlook the same but are completely different systems. Blade gives you zero feedback when you use the wrong one. If your page-level scripts silently vanish, check this first.toArray()is recursive and destructive. It doesn't just convert the collection to an array — it converts every model inside it too. Useall()when you want a plain array but need to keep your model instances intact.- Cherry-picking between repos creates duplicate method landmines. When you cherry-pick a commit that adds a method, and that method already exists from a previous merge, PHP doesn't give you a "duplicate method" error — it gives you a parse error that makes the entire controller fail. The error message about an "undefined method" is misleading because it's actually a parse failure.
- Orphaned foreign key records are inevitable. If you're building links from a mapping table, always check that the target record still exists. Soft deletes help, but nullable foreign keys without cascade deletes will always leave orphans eventually.
Tech Notes
- Stack: Laravel 11, modular architecture (nwidart/laravel-modules), Blade templates
- Multi-repo workflow: two remotes, features built on one, cherry-picked to the other
- Key pattern:
Collection::all()vsCollection::toArray()— critical distinction when collection values are Eloquent models - Blade gotcha:
@stack/@pushvs@yield/@section— not interchangeable, fails silently
Zero Focus Dev
Breaking things, learning fast, sharing it all.