update([...])` 호출 시 * - 대상 행을 preload (1 SELECT) * - 각 행별로 `calculateUserOverridesFor($values)` 호출하여 user_overrides 계산 * - per-row UPDATE 를 parent::update() 로 수행 (재귀 없이) * - 시더 컨텍스트(`user_overrides.seeding`) 에서는 기본 경로 유지 * - 입력 $values 에 trackable 필드가 하나도 없으면 계산 건너뛰고 기본 경로 (성능 최적화) * * 성능 트레이드오프: * - 기본: 1 UPDATE * - 추적 활성: 1 SELECT + N UPDATE (trackable 필드 포함 시에만) * - 관리자 UI 일괄 저장(수십 행 이하) 대상에는 차이 무시 가능. * - 대량 처리는 `DB::table()->update()` (Eloquent 우회) 권장. * * @since 7.0.0-beta.2 */ class UserOverridesAwareBuilder extends Builder { /** * 쿼리 조건에 부합하는 모델을 업데이트합니다. * * @param array $values * @return int 영향받은 행 수 */ public function update(array $values) { // 시더 컨텍스트에서는 trait 이벤트 handler 가 어차피 early return 하므로 // 성능을 위해 기본 mass update 경로 사용. if (app()->bound('user_overrides.seeding') && app('user_overrides.seeding') === true) { return parent::update($values); } $model = $this->getModel(); $trackable = method_exists($model, 'getTrackableFields') ? $model->getTrackableFields() : []; // 입력에 trackable 필드가 하나도 없으면 user_overrides 갱신 불필요 — 기본 경로. if (empty($trackable) || empty(array_intersect(array_keys($values), $trackable))) { return parent::update($values); } $models = $this->get(); if ($models->isEmpty()) { return 0; } $affected = 0; $keyName = $model->getKeyName(); foreach ($models as $row) { $newOverrides = $row->calculateUserOverridesFor($values); $rowValues = $values; // user_overrides 에 변경이 있으면 UPDATE payload 에 포함 (json cast 는 Eloquent 가 처리) if ($newOverrides !== ($row->user_overrides ?? [])) { $rowValues['user_overrides'] = $newOverrides; } // 재귀 없이 기본 UPDATE — PK 단일 행 UPDATE $affected += $row->newQueryWithoutScopes() ->where($keyName, $row->getKey()) ->toBase() ->update($this->prepareValuesForRow($rowValues, $row)); } return $affected; } /** * array-cast 필드를 DB 저장 형식(JSON 문자열)으로 변환합니다. * * Eloquent Builder::update 는 toBase() 로 진입 시 array 값을 자동 JSON 인코딩하지 * 않으므로, 본 메서드에서 모델의 cast 설정을 참조하여 사전 변환합니다. * * @param array $values * @param \Illuminate\Database\Eloquent\Model $model * @return array */ private function prepareValuesForRow(array $values, $model): array { foreach ($values as $key => $value) { if (is_array($value) || is_object($value)) { // array/json cast 필드는 JSON 인코딩 후 저장 $values[$key] = json_encode($value); } } return $values; } }