# 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

Create a generic sync utility:

```php
private function syncSubEntities(
    int $memberId,
    array $incoming,
    callable $findByMemberId,
    callable $add,
    callable $update,
    callable $delete
): void {
    // Generic sync logic
}
```

Or create a `SubEntitySyncService` for reusability across entities.

## Tasks

- [ ] Extract common sync logic into a generic method or service
- [ ] Refactor `syncAddresses()`, `syncPhones()`, `syncEmails()` to use the new pattern
- [ ] Ensure identical behavior is preserved (test thoroughly)
- [ ] Document the generic sync algorithm

## Labels

- refactoring
- backend
- priority:medium