Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fab8efd780 | |||
| 746b06e317 | |||
| 3fa131e1b2 | |||
| c0fd7b3fe5 | |||
| dab07e55ec | |||
| a10e864ba3 | |||
| 9d45bb5ceb | |||
| f1e6e92f46 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/composer.lock
|
||||||
|
/vendor
|
||||||
60
README.md
60
README.md
@ -2,6 +2,44 @@
|
|||||||
|
|
||||||
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.
|
||||||
@ -199,10 +237,10 @@ To connect to the API from TypeScript (e.g. Vue application) it is possible to d
|
|||||||
* 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
|
||||||
@ -227,7 +265,7 @@ export const backend = {
|
|||||||
});
|
});
|
||||||
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) => {
|
||||||
@ -239,34 +277,34 @@ export const backend = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------
|
/* ----------------------------------------------------
|
||||||
* 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
|
||||||
|
|||||||
129
bin/apilite-files
Normal file
129
bin/apilite-files
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
include $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
echo "ℹ️ APIlite files\n\n";
|
||||||
|
|
||||||
|
$project_root = findProjectRoot(__DIR__);
|
||||||
|
|
||||||
|
if (!$project_root) {
|
||||||
|
echo "⛔ Project root not found\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
$composer_arr = json_decode(file_get_contents($project_root . '/composer.json'), true);
|
||||||
|
|
||||||
|
echo "Project root: {$project_root}\n";
|
||||||
|
$source_dirs = array_values($composer_arr['autoload']['psr-4']);
|
||||||
|
foreach ($source_dirs as $index => $source_dir) {
|
||||||
|
echo "➡️ [$index]: $project_root/$source_dir\n";
|
||||||
|
}
|
||||||
|
$selected = readline("❔ Select source dir [default: 0]: ");
|
||||||
|
$selected = empty($selected) ? 0 : $selected;
|
||||||
|
if (!isset($source_dirs[$selected])) {
|
||||||
|
echo "⛔ Invalid selection\n";
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
$source_dir = $project_root . '/' . $source_dirs[$selected];
|
||||||
|
$source_dir = rtrim($source_dir, '/') . '/';
|
||||||
|
$namespaces = array_keys($composer_arr['autoload']['psr-4']);
|
||||||
|
$namespace = $namespaces[$selected];
|
||||||
|
echo "✔️ Source dir: {$source_dir} with namespace: {$namespace}\n";
|
||||||
|
|
||||||
|
// Copy files
|
||||||
|
echo "ℹ️ Copy files\n";
|
||||||
|
|
||||||
|
copyFile($source_dir . 'API.php', '<' . '?php
|
||||||
|
|
||||||
|
namespace ' . rtrim($namespace, '\\') . ';
|
||||||
|
|
||||||
|
use TPsoft\APIlite\APIlite;
|
||||||
|
|
||||||
|
class API extends APIlite {
|
||||||
|
|
||||||
|
}
|
||||||
|
');
|
||||||
|
|
||||||
|
copyFile($project_root . '/scripts/buildTypeScript.php', '<' . '?php
|
||||||
|
|
||||||
|
require __DIR__ . \'/../vendor/autoload.php\';
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
$backend_api = new TPsoft\BugreportBackend\API(\'typescript\', \'import.meta.env.VITE_BACKENDAPI_URL\', \'backend\');
|
||||||
|
|
||||||
|
$output = ob_get_contents();
|
||||||
|
ob_end_clean();
|
||||||
|
|
||||||
|
$ts_path = realpath(__DIR__ . \'/../../frontend/src\').\'/backend.js\';
|
||||||
|
$suc = file_put_contents($ts_path, $output);
|
||||||
|
if ($suc === false) {
|
||||||
|
echo "✗ TypeScript store into file failed\n";
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "✓ TypeScript backend script created\n";
|
||||||
|
');
|
||||||
|
|
||||||
|
copyFile($project_root . '/public/API.php', '<' . '?php
|
||||||
|
|
||||||
|
require_once __DIR__.\'/../src/API.php\';
|
||||||
|
new \\' . $namespace . 'API();
|
||||||
|
');
|
||||||
|
|
||||||
|
// Change composer.json
|
||||||
|
if (!isset($composer_arr['scripts']['build'])) {
|
||||||
|
$composer_arr['scripts']['build'] = 'php scripts/buildTypeScript.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$composer_json = json_encode($composer_arr, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
$composer_json = preg_replace_callback(
|
||||||
|
'/^( +)/m',
|
||||||
|
fn($m) => str_repeat("\t", intdiv(strlen($m[1]), 4)),
|
||||||
|
$composer_json
|
||||||
|
);
|
||||||
|
$suc = file_put_contents($project_root . '/composer.json', $composer_json);
|
||||||
|
if ($suc) {
|
||||||
|
echo "✅ File changed: $project_root/composer.json\n";
|
||||||
|
} else {
|
||||||
|
echo "🔴 Failed to change file: $project_root/composer.json\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions
|
||||||
|
*/
|
||||||
|
function findProjectRoot(string $startDir): ?string
|
||||||
|
{
|
||||||
|
$dir = realpath($startDir);
|
||||||
|
if (!$dir) return null;
|
||||||
|
while ($dir && $dir !== dirname($dir)) {
|
||||||
|
if (is_file($dir . '/vendor/autoload.php')) {
|
||||||
|
return $dir;
|
||||||
|
}
|
||||||
|
$dir = dirname($dir);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFile(string $destination, string $content): bool
|
||||||
|
{
|
||||||
|
$dir = dirname($destination);
|
||||||
|
if (!file_exists($dir)) {
|
||||||
|
mkdir($dir, 0777, true);
|
||||||
|
}
|
||||||
|
if (file_exists($destination)) {
|
||||||
|
echo "⚠️ File already exists: $destination\n";
|
||||||
|
$confirm = readline("❔ Overwrite? (y - yes, other - no): ");
|
||||||
|
if ($confirm != 'y') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$suc = file_put_contents($destination, $content) !== false;
|
||||||
|
if ($suc) {
|
||||||
|
echo "✅ File created: $destination\n";
|
||||||
|
} else {
|
||||||
|
echo "🔴 Failed to create file: $destination\n";
|
||||||
|
}
|
||||||
|
return $suc;
|
||||||
|
}
|
||||||
@ -32,5 +32,8 @@
|
|||||||
"psr-4": {
|
"psr-4": {
|
||||||
"TPsoft\\APIlite\\": "src/"
|
"TPsoft\\APIlite\\": "src/"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/apilite-files"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,10 +21,11 @@ class APIlite
|
|||||||
private string $endpoint = '';
|
private string $endpoint = '';
|
||||||
private $methods = array();
|
private $methods = array();
|
||||||
|
|
||||||
public function __construct(?string $format = null, ?string $endpoint = null)
|
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->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']);
|
||||||
@ -67,7 +68,9 @@ class APIlite
|
|||||||
private function analyzeClass(): bool
|
private function analyzeClass(): bool
|
||||||
{
|
{
|
||||||
$refClass = new \ReflectionClass($this);
|
$refClass = new \ReflectionClass($this);
|
||||||
|
if (strlen($this->apiName) <= 0) {
|
||||||
$this->apiName = $refClass->getShortName();
|
$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();
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class <?php echo $this->apiName; ?> {
|
class <?php echo $this->apiName; ?> {
|
||||||
endpont = "<?php echo $this->endpoint; ?>";
|
endpoint = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
|
||||||
|
|
||||||
/* ----------------------------------------------------
|
/* ----------------------------------------------------
|
||||||
* General API call
|
* General API call
|
||||||
@ -29,13 +29,17 @@ class <?php echo $this->apiName; ?> {
|
|||||||
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) => {
|
||||||
this.call(method, data, function(response) {
|
this.call(method, data, function(response) {
|
||||||
|
if (method == '__HELP__') {
|
||||||
|
resolve(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (response.status == 'OK') {
|
if (response.status == 'OK') {
|
||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
} else {
|
} else {
|
||||||
@ -61,4 +65,4 @@ class <?php echo $this->apiName; ?> {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default new BackendAPI();
|
export default new <?php echo $this->apiName; ?>();
|
||||||
|
|||||||
Reference in New Issue
Block a user