agust / mvc-structure
A simple MVC structure for PHP
Installs: 12
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
Type:project
Requires
- php: >= 8.1
- ext-pdo: *
- vlucas/phpdotenv: ^5.5
Requires (Dev)
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2025-04-19 15:26:53 UTC
README
Overview
A lightweight, flexible MVC (Model-View-Controller) framework built with vanilla PHP. This framework provides a structured architecture for building web applications without the overhead of larger frameworks, allowing developers to understand exactly what's happening under the hood.
Key Features
- Pure PHP implementation without dependencies on other frameworks
- MVC Architecture for clean separation of concerns
- Routing System with support for dynamic parameters and method spoofing
- Template Engine for rendering views with layouts
- Database Abstraction Layer with query building and ORM-like features
- Middleware Support for request/response manipulation
- Auth System with login, registration, and role-based access control
- Session Management and flash messages
- Input Handling with validation and sanitization
- Environment Configuration via .env files
- Helper Functions for common tasks
Installation
Prerequisites
- PHP 7.4 or higher
- Composer
- MySQL/MariaDB (or your preferred database)
Step 1: Clone the Repository
composer create-project agust/mvc-structure my-project
cd my-project
Step 2: Install Dependencies
composer install
Step 3: Configure Environment
Copy the example environment file and adjust settings for your environment:
cp .env.example .env
Edit the .env
file with your database credentials and application settings:
APP_NAME=PHP MVC Framework APP_ENV=development APP_DEBUG=true APP_URL=http://localhost:8000 APP_TIMEZONE=UTC DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database DB_USERNAME=your_username DB_PASSWORD=your_password DB_CHARSET=utf8mb4
Step 4: Set Permissions (OPTIONAL)
Ensure storage directories are writable:
chmod -R 775 storage/
Step 5: Create Database
Create your database and import schema:
mysql -u username -p database_name < database/schema.sql
Step 6: Run Development Server
php -S localhost:8000 -t public/
Visit http://localhost:8000
in your browser to see the application running.
Project Structure
project/
├── app/ # Application core files
│ ├── Controllers/ # Controller classes
│ ├── Core/ # Framework core classes and functionality
│ │ ├── Auth.php # Authentication manager
│ │ ├── Controller.php# Base controller class
│ │ ├── Database.php # Database connection and queries
│ │ ├── Helpers.php # Helper functions
│ │ ├── Input.php # Input handling and validation
│ │ ├── Middleware.php# Middleware base class
│ │ ├── Model.php # Base model class
│ │ └── Router.php # URL routing system
│ ├── Models/ # Model classes
│ ├── Views/ # View templates
│ │ ├── layouts/ # Layout templates
│ │ └── errors/ # Error pages
│ └── Middleware/ # Request/response middleware
├── config/ # Configuration files
│ ├── config.php # Main configuration
│ └── database.php # Database configuration
├── public/ # Publicly accessible files
│ ├── index.php # Application entry point
│ └── assets/ # Assets (CSS, JS, images)
├── tests/ # Test files
├── vendor/ # Composer dependencies
├── web/ # Web bootstrapping
│ ├── bootstrap.php # Application bootstrapping
│ └── routes.php # Route definitions
└── .env # Environment variables
Core Components
Routing System
Routes are defined in the web/routes.php
file:
// Basic routes $router->get('/', [HomeController::class, 'index']); $router->get('/about', [HomeController::class, 'about']); // Routes with parameters $router->get('/users/{id}', [UserController::class, 'show'], [AuthMiddleware::class]); // HTTP methods $router->post('/users', [UserController::class, 'store']); $router->put('/users/{id}', [UserController::class, 'update']); $router->delete('/users/{id}', [UserController::class, 'destroy']); // Routes with middleware $router->get('/users', [UserController::class, 'index'], [AuthMiddleware::class]);
Controllers
Controllers extend the base Controller
class and handle request logic:
// app/Controllers/UserController.php namespace App\Controllers; use App\Core\Controller; use App\Core\Input; use App\Models\User; class UserController extends Controller { protected $userModel; public function __construct() { $this->userModel = new User(); } public function index() { $users = $this->userModel->all(); return $this->view('users/index', [ 'title' => 'Users', 'users' => $users ]); } public function store() { $data = Input::sanitize([ 'name' => Input::post('name'), 'email' => Input::post('email'), 'password' => password_hash(Input::post('password'), PASSWORD_DEFAULT) ]); $userId = $this->userModel->create($data); if ($userId) { $_SESSION['success'] = 'User created successfully!'; $this->redirect('/users'); } } }
Models
Models extend the base Model
class and interact with the database:
// app/Models/User.php namespace App\Models; use App\Core\Model; class User extends Model { protected static $table = 'users'; public static function findByEmail(string $email) { return self::$db->query("SELECT * FROM " . static::$table . " WHERE email = ?") ->bind([1 => $email]) ->execute() ->fetch(); } public static function authenticate(string $email, string $password) { $user = self::findByEmail($email); if (!$user) { return false; } return password_verify($password, $user['password']) ? $user : false; } }
Views
Views are PHP files that render HTML with data passed from controllers:
<!-- app/Views/users/index.php --> <div class="d-flex justify-content-between align-items-center mb-4"> <h1><?= $title ?></h1> <a href="/users/create" class="btn btn-primary">Create New User</a> </div> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>Name</th> <th>Email</th> <th>Actions</th> </tr> </thead> <tbody> <?php foreach ($users as $user): ?> <tr> <td><?= $user['id'] ?></td> <td><?= $user['name'] ?></td> <td><?= $user['email'] ?></td> <td> <a href="/users/<?= $user['id'] ?>" class="btn btn-sm btn-info">View</a> <a href="/users/<?= $user['id'] ?>/edit" class="btn btn-sm btn-warning">Edit</a> <!-- Delete form --> </td> </tr> <?php endforeach; ?> </tbody> </table> </div>
Middleware
Middleware classes are used to filter HTTP requests:
// app/Middleware/AuthMiddleware.php namespace App\Middleware; use App\Core\Middleware; use Closure; class AuthMiddleware extends Middleware { public function handle($request, Closure $next) { if (!isset($_SESSION['users'])) { $_SESSION['intended_url'] = $_SERVER['REQUEST_URI']; header('Location: /login'); exit; } return $next($request); } }
Configuration
Main Configuration File
// config/config.php return [ 'debug' => Helpers::env('APP_DEBUG', false), 'app_name' => Helpers::env('APP_NAME', 'MVC Framework'), 'app_url' => Helpers::env('APP_URL', 'http://localhost'), 'timezone' => Helpers::env('APP_TIMEZONE', 'UTC'), ];
Database Configuration
// config/database.php return [ 'driver' => Helpers::env('DB_CONNECTION', 'mysql'), 'host' => Helpers::env('DB_HOST', 'localhost'), 'database' => Helpers::env('DB_DATABASE', 'mvc'), 'username' => Helpers::env('DB_USERNAME', 'root'), 'password' => Helpers::env('DB_PASSWORD', ''), 'charset' => Helpers::env('DB_CHARSET', 'utf8mb4'), ];
Helper Functions
The framework provides helper functions through the Helpers
class:
// Environment variables $debug = \App\Core\Helpers::env('APP_DEBUG', false); // Asset URLs <link rel="stylesheet" href="<?= \App\Core\Helpers::asset('css/style.css') ?>"> // Formatting dates <?= \App\Core\Helpers::formatDate($timestamp) ?>
Authentication
// Check if a user is logged in if (App\Core\Auth::check()) { // User is logged in } // Get the current user $user = App\Core\Auth::user(); // Check if user is admin if (App\Core\Auth::isAdmin()) { // User is admin } // Login a user App\Core\Auth::login($user); // Logout App\Core\Auth::logout();
Database Queries
// Direct query $users = $db->query("SELECT * FROM users WHERE active = ?") ->bind([1 => true]) ->execute() ->fetchAll(); // Using the Model $users = User::all(); $user = User::find(1); $userId = $userModel->create([ 'name' => 'John Doe', 'email' => 'john@example.com' ]); $updated = $userModel->update(1, ['name' => 'Jane Doe']); $deleted = $userModel->delete(1);
Input Handling
// Get from $_POST $name = Input::post('name'); // Get from $_GET $id = Input::get('id'); // Get from either $_GET or $_POST $search = Input::any('search'); // Sanitize input $data = Input::sanitize([ 'name' => Input::post('name'), 'email' => Input::post('email') ]);
Testing
The framework includes PHPUnit for testing. Run tests with:
./vendor/bin/phpunit
Example test:
// tests/Controllers/UserControllerTest.php namespace Tests\Controllers; use App\Controllers\UserController; use App\Models\User; use PHPUnit\Framework\TestCase; class UserControllerTest extends TestCase { private $userController; private $userModelMock; protected function setUp(): void { // Create a mock for the User model $this->userModelMock = $this->createMock(User::class); // Create a UserController instance $this->userController = new UserController(); // Use reflection to inject our mock $reflection = new \ReflectionClass(UserController::class); $property = $reflection->getProperty('userModel'); $property->setAccessible(true); $property->setValue($this->userController, $this->userModelMock); } public function testControllerInstanceCreation() { $this->assertInstanceOf(UserController::class, $this->userController); } // More tests... }
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
License
This framework is open-sourced software licensed under the MIT license.
Author
Sean Agustine L. Esparagoza
Github: agustinelumandong