API Ucu Açma
Modülünüzün yeteneklerini tek bir kancaya rota kaydı ekleyerek REST ucu olarak yayınlarsınız; çekirdeğe dokunmadan.
Genel Bakış
Kimlik doğrulama, uç başına izinler, hız sınırı, CORS, idempotency ve istek kaydı zaten var. Siz hangi adresin ne yaptığını söylersiniz, gerisini Kernel metodunuzu çağırmadan önce halleder.
Sistem sahibinin gördüğü bir onay kutusudur: her uç, API kimlik bilgileri ekranında ayrı bir satır olur ve anahtar yalnız işaretlenen uçlara ulaşır.
Ayarlar -> API kimlik bilgileri -> Oluştur
[ ] GET /admin/mymodule/items
[x] POST /admin/mymodule/items/{id}/rebuild <- yalnız bu verildi
[ ] DELETE /admin/mymodule/items/{id}
Ön Koşullar
hooks.phpdosyası olan bir modül; bkz. Modülden Kanca Kaydetme.- Yazma uçları için, ikinci bir uygulama yerine köprüleyeceğiniz mevcut bir panel operasyonu.
- Modülün
src/sınıfları otomatik yüklenmez;hooks.phpneye başvuruyorsa onuinclude_onceedin.
Yapı
$audience değeriyle çalıştırır (aşağıdaki tabloya bakın).
Module: ile başlayan bir grup görünce isteği modül örneğinize verir.
Adım Adım
Uç Kümesini Tek Yerde Bildirin
src/ApiSurface.phpoluşturun: birGROUPsabiti ve her ucu listeleyen birmap()(fiil, yol, aksiyon, hedef).- Rotaları ve izin kataloğunu aynı listeden türetin; böylece rota, kapsam ve onay kutusu ayrışamaz.
- Sabit yolları parametrik ikizlerinden önce yazın. Yönlendirici aynı parça sayısında ilk eşleşeni alır.
Rotaları Kaydedin
hooks.phpiçindefilter:api.routeskancasına bir dinleyici ekleyin ve hedef kitle sizinki değilse dönün.- Tuple'larınızı
$routesdizisine ekleyin. İki parametre de referansla gelir, yani ekleyin, döndürmeyin. - İzin kataloğunu dosya gövdesinde, her dinleyicinin dışında yayınlayın.
İşleyici Metotları Yazın
- Uç başına bir
api_{aksiyon}metodu ekleyin. Sert birmethod_existskontrolü vardır,__callyanıt veremez. - Her metoda köprüye ileten aynı tek satırlık gövdeyi verin ve onları bildirimden üretin.
- Bir
Responsedöndürün ya da Kernel'in normalize ettiği eski zarf dizisini verin. - Liste için zarfı çekirdek kaynaklarıyla aynı tutun: girdide
page,limitvesearch; çıktıdameta.total,meta.page,meta.limitvemeta.next_page.
Yazma İşlerini Panel Operasyonuna Köprüleyin
- Superglobal'leri yansıtın,
op_{ad}metodunu çağırın, çıktısını yakalayın ve birfinallybloğunda geri koyun. - Yakaladığınız zarfı çevirin:
data.htmldüşürülür,messagemetaiçine taşınır, fırlatılan exception doğrulama hatasına dönüşür. - Operasyonun yetki kapısı API çağrısında kenara çekilsin; Kernel daha dar bir kontrol uygulamıştır.
Referans
Kanca ve Rota Tuple'ı
// Dinleyici. İki argüman da referanstır; $routes dizisine ekleyin, hiçbir şey döndürmeyin.
Hook::add('filter:api.routes', 20, function (array &$routes, string &$audience): void {
if ($audience !== 'admin') return; // 'admin' | 'client' | 'module'
// ...
});
// Tek bir kayıt. 6 numaralı indeks yalnız serbest yüzeyde vardır.
// [0] string $method GET | POST | PUT | PATCH | DELETE
// [1] string $pattern yüzey önekinden sonraki tam yol; {x} bir yol parametresi yakalar
// [2] string $group 'Module:{Type}/{Name}' isteği modül örneğinize yönlendirir
// [3] string $action izin adı VE api_{action} metodunun son eki
// [4] bool $public false = kimlik bilgisi gerekir (varsayılan false)
// [5] bool $authOnly false = "Group/Action" kapsamını da AYRICA dayat
// admin/client tarafında varsayılan false, serbest yüzeyde TRUE
// [6] string $audience yalnız serbest yüzey: 'admin' (varsayılan) | 'client' | 'any'
$routes[] = ['POST', 'mymodule/items/{id}/rebuild', 'Module:Addons/MyModule', 'item_rebuild', false, false];
| Uç kümesi | Hedef kitle değeri | Kaydın yanıtladığı adres |
|---|---|---|
| kimlik isteyen admin | admin | /api/v1/admin/{desen} |
| müşteri | client | /api/v1/client/{desen} |
| serbest | module | /api/v1/{desen} |
Bir rotanın dayattığı kapsam her zaman {grup}/{aksiyon}'dur, yani yukarıdaki kayıt Module:Addons/MyModule/item_rebuild iznine karşılık harcanır. Sona eklemek bir çekirdek adresini gölgelemez: çekirdek kayıtları önce girer ve yönlendirici ilk eşleşmeyi döndürür. Birini devralmak için tuple'ı yerinde düzenleyin.
İşleyici Sözleşmesi
public function api_item_rebuild(Request $request, array $match): Response;
// $request, her özelliği public ve ayrıştırılmış halde:
// string $method 'GET', 'POST', ...
// string $audience 'admin' | 'client' | ''
// array $segments '/' ile bölünmüş yol
// string $resource ilk parça
// array $query sorgu dizesi
// array $body çözülmüş JSON gövdesi (ya da form gövdesi)
// array $headers küçük harfe çevrilmiş başlık adları
// string $ip çözümlenen istemci adresi
// ?string $token gönderilmişse ham kimlik bilgisi
// ?string $idempotencyKey
// string $rawBody
// $match, yönlendiricinin ürettiği:
// 'group' => 'Module:Addons/MyModule'
// 'action' => 'item_rebuild'
// 'params' => ['id' => '42'] ada göre anahtarlanmış {x} yakalamaları
// 'scope' => 'Module:Addons/MyModule/item_rebuild'
// 'public' => false
// 'authOnly' => false
// 'audience' => 'admin'
Yanıtı Kurma
class Response
{
public function __construct(int $status = 200, array $payload = []);
public static function success($data = null, int $status = 200, array $meta = []): self;
public static function error(string $code, string $message, int $status = 400, array $details = []): self;
public static function fromLegacy(array $ret): self; // {status, message, data} zarfı
public function withHeader(string $name, string $value): self;
public function withHeaders(array $headers): self;
public function getStatus(): int;
public function getPayload(): array;
public function send(): void; // bunu Kernel çağırır, siz değil
}
// Hatalar döndürülmez, fırlatılır. Her fabrika kendi HTTP durumunu taşır.
class ApiException extends Exception
{
public static function badRequest(string $message, string $code = 'bad_request', array $details = []);
public static function unauthorized(string $message = 'Authentication required.', string $code = 'unauthorized');
public static function forbidden(string $message = 'Insufficient scope.', string $code = 'forbidden');
public static function notFound(string $message = 'Resource not found.', string $code = 'not_found');
public static function methodNotAllowed(string $message = 'Method not allowed.', string $code = 'method_not_allowed');
public static function validation(string $message, array $details = [], string $code = 'validation_failed');
public static function rateLimited(string $message = 'Too many requests.', string $code = 'rate_limited');
public static function server(string $message = 'Internal server error.', string $code = 'server_error');
}
Onay Kutularını Yayınlama
public static function set($key, $values, $merge = false): array|false;
// Üçüncü argüman birleştirmeyi array_replace_recursive yerine array_merge yapar; böylece
// aksiyon listeniz aynı adlı bir listeye indeks indeks karışmak yerine bütün olarak yazılır.
// Kataloğun diğer grupları iki durumda da yerinde kalır.
Config::set('api-actions', ['Module:Addons/MyModule' => ['items_list', 'item_rebuild']], true);
Yazılan yalnız bellekteki kopyadır; Config::save() burada çağrılmaz, yani coremio/configuration/api-actions.php çekirdeğin gönderdiği hâliyle kalır.
Verilen Bir Kapsam Nereye Kadar Uzanır
| Anahtara verilen | Neyi açar | Önerilebilir mi |
|---|---|---|
Module:Addons/MyModule/item_rebuild | yalnız o uç | evet |
Module:Addons/* | sistemdeki tüm Addons modülleri | hayır |
* | admin API'sinin tamamı | hayır |
Kapı, gereken kapsamı ilk slash'ta keser ve grubunuz zaten bir slash içerir. Yani Module:Addons/MyModule/x kapsamının grubu Module:Addons'tur ve onunla eşleşen tek joker diğer bütün Addons modülleriyle de eşleşir.
Örnek
Asgari bir admin uç kümesi: bildirim, kayıt, bir işleyici ve köprü.
<?php
namespace WISECP\Modules\Addons\MyModule\Src;
use Config;
final class ApiSurface
{
public const GROUP = 'Module:Addons/MyModule';
/** [method, path, action, target]; sabit yollar {id} ikizlerinden ÖNCE. */
public static function map(): array
{
return [
['GET', 'mymodule/items', 'items_list', 'read:items_list'],
['GET', 'mymodule/items/export', 'items_export', 'read:items_export'],
['GET', 'mymodule/items/{id}', 'item_detail', 'read:item_detail'],
['POST', 'mymodule/items', 'item_save', 'op:save_item'],
['POST', 'mymodule/items/{id}/rebuild', 'item_rebuild', 'op:rebuild_item'],
['DELETE', 'mymodule/items/{id}', 'item_delete', 'op:delete_item'],
];
}
public static function routes(): array
{
$routes = [];
foreach (self::map() as [$method, $path, $action])
$routes[] = [$method, $path, self::GROUP, $action, false, false];
return $routes;
}
public static function publish_permission_catalog(): void
{
$actions = array_map(static fn (array $e): string => $e[2], self::map());
if (!$actions) return;
Config::set('api-actions', [self::GROUP => $actions], true);
}
/** @return array{0:string,1:string}|null bir aksiyon için [kind, name], örn. ['op', 'rebuild_item'] */
public static function target(string $action): ?array
{
foreach (self::map() as $entry)
if ($entry[2] === $action) return array_pad(explode(':', $entry[3], 2), 2, '');
return null;
}
}
<?php
use WISECP\Modules\Addons\MyModule\Src\ApiSurface;
include_once __DIR__ . DS . 'src' . DS . 'ApiSurface.php';
include_once __DIR__ . DS . 'src' . DS . 'ApiBridge.php';
// Adresler: admin yüzeyi, kimlik bilgisi zorunlu, kapsam uç başına dayatılır.
Hook::add('filter:api.routes', 20, function (&$routes, &$audience) {
if ($audience !== 'admin') return;
foreach (ApiSurface::routes() as $route) $routes[] = $route;
});
// Onay kutuları: DOSYA GÖVDESİNDE, asla yukarıdaki dinleyicinin içinde değil.
ApiSurface::publish_permission_catalog();
use WISECP\Api\Core\Request;
use WISECP\Api\Core\Response;
use WISECP\Modules\Addons\MyModule\Src\ApiBridge;
class MyModule extends AddonModule
{
public function api_items_list(Request $request, array $match): Response
{
return ApiBridge::dispatch('items_list', $request, $match);
}
public function api_item_rebuild(Request $request, array $match): Response
{
return ApiBridge::dispatch('item_rebuild', $request, $match);
}
// ... bildirilen her aksiyon için bir tane; ApiSurface::map() üzerinden üretin
}
public static function op(string $name, Request $request, array $match): Response
{
$area = self::area(); // lisans kapısı + AdminArea örneği
$input = array_merge($request->query, $request->body, $match['params'] ?? []);
$snapshot = [
'post' => $_POST,
'get' => $_GET,
'request' => $_REQUEST,
'method' => $_SERVER['REQUEST_METHOD'] ?? '',
];
$_POST = $_REQUEST = $input;
$_GET = [];
$_SERVER['REQUEST_METHOD'] = 'POST'; // operasyonlar form POST varsayar
self::$in_api = true;
$failure = null;
ob_start();
try { $area->{'op_' . $name}(new Operation($name)); }
catch (Exception $e) { $failure = $e; }
finally {
$printed = (string) ob_get_clean();
self::$in_api = false;
$_POST = $snapshot['post']; // ödünç alınan isteği temiz geri ver
$_GET = $snapshot['get'];
$_REQUEST = $snapshot['request'];
$_SERVER['REQUEST_METHOD'] = $snapshot['method'];
}
if ($failure) throw ApiException::validation($failure->getMessage(), [], 'operation_failed');
$envelope = Utility::jdecode($printed, true) ?: [];
$data = $envelope['data'] ?? null;
if (is_array($data)) unset($data['html']); // panelin modal gövdesi API yükü değildir
return Response::success($data, 200, ['message' => $envelope['message'] ?? '']);
}
Operasyonun kendi yetki kapısının köprü için, yalnız köprü için kenara çekilmesi gerekir:
private function require_operation(): void
{
// API çağrısı sırasında oturum açmış operatör yoktur ve Kernel zaten daha dar bir
// izni denetledi: tam olarak bu ucun kapsamını.
if (ApiBridge::in_api()) return;
if (!\Admin::isPrivilege(['MY_MODULE_OPERATION']))
throw new Exception($this->lang['err-no-privilege'] ?? 'You do not have permission for this action.');
}
Tuzaklar
items/{id} satırı items/export satırının üstündeyse, export isteği detay işleyicisinden 200 döner ve id = "export" olur. Ne log düşer ne bir şey başarısız olur. Adresi somut bir değerle yönlendiriciden geçirip dönen aksiyona bakın.
Ayarlar ekranı kataloğu kapsam haritasından önce okur, dolayısıyla henüz tetiklenmemiş bir dinleyici hiçbir şey yayınlamaz. Rotalar yine çalışır ve elle yazılmış bir anahtar yine kapsam kontrolünden düşer; bu da sorunu sıralama değil izin hatası gibi gösterir. Çağrıyı dosya gövdesinden yapın.
Dağıtıcı çağırmadan önce method_exists ile bakar ve bulamazsa 404 action_not_implemented döner. Bu kasıtlıdır: uç başına bir gerçek metot, her ucun ayrı ayrı verilebilir olmasını sağlar.
Argümanını ilk slash'ta böler, grubunuzda zaten bir slash olduğu için çözüm endpoint_not_found ile düşer. Sistemin içindeki kod doğrudan kendi sınıflarınızı çağırır.
Sekme etiketi, modülün ekleyemediği bir çekirdek çeviri anahtarından gelir; ekran Module:Addons/MyModule ifadesini olduğu gibi gösterir. Görünen metni değiştirirseniz onay kutusu değerlerine dokunmayın: onlar kapının karşılaştırdığı kapsam dizeleridir.
Panel dağıtıcısı her operasyondan önce lisansı denetler ve API isteği o dağıtıcıdan geçmez. Aynı kontrolü köprünüzün başına koyun ve 403 yanıtlayın. Lisans alan adına bağlı olduğu için bu kapı yerel makinede sürekli kapalıdır.
İlgili Makaleler
Geri bildiriminiz için teşekkürler!
Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.