Compare commits

..

No commits in common. "42df7cac0ff952771ed93ac4c36c03eee7cccceb" and "059d3d2f122526902be1c44c3cae9ff720afd002" have entirely different histories.

12 changed files with 3 additions and 281 deletions

View file

@ -60,7 +60,7 @@ milestones:
# Milestone 2: Core Infrastructure Router, DI Container, Service Provider # Milestone 2: Core Infrastructure Router, DI Container, Service Provider
milestone_2_core_infrastructure: milestone_2_core_infrastructure:
status: COMPLETE status: TODO
description: >- description: >-
Build the custom routing engine, lightweight dependency injection container, Build the custom routing engine, lightweight dependency injection container,
and service provider pattern that will be used throughout the application. and service provider pattern that will be used throughout the application.

View file

@ -1,11 +1,2 @@
<?php <?php
/**
* File: _bootstrap.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Bootstrap file for the application.
*/
require __DIR__ . '/vendor/autoload.php'; require __DIR__ . '/vendor/autoload.php';

View file

@ -1,13 +1,4 @@
<?php <?php
/**
* File: config/bootstrap.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Bootstrap configuration and DI container.
*/
declare(strict_types=1); declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
@ -19,12 +10,7 @@ $mergedEnv = $_ENV + $_SERVER;
use League\Container\Container; use League\Container\Container;
use SiteWeaver\Core\Config; use SiteWeaver\Core\Config;
use SiteWeaver\Core\Router;
$container = new Container(); $container = new Container();
// Register core services
$router = new Router();
$container->add('router', $router);
$container->add('config', function () use ($mergedEnv) { $container->add('config', function () use ($mergedEnv) {
return new Config($mergedEnv); return new Config($mergedEnv);
}); });

View file

@ -1,13 +1,4 @@
<?php <?php
/**
* File: public/index.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Front controller entry point that outputs a simple message.
*/
declare(strict_types=1); declare(strict_types=1);
$container = require __DIR__ . '/../config/bootstrap.php'; $container = require __DIR__ . '/../config/bootstrap.php';

View file

@ -1,13 +1,4 @@
<?php <?php
/**
* File: src/Site/Core/Config.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Configuration service that loads environment variables and optionally fetches secrets from Infisical.
*/
declare(strict_types=1); declare(strict_types=1);
namespace SiteWeaver\Core; namespace SiteWeaver\Core;
@ -24,7 +15,7 @@ class Config
/** @var array<string, mixed> */ /** @var array<string, mixed> */
private array $values = []; private array $values = [];
public function __construct(?array $env = null) public function __construct(array $env = null)
{ {
// Use provided env array or fallback to $_ENV // Use provided env array or fallback to $_ENV
$this->values = $env ?? $_ENV; $this->values = $env ?? $_ENV;
@ -83,4 +74,4 @@ class Config
return $this->values; return $this->values;
} }
} }
?>

View file

@ -1,112 +0,0 @@
<?php
/**
* File: src/Site/Core/Router.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Very small custom router.
*/
declare(strict_types=1);
namespace SiteWeaver\Core;
/**
* Very small custom router.
*/
class Router
{
/** @var array<int, array{method:string,path:string,handler:callable,middleware?:array<callable>}> */
private array $routes = [];
/** @var array<string,string> mapping of route name to path pattern */
private array $namedRoutes = [];
public function get(string $path, callable $handler, array $middleware = [], ?string $name = null): void
{
$this->addRoute('GET', $path, $handler, $middleware, $name);
}
public function post(string $path, callable $handler, array $middleware = [], ?string $name = null): void
{
$this->addRoute('POST', $path, $handler, $middleware, $name);
}
public function put(string $path, callable $handler, array $middleware = [], ?string $name = null): void
{
$this->addRoute('PUT', $path, $handler, $middleware, $name);
}
public function delete(string $path, callable $handler, array $middleware = [], ?string $name = null): void
{
$this->addRoute('DELETE', $path, $handler, $middleware, $name);
}
private function addRoute(
string $method,
string $path,
callable $handler,
array $middleware,
?string $name = null
): void {
$this->routes[] = [
'method' => strtoupper($method),
'path' => $path,
'handler' => $handler,
'middleware' => $middleware,
];
if ($name !== null) {
$this->namedRoutes[$name] = $path;
}
}
/**
* Dispatch a request.
*
* @return array{handler:callable,middleware:array<callable>,params:array<string,string>}|null
*/
public function dispatch(string $method, string $uri): ?array
{
foreach ($this->routes as $route) {
if (strtoupper($method) !== strtoupper($route['method'])) {
continue;
}
// Build regex for path pattern
$pattern = preg_replace('#\{([^}]+)\}#', '(?P<$1>[^/]+)', $route['path']);
$pattern = '#^' . $pattern . '$#';
if (preg_match($pattern, $uri, $matches)) {
// filter named groups
$params = array_filter(
$matches,
fn ($k) => is_string($k),
ARRAY_FILTER_USE_KEY
);
return [
'handler' => $route['handler'],
'middleware' => $route['middleware'] ?? [],
'params' => $params,
];
}
}
return null;
}
/**
* Generate a URL for a named route.
*/
public function url(string $name, array $params = []): string
{
if (!isset($this->namedRoutes[$name])) {
throw new \InvalidArgumentException("Route name {$name} not defined");
}
$path = $this->namedRoutes[$name];
foreach ($params as $key => $value) {
// replace both required and optional placeholders
$path = str_replace('{' . $key . '}', (string)$value, $path);
$path = str_replace('{?' . $key . '}', (string)$value, $path);
}
return $path;
}
}

View file

@ -1,39 +0,0 @@
<?php
/**
* File: src/Site/Core/ServiceProvider.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Base class for all service providers.
*/
declare(strict_types=1);
namespace SiteWeaver\Core;
use League\Container\Container;
/**
* Base class for all service providers.
*/
abstract class ServiceProvider
{
protected Container $container;
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* Register bindings in the container.
*/
abstract public function register(): void;
/**
* Optional boot method called after all providers are registered.
*/
public function boot(): void {}
}

View file

@ -1,11 +1,2 @@
<?php <?php
/**
* File: tests/_bootstrap.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Bootstrap file for Codeception tests.
*/
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';

View file

@ -1,13 +1,4 @@
<?php <?php
/**
* File: tests/_support/UnitTester.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Unit tester support class for Codeception.
*/
declare(strict_types=1); declare(strict_types=1);
/** /**

View file

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\unit\Core;
use SiteWeaver\Core\Router;
use PHPUnit\Framework\TestCase;
final class RouterTest extends TestCase
{
public function testRouteRegistrationAndDispatch(): void
{
$router = new Router();
$router->get('/users/{id}', fn($params) => 'handler', [], 'user_show');
$result = $router->dispatch('GET', '/users/42');
$this->assertNotNull($result);
$handler = $result['handler'];
$this->assertIsCallable($handler);
$this->assertSame('handler', ($handler)(['id' => '42']));
$this->assertEquals(['id' => '42'], $result['params']);
}
public function testUrlGeneration(): void
{
$router = new Router();
$router->get('/posts/{slug}', fn() => null, [], 'post_show');
$url = $router->url('post_show', ['slug' => 'hello-world']);
$this->assertSame('/posts/hello-world', $url);
}
}

View file

@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\unit\Core;
use SiteWeaver\Core\ServiceProvider;
use League\Container\Container;
use PHPUnit\Framework\TestCase;
final class DummyProvider extends ServiceProvider {
public function register(): void {
$this->container->add('dummy', fn() => 'value');
}
}
final class ServiceProviderTest extends TestCase
{
public function testRegisterAddsService(): void
{
$container = new Container();
$provider = new DummyProvider($container);
$provider->register();
$this->assertSame('value', $container->get('dummy'));
}
}

View file

@ -1,13 +1,4 @@
<?php <?php
/**
* File: tests/unit/UnitTester.php
*
* Author: Funky Waddle
* Date: 2026-09-10
*
* Purpose:
* Test actor used by Codeception unit suite.
*/
namespace Tests\unit; namespace Tests\unit;