9 Commits
v1.0.0 ... main

Author SHA1 Message Date
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 301 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.
## 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
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
/**
@ -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
```
```js
{
"name": "APIcalculator",
"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
```
```ts
/**
* Generated by APIlite
* https://gitea.tpsoft.org/TPsoft.org/APIlite
*
* 2025-05-28 15:44:07 */
* 2025-06-12 06:24:33 */
export const backend = {
endpont: window.location.origin + "APIcalculator.php",
class APIcalculator {
endpont = "http://";
/* ----------------------------------------------------
* General API call
*/
* General API call
*/
call(method, data, callback) {
var xhttp = new XMLHttpRequest();
xhttp.withCredentials = true;
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
if (callback != null) callback(JSON.parse(this.responseText));
} else {
if (callback != null) callback({'status': 'ERROR', 'message': 'HTTP STATUS ' + this.status});
}
var xhttp = new XMLHttpRequest();
xhttp.withCredentials = true;
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
if (callback != null) callback(JSON.parse(this.responseText));
} else {
if (callback != null) callback({'status': 'ERROR', 'message': 'HTTP STATUS ' + this.status});
}
}
}
}
var form_data = new FormData();
Object.keys(data).forEach(key => {
let val = data[key];
if (typeof val == 'object') val = JSON.stringify(val);
form_data.append(key, val);
});
xhttp.open('POST', this.endpont + '?action=' + method);
xhttp.send(form_data);
},
var form_data = new FormData();
Object.keys(data).forEach(key => {
let val = data[key];
if (typeof val == 'object') val = JSON.stringify(val);
form_data.append(key, val);
});
xhttp.open('POST', this.endpont + '?action=' + method);
xhttp.send(form_data);
}
callPromise(method, data) {
return new Promise((resolve, reject) => {
this.call(method, data, function(response) {
if (response.status == 'OK') {
resolve(response.data);
} else {
reject(response.msg);
}
});
})
},
return new Promise((resolve, reject) => {
this.call(method, data, function(response) {
if (response.status == 'OK') {
resolve(response.data);
} else {
reject(response.msg);
}
});
})
}
/* ----------------------------------------------------
* API actions
*/
* API actions
*/
help() {
return this.callPromise('__HELP__', {});
},
return this.callPromise('__HELP__', {});
}
add(a, b) {
return this.callPromise('add', {a: a, b: b});
},
return this.callPromise('add', {a: a, b: b});
}
subtract(a, b) {
return this.callPromise('subtract', {a: a, b: b});
},
return this.callPromise('subtract', {a: a, b: b});
}
multiply(a, b) {
return this.callPromise('multiply', {a: a, b: b});
},
return this.callPromise('multiply', {a: a, b: 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

View File

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

View File

@ -18,23 +18,40 @@ class APIlite
{
private string $apiName = '';
private string $endpoint = '';
private $methods = array();
public function __construct()
public function __construct(?string $format = null, ?string $endpoint = null)
{
register_shutdown_function(array($this, '_shutdownHandler'));
$this->endpoint = $endpoint ?? $this->getCurrentUrl();
$this->analyzeClass();
if (isset($_REQUEST['action'])) {
$this->doAction($_REQUEST['action']);
} else {
global $argv;
$switches = array_map('strtolower', is_array($argv) ? $argv : []);
if ((isset($_REQUEST['format']) && $_REQUEST['format'] == 'html') || in_array('--html', $switches)) {
$this->printHelpHTML();
} elseif ((isset($_REQUEST['format']) && $_REQUEST['format'] == 'typescript') || in_array('--typescript', $switches)) {
$this->printHelpTypescript();
} else {
$this->printHelpJSON();
if (isset($_REQUEST['format'])) {
$format = $_REQUEST['format'];
}
if (isset($argv)) {
$switches = array_map('strtolower', is_array($argv) ? $argv : []);
if (in_array('--html', $switches)) {
$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 +67,7 @@ class APIlite
private function analyzeClass(): bool
{
$refClass = new \ReflectionClass($this);
$this->apiName = $refClass->getName();
$this->apiName = $refClass->getShortName();
$this->methods = array();
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $ref_method) {
$method_name = $ref_method->getName();
@ -81,11 +98,28 @@ class APIlite
if ($ref_param->isOptional()) {
$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;
}
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;
}

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 */
.text-center {
text-align: center;
@ -266,6 +400,18 @@
</div>
</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 class="main-content">
<div class="endpoint-url mb-3">
@ -274,7 +420,7 @@
</div>
<?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>
<?php if (!empty($method['description'])) { ?>
@ -306,6 +452,16 @@
</div>
</div>
<?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>
<?php } ?>
</section>
@ -323,4 +479,4 @@
</div>
</body>
</html>
</html>

View File

@ -5,8 +5,8 @@
* <?php echo date('Y-m-d H:i:s'); ?>
*/
export const backend = {
endpont: window.location.origin + "<?php echo $this->apiName; ?>.php",
class <?php echo $this->apiName; ?> {
endpoint = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
/* ----------------------------------------------------
* General API call
@ -29,9 +29,9 @@ export const backend = {
if (typeof val == 'object') val = JSON.stringify(val);
form_data.append(key, val);
});
xhttp.open('POST', this.endpont + '?action=' + method);
xhttp.open('POST', this.endpoint + '?action=' + method);
xhttp.send(form_data);
},
}
callPromise(method, data) {
return new Promise((resolve, reject) => {
@ -43,20 +43,22 @@ export const backend = {
}
});
})
},
}
/* ----------------------------------------------------
* API actions
*/
help() {
return this.callPromise('__HELP__', {});
},
}
<?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 "\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();