From 534ac7c4c92318be7e7f7a96c47114dfef666ed8 Mon Sep 17 00:00:00 2001
From: Puleeno Nguyen <puleeno@gmail.com>
Date: Sun, 22 Oct 2023 10:11:38 +0700
Subject: [PATCH] Load simple service provider

---
 app/Console/Application.php                |   4 +-
 app/Constracts/ApplicationConstract.php    |   8 +-
 app/Core/Application.php                   | 146 ++++++++++++++++++++-
 app/Core/Cookies/CookieServiceProvider.php |  12 ++
 app/Core/Log/LogServiceProvider.php        |  88 +++++++++++++
 app/Facades/UserServiceFacade.php          |   3 +-
 app/Http/Kernel.php                        |   5 +-
 app/Http/Middleware/AuthGuard.php          |   2 +-
 app/Providers/AbstractServiceProvider.php  |  20 ---
 app/Providers/ServiceProvider.php          |  35 +++++
 app/Providers/UserServiceProvider.php      |   4 +-
 app/Repository/UserRepository.php          |  47 +++++++
 app/bootstrap.php                          |  60 +--------
 app/helpers.php                            |   9 ++
 configs/app.php                            |   3 +-
 15 files changed, 356 insertions(+), 90 deletions(-)
 create mode 100644 app/Core/Cookies/CookieServiceProvider.php
 create mode 100644 app/Core/Log/LogServiceProvider.php
 delete mode 100644 app/Providers/AbstractServiceProvider.php
 create mode 100644 app/Providers/ServiceProvider.php
 create mode 100644 app/Repository/UserRepository.php

diff --git a/app/Console/Application.php b/app/Console/Application.php
index e1233d7..c5b844b 100644
--- a/app/Console/Application.php
+++ b/app/Console/Application.php
@@ -1,7 +1,9 @@
 <?php
 
 namespace App\Console;
+
 use Slim\Console\App;
 
-class Application extends App {
+class Application extends App
+{
 }
diff --git a/app/Constracts/ApplicationConstract.php b/app/Constracts/ApplicationConstract.php
index afe6efd..4932a4b 100644
--- a/app/Constracts/ApplicationConstract.php
+++ b/app/Constracts/ApplicationConstract.php
@@ -2,6 +2,12 @@
 
 namespace App\Constracts;
 
-interface ApplicationConstract
+use Psr\Http\Server\RequestHandlerInterface;
+use Slim\Interfaces\RouteCollectorProxyInterface;
+
+interface ApplicationConstract extends RouteCollectorProxyInterface, RequestHandlerInterface
 {
+    public function booted();
+
+    public function isBooted();
 }
diff --git a/app/Core/Application.php b/app/Core/Application.php
index a27e852..446ffe4 100644
--- a/app/Core/Application.php
+++ b/app/Core/Application.php
@@ -10,9 +10,6 @@
 
 namespace App\Core;
 
-use App\Constracts\ApplicationConstract;
-use App\Core\Routing\RouteCollector;
-use App\Http\ResponseEmitter\ResponseEmitter;
 use Psr\Container\ContainerInterface;
 use Psr\Http\Message\ResponseFactoryInterface;
 use Psr\Http\Message\ResponseInterface;
@@ -20,7 +17,7 @@
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
 use Psr\Log\LoggerInterface;
-use PuleenoCMS\Exceptions\InvalidApplicationException;
+use Tightenco\Collect\Support\Arr;
 use Slim\App;
 use Slim\CallableResolver;
 use Slim\Factory\ServerRequestCreatorFactory;
@@ -33,6 +30,12 @@
 use Slim\Middleware\RoutingMiddleware;
 use Slim\Routing\RouteResolver;
 use Slim\Routing\RouteRunner;
+use App\Constracts\ApplicationConstract;
+use App\Core\Routing\RouteCollector;
+use App\Http\ResponseEmitter\ResponseEmitter;
+use App\Providers\ServiceProvider;
+use App\Core\Log\LogServiceProvider;
+use PuleenoCMS\Exceptions\InvalidApplicationException;
 
 use function strtoupper;
 
@@ -51,6 +54,19 @@ class Application extends App implements RequestHandlerInterface, ApplicationCon
 
     protected static $instance;
 
+    protected $isBooted = false;
+
+
+    /**
+     * @var \App\Providers\ServiceProvider[]
+     */
+    protected $serviceProviders = [];
+
+    /**
+     * @var boolean[]
+     */
+    protected $loadedProviders = [];
+
     public function __construct(
         ResponseFactoryInterface $responseFactory,
         ?ContainerInterface $container = null,
@@ -83,6 +99,19 @@ public function __construct(
         if (is_null(static::$instance)) {
             static::$instance = &$this;
         }
+
+        // Register base providers
+        $this->registerBaseServiceProviders();
+    }
+
+    public function booted()
+    {
+        $this->isBooted = true;
+    }
+
+    public function isBooted()
+    {
+        return $this->isBooted;
     }
 
     /**
@@ -235,4 +264,113 @@ public static function getInstance()
         }
         return static::$instance;
     }
+
+
+    /**
+     * Get the registered service provider instances if any exist.
+     *
+     * @param  \App\Providers\ServiceProvider|string  $provider
+     * @return array
+     */
+    public function getProviders($provider)
+    {
+        $name = is_string($provider) ? $provider : get_class($provider);
+
+        return Arr::where($this->serviceProviders, function ($value) use ($name) {
+            return $value instanceof $name;
+        });
+    }
+
+    /**
+     * Resolve a service provider instance from the class name.
+     *
+     * @param  string  $provider
+     * @return \App\Providers\ServiceProvider
+     */
+    public function resolveProvider($provider)
+    {
+        return new $provider($this);
+    }
+
+    /**
+     * Mark the given provider as registered.
+     *
+     * @param \App\Providers\ServiceProvider  $provider
+     * @return void
+     */
+    protected function markAsRegistered($provider)
+    {
+        $this->serviceProviders[] = $provider;
+
+        $this->loadedProviders[get_class($provider)] = true;
+    }
+
+    /**
+     * Get the registered service provider instance if it exists.
+     *
+     * @param  \App\Providers\ServiceProvider|string  $provider
+     * @return \App\Providers\ServiceProvider|null
+     */
+    public function getProvider($provider)
+    {
+        return array_values($this->getProviders($provider))[0] ?? null;
+    }
+
+
+    /**
+     * Boot the given service provider.
+     *
+     * @param  \App\Providers\ServiceProvider  $provider
+     * @return mixed
+     */
+    protected function bootProvider(ServiceProvider $provider)
+    {
+        if (method_exists($provider, 'boot')) {
+            return call_user_func([$provider, 'boot']);
+        }
+    }
+
+    /**
+     * Register a service provider with the application.
+     *
+     * @param  \App\Providers\ServiceProvider|string  $provider
+     * @param  bool  $force
+     * @return \App\Providers\ServiceProvider
+     */
+    public function register($provider, $force = false)
+    {
+        if (($registered = $this->getProvider($provider)) && ! $force) {
+            return $registered;
+        }
+
+        // If the given "provider" is a string, we will resolve it, passing in the
+        // application instance automatically for the developer. This is simply
+        // a more convenient way of specifying your service provider classes.
+        if (is_string($provider)) {
+            $provider = $this->resolveProvider($provider);
+        }
+
+        $provider->register();
+
+        $this->markAsRegistered($provider);
+
+        // If the application has already booted, we will call this boot method on
+        // the provider class so it has an opportunity to do its boot logic and
+        // will be ready for any usage by this developer's application logic.
+        if ($this->isBooted()) {
+            $this->bootProvider($provider);
+        }
+
+        return $provider;
+    }
+
+    /**
+     * Register all of the base service providers.
+     *
+     * @return void
+     */
+    protected function registerBaseServiceProviders()
+    {
+        $this->register(new LogServiceProvider($this));
+    }
 }
diff --git a/app/Core/Cookies/CookieServiceProvider.php b/app/Core/Cookies/CookieServiceProvider.php
new file mode 100644
index 0000000..b774200
--- /dev/null
+++ b/app/Core/Cookies/CookieServiceProvider.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Core\Cookies;
+
+use App\Providers\ServiceProvider;
+
+class CookieServiceProvider extends ServiceProvider
+{
+    public function register()
+    {
+    }
+}
diff --git a/app/Core/Log/LogServiceProvider.php b/app/Core/Log/LogServiceProvider.php
new file mode 100644
index 0000000..8f341ae
--- /dev/null
+++ b/app/Core/Log/LogServiceProvider.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Core\Log;
+
+use App\Core\Helper;
+use App\Core\Settings\SettingsInterface;
+use App\Http\Handlers\HttpErrorHandler;
+use App\Http\Handlers\ShutdownHandler;
+use App\Providers\ServiceProvider;
+use Psr\Container\ContainerInterface;
+use ReflectionClass;
+use Slim\Factory\ServerRequestCreatorFactory;
+
+class LogServiceProvider extends ServiceProvider
+{
+    protected function setupDashboardEnvironment($isDashboard)
+    {
+        $helperRelf = new ReflectionClass(Helper::class);
+        $isDashboardProperty = $helperRelf->getProperty('isDashboard');
+        $isDashboardProperty->setAccessible(true);
+        $isDashboardProperty->setValue($isDashboardProperty, $isDashboard);
+        $isDashboardProperty->setAccessible(false);
+    }
+
+    protected function setupHttpErrorHandle()
+    {
+        /**
+         * @var ContainerInterface
+         */
+        $container = $this->app->getContainer();
+
+        /** @var SettingsInterface $settings */
+        $settings = $container->get(SettingsInterface::class);
+
+        $displayErrorDetails = $settings->get('displayErrorDetails');
+
+        // Create Request object from globals
+        $serverRequestCreator = ServerRequestCreatorFactory::create();
+        $request = $serverRequestCreator->createServerRequestFromGlobals();
+
+        $requestPath = $request->getUri() != null ? $request->getUri()->getPath() : '/';
+        $isDashboard = $requestPath === $settings->get('admin_prefix', '/dashboard')
+            || strpos($requestPath, $settings->get('admin_prefix', '/dashboard') . '/') === 0;
+
+        $container->set('is_dashboard', $isDashboard);
+
+        $this->setupDashboardEnvironment($isDashboard);
+
+        $responseFactory = $this->app->getResponseFactory();
+        $callableResolver = $this->app->getCallableResolver();
+        $errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
+
+        // Create Shutdown Handler
+        $shutdownHandler = new ShutdownHandler($request, $errorHandler, $displayErrorDetails);
+        register_shutdown_function($shutdownHandler);
+
+        return $errorHandler;
+    }
+
+    protected function writeErrorLogs(HttpErrorHandler $errorHandler)
+    {
+        /**
+         * @var ContainerInterface
+         */
+        $container = $this->app->getContainer();
+        $settings = $container->get(SettingsInterface::class);
+
+        $displayErrorDetails = $settings->get('displayErrorDetails');
+        $logError = $settings->get('logError');
+        $logErrorDetails = $settings->get('logErrorDetails');
+
+        // Add Error Middleware
+        $errorMiddleware = $this->app->addErrorMiddleware($displayErrorDetails, $logError, $logErrorDetails);
+        $errorMiddleware->setDefaultErrorHandler($errorHandler);
+    }
+
+    public function register()
+    {
+        $this->writeErrorLogs(
+            $this->setupHttpErrorHandle()
+        );
+    }
+
+    public function boot()
+    {
+        die('zo');
+    }
+}
diff --git a/app/Facades/UserServiceFacade.php b/app/Facades/UserServiceFacade.php
index dc2a50f..e589c30 100644
--- a/app/Facades/UserServiceFacade.php
+++ b/app/Facades/UserServiceFacade.php
@@ -1,6 +1,7 @@
 <?php
 
 namespace App\Facades;
+
 use Psr\Container\ContainerInterface;
 
 class UserServiceFacade extends Facade
@@ -18,4 +19,4 @@ public function __get($name)
     {
         return $this->container->get(self::FACADE_ACCESSOR);
     }
-}
\ No newline at end of file
+}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 419d4f5..2aa6e6f 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -12,7 +12,7 @@
 class Kernel
 {
     /**
-     * @var App
+     * @var \App\Constracts\ApplicationConstract
      */
     private $app;
 
@@ -31,6 +31,8 @@ public function __construct(App $app)
      */
     public function configure()
     {
+        $this->app->booted();
+
         $this->app->addRoutingMiddleware();
         $this->app->addErrorMiddleware(true, true, true);
 
@@ -51,4 +53,3 @@ private function mapRoutes()
         });
     }
 }
-
diff --git a/app/Http/Middleware/AuthGuard.php b/app/Http/Middleware/AuthGuard.php
index 0fbdec4..1fdb85d 100644
--- a/app/Http/Middleware/AuthGuard.php
+++ b/app/Http/Middleware/AuthGuard.php
@@ -51,4 +51,4 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res
 
         return $next($request, $response);
     }
-}
\ No newline at end of file
+}
diff --git a/app/Providers/AbstractServiceProvider.php b/app/Providers/AbstractServiceProvider.php
deleted file mode 100644
index 97286fc..0000000
--- a/app/Providers/AbstractServiceProvider.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-namespace App\Providers;
-
-use Psr\Container\ContainerInterface;
-use App\Services\UserService;
-
-abstract class AbstractServiceProvider
-{
-    protected ContainerInterface $container;
-
-    public function __construct(ContainerInterface $container)
-    {
-        $this->container = $container;
-    }
-
-    abstract public function register();
-
-    abstract public function boot();
-}
diff --git a/app/Providers/ServiceProvider.php b/app/Providers/ServiceProvider.php
new file mode 100644
index 0000000..9de9e07
--- /dev/null
+++ b/app/Providers/ServiceProvider.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace App\Providers;
+
+use App\Core\Application;
+
+abstract class ServiceProvider
+{
+    /**
+     * The application instance.
+     *
+     * @var \App\Core\Application
+     */
+    protected $app;
+
+
+    /**
+     * @var \Psr\Container\ContainerInterface
+     */
+    protected $container;
+
+
+    public function __construct(Application $app)
+    {
+        $this->app = $app;
+        $this->container = $app->getContainer();
+    }
+
+    public function boot()
+    {
+        // empty function
+    }
+
+    abstract public function register();
+}
diff --git a/app/Providers/UserServiceProvider.php b/app/Providers/UserServiceProvider.php
index b135be9..840ee2e 100644
--- a/app/Providers/UserServiceProvider.php
+++ b/app/Providers/UserServiceProvider.php
@@ -4,7 +4,7 @@
 
 use App\Services\UserService;
 
-class UserServiceProvider extends AbstractServiceProvider
+class UserServiceProvider extends ServiceProvider
 {
     public function register()
     {
@@ -17,4 +17,4 @@ public function boot()
     {
         // ...
     }
-}
\ No newline at end of file
+}
diff --git a/app/Repository/UserRepository.php b/app/Repository/UserRepository.php
new file mode 100644
index 0000000..bb06514
--- /dev/null
+++ b/app/Repository/UserRepository.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Repositories;
+
+use OPIS\ORM\EntityManager;
+use App\Models\User;
+
+class UserRepository
+{
+    /**
+     * @var \OPIS\ORM\EntityManager
+     */
+    private $entityManager;
+
+    public function __construct(EntityManager $entityManager)
+    {
+        $this->entityManager = $entityManager;
+    }
+
+    public function all(): array
+    {
+        return $this->entityManager->getRepository(User::class)->findAll();
+    }
+
+    public function find(int $id): User
+    {
+        return $this->entityManager->getRepository(User::class)->find($id);
+    }
+
+    public function create(User $user): void
+    {
+        $this->entityManager->persist($user);
+        $this->entityManager->flush();
+    }
+
+    public function update(User $user): void
+    {
+        $this->entityManager->merge($user);
+        $this->entityManager->flush();
+    }
+
+    public function delete(User $user): void
+    {
+        $this->entityManager->remove($user);
+        $this->entityManager->flush();
+    }
+}
diff --git a/app/bootstrap.php b/app/bootstrap.php
index 050e35e..1e5117b 100644
--- a/app/bootstrap.php
+++ b/app/bootstrap.php
@@ -133,10 +133,6 @@ protected function setup()
 
         $this->app = AppFactory::createApp();
 
-        $this->writeErrorLogs(
-            $this->setupHttpErrorHandle()
-        );
-
         $kernel = new Kernel($this->app);
         $kernel->configure();
 
@@ -193,6 +189,9 @@ public function boot()
         $this->initAssets();
         $this->initExtensions();
         $this->loadExtensions();
+
+        $this->app->booted();
+
         $this->run();
     }
 
@@ -253,59 +252,6 @@ protected function run()
         );
     }
 
-    // Create Error Handler
-    protected function setupHttpErrorHandle()
-    {
-        /** @var SettingsInterface $settings */
-        $settings = $this->container->get(SettingsInterface::class);
-
-        $displayErrorDetails = $settings->get('displayErrorDetails');
-
-        // Create Request object from globals
-        $serverRequestCreator = ServerRequestCreatorFactory::create();
-        $this->request = $serverRequestCreator->createServerRequestFromGlobals();
-
-        $requestPath = $this->request->getUri() != null ? $this->request->getUri()->getPath() : '/';
-        $isDashboard = $requestPath === $settings->get('admin_prefix', '/dashboard')
-            || strpos($requestPath, $settings->get('admin_prefix', '/dashboard') . '/') === 0;
-
-        $this->container->set('is_dashboard', $isDashboard);
-
-        $this->setupDashboardEnvironment($isDashboard);
-
-        $responseFactory = $this->app->getResponseFactory();
-        $callableResolver = $this->app->getCallableResolver();
-        $errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
-
-        // Create Shutdown Handler
-        $shutdownHandler = new ShutdownHandler($this->request, $errorHandler, $displayErrorDetails);
-        register_shutdown_function($shutdownHandler);
-
-        return $errorHandler;
-    }
-
-    protected function setupDashboardEnvironment($isDashboard)
-    {
-        $helperRelf = new ReflectionClass(Helper::class);
-        $isDashboardProperty = $helperRelf->getProperty('isDashboard');
-        $isDashboardProperty->setAccessible(true);
-        $isDashboardProperty->setValue($isDashboardProperty, $isDashboard);
-        $isDashboardProperty->setAccessible(false);
-    }
-
-    protected function writeErrorLogs(HttpErrorHandler $errorHandler)
-    {
-        $settings = $this->container->get(SettingsInterface::class);
-
-        $displayErrorDetails = $settings->get('displayErrorDetails');
-        $logError = $settings->get('logError');
-        $logErrorDetails = $settings->get('logErrorDetails');
-
-        // Add Error Middleware
-        $errorMiddleware = $this->app->addErrorMiddleware($displayErrorDetails, $logError, $logErrorDetails);
-        $errorMiddleware->setDefaultErrorHandler($errorHandler);
-    }
-
     public function getApp(): App
     {
         return $this->app;
diff --git a/app/helpers.php b/app/helpers.php
index 6276a0c..9c6d698 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -3,6 +3,7 @@
 use App\Common\Constants;
 use App\Common\Option;
 use App\Core\Application;
+use App\Core\Env;
 use App\Core\Helper;
 use App\Core\HookManager;
 use Psr\Container\ContainerInterface;
@@ -66,3 +67,11 @@ function get_container(): ContainerInterface
         return Helper::getContainer();
     }
 }
+
+
+if (!function_exists('env')) {
+    function env($name, $defaultValue = null)
+    {
+        return Env::get($name, $defaultValue);
+    }
+}
diff --git a/configs/app.php b/configs/app.php
index 6985862..1b29fea 100644
--- a/configs/app.php
+++ b/configs/app.php
@@ -29,7 +29,8 @@
      */
 
     'middleware' => [
-        // ...
+        \App\Http\Middleware\SessionMiddleware::class,
+        \App\Http\Middleware\AssetsMiddleware::class,
     ],
 
     'aliases' => [