added version 1.0.0 of APIlite library,

added basic HTML template for help,
added test class APIcalculator for demo
This commit is contained in:
Igor Miňo 2025-05-28 09:50:45 +02:00
parent 1a6e173da9
commit 59b317cacd
3 changed files with 421 additions and 0 deletions

258
src/APIlite.php Normal file
View File

@ -0,0 +1,258 @@
<?php
/*
Copyright (c) TPsoft.org 2000-2025
Author: Ing. Igor Mino <mino@tpsoft.org>
License: GNU GPL-3.0 or later
Description: A set of tools to simplify the work of creating backend APIs for your frontend projects.
Version: 1.0.0
Milestones:
2025-05-28 07:42 Igor - Created
*/
namespace TPsoft\APIlite;
class APIlite
{
private string $apiName = '';
private $methods = array();
public function __construct()
{
register_shutdown_function(array($this, '_shutdownHandler'));
$this->analyzeClass();
if (isset($_REQUEST['action'])) {
$this->doAction($_REQUEST['action']);
} else {
if (isset($_REQUEST['format']) && $_REQUEST['format'] == 'html') {
$this->printHelpHTML();
} else {
$this->printHelpJSON();
}
}
}
private function _shutdownHandler()
{
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
$this->responseERROR($error['message']);
}
}
private function analyzeClass(): bool
{
$refClass = new \ReflectionClass($this);
$this->apiName = $refClass->getName();
$this->methods = array();
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $ref_method) {
$method_name = $ref_method->getName();
if (substr($method_name, 0, 1) == '_') continue;
$method = array(
'name' => $method_name,
'doc' => null,
'description' => null,
'params' => array(),
'return' => null
);
$docComment = $ref_method->getDocComment();
if ($docComment) {
$method['doc'] = trim($docComment);
$method['description'] = $this->parseDescription($method['doc']);
}
foreach ($ref_method->getParameters() as $ref_param) {
$param = array(
'name' => $ref_param->getName(),
'type' => null,
'optional' => $ref_param->isOptional(),
'default' => null,
'doc' => null,
);
if ($ref_param->hasType()) {
$param['type'] = $ref_param->getType()->getName();
}
if ($ref_param->isOptional()) {
$param['default'] = $ref_param->getDefaultValue();
}
$param['doc'] = $this->parseParamDoc($method['doc'], $param['name']);
$method['params'][] = $param;
}
if ($ref_method->hasReturnType()) {
$method['return'] = $ref_method->getReturnType()->getName();
}
$this->methods[] = $method;
}
return true;
}
private function parseDescription(string $doc): string
{
$lines = explode("\n", $doc);
$desc = array();
foreach ($lines as $line) {
$line = trim($line);
$line = trim(trim($line, '/*'));
if (substr($line, 0, 1) == '@') {
break;
}
if (strlen($line) <= 0) {
continue;
}
$desc[] = $line;
}
return implode("\n", $desc);
}
private function parseParamDoc(string $doc, string $param_name): string|null
{
$lines = explode("\n", $doc);
if (substr($param_name, 0, 1) != '$') {
$param_name = '$' . $param_name;
}
foreach ($lines as $line) {
if (strpos($line, $param_name) !== false) {
return trim(substr($line, strpos($line, $param_name) + strlen($param_name)));
}
}
return null;
}
private function response(array $arr): void
{
ob_clean();
header('Content-Type: application/json');
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*';
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');
echo json_encode($arr);
exit;
}
private function responseOK(array $data): void
{
$this->response(array('status' => 'OK', 'data' => $data));
}
private function responseERROR(string $error): void
{
$this->response(array('status' => 'ERROR', 'msg' => $error));
}
private function printHelpJSON(): void
{
$this->response(array(
'name' => $this->apiName,
'html_version' => $this->getCurrentUrl().'?format=html',
'actions' => $this->methods
));
}
private function printHelpHTML(): void
{
include __DIR__ . '/help.tpl.php';
}
private function getCurrentUrl(): string {
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ||
$_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$host = $_SERVER['HTTP_HOST'];
$uri = $_SERVER['REQUEST_URI'];
$path = parse_url($uri, PHP_URL_PATH);
return $protocol . $host . $path;
}
private function getMethod(string $action): ?array
{
foreach ($this->methods as $method) {
if ($method['name'] == $action) {
return $method;
}
}
return null;
}
private function doAction(string $action): void
{
$method = $this->getMethod($action);
if (is_null($method)) {
$this->responseERROR('Action "' . $action . '" not found');
return;
}
$params = array();
foreach ($method['params'] as $param) {
if (isset($_REQUEST[$param['name']])) {
$param_value = $_REQUEST[$param['name']];
switch ($param['type']) {
case 'int':
$param_value = (int) $param_value;
break;
case 'float':
$param_value = (float) $this->fixDecimalPoint($param_value);
break;
case 'string':
$param_value = (string) $param_value;
break;
case 'bool':
$param_value = (bool) $param_value;
break;
case 'array':
$param_value = json_decode($param_value, true);
break;
case 'object':
$param_value = json_decode($param_value);
break;
default:
// do nothing
break;
}
$params[$param['name']] = $param_value;
} else {
if (!$param['optional']) {
$this->responseERROR('Parameter "' . $param['name'] . '" is required');
return;
}
}
}
try {
$result = call_user_func_array(array($this, $method['name']), $params);
} catch (\Exception $e) {
$this->responseERROR($e->getMessage());
return;
}
$this->responseOK(array('status' => 'OK', 'data' => $result));
}
private function fixDecimalPoint(mixed $number): float|array
{
if (is_array($number)) {
foreach ($number as $index => $val) {
$number[$index] = $this->fixDecimalPoint($val);
}
return $number;
}
$position_comma = strpos($number, ',');
$position_dot = strpos($number, '.');
if (
$position_comma !== false
&& $position_dot !== false
) {
if ($position_comma < $position_dot) { // e.g. 2,845,478.55
$_number = str_replace(',', '', $number);
} else { // e.g. 2.845.478,55
$_number = str_replace('.', '', $number);
}
$number = $_number;
}
$number = str_replace(' ', '', $number);
$number = str_replace(',', '.', $number);
$pos = strpos($number, '.');
if ($pos !== false) $number = rtrim($number, '0');
if (substr($number, -1) == '.') $number .= '0';
return $number;
}
}

37
src/help.tpl.php Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $this->apiName; ?></title>
</head>
<body>
<h1><?php echo $this->apiName; ?></h1>
<p>API documentation in <a href="<?php echo $this->getCurrentUrl(); ?>?format=json">JSON</a></p>
<p>API endpoint URL <a href="<?php echo $this->getCurrentUrl(); ?>"><?php echo $this->getCurrentUrl(); ?></a></p>
<?php if (is_array($this->methods)) foreach ($this->methods as $index => $method) { ?>
<h2><?php echo $method['name']; ?></h2>
<i><?php echo $method['description']; ?></i>
<p>Endpoint URL: <a href="<?php echo $this->getCurrentUrl(); ?>?action=<?php echo $method['name']; ?>"><?php echo $this->getCurrentUrl(); ?>?action=<?php echo $method['name']; ?></a></p>
<?php if (is_array($method['params'])) foreach ($method['params'] as $param) { ?>
<p>
<?php echo $param['name']; ?>: <code><?php echo $param['type']; ?></code>
&mdash;
<?php
if ($param['optional']) echo '(optional) ';
echo $param['doc'];
?>
</p>
<?php } ?>
<?php } ?>
</body>
</html>

126
test/APIcalculator.php Normal file
View File

@ -0,0 +1,126 @@
<?php
/**
* Class APIcalculator
*
* A calculator class that extends the functionality of TPsoft's APIlite.
* This class provides basic arithmetic operations and a few additional
* mathematical functions.
*
*/
require_once __DIR__ . '/../src/APIlite.php';
class APIcalculator extends \TPsoft\APIlite\APIlite
{
/**
* Add two numbers.
* Standard plus operation.
*
* @param float $a The first number.
* @param float $b The second number.
* @return float The sum of the two numbers.
*/
public function add(float $a, float $b): float
{
return $a + $b;
}
/**
* Subtract two numbers.
* Standard minus operation.
*
* @param float $a The first number.
* @param float $b The second number.
* @return float The difference of the two numbers.
*/
public function subtract(float $a, float $b): float
{
return $a - $b;
}
/**
* Multiply two numbers.
* Standard multiplication operation.
*
* @param float $a The first number.
* @param float $b The second number.
* @return float The product of the two numbers.
*/
public function multiply(float $a, float $b): float
{
return $a * $b;
}
/**
* Divide two numbers.
* Standard division operation. Throws an exception if dividing by zero.
*
* @param float $a The first number.
* @param float $b The second number.
* @return float The quotient of the two numbers. Throws an exception if dividing by zero.
* @throws \Exception If dividing by zero.
*/
public function divide(float $a, float $b): float
{
if ($b == 0) {
throw new \Exception('Division by zero');
}
return $a / $b;
}
/**
* Raise a number to a power.
*
* @param float $a The base number.
* @param int $exponent The exponent.
* @return float The result of raising the base number to the exponent.
* @throws \Exception If the exponent is not an integer.
*/
public function power(float $a, int $exponent): float
{
$exponent = (int) $exponent;
if ($exponent == 0) {
return 1;
}
return pow($a, $exponent);
}
/**
* Calculate the square root of a number.
*
* @param int $a The number to calculate the square root of.
* @return float The square root of the number.
*/
public function sqrt(int $a): float
{
return sqrt($a);
}
/**
* Calculate the absolute value of a number.
*
* @param float $a The number to calculate the absolute value of.
* @return float The absolute value of the number.
*/
public function abs(float $a): float
{
return abs($a);
}
/**
* Round a number to the nearest integer.
*
* @param float $a The number to round.
* @param int $precision The number of decimal places to round to.
* @return int The rounded number.
*/
public function round(float $a, int $precision = 2): float
{
return round($a, $precision);
}
}
new APIcalculator();