extra = $extra; } public static function fromRequest(Request $request): static { $page = max(1, (int)$request->input('page', 1)); $perPage = max(1, (int)$request->input('per_page', $request->input('perPage', DEFAULT_LIST_PER_PAGE))); $pageCount = max(1, (int)$request->input('page_count', $request->input('pageCount', DEFAULT_LIST_PAGE_COUNT))); $offset = ($page - 1) * $perPage; $dto = new self( page: $page, perPage: $perPage, pageCount: $pageCount, offset: $offset, sort: (int)$request->input('sort', 1), field: $request->input('field'), keyword: $request->input('keyword'), startDate: $request->input('start_date'), endDate: $request->input('end_date') ); $merged = array_merge($request->route()?->parameters() ?? [] , $request->all()); foreach ($merged as $key => $value) { if (!property_exists($dto, $key)) { $dto->{$key} = $value; } } return $dto; } public function __set(string $name, mixed $value): void { // readonly core 필드 오염 방지 if (property_exists($this, $name)) { throw new \LogicException("Cannot set '{$name}' on SearchData (readonly property)."); } $this->extra[$name] = $value; } public function __get(string $name): mixed { return $this->extra[$name] ?? null; } public function __isset(string $name): bool { return isset($this->extra[$name]); } public function toResponse($request): JsonResponse { return response()->json([ 'page' => $this->page, 'perPage' => $this->perPage, 'pageCount' => $this->pageCount, 'offset' => $this->offset, 'sort' => $this->sort, 'field' => $this->field, 'keyword' => $this->keyword, 'startDate' => $this->startDate, 'endDate' => $this->endDate, ...$this->extra ]); } }