First commit
This commit is contained in:
commit
b67fac10bc
10
.env.example
Normal file
10
.env.example
Normal file
@ -0,0 +1,10 @@
|
||||
APP_NAME="KamilCraft.com"
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://127.0.0.1
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=test
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.idea
|
||||
vendor
|
||||
.env
|
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# KamilCraftApi
|
||||
|
||||
API for kamilcraft.com projects
|
||||
|
||||
## Installation
|
||||
|
||||
### Required
|
||||
|
||||
* PHP 8.0 or later
|
||||
* Composer 2.3.x or later
|
||||
|
||||
### Installation of dependencies
|
||||
|
||||
```shell
|
||||
composer install
|
||||
```
|
||||
|
||||
## Launching
|
||||
|
||||
```shell
|
||||
php -S localhost:80 public/index.php
|
||||
```
|
43
app/Controllers/CategoriesController.php
Normal file
43
app/Controllers/CategoriesController.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\App\Controllers;
|
||||
|
||||
use KamilCraftApi\App\Models\Category;
|
||||
use KamilCraftApi\Request\Request;
|
||||
use KamilCraftApi\Response;
|
||||
|
||||
class CategoriesController extends Controller
|
||||
{
|
||||
private Category $category;
|
||||
|
||||
public function __construct(Request $request) {
|
||||
parent::__construct();
|
||||
$this->category = new Category($request);
|
||||
}
|
||||
|
||||
public function showWhereName(string $slug): Response
|
||||
{
|
||||
if ( ! ($category = $this->category->getCategoryWhereName($slug)) ) {
|
||||
return (new Response())->json([
|
||||
'message' => 'Not found category'
|
||||
], 404);
|
||||
}
|
||||
return (new Response())->json($category);
|
||||
}
|
||||
|
||||
public function show(int $id): Response
|
||||
{
|
||||
if ( ! ($category = $this->category->getCategory($id)) ) {
|
||||
return (new Response())->json([
|
||||
'message' => 'Not found category'
|
||||
], 404);
|
||||
}
|
||||
return (new Response())->json($category);
|
||||
}
|
||||
|
||||
public function __invoke(): Response
|
||||
{
|
||||
$categories = $this->category;
|
||||
return (new Response())->json($categories());
|
||||
}
|
||||
}
|
21
app/Controllers/Controller.php
Normal file
21
app/Controllers/Controller.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\App\Controllers;
|
||||
|
||||
use KamilCraftApi\Interfaces\ControllerInterface;
|
||||
use KamilCraftApi\Response;
|
||||
|
||||
class Controller implements ControllerInterface
|
||||
{
|
||||
private Response $response;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->response = new Response();
|
||||
}
|
||||
|
||||
protected function response(): Response
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
17
app/Controllers/HomeController.php
Normal file
17
app/Controllers/HomeController.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\App\Controllers;
|
||||
|
||||
use KamilCraftApi\Interfaces\ControllerInterface;
|
||||
use KamilCraftApi\Request\Request;
|
||||
use KamilCraftApi\Response;
|
||||
|
||||
class HomeController implements ControllerInterface
|
||||
{
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
return (new Response())->json((object)[
|
||||
'message' => 'Hello World'
|
||||
]);
|
||||
}
|
||||
}
|
39
app/Controllers/ProjectController.php
Normal file
39
app/Controllers/ProjectController.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\App\Controllers;
|
||||
|
||||
use KamilCraftApi\App\Models\Project;
|
||||
use KamilCraftApi\Response;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
private Project $project;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->project = new Project();
|
||||
}
|
||||
|
||||
public function showWhereCategory(string $category): Response
|
||||
{
|
||||
$projects = $this->project->getProjectWhereCategory($category);
|
||||
return (new Response())->json($projects);
|
||||
}
|
||||
|
||||
public function show(int $id): Response
|
||||
{
|
||||
if ( ! ($project = $this->project->getProject($id)) ) {
|
||||
return (new Response())->json([
|
||||
'message' => 'Not found project'
|
||||
], 404);
|
||||
}
|
||||
return (new Response())->json($project);
|
||||
}
|
||||
|
||||
public function __invoke(): Response
|
||||
{
|
||||
$projects = $this->project;
|
||||
return (new Response())->json($projects());
|
||||
}
|
||||
}
|
78
app/Models/Category.php
Normal file
78
app/Models/Category.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\App\Models;
|
||||
|
||||
use KamilCraftApi\Request\Request;
|
||||
use KamilCraftApi\Response;
|
||||
use stdClass;
|
||||
|
||||
class Category
|
||||
{
|
||||
const JSON_PATH = ROOT_PATH . 'resources/json';
|
||||
private array $allCategory = [];
|
||||
|
||||
public function __construct(
|
||||
private Request $request
|
||||
) {
|
||||
$categories = file_get_contents(self::JSON_PATH . '/Categories.json');
|
||||
if ( $category = json_decode($categories) ) {
|
||||
$this->allCategory = $category;
|
||||
} else {
|
||||
$this->parseError();
|
||||
}
|
||||
}
|
||||
|
||||
public function getCategoryWhereName(string $slug): object|bool
|
||||
{
|
||||
if ( ( $key = array_search($slug, array_column($this->allCategory, 'slug') ) ) !== false ) {
|
||||
$category = $this->allCategory[$key];
|
||||
$category->default = $category->default ?? false;
|
||||
$category->visible = $category->visible ?? true;
|
||||
return $category;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getCategory(int $id): object|bool
|
||||
{
|
||||
if ( ( $key = array_search($id, array_column($this->allCategory, 'id') ) ) !== false ) {
|
||||
$category = $this->allCategory[$key];
|
||||
$category->default = $category->default ?? false;
|
||||
$category->visible = $category->visible ?? true;
|
||||
return $category;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function parseError(): void
|
||||
{
|
||||
if ( $this->request->getContentType() === 'application/json' ) {
|
||||
(new Response())->json((object)[
|
||||
'message' => 'Server error'
|
||||
], 500)->render();
|
||||
} else {
|
||||
(new Response())(
|
||||
file_get_contents(ROOT_PATH . '/errors/error500.html'),
|
||||
500
|
||||
)->render();
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
public function __invoke(): array
|
||||
{
|
||||
$tmp_categories = [];
|
||||
foreach ($this->allCategory as $category) {
|
||||
$tmp_category = new stdClass;
|
||||
$tmp_category->id = $category->id;
|
||||
$tmp_category->name = $category->name;
|
||||
$tmp_category->slug = $category->slug;
|
||||
$tmp_category->default = $category->default ?? false;
|
||||
$tmp_category->visible = $category->visible ?? true;
|
||||
|
||||
$tmp_categories[] = $tmp_category;
|
||||
}
|
||||
|
||||
return $tmp_categories;
|
||||
}
|
||||
}
|
72
app/Models/Project.php
Normal file
72
app/Models/Project.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\App\Models;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Project
|
||||
{
|
||||
const JSON_PATH = ROOT_PATH . 'resources/json';
|
||||
const SHORT_PROJECT = [
|
||||
'id',
|
||||
'title',
|
||||
'image',
|
||||
'categories',
|
||||
'version'
|
||||
];
|
||||
private array $allProjects = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$projects = file_get_contents(self::JSON_PATH . '/Projects.json');
|
||||
if ( $projects = json_decode($projects) ) {
|
||||
$this->allProjects = $projects;
|
||||
}
|
||||
}
|
||||
|
||||
public function getProject(int $id): object|bool
|
||||
{
|
||||
if ( ( $key = array_search($id, array_column($this->allProjects, 'id')) ) !== false ) {
|
||||
return $this->allProjects[$key];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getProjectWhereCategory(string $category): array
|
||||
{
|
||||
$listWhereCategory = [];
|
||||
foreach ( array_column($this->allProjects, 'categories') as $key => $value ) {
|
||||
if ( in_array($category, $value) ) {
|
||||
$listWhereCategory[] = $this->shortProjectSchema($this->allProjects[$key]);
|
||||
}
|
||||
}
|
||||
return $listWhereCategory;
|
||||
}
|
||||
|
||||
private function shortProjectSchema(object $project): object
|
||||
{
|
||||
$tmp_project = new stdClass;
|
||||
foreach ($project as $key => $value) {
|
||||
if ( in_array($key, self::SHORT_PROJECT) ) {
|
||||
$tmp_project->{$key} = $value;
|
||||
}
|
||||
}
|
||||
$tmp_project->short_description = substr(
|
||||
explode("\n", $project->description)[0],
|
||||
0,
|
||||
255
|
||||
);
|
||||
|
||||
return $tmp_project;
|
||||
}
|
||||
|
||||
public function __invoke(): array
|
||||
{
|
||||
$arr_map = [];
|
||||
foreach ($this->allProjects as $project) {
|
||||
$arr_map[] = $this->shortProjectSchema($project);
|
||||
}
|
||||
|
||||
return $arr_map;
|
||||
}
|
||||
}
|
25
composer.json
Normal file
25
composer.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "kamilniemczycki/kamilcraft-api",
|
||||
"description": "Api for kamilcraft.com",
|
||||
"keywords": ["kamilcraft", "api"],
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kamil Niemczycki",
|
||||
"email": "contact@kamilcraft.com"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"symfony/dotenv": "5.3.7"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"KamilCraftApi\\": "src/",
|
||||
"KamilCraftApi\\App\\": "app/"
|
||||
}
|
||||
}
|
||||
}
|
158
composer.lock
generated
Normal file
158
composer.lock
generated
Normal file
@ -0,0 +1,158 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a90b92d03b08e514cb09f27c75280b4c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v2.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
|
||||
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.4-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
"url": "https://github.com/symfony/contracts"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"function.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-03-23T23:28:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/dotenv",
|
||||
"version": "v5.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dotenv.git",
|
||||
"reference": "99a18c2e23f4d901c36cea2012f9f1a8e25e99e4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dotenv/zipball/99a18c2e23f4d901c36cea2012f9f1a8e25e99e4",
|
||||
"reference": "99a18c2e23f4d901c36cea2012f9f1a8e25e99e4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/deprecation-contracts": "^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/process": "^4.4|^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Dotenv\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Registers environment variables from a .env file",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"dotenv",
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/dotenv/tree/v5.3.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-02T16:35:09+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
14
errors/error400.html
Normal file
14
errors/error400.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Błąd 400</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Błąd 400</h1>
|
||||
<p>Niepoprawne zapytanie!</p>
|
||||
</body>
|
||||
</html>
|
14
errors/error404.html
Normal file
14
errors/error404.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Błąd 404</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Błąd 404</h1>
|
||||
<p>Nie znaleziono szukanej strony!</p>
|
||||
</body>
|
||||
</html>
|
14
errors/error500.html
Normal file
14
errors/error500.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Błąd 500</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Błąd 500</h1>
|
||||
<p>Wystąpił wewnętrzny błąd serwera!</p>
|
||||
</body>
|
||||
</html>
|
10
public/index.php
Normal file
10
public/index.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
const ROOT_PATH = __DIR__ . '/../';
|
||||
|
||||
use KamilCraftApi\Application;
|
||||
|
||||
require ROOT_PATH . 'vendor/autoload.php';
|
||||
|
||||
$app = new Application(ROOT_PATH . '.env');
|
||||
$app->run();
|
24
resources/json/Categories.json
Normal file
24
resources/json/Categories.json
Normal file
@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Wszystkie",
|
||||
"slug": "all",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Prywatne",
|
||||
"slug": "private"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Zlecenie",
|
||||
"slug": "custom"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Wybrane",
|
||||
"slug": "selected",
|
||||
"visible": false
|
||||
}
|
||||
]
|
24
resources/json/Projects.json
Normal file
24
resources/json/Projects.json
Normal file
@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Memstacja.pl",
|
||||
"categories": ["selected", "private"],
|
||||
"author": "Kamil Niemczycki",
|
||||
"image": "assets/img/projects/kamilcraft.jpg",
|
||||
"release_data": "28 sierpnia 2021",
|
||||
"project_url": "https://memstacja.pl",
|
||||
"version": "v1.4.9",
|
||||
"description": "Memstacja.pl jest to strona z memami. Umożliwia dodanie własnego mema przez panel administracyjny, nadanie tagów i opisu. Użytkownicy mogą głosować przy użyciu serduszek, mając wpływ na pozycję danego mema na liście polecanych i TOP 10."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "edwardgoff-art.co.uk",
|
||||
"categories": ["selected", "custom"],
|
||||
"author": "Kamil Niemczycki",
|
||||
"image": "assets/img/projects/edward.jpg",
|
||||
"release_data": "25 sierpnia 2021",
|
||||
"project_url": "https://edwardgoff-art.co.uk",
|
||||
"version": "v1.1.4",
|
||||
"description": "Strona jest wizytówką autora i galerią jego prac. Strona umożliwa tworzenie, a także dodawanie zdjęć i opisów dla utworzonych galerii."
|
||||
}
|
||||
]
|
32
router.php
Normal file
32
router.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use KamilCraftApi\Router\Router;
|
||||
use KamilCraftApi\App\Controllers\HomeController;
|
||||
use KamilCraftApi\App\Controllers\ProjectController;
|
||||
use KamilCraftApi\App\Controllers\CategoriesController;
|
||||
|
||||
$router = new Router;
|
||||
|
||||
$router
|
||||
->get('/', HomeController::class)
|
||||
->name('home');
|
||||
$router
|
||||
->get('/projects', ProjectController::class)
|
||||
->name('projects');
|
||||
$router
|
||||
->get('/projects/:id', ProjectController::class .'@show')
|
||||
->name('projects.show');
|
||||
$router
|
||||
->get('/projects/category/:category', ProjectController::class .'@showWhereCategory')
|
||||
->name('projects.category.show');
|
||||
$router
|
||||
->get('/categories', CategoriesController::class)
|
||||
->name('categories');
|
||||
$router
|
||||
->get('/categories/:id', CategoriesController::class .'@show')
|
||||
->name('categories.show');
|
||||
$router
|
||||
->get('/categories_name/:slug', CategoriesController::class .'@showWhereName')
|
||||
->name('categories.name.show');
|
||||
|
||||
return $router;
|
175
src/Application.php
Normal file
175
src/Application.php
Normal file
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi;
|
||||
|
||||
use Exception;
|
||||
use KamilCraftApi\Exceptions\ApplicationException;
|
||||
use KamilCraftApi\Interfaces\ControllerInterface;
|
||||
use KamilCraftApi\Interfaces\RenderInterface;
|
||||
use KamilCraftApi\Request\Request;
|
||||
use KamilCraftApi\Router\Router;
|
||||
use KamilCraftApi\Router\RouterElement;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use ReflectionNamedType;
|
||||
use stdClass;
|
||||
|
||||
class Application
|
||||
{
|
||||
protected Request $request;
|
||||
protected Router $router;
|
||||
protected ?RouterElement $selectedRouterElement;
|
||||
protected string $controller = '';
|
||||
|
||||
public function __construct(
|
||||
protected string $envFileConfig
|
||||
) {
|
||||
$this->request = new Request($_SERVER);
|
||||
$this->router = new Router($this->request);
|
||||
}
|
||||
|
||||
public function getRequest(): Request
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
public function getRouter(): Router
|
||||
{
|
||||
return $this->router;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function checkParameters(array $controller)
|
||||
{
|
||||
try {
|
||||
$methodParameters = (new ReflectionMethod($controller[0], $controller[1]))->getParameters();
|
||||
foreach ($methodParameters as $parameter) {
|
||||
if (($type = $parameter->getType()) instanceof ReflectionNamedType) {
|
||||
if (
|
||||
$type->getName() === 'int' &&
|
||||
isset($this->selectedRouterElement->getAttributes()[$parameter->getName()]) &&
|
||||
!is_numeric($this->selectedRouterElement->getAttributes()[$parameter->getName()])
|
||||
) {
|
||||
throw new Exception('Error type of attribute');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function selectController()
|
||||
{
|
||||
$controller = explode('@', $this->controller);
|
||||
$controller[1] = $controller[1] ?? '__invoke';
|
||||
try {
|
||||
if (! class_exists($controller[0]) ) {
|
||||
throw new ApplicationException('Class not exists');
|
||||
}
|
||||
if (! method_exists($controller[0], $controller[1]) ) {
|
||||
throw new ApplicationException('Method not exists');
|
||||
}
|
||||
$this->checkParameters($controller);
|
||||
|
||||
$classController = $this->createClass($controller[0]);
|
||||
$loadedController = $this->createMethod($classController, $controller[1]);
|
||||
$this->responseRender($loadedController);
|
||||
} catch (ApplicationException $e) {
|
||||
if ( $this->request->getContentType() === 'application/json' ) {
|
||||
(new Response())->json((object)[
|
||||
'message' => 'Server error'
|
||||
], 500)->render();
|
||||
} else {
|
||||
(new Response())(
|
||||
file_get_contents(ROOT_PATH . 'errors/error500.html'), 500
|
||||
)->render();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ( $this->request->getContentType() === 'application/json' ) {
|
||||
(new Response())->json((object)[
|
||||
'message' => 'Invalid request'
|
||||
], 400)->render();
|
||||
} else {
|
||||
(new Response())(
|
||||
file_get_contents(ROOT_PATH . 'errors/error400.html'), 400
|
||||
)->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->router = require ROOT_PATH . 'router.php';
|
||||
|
||||
if ( ! $this->router->requestIsAdded() ) {
|
||||
$this->router->setRequest($this->request);
|
||||
}
|
||||
|
||||
$selected = $this->getRouter()->selectRouter();
|
||||
|
||||
if ( $selected ) {
|
||||
$this->controller = $selected->controller;
|
||||
$this->selectedRouterElement = $selected;
|
||||
$this->selectController();
|
||||
} else {
|
||||
if ( $this->request->getContentType() === 'application/json' ) {
|
||||
(new Response())->json((object)[
|
||||
'message' => 'Not found'
|
||||
], 404)->render();
|
||||
} else {
|
||||
(new Response())(
|
||||
file_get_contents(ROOT_PATH . 'errors/error404.html'), 404
|
||||
)->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function createClass(string $className): ControllerInterface|stdClass
|
||||
{
|
||||
$parameters = [];
|
||||
if ( $classReflection = (new ReflectionClass($className))->getConstructor() ) {
|
||||
foreach ($classReflection->getParameters() as $parameter) {
|
||||
if (
|
||||
$parameter->getType() instanceof ReflectionNamedType &&
|
||||
$parameter->getType()->getName() === Request::class
|
||||
) {
|
||||
$parameters[$parameter->getName()] = $this->request;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new $className(...$parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function createMethod(ControllerInterface|stdClass $controller, string $methodName): mixed
|
||||
{
|
||||
$reflectionMethod = new ReflectionMethod($controller, $methodName);
|
||||
$parameters = $this->selectedRouterElement->getAttributes();
|
||||
foreach ( $reflectionMethod->getParameters() as $parameter ) {
|
||||
if (
|
||||
$parameter->getType() instanceof ReflectionNamedType &&
|
||||
$parameter->getType()->getName() === Request::class
|
||||
) {
|
||||
$parameters[$parameter->getName()] = $this->request;
|
||||
}
|
||||
}
|
||||
return call_user_func_array([$controller, $methodName], $parameters);
|
||||
}
|
||||
|
||||
private function responseRender($loadedController): void
|
||||
{
|
||||
if($loadedController instanceof RenderInterface) {
|
||||
$loadedController->render();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
8
src/Exceptions/ApplicationException.php
Normal file
8
src/Exceptions/ApplicationException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ApplicationException extends Exception
|
||||
{}
|
6
src/Interfaces/ControllerInterface.php
Normal file
6
src/Interfaces/ControllerInterface.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\Interfaces;
|
||||
|
||||
interface ControllerInterface
|
||||
{}
|
8
src/Interfaces/RenderInterface.php
Normal file
8
src/Interfaces/RenderInterface.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\Interfaces;
|
||||
|
||||
interface RenderInterface
|
||||
{
|
||||
public function render(): void;
|
||||
}
|
96
src/Request/Request.php
Normal file
96
src/Request/Request.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\Request;
|
||||
|
||||
class Request
|
||||
{
|
||||
protected array $request = [
|
||||
'method' => '',
|
||||
'uri' => '',
|
||||
'attributes' => [],
|
||||
'content_type' => ''
|
||||
];
|
||||
protected array $uri_root = [];
|
||||
|
||||
public function __construct(
|
||||
protected array $request_data
|
||||
)
|
||||
{
|
||||
$this->request['method'] = $this->request_data['REQUEST_METHOD'];
|
||||
$this->request['uri'] = explode('?', $this->request_data['REQUEST_URI'])[0];
|
||||
|
||||
$this->setAttributes();
|
||||
$this->setContentType();
|
||||
$this->createUriRoot($this->getUri());
|
||||
}
|
||||
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->request['method'];
|
||||
}
|
||||
|
||||
public function getUri(): string
|
||||
{
|
||||
return $this->request['uri'];
|
||||
}
|
||||
|
||||
public function getAttributes(): RequestData
|
||||
{
|
||||
return $this->request['attributes'];
|
||||
}
|
||||
|
||||
public function getUriRoot(): array
|
||||
{
|
||||
return $this->uri_root;
|
||||
}
|
||||
|
||||
private function setAttributes()
|
||||
{
|
||||
$requestData = new RequestData();
|
||||
$this->setGetAttributes($requestData);
|
||||
$this->setPostAttributes($requestData);
|
||||
$this->request['attributes'] = $requestData;
|
||||
}
|
||||
|
||||
private function setGetAttributes(RequestData $requestData)
|
||||
{
|
||||
foreach ( $_GET ?? [] as $name => $value ) {
|
||||
$requestData->add('get', [ 'name' => $name, 'value' => $this->valueParser($value) ]);
|
||||
}
|
||||
}
|
||||
|
||||
private function setPostAttributes(RequestData $requestData)
|
||||
{
|
||||
foreach ( $_POST ?? [] as $name => $value ) {
|
||||
$requestData->add('post', [ 'name' => $name, 'value' => $this->valueParser($value) ]);
|
||||
}
|
||||
}
|
||||
|
||||
private function valueParser(string $value): string|int|null
|
||||
{
|
||||
$value = is_numeric($value) ? (int)$value : $value;
|
||||
return empty($value) ? null : $value;
|
||||
}
|
||||
|
||||
private function setContentType()
|
||||
{
|
||||
$this->request['content_type'] =
|
||||
$this->request_data['HTTP_CONTENT_TYPE'] ??
|
||||
$this->request_data['CONTENT_TYPE'] ??
|
||||
'GET';
|
||||
}
|
||||
|
||||
public function getContentType()
|
||||
{
|
||||
return $this->request['content_type'];
|
||||
}
|
||||
|
||||
private function createUriRoot(string $uri): void
|
||||
{
|
||||
foreach (explode('/', $uri) as $uri_root_element) {
|
||||
if ( is_numeric($uri_root_element) || !empty($uri_root_element) ) {
|
||||
$this->uri_root[] = $uri_root_element;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
src/Request/RequestData.php
Normal file
29
src/Request/RequestData.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\Request;
|
||||
|
||||
class RequestData
|
||||
{
|
||||
protected array $data = [
|
||||
'get' => [],
|
||||
'post' => [],
|
||||
'json' => [],
|
||||
'other' => []
|
||||
];
|
||||
|
||||
public function add(string $type, array $data = []): void
|
||||
{
|
||||
if ( array_key_exists($type, $this->data) ) {
|
||||
array_push($this->data[$type], $data);
|
||||
} else {
|
||||
array_push($this->data['other'], $data);
|
||||
}
|
||||
}
|
||||
|
||||
public function get(string $type, string $name): array|null
|
||||
{
|
||||
$key = array_search($name, array_column($this->data[$type], 'name'));
|
||||
|
||||
return $this->data[$type][$key] ?? null;
|
||||
}
|
||||
}
|
52
src/Response.php
Normal file
52
src/Response.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi;
|
||||
|
||||
use KamilCraftApi\Interfaces\RenderInterface;
|
||||
|
||||
class Response implements RenderInterface
|
||||
{
|
||||
protected int $http_code = 200;
|
||||
protected string $content_type = 'text/html';
|
||||
protected string $body_content = '';
|
||||
|
||||
public function __construct(
|
||||
protected string $charset = 'utf-8'
|
||||
)
|
||||
{}
|
||||
|
||||
public function json(object|array $response, int $http_code = 200): Response
|
||||
{
|
||||
$this->http_code = $http_code;
|
||||
$this->content_type = 'application/json';
|
||||
$this->body_content = json_encode($response);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function html(string $body_content = '', int $http_code = 200): Response
|
||||
{
|
||||
$this->http_code = $http_code;
|
||||
$this->content_type = 'text/html';
|
||||
$this->body_content = $body_content;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function responseHeader(): void
|
||||
{
|
||||
http_response_code($this->http_code);
|
||||
header('Content-Type: '. $this->content_type .'; charset='. $this->charset);
|
||||
}
|
||||
|
||||
public function render(): void
|
||||
{
|
||||
$this->responseHeader();
|
||||
echo $this->body_content;
|
||||
}
|
||||
|
||||
public function __invoke(string $body_content = '', int $http_code = 200): Response
|
||||
{
|
||||
return $this->html($body_content, $http_code);
|
||||
}
|
||||
}
|
73
src/Router/Router.php
Normal file
73
src/Router/Router.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\Router;
|
||||
|
||||
use KamilCraftApi\Request\Request;
|
||||
|
||||
/**
|
||||
* @method RouterElement|null get(string $uri, string $controller, array $attributes = [])
|
||||
* @method RouterElement|null post(string $uri, string $controller, array $attributes = [])
|
||||
* @method RouterElement|null put(string $uri, string $controller, array $attributes = [])
|
||||
* @method RouterElement|null path(string $uri, string $controller, array $attributes = [])
|
||||
* @method RouterElement|null delete(string $uri, string $controller, array $attributes = [])
|
||||
*/
|
||||
|
||||
class Router
|
||||
{
|
||||
const METHODS = [
|
||||
'get',
|
||||
'post',
|
||||
'put',
|
||||
'path',
|
||||
'delete'
|
||||
];
|
||||
protected ?RouterCollection $routerCollection;
|
||||
|
||||
public function __construct(
|
||||
protected ?Request $request = null
|
||||
)
|
||||
{
|
||||
$this->routerCollection = new RouterCollection();
|
||||
}
|
||||
|
||||
public function setRequest(Request $request): void
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function requestIsAdded(): bool
|
||||
{
|
||||
return isset($this->request);
|
||||
}
|
||||
|
||||
public function selectRouter(): RouterElement|null
|
||||
{
|
||||
return $this->routerCollection->findUri($this->request->getUriRoot(), $this->request);
|
||||
}
|
||||
|
||||
private function addToCollection(
|
||||
string $method,
|
||||
string $uri,
|
||||
string $controller,
|
||||
array $attributes = []
|
||||
): RouterElement
|
||||
{
|
||||
$routerElement = new RouterElement($method, $uri, $controller, $attributes);
|
||||
$this->routerCollection->add($routerElement);
|
||||
|
||||
return $routerElement;
|
||||
}
|
||||
|
||||
public function __call(
|
||||
string $name,
|
||||
array $arguments
|
||||
): RouterElement|null
|
||||
{
|
||||
if ( in_array($name, self::METHODS, true) ) {
|
||||
$args = array_merge([$name], $arguments);
|
||||
return call_user_func_array([$this, 'addToCollection'], $args);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
49
src/Router/RouterCollection.php
Normal file
49
src/Router/RouterCollection.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\Router;
|
||||
|
||||
use KamilCraftApi\Request\Request;
|
||||
|
||||
class RouterCollection
|
||||
{
|
||||
private array $collection;
|
||||
|
||||
public function add(RouterElement $routerElement): void
|
||||
{
|
||||
$this->collection[] = $routerElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<RouterElement>|null
|
||||
*/
|
||||
public function getAll(): array|null
|
||||
{
|
||||
return $this->collection ?? null;
|
||||
}
|
||||
|
||||
public function findUri(array $request_uri_root, Request $request): RouterElement|null
|
||||
{
|
||||
foreach ($this->getAll() as $routerElement) {
|
||||
/** @var RouterElement $routerElement */
|
||||
if (
|
||||
$routerElement->itsMe($request_uri_root) &&
|
||||
$routerElement->method_request === strtolower($request->getMethod())
|
||||
) {
|
||||
return $routerElement;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get(string $name): RouterElement|null
|
||||
{
|
||||
foreach ( $this->collection as $routeElement ) {
|
||||
/** @var RouterElement $routeElement */
|
||||
if ( $routeElement->name === $name ) {
|
||||
return $routeElement;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
78
src/Router/RouterElement.php
Normal file
78
src/Router/RouterElement.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace KamilCraftApi\Router;
|
||||
|
||||
class RouterElement
|
||||
{
|
||||
public string $name;
|
||||
private array $uri_root = [];
|
||||
|
||||
public function __construct(
|
||||
public string $method_request,
|
||||
public string $uri,
|
||||
public string $controller,
|
||||
public array $attributes = []
|
||||
)
|
||||
{
|
||||
$this->createUriRoot($uri);
|
||||
}
|
||||
|
||||
public function name(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getUriRoot(): array
|
||||
{
|
||||
return $this->uri_root;
|
||||
}
|
||||
|
||||
public function addAttributes(array $attributes): void
|
||||
{
|
||||
$this->attributes = array_merge($this->attributes, $attributes);
|
||||
}
|
||||
|
||||
public function getAttributes(): array
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
public function itsMe(array $request_root = []): bool
|
||||
{
|
||||
if ( count($request_root) !== count($this->getUriRoot()) ) {
|
||||
return false;
|
||||
}
|
||||
$ok = 0;
|
||||
$tmpAttributes = [];
|
||||
foreach ( $request_root as $key => $root_element ) {
|
||||
$localRootElement = $this->getUriRoot()[$key];
|
||||
if ( $root_element === $localRootElement ) {
|
||||
$ok++;
|
||||
} else if ( preg_match('/^:[_a-zA-Z\-]+/i', $localRootElement) ) {
|
||||
$arrayKey = explode(':', $localRootElement)[1];
|
||||
if ( $arrayKey ) {
|
||||
$tmpAttributes[$arrayKey] = $root_element;
|
||||
}
|
||||
$ok++;
|
||||
} else {
|
||||
$tmpAttributes = [];
|
||||
}
|
||||
}
|
||||
|
||||
if ( count($request_root) === $ok ) {
|
||||
$this->addAttributes($tmpAttributes);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function createUriRoot(string $uri): void
|
||||
{
|
||||
foreach (explode('/', $uri) as $uri_root_element) {
|
||||
if ( is_numeric($uri_root_element) || !empty($uri_root_element) ) {
|
||||
$this->uri_root[] = $uri_root_element;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user