diff --git a/docs/user-guide/mvc-usage.md b/docs/user-guide/mvc-usage.md new file mode 100644 index 000000000..12ee269d7 --- /dev/null +++ b/docs/user-guide/mvc-usage.md @@ -0,0 +1,60 @@ +# MVC Usage Guide + +This document provides an overview of how to use the new MVC components in the Pancake toolkit. + +## BaseController + +The `BaseController` class provides common functionality for rendering views and redirecting URLs. + +### Example + +```php +use Pancake\MVC\BaseController; + +$templateEngine = new YourTemplateEngine(); +$controller = new BaseController($templateEngine); + +$controller->render('home', ['key' => 'value']); +$controller->redirect('http://example.com'); +``` + +## ApiController + +The `ApiController` class extends `BaseController` and is designed for API responses, rendering JSON. + +### Example + +```php +use Pancake\MVC\ApiController; + +$controller = new ApiController(null); +$controller->render(['key' => 'value']); +``` + +## Router + +The `Router` class manages route registration and dispatching. + +### Example + +```php +use Pancake\MVC\Router; + +$router = new Router(); +$router->add('GET', '/home', 'HomeController', 'index'); +$router->dispatch('GET', '/home'); +``` + +## DIContainer Integration + +The `DIContainer` is used to manage dependencies for controllers and the template engine. + +### Example + +```php +$container = new Pancake\DIContainer(); +$controller = $container->resolve('BaseController'); +$controller->render('home', ['key' => 'value']); +``` + +``` \ No newline at end of file diff --git a/src/Pancake/DIContainer.php b/src/Pancake/DIContainer.php new file mode 100644 index 000000000..090c612cc --- /dev/null +++ b/src/Pancake/DIContainer.php @@ -0,0 +1,48 @@ +bindings[$name] = ['resolver' => $resolver, 'shared' => true, 'instance' => null]; + } + + public function registerTransient($name, $resolver) + { + $this->bindings[$name] = ['resolver' => $resolver, 'shared' => false]; + } + + public function resolve($name) + { + if (!isset($this->bindings[$name])) { + throw new \Exception("No binding registered for {$name}"); + } + + $binding = $this->bindings[$name]; + + if ($binding['shared']) { + if ($binding['instance'] === null) { + $binding['instance'] = $binding['resolver'](); + } + return $binding['instance']; + } + + return $binding['resolver'](); + } +} + +// Registering components +$container = new DIContainer(); +$container->registerSingleton('templateEngine', function () { + return new DefaultTemplateEngine(); // Assume DefaultTemplateEngine is defined elsewhere +}); +$container->registerTransient('BaseController', function () use ($container) { + return new MVC\BaseController($container->resolve('templateEngine')); +}); +$container->registerTransient('ApiController', function () use ($container) { + return new MVC\ApiController($container->resolve('templateEngine')); +}); diff --git a/src/Pancake/MVC/ApiController.php b/src/Pancake/MVC/ApiController.php new file mode 100644 index 000000000..9732586b2 --- /dev/null +++ b/src/Pancake/MVC/ApiController.php @@ -0,0 +1,12 @@ +templateEngine = $templateEngine; + } + + public function render($view, $data = []) + { + echo $this->templateEngine->render($view, $data); + } + + public function redirect($url) + { + header('Location: ' . $url); + exit(); + } +} diff --git a/src/Pancake/MVC/Router.php b/src/Pancake/MVC/Router.php new file mode 100644 index 000000000..66a8fb6d1 --- /dev/null +++ b/src/Pancake/MVC/Router.php @@ -0,0 +1,27 @@ +routes[] = compact('method', 'uri', 'controller', 'action'); + } + + public function dispatch($method, $uri) + { + foreach ($this->routes as $route) { + if ($route['method'] === $method && $route['uri'] === $uri) { + $controller = new $route['controller'](); + $action = $route['action']; + return $controller->$action(); + } + } + // Handle 404 + header("HTTP/1.0 404 Not Found"); + echo "404 Not Found"; + } +} diff --git a/tests/MVC/ApiControllerTest.php b/tests/MVC/ApiControllerTest.php new file mode 100644 index 000000000..fbf227bde --- /dev/null +++ b/tests/MVC/ApiControllerTest.php @@ -0,0 +1,17 @@ +expectOutputString('{"key":"value"}'); + $controller->render(['key' => 'value']); + } +} diff --git a/tests/MVC/BaseControllerTest.php b/tests/MVC/BaseControllerTest.php new file mode 100644 index 000000000..313acb182 --- /dev/null +++ b/tests/MVC/BaseControllerTest.php @@ -0,0 +1,32 @@ +createMock(TemplateEngine::class); + $templateEngine->expects($this->once()) + ->method('render') + ->with('view', ['key' => 'value']) + ->willReturn('rendered view'); + + $controller = new BaseController($templateEngine); + $this->expectOutputString('rendered view'); + $controller->render('view', ['key' => 'value']); + } + + public function testRedirect() + { + $controller = new BaseController(null); + + $this->expectException(\PHPUnit\Framework\Error\Warning::class); + $this->expectExceptionMessage('Cannot modify header information'); + + $controller->redirect('http://example.com'); + } +} diff --git a/tests/MVC/RouterTest.php b/tests/MVC/RouterTest.php new file mode 100644 index 000000000..44b8b4bb5 --- /dev/null +++ b/tests/MVC/RouterTest.php @@ -0,0 +1,32 @@ +createMock(\stdClass::class); + $mockController->expects($this->once()) + ->method('testAction') + ->willReturn('action executed'); + + $router->add('GET', '/test', get_class($mockController), 'testAction'); + + $this->expectOutputString('action executed'); + $router->dispatch('GET', '/test'); + } + + public function testDispatchNotFound() + { + $router = new Router(); + + $this->expectOutputString('404 Not Found'); + $router->dispatch('GET', '/non-existent'); + } +}