--- title: PHP Server SDK --- # PHP Server SDK 공식 PHP 서버 측 SDK입니다. Packagist에 [`captchala/captchala-php`](https://packagist.org/packages/captchala/captchala-php) 로 배포되어 있습니다. SDK가 대신 처리해 주는 세 가지 작업: 1. **Validate** — 브라우저 SDK에서 받은 `pt_` pass token 을 검증합니다. 2. **Issue** — 다가올 챌린지를 특정 액션 / IP / UID 에 바인딩하는 일회용 `sct_` server token 을 발급합니다. 3. **Moderate** — 대시보드가 사용하는 동일한 OpenAI 호환 파이프라인을 통한 멀티모달(텍스트 + 이미지) 콘텐츠 모더레이션을 수행합니다. ## 설치 ```bash composer require captchala/captchala-php ``` **PHP ≥ 8.0** 과 `ext-json` 이 필요합니다. cURL이 사용 가능하면 cURL을, 그렇지 않으면 `file_get_contents` 로 폴백합니다. ## Validate (`pt_` 토큰) 최종 사용자의 IP 를 세 번째 인자로 전달하십시오(`CF-Connecting-IP` / `X-Forwarded-For`, 최종적으로 `REMOTE_ADDR` 로 폴백). 선택 사항이지만 **권장**됩니다 — 추가 위험 검사에 사용됩니다. ```php use Captchala\Client; $client = new Client('your_app_key', 'your_app_secret'); $result = $client->validate($_POST['captcha_token'], false, captchala_client_ip()); if (!$result->isValid()) { http_response_code(400); exit($result->getError()); // e.g. "token_expired" } // Verification passed; proceed with the request. // $result->getCaptchaArgs() has platform / user_ip / referer / pkg / solved_at / risk_score // Real end-user IP behind a CDN / proxy function captchala_client_ip(): string { foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'] as $h) { if (!empty($_SERVER[$h])) { $ip = trim(explode(',', $_SERVER[$h])[0]); if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip; } } return ''; } ``` 가진 IP 가 없나요? `$client->validate($_POST['captcha_token'])` 를 사용하십시오 — 여전히 검증되며, IP 기반의 추가 위험 신호만 빠집니다. 원본 `server_token` 을 `bind_uid` 와 함께 발급했다면, 반환된 `uid` 를 실제로 기대하는 사용자와 비교하십시오: ```php if ($result->getUid() !== $expectedUserId) { http_response_code(400); exit('user mismatch'); } ``` ## Server token 발급 가치가 높은 플로우(로그인, 회원가입, 결제)에서 권장되는 패턴은 다음과 같습니다: 백엔드가 일회용 `sct_` 토큰을 발급하여 브라우저에 전달하고, 브라우저는 이를 `serverToken` prop 으로 전달합니다. 각 토큰은 단일 사용이며 액션에 한정되고, 발급 시점에 IP / UID 에 선택적으로 바인딩됩니다. ```php $issue = $client->issueServerToken( action: 'login', bindingIp: $request->ip(), ttl: 300, // 초; 기본값 300 maxUses: 5, // SDK 재시도 예산 bindUid: $user->id, // 검증 시 ValidateResult::getUid() 와 매칭 ); if (!$issue->isOk()) { return ['error' => $issue->getError()]; // rate_limit_exceeded, invalid_action, ... } return ['server_token' => $issue->getToken()]; // 브라우저로 전달 ``` ## 콘텐츠 모더레이션 멀티모달 — OpenAI 호환 형식의 텍스트 및 이미지 URL 조합을 입력으로 받습니다: ```php $result = $client->moderationCheck([ ['type' => 'text', 'text' => $userComment], ['type' => 'image_url', 'image_url' => ['url' => $uploadedImageUrl]], ], userId: $user->id); if (!$result->isOk()) { // 요청 오류: invalid_credentials, no_content, transport failure, ... return ['error' => $result->getError()]; } if ($result->isFlagged()) { // 업스트림 모델이 플래그를 지정한 경우; 사유는 카테고리에서 확인 if ($result->hasCategory('violence', 'csam')) { // 강제 차단 } } ``` 순수 텍스트용 단축 메서드: ```php $result = $client->moderationText('user comment here', userId: $user->id); ``` 카테고리는 모델이 정의합니다(예: `violence`, `hate`, `sexual`, `self-harm`). 고정된 집합을 하드코딩하는 대신 `getCategories()` 를 방어적으로 순회하십시오. ## 결과 클래스 | Class | Methods | |---|---| | `ValidateResult` | `isValid()`, `getError()`, `getUid()`, `getChallengeId()`, `getAction()`, `isOffline()`, `isClientOnly()`, `getWarning()`, `toArray()` | | `IssueResult` | `isOk()`, `getToken()`, `getExpiresIn()`, `getIssuedAt()`, `getError()`, `getMessage()`, `toArray()` | | `ModerationResult` | `isOk()`, `isFlagged()`, `hasCategory(...$names)`, `getCategories()`, `getContentType()`, `getRaw()`, `getError()`, `getMessage()`, `toArray()` | ## 링크 - [Packagist](https://packagist.org/packages/captchala/captchala-php) · [GitHub](https://github.com/Captcha-La/captchala-php) - [Web SDK 개요](/ko/web-sdk) · [API 레퍼런스](/ko/api-reference)