fix(profile): sync User.real_name from profile name on create/update
When a user completes their profile (POST/PATCH /api/v1/user-profile), the name was only stored on the profile (label/family) and User.real_name stayed empty — so users who registered via booking showed with no name in the admin user list. hydrate() now mirrors the profile's full name (label + family) onto the owning User.realName, persisted in the same flush. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# همگامسازی نام کاربر (User.real_name) هنگام تکمیل پروفایل
|
||||
|
||||
## پروژه
|
||||
|
||||
`clinicpro` (Backend).
|
||||
|
||||
> کاملاً backend است. سایت عمومی `nobat724_front` همان `PATCH/POST /api/v1/user-profile` را با فیلدهای `name`/`label` و `family` صدا میزند؛ قرارداد API تغییر نمیکند، فقط یک side-effect سرور اضافه میشود. نیازی به تغییر frontend نیست.
|
||||
|
||||
## زمینه
|
||||
|
||||
در پنل ادمین، لیست کاربران (`/api/v1/admin/users`) ستون «نام» را از `User.realName` (`users.real_name`) میخواند. اما وقتی کاربری از طریق نوبتدهی ثبتنام و پروفایلش را تکمیل میکند، نام فقط در `UserProfile` (`profiles.label` و `profiles.family`) ذخیره میشود و `users.real_name` خالی میماند. نتیجه: کاربرانِ ثبتنامشده از مسیر نوبتدهی در لیست ادمین **بدون نام** نمایش داده میشوند.
|
||||
|
||||
## مشکل / هدف
|
||||
|
||||
هنگام ایجاد/بهروزرسانی پروفایل، اگر `name`/`label` یا `family` تغییر کند، `User.realName` صاحبِ پروفایل هم با «نام کامل» (ترکیب نام و نام خانوادگی) همگام شود تا در پنل ادمین و هر جای دیگری که `real_name` را میخواند درست نمایش داده شود.
|
||||
|
||||
## فایلهای مرتبط
|
||||
|
||||
| فایل | نقش |
|
||||
|------|-----|
|
||||
| `src/UserProfile/Controller/UserProfileController.php` | `hydrate()` فقط Profile را پر میکند؛ باید `User.realName` را هم sync کند |
|
||||
| `src/UserProfile/Entity/UserProfile.php` | `getUser()`، `getLabel()`، `getFamily()` (مرجع داده) |
|
||||
| `src/Auth/Entity/User.php` | `setRealName()` (موجود) |
|
||||
|
||||
## وضعیت فعلی (کد واقعی)
|
||||
|
||||
`hydrate()` فقط روی `UserProfile` کار میکند — `User.realName` هرگز ست نمیشود:
|
||||
```php
|
||||
private function hydrate(UserProfile $profile, array $data): void
|
||||
{
|
||||
if (array_key_exists('name', $data)) $profile->setLabel($data['name']);
|
||||
if (array_key_exists('label', $data)) $profile->setLabel($data['label']);
|
||||
if (array_key_exists('family', $data)) $profile->setFamily($data['family']);
|
||||
// ... بقیه فیلدها
|
||||
}
|
||||
```
|
||||
|
||||
و `create()` / `update()` بعد از `hydrate` فقط `$this->repository->save($profile)` را صدا میزنند.
|
||||
|
||||
مرجع: لیست ادمین `u.realName as name` را برمیگرداند (`AdminApiController::users`)، پس `real_name` خالی = نام خالی در پنل.
|
||||
|
||||
## وظایف
|
||||
|
||||
### ۱. همگامسازی `User.realName` در `hydrate`
|
||||
|
||||
در انتهای `hydrate()` (بعد از اعمال همهی فیلدها روی profile)، اگر `name`/`label` یا `family` در ورودی بود، `real_name` کاربر را از مقدار **نهاییِ** پروفایل بساز:
|
||||
|
||||
```php
|
||||
private function hydrate(UserProfile $profile, array $data): void
|
||||
{
|
||||
// ... ستکردن فیلدهای profile (بدون تغییر) ...
|
||||
|
||||
if (array_key_exists('name', $data) || array_key_exists('label', $data) || array_key_exists('family', $data)) {
|
||||
$fullName = trim(implode(' ', array_filter([$profile->getLabel(), $profile->getFamily()])));
|
||||
$profile->getUser()->setRealName($fullName !== '' ? $fullName : null);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- منبع، مقدار نهاییِ خودِ `profile` است (نه آرایهی `$data` خام) تا اگر فقط یکی از `name`/`family` در PATCH آمده باشد، نام کامل با مقدار موجودِ دیگری بهدرستی ساخته شود.
|
||||
- `array_filter` بخشهای خالی را حذف میکند (مثلاً وقتی فقط `name` پر است). اگر نتیجه خالی شد، `null` (هماهنگ با امضای `setRealName(?string)`).
|
||||
|
||||
### ۲. اطمینان از flush شدن تغییر `User`
|
||||
|
||||
`create()` و `update()` بعد از `hydrate` `$this->repository->save($profile)` را صدا میزنند که `flush=true` است. چون `User` و `UserProfile` در همان Unit of Work مدیریت میشوند، تغییر `realName` در همان flush ذخیره میشود — نیازی به save جداگانهی User نیست. فقط **تأیید کن** که `UserProfileRepository::save` واقعاً flush میکند (میکند) و اگر جایی `flush=false` پاس داده شده، اصلاح کن.
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **قرارداد API تغییر نمیکند** — همان فیلدهای `name`/`label`/`family` ورودی؛ فقط side-effect روی `User.realName`.
|
||||
- این منطق هم در ایجاد (`POST`) و هم بهروزرسانی (`PATCH`) باید اجرا شود — چون هر دو از همان `hydrate` استفاده میکنند، یکجا اصلاح کافی است.
|
||||
- نام کامل = `label + ' ' + family`؛ اگر محصول ترجیح دیگری دارد (مثلاً فقط `label`)، در پیادهسازی همان را اعمال کن — ولی چون لیست ادمین یک ستون واحد «نام» دارد، نام کامل طبیعیتر است.
|
||||
- این فقط نام را روی **صاحبِ پروفایل** ست میکند؛ پروفایلِ «شخص دیگر» (که بیمارِ غیرِخود است) از مسیر دیگری (`patient_name` در نوبت) مدیریت میشود و نباید `real_name` کاربر لاگین را تغییر دهد — `hydrate` همیشه روی پروفایلِ همان کاربر کار میکند، پس مشکلی نیست.
|
||||
- تاریخها/الگوها طبق `BaseController`. بعد از تغییر: `ddev exec php -l` روی کنترلر. migration لازم نیست (فیلد `real_name` از قبل وجود دارد).
|
||||
- تست رفتاری: یک کاربر بدون `real_name` بساز/پیدا کن → `PATCH /api/v1/user-profile/{userUuid}` با `{name, family}` → تأیید کن `users.real_name` پر شد و `GET /api/v1/admin/users` آن را در `name` نشان میدهد. همچنین PATCH فقط با `family` → نام کامل با `label` موجود ساخته شود.
|
||||
- مستندسازی: اگر رفتار قرارداد را تغییر نمیدهد، `docs/api/` نیاز به تغییر بزرگ ندارد؛ ولی میتوان در سند پروفایل (اگر وجود دارد) یک جمله دربارهی sync نام افزود.
|
||||
@@ -182,5 +182,12 @@ class UserProfileController extends BaseController
|
||||
: $data['other'];
|
||||
$profile->setOther($other);
|
||||
}
|
||||
|
||||
// Keep the owning user's display name (real_name) in sync with the profile,
|
||||
// so admin user listings and anywhere reading real_name show a name.
|
||||
if (array_key_exists('name', $data) || array_key_exists('label', $data) || array_key_exists('family', $data)) {
|
||||
$fullName = trim(implode(' ', array_filter([$profile->getLabel(), $profile->getFamily()])));
|
||||
$profile->getUser()->setRealName($fullName !== '' ? $fullName : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user