4 Commits
v1.2.3 ... main

Author SHA1 Message Date
808fd747fe fixed empty docblock 2026-04-14 12:33:33 +02:00
70701bb8f1 allowed header Authorization 2026-04-02 08:23:39 +02:00
8f9c744134 added documentation for return 2026-04-01 07:26:06 +02:00
c758dc9317 extended JS and TS template with Bearer token 2026-03-30 23:32:38 +02:00
6 changed files with 149 additions and 5 deletions

View File

@ -4,8 +4,8 @@
APIlite is a lightweight PHP library. A class extends `TPsoft\APIlite\APIlite`, public methods become API actions, requests are processed automatically, and responses are returned as JSON.
The project also generates frontend clients:
- JavaScript client: `format=javascript` (`.js`)
- TypeScript client: `format=typescript` (`.ts`, real typed output)
- JavaScript client: `format=javascript` (`.js`, includes `bearerSet()` helper backed by `localStorage`)
- TypeScript client: `format=typescript` (`.ts`, real typed output, includes `bearerSet()` helper backed by `localStorage`)
The HTML help page is also interactive:
- HTML help: `format=html` includes endpoint docs, a built-in request tester, and optional Bearer token support stored in browser `localStorage`
@ -22,6 +22,7 @@ The HTML help page is also interactive:
- Maintain both generated clients:
- JavaScript stays plain JS.
- TypeScript stays typed (interfaces + typed method signatures).
- Bearer token behavior stays aligned between JavaScript and TypeScript clients (`bearerSet()`, `apilite_bearer_token`, automatic `Authorization: Bearer ...` header).
- Keep the HTML help page usable both as documentation and as a lightweight in-browser tester.
- If you change output field names in JSON help, update README and templates consistently.
- If you change request metadata or tester behavior, update `README.md` and keep `src/help.tpl.php` aligned with the actual runtime request model.
@ -42,3 +43,4 @@ The HTML help page is also interactive:
## Documentation rule
When output format names/flags/URLs change, update `README.md` in the same change set.
When Bearer token helper/storage behavior changes, update `README.md` and keep JS/TS client docs aligned with the actual generated templates.

View File

@ -238,11 +238,15 @@ You can also download generated frontend clients:
* JavaScript: `?format=javascript`
* TypeScript (typed for Vue/TS projects): `?format=typescript`
Both generated clients support a shared Bearer token helper. Call `bearerSet(token)` once, the token is stored in `localStorage` under `apilite_bearer_token`, and subsequent requests automatically send `Authorization: Bearer ...`.
JavaScript usage example:
```js
import backend from './backend.js';
backend.bearerSet('your-access-token');
backend.add(1, 2).then((response) => {
console.log(response.data);
});
@ -253,6 +257,8 @@ TypeScript usage example (Vue + TS):
```ts
import backend from './backend';
backend.bearerSet('your-access-token');
backend.add(1, 2).then((response) => {
console.log(response.data); // typed value based on PHP return type
});

View File

@ -118,6 +118,7 @@ class APIlite
$ref_type = $ref_method->getReturnType();
$method['return'] = $this->reflectionTypeToNames($ref_type, $ref_method->getDeclaringClass());
$method['return_structure'] = $this->reflectionTypeToStructure($ref_type, $ref_method->getDeclaringClass());
$method['return_doc'] = $this->parseReturnDoc($method['doc']);
}
$this->methods[] = $method;
}
@ -271,6 +272,22 @@ class APIlite
return null;
}
private function parseReturnDoc(?string $doc): string|null
{
if (is_null($doc) || $doc === '') {
return null;
}
$lines = explode("\n", $doc);
foreach ($lines as $line) {
if (strpos($line, '@return') !== false) {
$ret = trim(substr($line, strpos($line, '@return') + strlen('@return')));
if (strlen($ret) <= 0) return null;
return trim(stristr($ret, ' '));
}
}
return null;
}
private function response(array $arr): void
{
ob_clean();
@ -279,7 +296,7 @@ class APIlite
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Origin, Content-Type, Accept');
header('Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization');
echo json_encode($arr);
exit;
}

View File

@ -827,6 +827,7 @@ $renderParamControl = function (string $methodName, array $param) use ($formatDe
<h3 style="margin-bottom: 1rem; color: #2c3e50;">Return:</h3>
<?php if (is_string($method['return'])) { ?>
<code class="parameter-type"><?php echo htmlspecialchars($method['return']); ?></code>
<?php echo htmlspecialchars((string) $method['return_doc']); ?>
<?php } ?>
<?php if (is_array($method['return'])) foreach ($method['return'] as $return) { ?>
<code class="parameter-type"><?php echo htmlspecialchars((string) $return); ?></code>

View File

@ -7,12 +7,63 @@
class <?php echo $this->apiName; ?> {
endpoint = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
bearerStorageKey = 'apilite_bearer_token';
normalizeBearerToken(token) {
if (typeof token !== 'string') return null;
token = token.trim();
return token === '' ? null : token;
}
getStorage() {
try {
if (typeof window !== 'undefined' && window.localStorage) return window.localStorage;
} catch (error) {
return null;
}
return null;
}
getBearerToken() {
var storage = this.getStorage();
if (storage == null) return null;
return this.normalizeBearerToken(storage.getItem(this.bearerStorageKey));
}
getRequestHeaders(headers = {}) {
var requestHeaders = Object.assign({}, headers);
if (typeof requestHeaders.Authorization === 'undefined') {
var token = this.getBearerToken();
if (token != null) requestHeaders.Authorization = 'Bearer ' + token;
}
return requestHeaders;
}
applyHeaders(xhttp, headers) {
Object.keys(headers).forEach(key => {
var value = headers[key];
if (typeof value === 'undefined' || value === null) return;
xhttp.setRequestHeader(key, value);
});
}
bearerSet(token) {
var storage = this.getStorage();
if (storage == null) return;
token = this.normalizeBearerToken(token);
if (token == null) {
storage.removeItem(this.bearerStorageKey);
return;
}
storage.setItem(this.bearerStorageKey, token);
}
/* ----------------------------------------------------
* General API call
*/
call(method, data, callback) {
var xhttp = new XMLHttpRequest();
var headers = this.getRequestHeaders();
xhttp.withCredentials = true;
xhttp.onreadystatechange = function() {
if (this.readyState === 4) {
@ -31,6 +82,7 @@ class <?php echo $this->apiName; ?> {
form_data.append(key, val);
});
xhttp.open('POST', this.endpoint + '?action=' + method);
this.applyHeaders(xhttp, headers);
xhttp.send(form_data);
}

View File

@ -152,8 +152,72 @@ export interface APIliteHelpResponse {
msg: string;
}
type APIliteRequestHeaders = Partial<Record<string, string>>;
class <?php echo $this->apiName; ?> {
endpoint: string = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
private readonly bearerStorageKey: string = 'apilite_bearer_token';
private normalizeBearerToken(token: string | null | undefined): string | null {
if (typeof token !== 'string') {
return null;
}
const normalizedToken = token.trim();
return normalizedToken === '' ? null : normalizedToken;
}
private getStorage(): Storage | null {
try {
if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
return window.localStorage;
}
} catch {
return null;
}
return null;
}
private getBearerToken(): string | null {
const storage = this.getStorage();
if (storage === null) {
return null;
}
return this.normalizeBearerToken(storage.getItem(this.bearerStorageKey));
}
private getRequestHeaders(headers: APIliteRequestHeaders = {}): APIliteRequestHeaders {
const requestHeaders: APIliteRequestHeaders = { ...headers };
if (typeof requestHeaders.Authorization === 'undefined') {
const token = this.getBearerToken();
if (token !== null) {
requestHeaders.Authorization = `Bearer ${token}`;
}
}
return requestHeaders;
}
private applyHeaders(xhttp: XMLHttpRequest, headers: APIliteRequestHeaders): void {
Object.keys(headers).forEach((key) => {
const value = headers[key];
if (typeof value === 'undefined' || value === null) {
return;
}
xhttp.setRequestHeader(key, value);
});
}
bearerSet(token: string | null): void {
const storage = this.getStorage();
if (storage === null) {
return;
}
const normalizedToken = this.normalizeBearerToken(token);
if (normalizedToken === null) {
storage.removeItem(this.bearerStorageKey);
return;
}
storage.setItem(this.bearerStorageKey, normalizedToken);
}
private call(
method: string,
@ -161,6 +225,7 @@ class <?php echo $this->apiName; ?> {
callback: (response: APIliteHelpResponse | APIliteActionResponse<unknown> | APIliteErrorResponse) => void
): void {
const xhttp = new XMLHttpRequest();
const headers = this.getRequestHeaders();
xhttp.withCredentials = true;
xhttp.onreadystatechange = function() {
if (this.readyState === 4) {
@ -191,6 +256,7 @@ class <?php echo $this->apiName; ?> {
});
xhttp.open('POST', this.endpoint + '?action=' + method);
this.applyHeaders(xhttp, headers);
xhttp.send(formData);
}