` โ†’ `Api::actor()`๏ผ› * ยท **ๅ–ๅ‚**๏ผš`Api::param()`๏ผˆJSON body ไธŽ่กจๅ•้ƒฝ่ƒฝๅƒ๏ผ‰๏ผ› * ยท **ๅ‡บๅ‚**๏ผš`Api::ok() / Api::fail()`๏ผˆ็ปŸไธ€ไฟกๅฐ + HTTP ็ ๏ผ‰๏ผ› * ยท **ๅŠจไฝœๅฎˆๅซ**๏ผš`Api::requireWrite()`๏ผˆๅช่ฏป่ดฆๅทไธ่ƒฝๅ†™๏ผŒไธŽ็ฝ‘้กตไธ€่‡ด๏ผ‰ใ€‚ * * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• * ็ปŸไธ€ๅ“ๅบ”ไฟกๅฐ๏ผˆ**App ็ซฏๅชๆŒ‰่ฟ™ไธ€ๅฅ—่งฃๆž**๏ผ‰ * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• * ๆˆๅŠŸ๏ผš{ "ok": true, "data": <ไปปๆ„> } * ๅคฑ่ดฅ๏ผš{ "ok": false, "error": "ไบบ่ฏ้”™่ฏฏไฟกๆฏ", "code": "bad_request" } * HTTP ็ ๅŒๆ—ถ็ป™ๅ‡บ๏ผš200 / 400 / 401 / 403 / 404 / 409 / 422 / 500ใ€‚ * โš ๏ธ ็”จ**ไฟกๅฐ**่€Œไธๆ˜ฏ่ฃธๆ•ฐๆฎ๏ผšๅฆๅˆ™ App ็ซฏๆ— ๆณ•ๅŒบๅˆ†"ๆˆๅŠŸไฝ†ๅ€ผๆ˜ฏ null" * ไธŽ"่ฏทๆฑ‚ๅคฑ่ดฅ"๏ผˆHTTP 200 + ็ฉบ body ็š„ๆญงไน‰๏ผ‰ใ€‚ * * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• * ไธŽ็ฝ‘้กตๅ…ฑ็”จไธšๅŠก็ฑป็š„**ๅ”ฏไธ€็œŸๅ€ผๆฅๆบ** * โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• * ๆ‰€ๆœ‰ๅ†™ๆ“ไฝœ้ƒฝ่ตฐ `App\Movie::create()` ่ฟ™็ฑป**ๆจกๅž‹ๆ–นๆณ•**๏ผˆ็ฝ‘้กต่ทฏ็”ฑไนŸๆ˜ฏ่ฐƒๅฎƒไปฌ๏ผ‰๏ผŒ * ไธๅœจ API ้‡Œๅฆๅ†™ไธ€ๅฅ— SQLใ€‚็†็”ฑ๏ผš * ยท ๆƒ้™/ๅฝ’ๅฑžๆ ก้ชŒ๏ผˆ"่ฟ™ๆœฌๆ—ฅๅކ่ƒฝไธ่ƒฝๅ†™""่ฟ™ไธชไบบๅœจไธๅœจๆˆ‘ๅฎถ"๏ผ‰ๅทฒ็ปๅœจๆจกๅž‹้‡Œ๏ผ› * ยท ๅฐ†ๆฅ็ฝ‘้กต้‚ฃ่พนไฟฎไบ†ไธšๅŠก่ง„ๅˆ™๏ผŒAPI ่‡ชๅŠจ่ทŸไธŠ๏ผŒไธไผšๅ‡บ็Žฐ"App ๅปบ็š„่ฎฐๅฝ•ไธŽ็ฝ‘้กตๅปบ็š„ไธๅŒ"ใ€‚ */ declare(strict_types=1); namespace App; final class Api { /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ้‰ดๆƒ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ /** ไปŽ Authorization ๅคด้‡Œๅ– Bearer ไปค็‰Œ๏ผˆไนŸๆŽฅๅ— `?token=` โ€”โ€” ่งไธ‹๏ผ‰ใ€‚ */ public static function bearer(): string { /* โš ๏ธ ๅ–ๅคด็š„ไธ‰็งๅ†™ๆณ•้ƒฝ่ฆ่ฎค๏ผšApache ็›ดๆŽฅ็›‘ๅฌๆ—ถๆ˜ฏ `HTTP_AUTHORIZATION`๏ผ› PHP-FPM ไธ‹ๆœ‰ๆ—ถๅชๅ‡บ็Žฐๅœจ `REDIRECT_HTTP_AUTHORIZATION`๏ผˆ้‡ๅ†™ไน‹ๅŽ๏ผ‰๏ผ› ่ฟ˜ๆœ‰ `apache_request_headers()`๏ผˆ่€ Apache + CGI ็š„ๅ…œๅบ•๏ผ‰ใ€‚ ๆœฌ้กน็›ฎๅทฒ็ปๅ› ไธบ"ๅคดๅ–ไธๅˆฐ"ๆ ฝ่ฟ‡ไธ€ๆฌก๏ผˆDAV ็š„ `CGIPassAuth On`๏ผŒ่ง README๏ผ‰๏ผŒ ๆ‰€ไปฅ่ฟ™้‡Œไธ‰ๅฑ‚้ƒฝ่ฏ•๏ผŒ**ไธ่ตŒ**็Žฏๅขƒใ€‚ */ $raw = (string)($_SERVER['HTTP_AUTHORIZATION'] ?? ''); if ($raw === '') { $raw = (string)($_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? ''); } if ($raw === '' && function_exists('apache_request_headers')) { $h = apache_request_headers(); foreach ($h as $k => $v) { if (strcasecmp((string)$k, 'Authorization') === 0) { $raw = (string)$v; break; } } } if (preg_match('/^\s*Bearer\s+(.+)$/i', $raw, $m)) { return trim($m[1]); } return ''; } /** * ๅฝ“ๅ‰่ฐƒ็”จ่€…ใ€‚ๆˆๅŠŸ่ฟ”ๅ›ž ['person_id','family_id','token_id','role','can_write']ใ€‚ * * ๐Ÿ”ด๐Ÿ”ด **ๆœฌๅ‡ฝๆ•ฐไผš็›ดๆŽฅ็ป“ๆŸ่ฏทๆฑ‚**๏ผˆๆœช้‰ดๆƒๆ—ถ 401 + exit๏ผ‰โ€”โ€”่ฟ™ๆ˜ฏๅˆปๆ„็š„๏ผš * ่‹ฅ่ฟ”ๅ›ž null ่ฎฉๆฏไธช่ฐƒ็”จ็‚น่‡ชๅทฑๅˆคๆ–ญ๏ผŒๆ€ปๆœ‰ไธ€ๅคฉๆœ‰ไบบๅฟ˜ไบ†ๅˆคใ€‚ */ public static function actor(): array { static $cache = null; if (is_array($cache)) { return $cache; } $token = self::bearer(); if ($token === '') { self::fail('็ผบๅฐ‘ API ไปค็‰Œ๏ผˆ่ฏทๅœจ่ฏทๆฑ‚ๅคดๅธฆ `Authorization: Bearer `๏ผ‰ใ€‚', 'unauthorized', 401); } $hit = ApiToken::verify($token); if ($hit === null) { self::fail('API ไปค็‰Œๆ— ๆ•ˆใ€ๅทฒ่ฟ‡ๆœŸๆˆ–่ขซๅŠ้”€ใ€‚', 'unauthorized', 401); } $personId = (int)$hit[0]; $p = Person::find($personId); if (!$p) { self::fail('ไปค็‰Œๅฏนๅบ”็š„ไบบไธๅญ˜ๅœจใ€‚', 'unauthorized', 401); } /* ่ง’่‰ฒไธŽๅ†™ๆƒ้™**็…งๆŠ„็ฝ‘้กต้‚ฃๅฅ—ๅˆคๅฎš**๏ผˆAuth::canWrite / isGuest๏ผ‰๏ผŒ ๅฆๅˆ™ไผšๅ‡บ็Žฐ"็ฝ‘้กต้‡Œไธ่ƒฝๅ†™ใ€App ้‡Œ่ƒฝๅ†™"่ฟ™็งๆƒ้™่ฃ‚็ผใ€‚ โš ๏ธ Auth ็š„ๆ–นๆณ•่ฏป็š„ๆ˜ฏ $_SESSION โ€”โ€” API ๆ—  session๏ผŒๆ‰€ไปฅ่ฟ™้‡ŒๆŒ‰ๅŒไธ€ไธช ่ฏญไน‰๏ผˆrole + status๏ผ‰**้‡็ฎ—**ไธ€้๏ผŒ่€Œไธๆ˜ฏๅŽป่ฏป Authใ€‚ */ $st = Database::get()->prepare( 'SELECT role, status FROM accounts WHERE person_id = ? LIMIT 1' ); $st->execute([$personId]); $acct = $st->fetch() ?: []; $role = (string)($acct['role'] ?? 'member'); if ((string)($acct['status'] ?? 'active') !== 'active') { self::fail('่ฟ™ไธช่ดฆๅทๅทฒ่ขซๅœ็”จใ€‚', 'forbidden', 403); } $cache = [ 'person_id' => $personId, 'family_id' => (int)$p['family_id'], 'token_id' => (int)$hit[1], 'role' => $role, 'can_write' => $role !== 'guest', ]; return $cache; } /** ๅช่ฏป่ดฆๅท๏ผˆguest๏ผ‰ไธ่ƒฝๅ†™ โ€”โ€” ไธŽ็ฝ‘้กต็š„ `Auth::requireWrite()` ๅŒไธ€่ฏญไน‰ใ€‚ */ public static function requireWrite(): void { $a = self::actor(); if (!$a['can_write']) { self::fail('ๅช่ฏป่ดฆๅทไธ่ƒฝไฟฎๆ”นๆ•ฐๆฎใ€‚', 'forbidden', 403); } } /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ๅ–ๅ‚ไธŽๅ‡บๅ‚ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ private static ?array $body = null; /** ่ฏทๆฑ‚ไฝ“๏ผˆJSON ๆˆ–่กจๅ•๏ผ‰๏ผŒๅช่งฃๆžไธ€ๆฌกใ€‚ */ public static function body(): array { if (self::$body !== null) { return self::$body; } $ct = (string)($_SERVER['CONTENT_TYPE'] ?? ''); if (stripos($ct, 'application/json') !== false) { $raw = (string)file_get_contents('php://input'); $dec = $raw === '' ? [] : json_decode($raw, true); self::$body = is_array($dec) ? $dec : []; } else { self::$body = array_merge($_GET, $_POST); } return self::$body; } /** * ๅ–ไธ€ไธชๅ‚ๆ•ฐ๏ผšGET / POST ่กจๅ• / JSON body ้ƒฝ่ƒฝๅƒใ€‚ * โš ๏ธ ๅˆๅนถไผ˜ๅ…ˆ็บง JSON body > POST > GET โ€”โ€” ไธŽ็›ด่ง‰ไธ€่‡ด๏ผˆๆ˜พๅผ body ๆœ€ๆƒๅจ๏ผ‰ใ€‚ */ public static function param(string $key, $default = null) { $b = self::body(); if (array_key_exists($key, $b)) { return $b[$key]; } if (array_key_exists($key, $_GET)) { return $_GET[$key]; } return $default; } /** ๅ–ๅญ—็ฌฆไธฒๅ‚ๆ•ฐ๏ผˆtrim + ๅฏ้€‰้•ฟๅบฆไธŠ้™๏ผ‰ */ public static function str(string $key, string $default = '', int $max = 0): string { $v = self::param($key, $default); $v = is_scalar($v) ? (string)$v : ''; $v = trim($v); return ($max > 0) ? mb_substr($v, 0, $max) : $v; } /** ๅ–ๆ•ดๆ•ฐๅ‚ๆ•ฐ */ public static function int(string $key, int $default = 0): int { $v = self::param($key, $default); return is_scalar($v) ? (int)$v : $default; } /** ๅ–ๅธƒๅฐ”ๅ‚ๆ•ฐ๏ผˆ่ฎค 1/true/'on'/'yes'๏ผ‰ */ public static function bool(string $key, bool $default = false): bool { $v = self::param($key, $default); if (is_bool($v)) { return $v; } if (is_scalar($v)) { return in_array(strtolower((string)$v), ['1', 'true', 'on', 'yes'], true); } return $default; } /** ๅ–ๆ•ดๆ•ฐๆ•ฐ็ป„๏ผˆๅฆ‚ attendees[] / shares[]๏ผ‰ */ public static function ints(string $key): array { $v = self::param($key, []); if (!is_array($v)) { return []; } return array_values(array_map('intval', $v)); } /** * ๐Ÿ”ด ๆ—ถ้—ดๅ‚ๆ•ฐ็š„**ๅŒๆ ผๅผ**ๆŽฅๅ—๏ผˆApp ็š„็—›็‚น๏ผŒๅˆปๆ„ๅšๅพ—ๅฎฝๅฎน๏ผ‰๏ผš * ยท 'YYYY-MM-DD HH:MM(:SS)' โ€”โ€” ็ฝ‘้กตๆŽงไปถไบงๅ‡บ็š„ๆ ผๅผ๏ผ› * ยท 'YYYY-MM-DDTHH:MM(:SS)โ€ฆ' โ€”โ€” ISO 8601๏ผˆiOS/Android ๅŽŸ็”Ÿๆ—ฅๆœŸๆŽงไปถ้ป˜่ฎค๏ผ‰๏ผ› * ยท 'YYYY-MM-DD' โ€”โ€” ็บฏๆ—ฅๆœŸ๏ผˆๅ…จๅคฉๆ—ฅ็จ‹๏ผ›ๆ—ถ้—ด่กฅ 00:00:00๏ผ‰ใ€‚ * ่งฃๆžไธๅ‡บ โ†’ ่ฟ”ๅ›ž null๏ผˆ**ไธๆŠ›ๅผ‚ๅธธ**๏ผŒ่ฎฉ่ฐƒ็”จๆ–นๅ†ณๅฎš"ๅฟฝ็•ฅ"่ฟ˜ๆ˜ฏ"ๆŠฅ้”™"๏ผ‰ใ€‚ */ public static function dt(string $key, ?string $default = null): ?string { $v = self::param($key, null); if ($v === null || $v === '') { return $default; } if (!is_scalar($v)) { return $default; } $s = trim((string)$v); if ($s === '') { return $default; } // ๅ…ˆๅˆฐ UTC ๅ†่ฝๅœฐ๏ผšiOS ็ป™็š„ๆ˜ฏๅธฆ Z / ๅธฆๅ็งป็š„ ISO ไธฒ๏ผŒ็›ดๆŽฅ substr ไผšๆ‹ฟๅˆฐ UTC ๆ—ถๅˆป $ts = strtotime($s); if ($ts === false) { return null; } // ็บฏๆ—ฅๆœŸ๏ผšๆ—ถ้—ด้ƒจๅˆ†ๅฝ’้›ถ๏ผˆไธŽ็ฝ‘้กต"ๅ…จๅคฉๆ—ฅ็จ‹"็š„ๅฃๅพ„ไธ€่‡ด๏ผ‰ if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $s)) { return date('Y-m-d', $ts) . ' 00:00:00'; } return date('Y-m-d H:i:s', $ts); } /** ๆˆๅŠŸๅ“ๅบ”ๅนถ็ป“ๆŸ่ฏทๆฑ‚ใ€‚ */ public static function ok($data = null, int $code = 200, array $extra = []): void { self::send(array_merge(['ok' => true, 'data' => $data], $extra), $code); } /** ๅคฑ่ดฅๅ“ๅบ”ๅนถ็ป“ๆŸ่ฏทๆฑ‚ใ€‚ */ public static function fail(string $msg, string $code = 'bad_request', int $http = 400): void { self::send(['ok' => false, 'error' => $msg, 'code' => $code], $http); } private static function send(array $payload, int $http): void { if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store'); http_response_code($http); } echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE); exit; } /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ๅˆ†้กต๏ผˆๅˆ—่กจๆŽฅๅฃ็ปŸไธ€็”จๅฎƒ๏ผŒApp ็ซฏๆปšๅŠจๅŠ ่ฝฝ๏ผ‰ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ /** ่ฟ”ๅ›ž [limit, offset, page]๏ผŒๅนถๆŠŠๅฎƒไปฌ่ฎฐๅœจ้™ๆ€ๅญ—ๆฎต้‡Œไพ› `page()` ๅ›žๅกซ metaใ€‚ */ private static array $pageInfo = ['page' => 1, 'per_page' => 50, 'offset' => 0]; public static function page(int $defaultPer = 50, int $maxPer = 200): array { $per = max(1, min($maxPer, self::int('per_page', $defaultPer))); $pg = max(1, self::int('page', 1)); $plan = self::str('paginate', ''); // `paginate=1` ๆ—ถ่ฟ”ๅ›žๅธฆ meta ็š„ไฟกๅฐ๏ผ›ๅฆๅˆ™๏ผˆ้ป˜่ฎค๏ผ‰่ฟ”ๅ›ž่ฃธๆ•ฐ็ป„๏ผŒApp ๅฐ‘ๅ†™ไธ€ๅฑ‚ self::$pageInfo = ['page' => $pg, 'per_page' => $per, 'offset' => ($pg - 1) * $per]; return [$per, self::$pageInfo['offset'], $pg]; } /** ๅˆ—่กจๅ“ๅบ”๏ผš้ป˜่ฎค่ฃธๆ•ฐ็ป„๏ผ›ๅธฆ `paginate=1` ๆ—ถ็ป™ๅ‡บๅˆ†้กต metaใ€‚ */ public static function list(array $rows, int $total = -1): void { if (self::bool('paginate')) { self::ok([ 'items' => $rows, 'total' => $total >= 0 ? $total : count($rows), 'page' => self::$pageInfo['page'], 'per_page' => self::$pageInfo['per_page'], ]); } self::ok($rows); } /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ไพฟๆท๏ผšๆ‹ฟ"ๆŸ็ฑป่ต„ๆบ"็š„ๅ…ฌๅ…ฑๅ‰็ฝฎ โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */ /** ๅฝ“ๅ‰ๅฎถๅบญ + ๅฝ“ๅ‰ไบบ๏ผˆๅ‡ ไนŽๆ‰€ๆœ‰ๆŽฅๅฃ้ƒฝ่ฆ๏ผŒ็œๅพ—ๆฏๅค„ `self::actor()` ่งฃๆž„๏ผ‰ใ€‚ */ public static function me(): array { $a = self::actor(); return [$a['family_id'], $a['person_id']]; } /** ๆ‰พไธๅˆฐ่ต„ๆบๆ—ถ็š„็ปŸไธ€ 404 */ public static function notFound(string $what = '่ต„ๆบ'): void { self::fail($what . 'ไธๅญ˜ๅœจ๏ผŒๆˆ–่€…ไฝ ไธ่ฏฅ็œ‹ๅˆฐๅฎƒใ€‚', 'not_found', 404); } /** * ๆŠŠไธ€ๆ‰น id ่ฟ‡ๆปคๆˆ"็กฎๅฎžๅฑžไบŽ่ฟ™ไธชๅฎถๅบญ็š„ไบบ"๏ผˆ้˜ฒๆ‰‹ๆ“่ฏทๆฑ‚ๆŠŠๅˆซๅฎถ็š„ไบบๆŒ‚่ฟ›ๆฅ๏ผ‰ใ€‚ * * โš ๏ธ ไธŽ็ฝ‘้กต่กจๅ•ๅŒไธ€้“้˜ฒ็บฟ๏ผˆindex.php ้‡Œ็š„ๅ…จๅฑ€ๅ‡ฝๆ•ฐ `filter_family_person_ids`๏ผ‰โ€”โ€” * ไฝ†้‚ฃไธชๅ‡ฝๆ•ฐๅฎšไน‰ๅœจ **index.php ้กถๅฑ‚**๏ผŒAPI ่ฟ™ๆก่ทฏไธ็ป่ฟ‡ index.php * ๏ผˆ.htaccess ็›ดๆŽฅ้‡ๅ†™ๅˆฐ api.php๏ผ‰๏ผŒๆ‰€ไปฅ**ๅ–ไธๅˆฐๅฎƒ**ใ€‚ * ๅฎžๆต‹๏ผšๅœจ ApiHandler ้‡Œ็›ดๆŽฅ่ฐƒๅฎƒไผš `Call to undefined function * App\filter_family_person_ids()` โ€”โ€” PHP ไผšๅ…ˆๅœจๅฝ“ๅ‰ๅ‘ฝๅ็ฉบ้—ดๆ‰พใ€‚ * โ‡’ ่ฟ™้‡Œ่‡ชๅทฑๅฎž็Žฐไธ€้๏ผŒ่ฏญไน‰ไธŽ้‚ฃไธชๅ…จๅฑ€ๅ‡ฝๆ•ฐ**้€ๅญ—ๅฏน้ฝ** * ๏ผˆๆŸฅ persons ่กจใ€้™ family_idใ€่ฟ”ๅ›žๅŽป้‡ๅŽ็š„ๆ•ดๅž‹ๆ•ฐ็ป„๏ผ‰ใ€‚ */ public static function people(array $ids, int $familyId): array { $ids = array_values(array_unique(array_filter(array_map('intval', $ids), static fn(int $x): bool => $x > 0))); if (!$ids) { return []; } $in = implode(',', array_fill(0, count($ids), '?')); $st = Database::get()->prepare( "SELECT id FROM persons WHERE family_id = ? AND id IN ($in)" ); $st->execute(array_merge([$familyId], $ids)); return array_map('intval', array_column($st->fetchAll(), 'id')); } /** * ่ทฏ็”ฑๅˆ†ๅ‘๏ผš`/api/v1/<่ต„ๆบ>[/][/<ๅŠจไฝœ>]`ใ€‚ * * โš ๏ธ ๅ•ไธ€ๅ…ฅๅฃ๏ผˆapi.php ็š„ .htaccess ้‡ๅ†™๏ผ‰โ€”โ€” ไธๅœจ URL ้‡Œๅ † query ๅ‚ๆ•ฐ๏ผŒ * ่ฟ™ๆ ท App ็š„็ฝ‘็ปœๅฑ‚ๅฏไปฅๆŒ‰ REST ไน ๆƒฏๅ†™๏ผˆGET/POST/PATCH/DELETE + ่ทฏๅพ„๏ผ‰ใ€‚ */ public static function dispatch(): void { // ๆ–นๆณ•ๆจกๆ‹Ÿ๏ผšApp ็ซฏ๏ผˆๅฐคๅ…ถ้ƒจๅˆ†ๅฎ‰ๅ“็ฝ‘็ปœๅบ“๏ผ‰ไธๆ–นไพฟๅ‘ PATCH/DELETE ๆ—ถ // ็”จ POST + `_method=DELETE`๏ผŒ่ฟ™้‡Œๅšไธ€ๆฌก็ปŸไธ€ๆ”นๅ†™ใ€‚ // โš ๏ธ ๅฟ…้กป็”จ `Api::param()`๏ผˆๅฎƒๅŒๆ—ถ่ฎค JSON body / POST ่กจๅ• / GET๏ผ‰โ€”โ€” // ๅช่ฏป `$_POST['_method']` ็š„่ฏ๏ผŒJSON ่ฏทๆฑ‚ไฝ“็š„ๆ”นๅ†™ไผš**้™้ป˜ๅคฑๆ•ˆ** // ๏ผˆๅฎžๆต‹่ธฉ่ฟ‡๏ผšPOST + JSON `_method:DELETE` ๅพ—ๅˆฐ 405๏ผ‰ใ€‚ $method = strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET')); $override = strtoupper((string)($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '')); if ($override === '') { $override = strtoupper((string)self::param('_method', '')); } if ($method === 'POST' && in_array($override, ['PUT', 'PATCH', 'DELETE'], true)) { $method = $override; } $path = (string)parse_url((string)($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH); /* ๅŽปๆމๅ‰็ผ€๏ผš`/api/v1/...`๏ผˆ.htaccess / router.php ้‚ฃๆก้‡ๅ†™๏ผ‰๏ผŒ ๆˆ–่€… `?r=api/v1/...`๏ผˆindex.php ้‚ฃๆกๅ…œๅบ• โ€”โ€” ๅฎƒไผšๆŠŠ REQUEST_URI ๆ”นๆˆ `/api/v1/...` ๅ†่ฟ›ๆฅ๏ผŒๆ‰€ไปฅ่ฟ™้‡Œไธ€ๆกๆญฃๅˆ™ๅฐฑๅคŸ๏ผ‰ใ€‚ ไธค็งๅ…ฅๅฃ็š„่ทฏๅพ„ๆœ€็ปˆ้ƒฝๅฝ’ไธ€ๆˆ `tasks/12/status` ่ฟ™ๆ ท็š„็›ธๅฏนไธฒใ€‚ */ $path = preg_replace('#^.*?/api/v1#', '', $path); $path = trim((string)$path, '/'); $parts = $path === '' ? [] : explode('/', $path); $res = strtolower($parts[0] ?? ''); $id = isset($parts[1]) && ctype_digit((string)$parts[1]) ? (int)$parts[1] : 0; $sub = strtolower($parts[1] ?? ''); if (ctype_digit($sub)) { $sub = strtolower((string)($parts[2] ?? '')); } $h = new ApiHandler(); $h->route($res, $id, $sub, $method); } }