Servicios
API REST
Cómo funciona
La API REST tiene un único punto de entrada, /api (ApiController). Cada llamada es un POST con:
| Parámetro | Contenido |
|---|---|
username, password |
Credenciales de un usuario del sistema (el tenant). |
action |
clase.método para el núcleo, o plugins.<plugin>.clase.método. |
| el resto | Parámetros de la acción. |
curl -X POST https://misitio.com/api \
-d username=apiuser -d password=secreto \
-d action=usuarios.obtenerPerfilUsuario -d usr_codigo=12 -d language=en
{ "status": "ok", "data": { "profile": { "…": "…" } } }
status |
Significado |
|---|---|
ok |
Éxito; los datos están en data. |
error |
Error de la operación (credenciales, validación, registro inexistente…). Incluye message. |
fail |
La llamada está mal formada (acción inexistente, plugin inactivo…). |
El parámetro language cambia el idioma de los mensajes de esa llamada.
Resolución de la acción
| Acción | Archivo | Clase |
|---|---|---|
usuarios.loginUsuario |
src/api/usuarios.php |
UsuariosApiController::loginUsuario() |
plugins.noticias.manager.obtenerNoticias |
plugins/noticias/src/Api/manager.php |
ManagerApiController::obtenerNoticias() |
Solo se pueden llamar métodos públicos declarados en la propia clase, que debe extender RestProvider. Los nombres se validan como identificadores, así que la acción no puede apuntar a otro archivo.
Llamadas en lote
action=batch.call ejecuta varias acciones en una sola petición. bulk es un objeto (o un JSON) con una clave por acción y sus parámetros; lo que se envía fuera de bulk se comparte entre todas. returnAs cambia la clave del resultado (para llamar dos veces a la misma acción).
curl -X POST https://misitio.com/api -d username=… -d password=… \
-d action=batch.call \
--data-urlencode 'bulk={"plugins.tablas.paises.obtenerPaises":{},
"plugins.noticias.manager.obtenerNoticias":{"limit":5,"returnAs":"ultimas"}}'
Escribir un endpoint
<?php
// src/api/productos.php -> acciones productos.*
Koshkil::uses('sys.web.Rest.RestProvider');
Koshkil::usesModel('productos');
class ProductosApiController extends RestProvider {
// action=productos.listar&limit=10&offset=0&categoria=3
public function listar() {
$qb = TMProductos::where('usr_codigo', $this->apiUser()->usr_codigo)
->order('prd_nombre');
if ($categoria = $this->intParam('categoria')) {
$qb->where('cat_codigo', $categoria);
}
$total = $this->countRows(clone $qb);
$items = $this->paginate($qb)->getAsArray();
return $this->successResponse(['products' => $items, 'total' => $total]);
}
// action=productos.obtener&prd_codigo=15
public function obtener() {
$producto = TMProductos::where('prd_codigo', $this->intParam('prd_codigo'))
->where('usr_codigo', $this->apiUser()->usr_codigo)
->first();
if (!$producto) {
return $this->errorResponse('productos.not_found'); // clave del dominio "api"
}
return $this->successResponse(['product' => $this->onlyRequestedFields($producto->record())]);
}
}
Ayudas de RestProvider
| Método | Uso |
|---|---|
apiUser() |
Usuario autenticado (el tenant). Filtrá siempre por él. |
params(), param($k, $def) |
Parámetros de la llamada. |
intParam($k), arrayParam($k), intList($v) |
Parámetros convertidos. |
esc($v) |
Escapa un valor para escribirlo dentro de SQL a mano. No hace falta con where(), que ya escapa. |
paginate($qb) |
Aplica limit y offset de los parámetros. |
countRows($qb) |
Cuenta registros de una consulta. |
onlyRequestedFields($r) |
Filtra campos según shortVersion. |
translated($record), requestedLanguage() |
Contenido traducible. |
findSubUser($valor, $columna) |
Busca un subusuario del tenant. |
baseUrl(), absoluteUrl($ruta) |
URLs absolutas del sitio del tenant. |
successResponse($data, $msg) |
['status' => 'ok', 'data' => …] |
errorResponse($clave, $params) |
['status' => 'error', 'message' => …] (mensaje del dominio api) |
failResponse($clave, $params) |
['status' => 'fail', …] |
Uso interno, sin HTTP
El sitio puede consumir su propia API sin hacer una petición, con ApiService. Las credenciales del cliente van en config/domains/<dominio>/api.php:
<?php
$API = [
'default' => ['username' => 'apiuser', 'password' => '…'],
// 'mobile' => ['username' => '…', 'password' => '…'],
];
Koshkil::uses('sys.web.Rest.ApiService');
$api = ApiService::domain(); // cliente 'default' del dominio actual
$r = $api->call('plugins.noticias.manager.obtenerNoticias', ['limit' => 5]);
if ($r['status'] == 'ok') { $noticias = $r['data']['news']; }
$r = $api->batch([
'plugins.tablas.paises.obtenerPaises' => [],
'plugins.noticias.etiquetas.obtenerEtiquetas' => [],
]);
SuperController y FrontAjaxController ya incluyen ApiAccessTrait:
$respuesta = $this->api('usuarios.loginUsuario', ['subuser' => $email, 'pass' => $clave]);
$usuario = $this->apiData($respuesta, 'users'); // data['users'] o null si hubo error
Así el sitio y las aplicaciones externas usan exactamente la misma lógica y las mismas validaciones.