summaryrefslogtreecommitdiff
path: root/src/http
diff options
context:
space:
mode:
Diffstat (limited to 'src/http')
-rw-r--r--src/http/Controller/Building.php47
-rw-r--r--src/http/Controller/Event.php85
-rw-r--r--src/http/Controller/Login.php55
-rw-r--r--src/http/Controller/Map.php43
-rw-r--r--src/http/Controller/Unit.php242
-rw-r--r--src/http/Controller/Village.php146
-rw-r--r--src/http/Http.php37
-rw-r--r--src/http/Router.php80
-rw-r--r--src/http/Support/RouteLoader.php24
9 files changed, 759 insertions, 0 deletions
diff --git a/src/http/Controller/Building.php b/src/http/Controller/Building.php
new file mode 100644
index 0000000..4a59f0e
--- /dev/null
+++ b/src/http/Controller/Building.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace App\http\Controller;
+
+use App\Model\Building as Model;
+use App\Model\Event;
+use App\Model\Event\UpgradeBuilding;
+use App\Model\Village;
+use App\http\Router;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class Building
+{
+ #[Route(path: '/village/{x}/{y}/building/{type}/level-up', methods: ['POST'])]
+ public function levelUp(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+ $building = Model::getByVillage($village->id, $request->get('type')) ?? Model::getEmpty($village->id, $request->get('type'));
+
+ // resources
+ foreach ($building->getResourceRequirements() as $resourceType => $resourceValue) {
+ $village->{$resourceType} -= $resourceValue;
+ }
+ $village->updateResources();
+
+ // event
+ $event = new Event();
+ $event->time = (new \DateTime())->add(\DateInterval::createFromDateString(
+ $building->getBuildTimeForLevel($building->getEffectiveLevel() + 1) . ' seconds'
+ ));
+ $event->villageId = $building->villageId;
+ $upgradeBuildingEvent = new UpgradeBuilding();
+ $upgradeBuildingEvent->event = $event;
+ $upgradeBuildingEvent->type = $building->type;
+ $upgradeBuildingEvent->dbInsert();
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+}
diff --git a/src/http/Controller/Event.php b/src/http/Controller/Event.php
new file mode 100644
index 0000000..070d449
--- /dev/null
+++ b/src/http/Controller/Event.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace App\http\Controller;
+
+use App\DB;
+use App\Model\Event as Model;
+use App\Model\Event\SendUnits;
+use App\Model\Village;
+use App\http\Router;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class Event
+{
+ #[Route(path: '/village/{x}/{y}/send-resources', methods: ['POST'])]
+ public function sendResources(Request $request): Response
+ {
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ #[Route(path: '/event/{id}/cancel', methods: ['POST'])]
+ public function cancel(Request $request): Response
+ {
+ $event = DB::fetch(Model::class, 'select * from events where id=:id', ['id' => $request->get('id')])[0] ?? null;
+ $village = Village::get($event->villageId);
+
+ /**@var SendUnits $sendUnitsEvent*/
+ $sendUnitsEvent = DB::fetch(SendUnits::class, 'select * from events_send_units where event_id=:id', ['id' => $event->id])[0] ?? null;
+ if (! empty($sendUnitsEvent)) {
+ if ($sendUnitsEvent->type === 'SendBack') {
+ $cancelTimeDiff = $event->createdAt->diff(new \DateTime());
+ $cancelTime = (new \DateTime())->add($cancelTimeDiff);
+
+ $sendUnitsEvent->isCanceled = true;
+ $sendUnitsEvent->home = $sendUnitsEvent->destination;
+ $sendUnitsEvent->residence = $sendUnitsEvent->source;
+
+ DB::query(
+ 'update events set time=:time where id=:id',
+ ['time' => $cancelTime->format('c'), 'id' => $request->get('id')]
+ );
+ DB::query(
+ 'update events_send_units set is_canceled=:is_canceled, home=:home, residence=:residence where id=:id',
+ ['is_canceled' => $sendUnitsEvent->isCanceled, 'home' => $sendUnitsEvent->home, 'residence' => $sendUnitsEvent->residence, 'id' => $sendUnitsEvent->id]
+ );
+ }
+
+ else if ($sendUnitsEvent->type === 'Recall') {
+ $cancelTimeDiff = $event->createdAt->diff(new \DateTime());
+ $cancelTime = (new \DateTime())->add($cancelTimeDiff);
+
+ $sendUnitsEvent->isCanceled = true;
+ $sendUnitsEvent->home = $sendUnitsEvent->destination;
+ $sendUnitsEvent->residence = $sendUnitsEvent->source;
+
+ DB::query(
+ 'update events set time=:time where id=:id',
+ ['time' => $cancelTime->format('c'), 'id' => $request->get('id')]
+ );
+ DB::query(
+ 'update events_send_units set is_canceled=:is_canceled, home=:home, residence=:residence where id=:id',
+ ['is_canceled' => $sendUnitsEvent->isCanceled, 'home' => $sendUnitsEvent->home, 'residence' => $sendUnitsEvent->residence, 'id' => $sendUnitsEvent->id]
+ );
+ }
+ }
+
+ else {
+ DB::query('delete from event where id=:id', ['id' => $request->get('id')]);
+ }
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $village->x, 'y' => $village->y]
+ )
+ );
+ }
+}
diff --git a/src/http/Controller/Login.php b/src/http/Controller/Login.php
new file mode 100644
index 0000000..8c04d85
--- /dev/null
+++ b/src/http/Controller/Login.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace App\http\Controller;
+
+use App\DB;
+use App\View;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class Login
+{
+ #[Route(path: '/login', methods: ['GET'])]
+ public function form(Request $request): Response
+ {
+ return new Response(View::render('login.twig'));
+ }
+
+ #[Route(path: '/login', methods: ['POST'])]
+ public function login(Request $request): Response
+ {
+ $email = $request->get('email');
+ $user = DB::query('select id,username,password from users where email=:email or username=:email', ['email' => $email])->fetch();
+
+ if (empty($user)) {
+ $password = password_hash($request->get('password'), PASSWORD_DEFAULT);
+ DB::query('insert into users (username, password, email) values (:username, :password, :email)', ['username' => $email, 'password' => $password, 'email' => $email]);
+
+ // TODO: also insert new village at random free coordinates
+ } else {
+ $password = $user['password'];
+ }
+
+ if (password_verify($request->get('password'), $password)) {
+ $_SESSION['user'] = [
+ 'id' => $user['id'],
+ 'username' => $user['username'],
+ ];
+
+ return new RedirectResponse('/villages');
+ }
+
+ return new RedirectResponse('/login');
+ }
+
+ #[Route(path: '/logout', methods: ['GET'])]
+ public function logout(Request $request): Response
+ {
+ session_unset();
+ session_destroy();
+
+ return new RedirectResponse('/login');
+ }
+}
diff --git a/src/http/Controller/Map.php b/src/http/Controller/Map.php
new file mode 100644
index 0000000..69d23e1
--- /dev/null
+++ b/src/http/Controller/Map.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace App\http\Controller;
+
+use App\DB;
+use App\View;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class Map
+{
+ #[Route(path: '/map/{x}/{y}/{range}', defaults: ['range' => 1], methods: ['GET'])]
+ public function region(Request $request): Response
+ {
+ $x = $request->get('x');
+ $y = $request->get('y');
+ $range = $request->get('range');
+
+ $statement = DB::query(
+ 'select * from villages where x>=:x1 and x<=:x2 and y>=:y1 and y<=:y2',
+ [
+ 'x1' => $x - $range,
+ 'x2' => $x + $range,
+ 'y1' => $y - $range,
+ 'y2' => $y + $range,
+ ]
+ );
+ $villages = $statement->fetchAll();
+
+ $map = [];
+ foreach ($villages as $village) {
+ $map[$village['x']][$village['y']] = $village;
+ }
+
+ return new Response(View::render('map.twig', [
+ 'x' => $x,
+ 'y' => $y,
+ 'range' => $range,
+ 'map' => $map,
+ ]));
+ }
+}
diff --git a/src/http/Controller/Unit.php b/src/http/Controller/Unit.php
new file mode 100644
index 0000000..c314cda
--- /dev/null
+++ b/src/http/Controller/Unit.php
@@ -0,0 +1,242 @@
+<?php
+
+namespace App\http\Controller;
+
+use App\DB;
+use App\Model\Event\SendUnits;
+use App\Model\Event\TrainUnits;
+use App\Model\Unit as Model;
+use App\Model\Event;
+use App\Model\Village;
+use App\http\Router;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class Unit
+{
+ #[Route(path: '/village/{x}/{y}/unit/{type}/create', methods: ['POST'])]
+ public function train(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+
+ /**@var Model $unit*/
+ $unit = new (Model::resolveType($request->get('type')))();
+ $unit->type = $request->get('type');
+ $unit->homeVillageId = $village->id;
+
+ $amount = intval($request->get('amount'));
+
+ if (! Village::canTrain($village, $unit, $amount)) {
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ // resources
+ foreach (Model::getResourceRequirements($unit, $amount) as $resourceType => $resourceValue) {
+ $village->{$resourceType} -= $resourceValue;
+ }
+ $village->updateResources();
+
+ // event
+ $event = new Event();
+ $event->time = (new \DateTime())->add(\DateInterval::createFromDateString($unit->getBuildTime($amount) . ' seconds'));
+ $event->villageId = $village->id;
+ $trainUnitsEvent = new TrainUnits();
+ $trainUnitsEvent->event = $event;
+ $trainUnitsEvent->type = $request->get('type');
+ $trainUnitsEvent->amount = $amount;
+ $trainUnitsEvent->dbInsert();
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ #[Route(path: '/village/{x}/{y}/unit/{type}/location/{lx}/{ly}/recall', methods: ['POST'])]
+ public function recall(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+ $location = Village::getByCoordinates($request->get('lx'), $request->get('ly'));
+
+ /**@var Model $unit*/
+ $unit = new (Model::resolveType($request->get('type')))();
+
+ $amount = intval($request->get('amount'));
+ $amountUnits = DB::query(
+ 'select amount from village_units where home_village_id=:home and residence_village_id=:residence and type=:type',
+ ['home' => $village->id, 'residence' => $location->id, 'type' => $request->get('type')]
+ )->fetchColumn();
+
+ if ($amountUnits - $amount > 0) {
+ $statement = DB::query(
+ <<<SQL
+ update village_units set amount=:amount where home_village_id=:home and residence_village_id=:residence and type=:type
+ SQL,
+ ['amount' => $amountUnits - $amount, 'home' => $village->id, 'residence' => $location->id, 'type' => $request->get('type')]
+ );
+ } else if ($amountUnits - $amount === 0) {
+ DB::query(
+ <<<SQL
+ delete from village_units where home_village_id=:home and residence_village_id=:residence and type=:type
+ SQL,
+ ['home' => $village->id, 'residence' => $location->id, 'type' => $request->get('type')]
+ );
+ }
+
+ // event
+ $event = new Event();
+ $event->time = (new \DateTime())->add(
+ \DateInterval::createFromDateString(
+ Model::getTravelTime($unit, Village::getDistance($village->x, $village->y, $location->x, $location->y))
+ . ' seconds'
+ )
+ );
+ $event->villageId = $village->id;
+ $sendUnitsEvent = new SendUnits();
+ $sendUnitsEvent->event = $event;
+ $sendUnitsEvent->type = 'Recall';
+ $sendUnitsEvent->unit = $request->get('type');
+ $sendUnitsEvent->amount = $amount;
+ $sendUnitsEvent->source = $location->id;
+ $sendUnitsEvent->destination = $village->id;
+ $sendUnitsEvent->dbInsert();
+
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ #[Route(path: '/village/{x}/{y}/unit/{type}/location/{lx}/{ly}/send-back', methods: ['POST'])]
+ public function sendBack(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+ $location = Village::getByCoordinates($request->get('lx'), $request->get('ly'));
+
+ /**@var Model $unit*/
+ $unit = new (Model::resolveType($request->get('type')))();
+
+ $amount = intval($request->get('amount'));
+ $amountUnits = DB::query(
+ 'select amount from village_units where home_village_id=:home and residence_village_id=:residence and type=:type',
+ ['home' => $location->id, 'residence' => $village->id, 'type' => $request->get('type')]
+ )->fetchColumn();
+
+ if ($amountUnits - $amount > 0) {
+ $statement = DB::query(
+ <<<SQL
+ update village_units set amount=:amount where home_village_id=:home and residence_village_id=:residence and type=:type
+ SQL,
+ ['amount' => $amountUnits - $amount, 'home' => $location->id, 'residence' => $village->id, 'type' => $request->get('type')]
+ );
+ } else if ($amountUnits - $amount === 0) {
+ DB::query(
+ <<<SQL
+ delete from village_units where home_village_id=:home and residence_village_id=:residence and type=:type
+ SQL,
+ ['home' => $location->id, 'residence' => $village->id, 'type' => $request->get('type')]
+ );
+ }
+
+ // event
+ $event = new Event();
+ $event->time = (new \DateTime())->add(
+ \DateInterval::createFromDateString(
+ Model::getTravelTime($unit, Village::getDistance($village->x, $village->y, $location->x, $location->y))
+ . ' seconds'
+ )
+ );
+ $event->villageId = $village->id;
+ $sendUnitsEvent = new SendUnits();
+ $sendUnitsEvent->event = $event;
+ $sendUnitsEvent->type = 'SendBack';
+ $sendUnitsEvent->unit = $request->get('type');
+ $sendUnitsEvent->amount = $amount;
+ $sendUnitsEvent->source = $village->id;
+ $sendUnitsEvent->destination = $location->id;
+ $sendUnitsEvent->dbInsert();
+
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+
+ #[Route(path: '/village/{x}/{y}/send-units', methods: ['POST'])]
+ public function sendUnits(Request $request): Response
+ {
+ $village = Village::getByCoordinates($request->get('x'), $request->get('y'));
+ $destination = Village::get($request->get('village'));
+
+ /**@var Model $unit*/
+ $unit = new (Model::resolveType($request->get('unit')))();
+
+ $amount = intval($request->get('amount'));
+ $amountUnits = DB::query(
+ 'select amount from village_units where home_village_id=:home and residence_village_id=:home and type=:type',
+ ['home' => $village->id, 'type' => $request->get('unit')]
+ )->fetchColumn();
+
+ if ($amountUnits - $amount > 0) {
+ $statement = DB::query(
+ <<<SQL
+ update village_units set amount=:amount where home_village_id=:home and residence_village_id=:home and type=:type
+ SQL,
+ ['amount' => $amountUnits - $amount, 'home' => $village->id, 'type' => $request->get('unit')]
+ );
+ } else if ($amountUnits - $amount === 0) {
+ DB::query(
+ <<<SQL
+ delete from village_units where home_village_id=:home and residence_village_id=:home and type=:type
+ SQL,
+ ['home' => $village->id, 'type' => $request->get('unit')]
+ );
+ }
+
+ DB::query(
+ 'insert into village_units (amount, type, home_village_id, residence_village_id, is_traveling) values (:amount, :type, :home, :home, true)',
+ ['amount' => $amount, 'type' => $request->get('unit'), 'home' => $village->id]
+ );
+
+ // event
+ $event = new Event();
+ $event->time = (new \DateTime())->add(
+ \DateInterval::createFromDateString(
+ Model::getTravelTime($unit, Village::getDistance($village->x, $village->y, $destination->x, $destination->y))
+ . ' seconds'
+ )
+ );
+ $event->villageId = $village->id;
+ $sendUnitsEvent = new SendUnits();
+ $sendUnitsEvent->event = $event;
+ $sendUnitsEvent->type = $request->get('type');
+ $sendUnitsEvent->unit = $request->get('unit');
+ $sendUnitsEvent->amount = $amount;
+ $sendUnitsEvent->source = $village->id;
+ $sendUnitsEvent->destination = $destination->id;
+ $sendUnitsEvent->dbInsert();
+
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+}
diff --git a/src/http/Controller/Village.php b/src/http/Controller/Village.php
new file mode 100644
index 0000000..cd38442
--- /dev/null
+++ b/src/http/Controller/Village.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace App\http\Controller;
+
+use App\DB;
+use App\Guard;
+use App\Model\Event\SendUnits;
+use App\Model\Event\TrainUnits;
+use App\Model\Event\UpgradeBuilding;
+use App\Model\Village as Model;
+use App\http\Router;
+use App\View;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+class Village
+{
+ #[Route(path: '/villages', methods: ['GET'])]
+ public function list(): Response
+ {
+ $villages = DB::fetch(
+ Model::class,
+ <<<SQL
+ select * from villages
+ join user_villages on villages.id = user_villages.village_id
+ where user_villages.user_id=:id
+ SQL,
+ ['id' => $_SESSION['user']['id']]
+ );
+
+ return new Response(View::render('villages.twig', [
+ 'villages' => $villages,
+ ]));
+ }
+
+ #[Route(path: '/village/{x}/{y}', methods: ['GET'])]
+ public function show(Request $request): Response
+ {
+ $village = Model::getByCoordinates($request->get('x'), $request->get('y'));
+
+ if (! Guard::ownsVillage($village->id)) {
+ return new Response(View::render('error.twig', ['message' => 'Insufficient permission']), 403);
+ }
+
+ $events = [];
+
+ $eventsBuilding = DB::query(
+ <<<SQL
+ select * from events_upgrade_building as event
+ left join events on event.event_id = events.id
+ where events.village_id=:id
+ SQL, ['id' => $village->id]
+ )->fetchAll();
+
+ foreach ($eventsBuilding as $row) {
+ $events['UpgradeBuilding'][$row['type']][] = DB::convertToModel(UpgradeBuilding::class, $row);
+ }
+
+ $eventsUnits = DB::query(
+ <<<SQL
+ select * from events_train_units as event
+ left join events on event.event_id = events.id
+ where village_id=:id
+ SQL, ['id' => $village->id]
+ )->fetchAll();
+
+ foreach ($eventsUnits as $row) {
+ $events['TrainUnits'][] = DB::convertToModel(TrainUnits::class, $row);
+ }
+
+ $eventsUnitsSendOwn = DB::query(
+ <<<SQL
+ select * from events_send_units as event
+ left join events on event.event_id = events.id
+ where village_id=:id
+ SQL, ['id' => $village->id]
+ )->fetchAll();
+
+ $eventsUnitsSendOther = DB::query(
+ <<<SQL
+ select * from events_send_units as event
+ left join events on event.event_id = events.id
+ where (destination=:id or source=:id) and village_id!=:id and is_canceled=false
+ SQL, ['id' => $village->id]
+ )->fetchAll();
+
+ foreach ([...$eventsUnitsSendOwn, ...$eventsUnitsSendOther] as $row) {
+ $events['SendUnits'][] = DB::convertToModel(SendUnits::class, $row);;
+ }
+
+ $buildings = [];
+ foreach (Model::getBuildings($village->id, true) as $building) {
+ $buildings[$building->type] = $building;
+ }
+
+
+ return new Response(View::render('village.twig', [
+ 'village' => $village,
+ 'events' => $events,
+ 'buildings' => $buildings,
+ 'villages' => DB::fetch(Model::class, "select * from villages where id!=:id", ['id' => $village->id]),
+ ]));
+ }
+
+ #[Route(path: '/village/{x}/{y}/storage/config', methods: ['POST'])]
+ public function storageConfig(Request $request): Response
+ {
+ $village = Model::getByCoordinates($request->get('x'), $request->get('y'));
+
+ // calculate to max 100%
+ $wood = intval($request->get('wood'));
+ $clay = intval($request->get('clay'));
+ $iron = intval($request->get('iron'));
+ $food = intval($request->get('food'));
+ $total = $wood + $clay + $iron + $food;
+ $woodPercent = $wood / $total;
+ $clayPercent = $clay / $total;
+ $ironPercent = $iron / $total;
+ $foodPercent = $food / $total;
+
+ $wood = round($woodPercent * 100);
+ $clay = round($clayPercent * 100);
+ $iron = round($ironPercent * 100);
+ $food = round($foodPercent * 100);
+ $newTotal = $wood+$clay+$iron+$food;
+ $food += (100 - $newTotal);
+
+ DB::query(
+ <<<SQL
+ update village_storage_config
+ set wood=:wood, clay=:clay, iron=:iron, food=:food
+ where village_id=:id
+ SQL,
+ ['wood' => $wood, 'clay' => $clay, 'iron' => $iron, 'food' => $food, 'id' => $village->id]
+ );
+
+ return new RedirectResponse(
+ Router::generate(
+ 'village.show',
+ ['x' => $request->get('x'), 'y' => $request->get('y')]
+ )
+ );
+ }
+}
diff --git a/src/http/Http.php b/src/http/Http.php
new file mode 100644
index 0000000..1867eb4
--- /dev/null
+++ b/src/http/Http.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace App\http;
+
+use App\DB;
+use App\EventRunner;
+use App\http\Router;
+use App\View;
+use Symfony\Component\HttpFoundation\Request;
+
+class Http
+{
+ public function __construct()
+ {
+ // Session
+ session_start();
+
+ // DB
+ DB::init();
+
+ // Router
+ Router::init(Request::createFromGlobals());
+
+ // View
+ View::init();
+ View::addGlobal('session', $_SESSION);
+
+ // Events
+ new EventRunner();
+ }
+
+ public function run(): void
+ {
+ $response = Router::execute();
+ $response->send();
+ }
+}
diff --git a/src/http/Router.php b/src/http/Router.php
new file mode 100644
index 0000000..db75f81
--- /dev/null
+++ b/src/http/Router.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace App\http;
+
+use App\http\Support\RouteLoader;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Generator\UrlGenerator;
+use Symfony\Component\Routing\Loader\AnnotationFileLoader;
+use Symfony\Component\Routing\Matcher\UrlMatcher;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\RouteCollection;
+
+class Router
+{
+ public static Request $request;
+ public static RequestContext $context;
+ public static RouteCollection $routes;
+
+ public static function init(Request $request): void
+ {
+ self::$request = $request;
+
+ self::$context = new RequestContext();
+ self::$context->fromRequest($request);
+
+ self::$routes = new RouteCollection();
+ $loader = new AnnotationFileLoader(new FileLocator(), new RouteLoader());
+ $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__ . '/Controller'));
+ foreach ($iterator as $file) {
+ /**@var \SplFileInfo $file*/
+ if (in_array($file->getFilename(), ['.', '..'])) continue;
+
+ $collection = $loader->load($file->getPathname(), 'attribute');
+ self::$routes->addCollection($collection);
+ }
+ }
+
+ public static function execute(): Response
+ {
+ try {
+ $matcher = new UrlMatcher(self::$routes, self::$context);
+ $match = $matcher->matchRequest(self::$request);
+
+ foreach ($match as $key => $value) {
+ if (str_starts_with($key, '_')) continue;
+
+ self::$request->query->set($key, $value);
+ }
+
+ /**@var \ReflectionClass $class*/
+ $class = $match['_']['class'];
+ /**@var \ReflectionMethod $method*/
+ $method = $match['_']['method'];
+
+ return ($class->newInstance())->{$method->getName()}(self::$request);
+ } catch (ResourceNotFoundException $exception) {
+ return new Response('404', 404);
+ } catch (MethodNotAllowedException $exception) {
+ return new Response('403', 403);
+ } catch (\Exception $exception) {
+ return new Response('500: ' . $exception->getMessage(), 500);
+ }
+ }
+
+ /**
+ * @param string $name
+ * @param array $parameters
+ * @param int $referenceType
+ */
+ public static function generate(string $name, array $parameters = [], int $referenceType = 1): string
+ {
+ $generator = new UrlGenerator(self::$routes, self::$context);
+
+ return $generator->generate($name, $parameters, $referenceType);
+ }
+}
diff --git a/src/http/Support/RouteLoader.php b/src/http/Support/RouteLoader.php
new file mode 100644
index 0000000..b0e74cb
--- /dev/null
+++ b/src/http/Support/RouteLoader.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace App\http\Support;
+
+use Symfony\Component\Routing\Loader\AnnotationClassLoader;
+use Symfony\Component\Routing\Route;
+
+class RouteLoader extends AnnotationClassLoader
+{
+ protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annotation) {
+ $route->setDefault('_', compact('class', 'method', 'annotation'));
+ }
+
+ protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
+ {
+ $name = parent::getDefaultRouteName($class, $method);
+
+ return str_replace(
+ '_',
+ '.',
+ str_replace('app_controller_', '', $name)
+ );
+ }
+}