Initial commit of LightMVC framework files.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ public/index.php [L,QSA]
|
||||
</IfModule>
|
||||
@@ -1,3 +1,94 @@
|
||||
# LightMVC
|
||||
# LightMVC - Lightweight PHP MVC Framework
|
||||
|
||||
A minimal, PSR-4 compliant PHP MVC framework designed for simplicity and scalability.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```text
|
||||
├── app/
|
||||
│ ├── Controllers/ # Application logic (Controllers)
|
||||
│ ├── Models/ # Data structures (Models)
|
||||
│ └── Views/ # UI templates (Views)
|
||||
├── core/ # Framework engine
|
||||
│ ├── Application.php
|
||||
│ ├── Controller.php
|
||||
│ ├── Request.php
|
||||
│ ├── Response.php
|
||||
│ ├── Router.php
|
||||
│ └── View.php
|
||||
├── public/ # Entry point and static assets
|
||||
│ ├── css/ # Custom CSS files
|
||||
│ ├── js/ # Custom JS files
|
||||
│ └── images/ # Static images
|
||||
├── node_modules/ # Frontend dependencies
|
||||
├── vendor/ # Composer dependencies
|
||||
```
|
||||
|
||||
## Asset Management & npm
|
||||
|
||||
This project uses `npm` for frontend dependencies (e.g., Bootstrap).
|
||||
|
||||
1. Initialize/Install: `npm install`
|
||||
2. Add custom CSS/JS in `public/css/app.css` and `public/js/app.js`.
|
||||
3. Reference assets in `app/Views/layouts/main.php`.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Request Flow**: Requests hit `public/index.php`.
|
||||
2. **Initialization**: `Core\Application` initializes `Request`, `Response`, `Router`, and `View`.
|
||||
3. **Routing**: The `Router` matches the `Request` path against registered routes.
|
||||
4. **Dispatching**: If a match is found, the `Router` instantiates the controller and invokes the requested method.
|
||||
5. **Controller Action**: The controller handles business logic and returns a `View` using `$this->render()`.
|
||||
6. **Response**: `View` loads the layout from `app/Views/layouts/main.php`, injects the view content into the `{{content}}` placeholder, and returns the response.
|
||||
|
||||
## Adding a New Route
|
||||
|
||||
In `public/index.php`, register a route mapping a path to a controller method:
|
||||
|
||||
```php
|
||||
$app->router->get('/path', [ControllerClass::class, 'methodName']);
|
||||
```
|
||||
|
||||
## API Routing
|
||||
|
||||
API routes are registered in `public/index.php` with the `/api/` prefix to keep them separate from regular page routes.
|
||||
|
||||
### Example API Route
|
||||
|
||||
```php
|
||||
$app->router->get('/api/ping', [App\Controllers\ApiController::class, 'ping']);
|
||||
```
|
||||
|
||||
### API Controller
|
||||
|
||||
The `ApiController` class lives in `app/Controllers/ApiController.php` and should contain methods for API endpoints.
|
||||
|
||||
Example:
|
||||
```php
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use Core\Controller;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
public function ping()
|
||||
{
|
||||
// Simple health-check endpoint
|
||||
$this->response->setHeader('Content-Type', 'application/json');
|
||||
$this->response->setBody(json_encode(['status' => 'ok', 'time' => microtime(true)]));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Testing the API
|
||||
|
||||
You can test the `/api/ping` endpoint with:
|
||||
```bash
|
||||
curl http://localhost/api/ping
|
||||
```
|
||||
|
||||
Expected response:
|
||||
```json
|
||||
{"status":"ok","time":1718409600.123}
|
||||
```
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace App\Controllers;
|
||||
|
||||
use Core\Controller;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
public function ping()
|
||||
{
|
||||
// Simple health-check endpoint
|
||||
$this->response->setHeader('Content-Type', 'application/json');
|
||||
$this->response->setBody(json_encode(['status' => 'ok', 'time' => microtime(true)]));
|
||||
}
|
||||
}
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use Core\Controller;
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
public function home()
|
||||
{
|
||||
return $this->render('home', [
|
||||
'name' => 'My PHP MVC Framework'
|
||||
]);
|
||||
}
|
||||
|
||||
public function contact()
|
||||
{
|
||||
return $this->render('contact');
|
||||
}
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
<h1>404 - Page Not Found</h1>
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
<h1>Contact</h1>
|
||||
<form action="" method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Subject</label>
|
||||
<input type="text" name="subject" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Body</label>
|
||||
<textarea name="body" class="form-control"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
<h1>Home</h1>
|
||||
<h3>Welcome to {{name}}</h3>
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PHP MVC Framework</title>
|
||||
<!-- Bootstrap from node_modules (needs mapping) -->
|
||||
<link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Custom CSS -->
|
||||
<link href="/css/app.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Navbar</a>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/contact">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
{{content}}
|
||||
</div>
|
||||
|
||||
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "jaspreet/lightmvc",
|
||||
"description": "A lightweight PHP MVC framework",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Core\\": "core/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jaspreet Singh",
|
||||
"email": "jaspreet.online2012@gmail.com",
|
||||
"role": "developer"
|
||||
},
|
||||
{
|
||||
"name": "Gemini CLI",
|
||||
"role": "Artificial Intelligence"
|
||||
},
|
||||
{
|
||||
"name": "OpenClaw",
|
||||
"role": "Artificial Intelligence"
|
||||
}
|
||||
],
|
||||
"require": {}
|
||||
}
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class Application
|
||||
{
|
||||
public static string $ROOT_DIR;
|
||||
public Router $router;
|
||||
public Request $request;
|
||||
public Response $response;
|
||||
public View $view;
|
||||
public static Application $app;
|
||||
|
||||
public function __construct($rootPath)
|
||||
{
|
||||
self::$ROOT_DIR = $rootPath;
|
||||
self::$app = $this;
|
||||
$this->request = new Request();
|
||||
$this->response = new Response();
|
||||
$this->view = new View();
|
||||
$this->router = new Router($this->request, $this->response);
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo $this->router->resolve();
|
||||
}
|
||||
}
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class Controller
|
||||
{
|
||||
public function render($view, $params = [])
|
||||
{
|
||||
return Application::$app->router->renderView($view, $params);
|
||||
}
|
||||
}
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class Request
|
||||
{
|
||||
public function getPath()
|
||||
{
|
||||
$path = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
$position = strpos($path, '?');
|
||||
if ($position === false) {
|
||||
return $path;
|
||||
}
|
||||
return substr($path, 0, $position);
|
||||
}
|
||||
|
||||
public function getMethod()
|
||||
{
|
||||
return strtolower($_SERVER['REQUEST_METHOD']);
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
$body = [];
|
||||
if ($this->getMethod() === 'get') {
|
||||
foreach ($_GET as $key => $value) {
|
||||
$body[$key] = filter_input(INPUT_GET, $key, FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
}
|
||||
}
|
||||
if ($this->getMethod() === 'post') {
|
||||
foreach ($_POST as $key => $value) {
|
||||
$body[$key] = filter_input(INPUT_POST, $key, FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
}
|
||||
}
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class Response
|
||||
{
|
||||
public function setStatusCode(int $code)
|
||||
{
|
||||
http_response_code($code);
|
||||
}
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class Router
|
||||
{
|
||||
protected array $routes = [];
|
||||
public Request $request;
|
||||
public Response $response;
|
||||
|
||||
public function __construct(Request $request, Response $response)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function get($path, $callback)
|
||||
{
|
||||
$this->routes['get'][$path] = $callback;
|
||||
}
|
||||
|
||||
public function post($path, $callback)
|
||||
{
|
||||
$this->routes['post'][$path] = $callback;
|
||||
}
|
||||
|
||||
public function resolve()
|
||||
{
|
||||
$path = $this->request->getPath();
|
||||
$method = $this->request->getMethod();
|
||||
$callback = $this->routes[$method][$path] ?? false;
|
||||
|
||||
if ($callback === false) {
|
||||
$this->response->setStatusCode(404);
|
||||
return $this->renderView("_404");
|
||||
}
|
||||
|
||||
if (is_string($callback)) {
|
||||
return $this->renderView($callback);
|
||||
}
|
||||
|
||||
if (is_array($callback)) {
|
||||
$callback[0] = new $callback[0]();
|
||||
}
|
||||
|
||||
return call_user_func($callback, $this->request);
|
||||
}
|
||||
|
||||
public function renderView($view, $params = [])
|
||||
{
|
||||
return Application::$app->view->renderView($view, $params);
|
||||
}
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
class View
|
||||
{
|
||||
public function renderView($view, $params = [])
|
||||
{
|
||||
$viewContent = $this->renderOnlyView($view, $params);
|
||||
$layoutContent = $this->layoutContent();
|
||||
return str_replace('{{content}}', $viewContent, $layoutContent);
|
||||
}
|
||||
|
||||
protected function layoutContent()
|
||||
{
|
||||
ob_start();
|
||||
include_once Application::$ROOT_DIR . "/app/Views/layouts/main.php";
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
protected function renderOnlyView($view, $params)
|
||||
{
|
||||
foreach ($params as $key => $value) {
|
||||
$$key = $value;
|
||||
}
|
||||
ob_start();
|
||||
include_once Application::$ROOT_DIR . "/app/Views/$view.php";
|
||||
$content = ob_get_clean();
|
||||
|
||||
// Simple {{variable}} replacement
|
||||
foreach ($params as $key => $value) {
|
||||
if (is_scalar($value)) {
|
||||
$content = str_replace('{{'.$key.'}}', $value, $content);
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "php-mvc-framework",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset management for PHP MVC Framework",
|
||||
"scripts": {
|
||||
"dev": "echo 'Build assets for development'",
|
||||
"build": "echo 'Build assets for production'"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.0"
|
||||
}
|
||||
}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use App\Controllers\SiteController;
|
||||
use Core\Application;
|
||||
|
||||
$app = new Application(dirname(__DIR__));
|
||||
|
||||
$app->router->get('/', [SiteController::class, 'home']);
|
||||
$app->router->get('/contact', [SiteController::class, 'contact']);
|
||||
|
||||
// API routes
|
||||
$app->router->get('/api/ping', [App\Controllers\ApiController::class, 'ping']);
|
||||
|
||||
$app->run();
|
||||
Reference in New Issue
Block a user