notrab / dumbo

A lightweight, friendly PHP framework for HTTP.
MIT License
156 stars 16 forks source link

Add Config System #28

Closed Nyxalis closed 1 month ago

Nyxalis commented 1 month ago

Just like laravel, maybe a config folder where we can put different configs files. Example: /config/database.php

<?php

return [
    'dbhost' => 'localhost',
    'dbport' => '3306',
    'dbname' => 'my_database',
    'dbuser' => 'root',
    'dbpass' => 'password'
];

We can then access like this:

// The first word is the config file, and then it's the variable in the array
config('database.dbuser');

This would be such a usefull and helpfull feature.

Nyxalis commented 1 month ago

I kinda had AI help with this since I had 0 idea but it works!!

/bootstrap/app.php

<?php

function config($key = null, $default = null) {
    static $config = [];

    if (empty($config)) {
        foreach (glob(__DIR__ . '/../config/*.php') as $file) {
            $name = basename($file, '.php');
            $config[$name] = require $file;
        }
    }

    if (is_null($key)) {
        return $config;
    }

    $keys = explode('.', $key);
    $result = $config;

    foreach ($keys as $keyPart) {
        if (isset($result[$keyPart])) {
            $result = $result[$keyPart];
        } else {
            return $default;
        }
    }

    return $result;
}

/public/index.php

<?php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../bootstrap/app.php';

use Dumbo\Dumbo;

$app = new Dumbo();

/**
 * All Routes will be imported into here.
 * Each Route File will have it's work prefix endpoint
 */

require_once '../router/api.php';
//require_once '../router/web.php';

$app->run();

api.php

<?php

require_once __DIR__ . '/../vendor/autoload.php';

use Dumbo\Dumbo;
use App\Controllers\UserController;
use Dumbo\Helpers\BearerAuth;

// $token = "mysupersecret";
// $app->use(BearerAuth::bearerAuth($token));

$protectedRoutes = new Dumbo();

$protectedRoutes->get("/", function ($c) {
    return $c->json(
        [
            "message" => "Welcome to the protected routes!",
            "dbhost" => config('database.dbhost'),
            "redishost" => config('redis.dbhost')
        ]
    );
});
prince-noman commented 1 month ago

@Nyxalis Yeah it will be helpful. We can work on this if @notrab agrees. If you need any help, I am here. I know Laravel as I work mostly with it.

notrab commented 1 month ago

I like the thought behind this, but coming from a JS and Hono world (please correct me if I'm wrong) but I think this could be done another way.

I don't think it's clear from the docs, but you can use get and set on the $context to store your own stuff, like this:

<?php

$app = new Dumbo();

// Set configuration values
$app->set('DB_URL', 'mysql://user:pass@localhost/mydb');
$app->set('API_KEY', 'your-secret-key');
$app->set('DEBUG', true);

// Get configuration values
$dbUrl = $app->get('DB_URL');
$apiKey = $app->get('API_KEY');
$debug = $app->get('DEBUG');

// Use configuration in your routes
$app->get('/api/data', function(Context $ctx) {
    $apiKey = $ctx->get('API_KEY');
    // Use $apiKey in your logic...
    return $ctx->json(['message' => 'API key is set']);
});

$app->run();

Would this be enough to achieve what you're looking to do?

In JS land we have the the dotenv package which loads various different environment variables depending on the runtime. You can even pass it .env.development and .env.production to simulate the different environments. Does PHP let us do this?

Nyxalis commented 1 month ago

@notrab Having different enviroments definitely possible. Can't the get and set method be used config files? Since I don't think everything should be in an .env file. For example, config for authentication for password hashing or something like that, being in a config php file and returning the values like this:

return [
     'hash' => 'something'
     'password_min' => 9
]

The Get and Set could be used within this

return [
     'dbhost' => $app-get('DBHOST)
]

Or something similar.

darkterminal commented 1 month ago

@notrab the set() method is undefined and I dance for that. The set() method is defined inside the Context but not in Dumbo class.

To make it work, add this inside the Dumbo class

<?php

class Dumbo
{
+    /** @var array<string, mixed> Variables stored in the context */
+    private $configs = [];
+
+    public function set(string $key, $value): void
+    {
+        $this->configs[$key] = $value;
+    }
+
+    public function getConfig(): array
+    {
+        return $this->configs;
+    }
+
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        if ($this->removeTrailingSlash) {
            $uri = $request->getUri();
            $path = $uri->getPath();

            if (strlen($path) > 1 && substr($path, -1) === "/") {
                $newPath = rtrim($path, "/");
                $newUri = $uri->withPath($newPath);
                return new Response(301, ["Location" => (string) $newUri]);
            }
        }

        try {
            $route = $this->router->findRoute($request);

            $context = new Context(
                $request,
                $route ? $route["params"] : [],
                $route ? $route["routePath"] : "",
+                $this->getConfig()
            );

            $fullMiddlewareStack = $this->getFullMiddlewareStack();

            if ($route) {
                $fullMiddlewareStack = array_unique(
                    array_merge(
                        $fullMiddlewareStack,
                        $route["middleware"] ?? []
                    ),
                    SORT_REGULAR
                );

                $handler = $route["handler"];
            } else {
                $handler = function () {
                    return new Response(404, [], "404 Not Found");
                };
            }

            $response = $this->runMiddleware(
                $context,
                $handler,
                $fullMiddlewareStack
            );

            return $response instanceof ResponseInterface
                ? $response
                : $context->getResponse();
        } catch (HTTPException $e) {
            return $this->handleHTTPException($e, $request);
        } catch (\Exception $e) {
            return $this->handleGenericException($e, $request);
        }
    }
}

And get the config in the Context:

<?php

class Context
{

+    /** @var array<string, mixed> Variables stored in the context */
-    private $variables = [];
+    private $configs = [];

     public function __construct(
        private ServerRequestInterface $request,
        private array $params,
        private string $routePath,
+        array $configs = []
    ) {
        $this->response = new Response();
        $this->req = new RequestWrapper($request, $params, $routePath);
+        $this->configs = array_merge($this->configs, $configs);
    }

    public function set(string $key, mixed $value): void
    {
-        $this->variables[$key] = $value;
+        $this->configs[$key] = $value;
    }

    public function get(string $key): mixed
    {
-        $this->variables[$key] ?? null;
+        return $this->configs[$key] ?? null;
    }
notrab commented 1 month ago

I'm going to close this now that we have better support for config across root and grouped routes.