# Issue #204: Duplicate Sub-Entity Sync Methods ## Problem `MemberService` contains three nearly identical methods for syncing sub-entities: - `syncAddresses()` - `syncPhones()` - `syncEmails()` They all follow the same pattern: 1. Fetch existing entities 2. Index by ID 3. Update kept IDs, create new ones 4. Delete removed IDs This violates DRY (Don't Repeat Yourself) and makes maintenance harder. ## Current Code ```php private function syncAddresses(int $memberId, array $incoming): void { $existing = $this->addressMapper->findByMemberId($memberId); $existingById = []; foreach ($existing as $addr) { $existingById[$addr->getId()] = $addr; } $keptIds = []; foreach ($incoming as $item) { $id = isset($item['id']) ? (int)$item['id'] : 0; if ($id > 0 && isset($existingById[$id])) { $this->updateAddress($id, $item); $keptIds[$id] = true; } else { $this->addAddress($memberId, $item); } } foreach ($existingById as $id => $addr) { if (!isset($keptIds[$id])) { $this->deleteAddress($id); } } } // syncPhones() and syncEmails() are identical except for Address → Phone/Email ``` ## Solution Extract the common sync logic into a single private generic method that accepts callables for the mapper-specific operations. Each of the three sync methods becomes a thin 10-line wrapper calling the generic method. ## Tasks - [ ] Extract common sync logic into `syncSubEntities()` private generic method - [ ] Refactor `syncAddresses()`, `syncPhones()`, `syncEmails()` to use the new pattern - [ ] Ensure identical behavior is preserved (all existing tests must pass) - [ ] Run `make test` — all must pass - [ ] Commit, push, create PR via tea, merge it - [ ] Move plan file from `.plans/open/` to `.plans/done/` - [ ] Check out main again ## Labels - refactoring - backend - priority:medium