10 Commits

Author SHA1 Message Date
c0fd7b3fe5 added apiName 2025-10-13 23:36:34 +02:00
dab07e55ec fixed typo 2025-06-15 19:05:30 +02:00
a10e864ba3 transformed spaces to tabs 2025-06-12 08:28:15 +02:00
9d45bb5ceb update example of typescript in README 2025-06-12 08:26:58 +02:00
f1e6e92f46 added instruction of installation into README 2025-06-12 08:22:21 +02:00
2d3f8bfdd4 added list of method for HTML view 2025-06-12 07:54:37 +02:00
8502508a11 Merge branch 'main' of https://gitea.tpsoft.org/TPsoft.org/APIlite 2025-06-01 19:20:11 +02:00
a59044102c added format and endpoint into constructor,
fixed processing of parameters in methods,
added return for HTML manual,
chaged TypeScript code to class object export as default
2025-06-01 19:19:48 +02:00
b8ef58e132 added code types into README 2025-05-28 18:12:54 +02:00
2f7f63b620 removed require ext-pdo 2025-05-28 18:07:35 +02:00
5 changed files with 304 additions and 72 deletions

138
README.md
View File

@ -2,11 +2,49 @@
A set of tools to simplify the work of creating backend APIs for your frontend projects. Includes tutorials, patterns and practical examples for creating projects based on REST APIs. A set of tools to simplify the work of creating backend APIs for your frontend projects. Includes tutorials, patterns and practical examples for creating projects based on REST APIs.
## Installation
Download source code and add to your project
```php
<?php
require_once __DIR__ . '/APIlite/src/APIlite.php';
```
or use composer
```bash
composer require tpsoft/apilite
```
and add to your project
```php
<?php
require __DIR__ . '/vendor/autoload.php';
class YourAPI extends \TPsoft\APIlite\APIlite {
/**
* My first method for greetings.
*
* @param string $name Your name
* @param int $age Your age
* @return string Greetings
*/
public function myFirstMethod(string $name, int $age): string
{
return 'Hi, I`m '.$name.' and I`m '.$age.' years old.';
}
}
```
## Basic usage ## Basic usage
For example, we create an API for calculator. So we create class `APIcalculator` and store in file `test/APIcalculator.php` where we defined each actions for API as public method. For example, we create an API for calculator. So we create class `APIcalculator` and store in file `test/APIcalculator.php` where we defined each actions for API as public method.
``` ```php
<?php <?php
/** /**
@ -90,7 +128,7 @@ It is important to extend the `APIcalculator` class with the `\TPsoft\APIlite\AP
When you run this subfile through the webserver, you will see the JSON documentation in the browser When you run this subfile through the webserver, you will see the JSON documentation in the browser
``` ```js
{ {
"name": "APIcalculator", "name": "APIcalculator",
"html_version": "http://localhost/APIlite/test/APIcalculator.php?format=html", "html_version": "http://localhost/APIlite/test/APIcalculator.php?format=html",
@ -194,79 +232,79 @@ and there is also an HTML version available
To connect to the API from TypeScript (e.g. Vue application) it is possible to download the backend script To connect to the API from TypeScript (e.g. Vue application) it is possible to download the backend script
``` ```ts
/** /**
* Generated by APIlite * Generated by APIlite
* https://gitea.tpsoft.org/TPsoft.org/APIlite * https://gitea.tpsoft.org/TPsoft.org/APIlite
* *
* 2025-05-28 15:44:07 */ * 2025-06-12 06:24:33 */
export const backend = { class APIcalculator {
endpont: window.location.origin + "APIcalculator.php", endpont = "http://";
/* ---------------------------------------------------- /* ----------------------------------------------------
* General API call * General API call
*/ */
call(method, data, callback) { call(method, data, callback) {
var xhttp = new XMLHttpRequest(); var xhttp = new XMLHttpRequest();
xhttp.withCredentials = true; xhttp.withCredentials = true;
xhttp.onreadystatechange = function() { xhttp.onreadystatechange = function() {
if (this.readyState == 4) { if (this.readyState == 4) {
if (this.status == 200) { if (this.status == 200) {
if (callback != null) callback(JSON.parse(this.responseText)); if (callback != null) callback(JSON.parse(this.responseText));
} else { } else {
if (callback != null) callback({'status': 'ERROR', 'message': 'HTTP STATUS ' + this.status}); if (callback != null) callback({'status': 'ERROR', 'message': 'HTTP STATUS ' + this.status});
} }
}
} }
} var form_data = new FormData();
var form_data = new FormData(); Object.keys(data).forEach(key => {
Object.keys(data).forEach(key => { let val = data[key];
let val = data[key]; if (typeof val == 'object') val = JSON.stringify(val);
if (typeof val == 'object') val = JSON.stringify(val); form_data.append(key, val);
form_data.append(key, val); });
}); xhttp.open('POST', this.endpont + '?action=' + method);
xhttp.open('POST', this.endpont + '?action=' + method); xhttp.send(form_data);
xhttp.send(form_data); }
},
callPromise(method, data) { callPromise(method, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.call(method, data, function(response) { this.call(method, data, function(response) {
if (response.status == 'OK') { if (response.status == 'OK') {
resolve(response.data); resolve(response.data);
} else { } else {
reject(response.msg); reject(response.msg);
} }
}); });
}) })
}, }
/* ---------------------------------------------------- /* ----------------------------------------------------
* API actions * API actions
*/ */
help() { help() {
return this.callPromise('__HELP__', {}); return this.callPromise('__HELP__', {});
}, }
add(a, b) { add(a, b) {
return this.callPromise('add', {a: a, b: b}); return this.callPromise('add', {a: a, b: b});
}, }
subtract(a, b) { subtract(a, b) {
return this.callPromise('subtract', {a: a, b: b}); return this.callPromise('subtract', {a: a, b: b});
}, }
multiply(a, b) { multiply(a, b) {
return this.callPromise('multiply', {a: a, b: b}); return this.callPromise('multiply', {a: a, b: b});
}, }
divide(a, b) { divide(a, b) {
return this.callPromise('divide', {a: a, b: b}); return this.callPromise('divide', {a: a, b: b});
}, }
}; };
export default new BackendAPI();
``` ```
These outputs can also be generated in the command line as follows These outputs can also be generated in the command line as follows

View File

@ -26,8 +26,7 @@
} }
], ],
"require": { "require": {
"php": ">=8.2", "php": ">=8.2"
"ext-pdo": "*"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -18,23 +18,41 @@ class APIlite
{ {
private string $apiName = ''; private string $apiName = '';
private string $endpoint = '';
private $methods = array(); private $methods = array();
public function __construct() public function __construct(?string $format = null, ?string $endpoint = null, ?string $apiName = null)
{ {
register_shutdown_function(array($this, '_shutdownHandler')); register_shutdown_function(array($this, '_shutdownHandler'));
$this->endpoint = $endpoint ?? $this->getCurrentUrl();
$this->apiName = $apiName ?? '';
$this->analyzeClass(); $this->analyzeClass();
if (isset($_REQUEST['action'])) { if (isset($_REQUEST['action'])) {
$this->doAction($_REQUEST['action']); $this->doAction($_REQUEST['action']);
} else { } else {
global $argv; global $argv;
$switches = array_map('strtolower', is_array($argv) ? $argv : []); if (isset($_REQUEST['format'])) {
if ((isset($_REQUEST['format']) && $_REQUEST['format'] == 'html') || in_array('--html', $switches)) { $format = $_REQUEST['format'];
$this->printHelpHTML(); }
} elseif ((isset($_REQUEST['format']) && $_REQUEST['format'] == 'typescript') || in_array('--typescript', $switches)) { if (isset($argv)) {
$this->printHelpTypescript(); $switches = array_map('strtolower', is_array($argv) ? $argv : []);
} else { if (in_array('--html', $switches)) {
$this->printHelpJSON(); $format = 'html';
}
if (in_array('--typescript', $switches)) {
$format = 'typescript';
}
}
switch ($format) {
case 'html':
$this->printHelpHTML();
break;
case 'typescript':
$this->printHelpTypescript();
break;
default:
$this->printHelpJSON();
break;
} }
} }
} }
@ -50,7 +68,9 @@ class APIlite
private function analyzeClass(): bool private function analyzeClass(): bool
{ {
$refClass = new \ReflectionClass($this); $refClass = new \ReflectionClass($this);
$this->apiName = $refClass->getName(); if (strlen($this->apiName) <= 0) {
$this->apiName = $refClass->getShortName();
}
$this->methods = array(); $this->methods = array();
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $ref_method) { foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $ref_method) {
$method_name = $ref_method->getName(); $method_name = $ref_method->getName();
@ -81,11 +101,28 @@ class APIlite
if ($ref_param->isOptional()) { if ($ref_param->isOptional()) {
$param['default'] = $ref_param->getDefaultValue(); $param['default'] = $ref_param->getDefaultValue();
} }
$param['doc'] = $this->parseParamDoc($method['doc'], $param['name']); if (!is_null($method['doc'])) {
$param['doc'] = $this->parseParamDoc($method['doc'], $param['name']);
}
$method['params'][] = $param; $method['params'][] = $param;
} }
if ($ref_method->hasReturnType()) { if ($ref_method->hasReturnType()) {
$method['return'] = $ref_method->getReturnType()->getName(); $ref_type = $ref_method->getReturnType();
if ($ref_type instanceof \ReflectionNamedType) {
$method['return'] = $ref_type->getName();
}
if ($ref_type instanceof \ReflectionUnionType
|| $ref_type instanceof \ReflectionIntersectionType )
{
$types = $ref_type->getTypes();
$method['return'] = [];
foreach ($types as $type) {
if ($type instanceof \ReflectionNamedType) {
$method['return'][] = $type->getName();
}
}
}
} }
$this->methods[] = $method; $this->methods[] = $method;
} }

View File

@ -236,6 +236,140 @@
} }
} }
/* Methods grid */
.methods-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.method-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
padding: 1.5rem;
text-align: center;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.method-card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.method-card:hover::before {
left: 100%;
}
.method-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.method-card a {
color: white;
text-decoration: none;
font-weight: 600;
font-size: 1.1rem;
display: block;
position: relative;
z-index: 1;
}
/*
.method-card:nth-child(odd) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.method-card:nth-child(even) {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.method-card:nth-child(3n) {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.method-card:nth-child(4n) {
background: linear-gradient(135deg,rgb(86, 209, 127) 0%,rgb(52, 199, 172) 100%);
}
.method-card:nth-child(5n) {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
*/
.method-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.methods-section-title {
font-size: 2rem;
font-weight: 700;
color: #2c3e50;
margin-bottom: 1.5rem;
text-align: center;
position: relative;
}
.methods-section-title::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 2px;
}
/* Responsive adjustments for methods grid */
@media (max-width: 768px) {
.methods-grid {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 0.8rem;
}
.method-card {
padding: 1.2rem;
}
.method-card a {
font-size: 1rem;
}
.methods-section-title {
font-size: 1.7rem;
}
}
@media (max-width: 480px) {
.methods-grid {
grid-template-columns: 1fr;
gap: 0.6rem;
}
.method-card {
padding: 1rem;
}
.method-card a {
font-size: 0.95rem;
}
.methods-section-title {
font-size: 1.5rem;
}
}
/* Utility classes */ /* Utility classes */
.text-center { .text-center {
text-align: center; text-align: center;
@ -266,6 +400,18 @@
</div> </div>
</nav> </nav>
<!-- List of Methods -->
<section class="method-section">
<h2 class="methods-section-title">List of Methods</h2>
<div class="methods-grid">
<?php if (is_array($this->methods)) foreach ($this->methods as $index => $method) { ?>
<div class="method-card">
<a href="#<?php echo $method['name']; ?>"><?php echo $method['name']; ?></a>
</div>
<?php } ?>
</div>
</section>
<!-- Main Content --> <!-- Main Content -->
<main class="main-content"> <main class="main-content">
<div class="endpoint-url mb-3"> <div class="endpoint-url mb-3">
@ -274,7 +420,7 @@
</div> </div>
<?php if (is_array($this->methods)) foreach ($this->methods as $index => $method) { ?> <?php if (is_array($this->methods)) foreach ($this->methods as $index => $method) { ?>
<section class="method-section"> <section class="method-section" id="<?php echo $method['name']; ?>">
<h2 class="method-title"><?php echo $method['name']; ?></h2> <h2 class="method-title"><?php echo $method['name']; ?></h2>
<?php if (!empty($method['description'])) { ?> <?php if (!empty($method['description'])) { ?>
@ -306,6 +452,16 @@
</div> </div>
</div> </div>
<?php } ?> <?php } ?>
<h3 style="margin-bottom: 1rem; color: #2c3e50; display: inline-block;">Return:</h3>
<?php if (is_string($method['return'])) { ?>
<code class="parameter-type"><?php echo $method['return']; ?></code>
<?php } ?>
<?php if (is_array($method['return'])) foreach ($method['return'] as $return) { ?>
<code class="parameter-type"><?php echo $return; ?></code>
<?php } ?>
</div> </div>
<?php } ?> <?php } ?>
</section> </section>

View File

@ -5,8 +5,8 @@
* <?php echo date('Y-m-d H:i:s'); ?> * <?php echo date('Y-m-d H:i:s'); ?>
*/ */
export const backend = { class <?php echo $this->apiName; ?> {
endpont: window.location.origin + "<?php echo $this->apiName; ?>.php", endpoint = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
/* ---------------------------------------------------- /* ----------------------------------------------------
* General API call * General API call
@ -29,9 +29,9 @@ export const backend = {
if (typeof val == 'object') val = JSON.stringify(val); if (typeof val == 'object') val = JSON.stringify(val);
form_data.append(key, val); form_data.append(key, val);
}); });
xhttp.open('POST', this.endpont + '?action=' + method); xhttp.open('POST', this.endpoint + '?action=' + method);
xhttp.send(form_data); xhttp.send(form_data);
}, }
callPromise(method, data) { callPromise(method, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -43,20 +43,22 @@ export const backend = {
} }
}); });
}) })
}, }
/* ---------------------------------------------------- /* ----------------------------------------------------
* API actions * API actions
*/ */
help() { help() {
return this.callPromise('__HELP__', {}); return this.callPromise('__HELP__', {});
}, }
<?php if (is_array($this->methods)) foreach ($this->methods as $method) { <?php if (is_array($this->methods)) foreach ($this->methods as $method) {
echo "\t".$method['name'].'('.implode(', ', array_map(function($param) { return $param['name']; }, $method['params'])).') {'; echo "\t".$method['name'].'('.implode(', ', array_map(function($param) { return $param['name']; }, $method['params'])).') {';
echo "\n\t\treturn this.callPromise('".$method['name']."', {".implode(', ', array_map(function($param) { return $param['name'].': '.$param['name']; }, $method['params']))."});"; echo "\n\t\treturn this.callPromise('".$method['name']."', {".implode(', ', array_map(function($param) { return $param['name'].': '.$param['name']; }, $method['params']))."});";
echo "\n\t},\n\n"; echo "\n\t}\n\n";
} }
?> ?>
}; };
export default new BackendAPI();