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.
|
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