Skip to content

Repository files navigation

PHP MVC Framework

A simple PHP MVC framework.

Features

  • Attribute-Based Routing - Use PHP attributes for clean route definitions
  • Dependency Injection - Automatic DI container with singleton support
  • Middleware System - Flexible middleware with attribute support
  • Database Abstraction - PDO wrapper with ActiveRecord-style ORM
  • Twig Templates - Powerful and secure template engine support

Quick Start

  1. Clone the repository

    git clone <repository-url>cd php-mvc-framework
  2. Install dependencies

    composer install
  3. Setup environment

    cp .env.example .env
  4. Create database (for SQLite)

    touch storage/database.sqlite
  5. Start development server

    composer serve
  6. Visit your application Open http://localhost:8000 in your browser

Project Structure

mvc-framework/
├── app/ # Application code
│ ├── Controllers/ # HTTP controllers
│ ├── Models/ # Data models
│ ├── Services/ # Business logic
│ └── Middleware/ # HTTP middleware
├── config/ # Configuration files
├── core/ # Framework core
│ ├── Attributes/ # PHP attributes
│ └── Http/ # HTTP components
├── public/ # Web server document root
├── views/ # Template files
└── storage/ # App storage (logs, cache, etc.)

Creating Controllers

<?phpnamespaceApp\Controllers;
useCore\Attributes\Route;
useCore\Attributes\Controller;
useCore\Http\Request;
#[Controller(prefix: '/api')]
class ApiController extends BaseController
{
#[Route('/users', 'GET', name: 'users.index')]
publicfunctionindex(): array
{
return ['users' => []];
}
#[Route('/users/{id}', 'GET')]
publicfunctionshow(int$id): array
{
return ['user' => ['id' => $id]];
}
}

Templates (Twig)

The framework uses Twig as its template engine. Templates are located in the views/ directory.

Rendering a Template

In your controller:

publicfunctionindex()
{
return$this->view()->render('home', [
'name' => 'John Doe'
]);
}

Template Example (views/home.twig)

<!DOCTYPE html>
<html>
<head>
<title>{{ app_name }}</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
<p>Welcome to our simple MVC framework.</p>
</body>
</html>

Database & ORM

The framework includes a powerful ActiveRecord-style ORM with support for relationships and eager loading.

Defining Models

Models should extend Core\Model. The table name is automatically derived from the class name (snake_case + plural), or can be explicitly defined.

<?phpnamespaceApp\Models;
useCore\Model;
useCore\Relations\HasMany;
class User extends Model
{
// Optional: Override table nameprotectedstring$table = 'users';
// Define relationshipspublicfunctionposts(): HasMany
{
return$this->hasMany(Post::class);
}
}

Basic CRUD

// Create$user = User::create([
'name' => 'John Doe',
'email' => 'john@example.com'
]);
// Read$users = User::all();
$user = User::find(1);
$activeUsers = User::where('status', 'active');
// Update$user->name = 'Jane Doe';
$user->save();
// Delete$user->delete();

Relationships & Eager Loading

Support for hasMany and belongsTo relationships with efficient eager loading to solve the N+1 problem.

// Define the inverse relationship in Post modelpublicfunctionuser(): BelongsTo
{
return$this->belongsTo(User::class);
}
// Eager load posts with users$users = User::with('posts')->get();
foreach ($usersas$user) {
// Relationships are accessible as propertiesforeach ($user->postsas$post) {
echo$post->title;
}
}

Database Migrations

The framework includes a simple migration system to manage your database schema.

Creating a Migration

Create a new file in the migrations/ directory.

<?phpuseCore\Database\Migration;
class CreatePostsTable extends Migration
{
publicfunctionup()
{
$table = $this->table('posts');
$table->addColumn('id', 'id') // Helper for auto-incrementing primary key
->addColumn('user_id', 'integer')
->addColumn('title', 'string')
->addColumn('body', 'text')
->addColumn('created_at', 'datetime')
->foreign('user_id', 'id', 'users') // Foreign key
->create();
}
publicfunctiondown()
{
$this->table('posts')->drop();
}
}

Running Migrations

Use the console script to run migrations.

# Run all pending migrations
php bin/console migrate
# Rollback the last migration batch
php bin/console migrate:rollback
# Rollback all migrations and run them again
php bin/console migrate:refresh
# Drop all tables and re-run all migrations
php bin/console migrate:fresh

Services with DI

<?phpnamespaceApp\Services;
useCore\Attributes\Service;
#[Service(singleton: true)]
class UserService
{
publicfunction__construct(
privateUser$user
) {}
publicfunctiongetAllUsers(): array
{
return$this->user->all();
}
}

Middleware

<?phpnamespaceApp\Middleware;
useCore\Http\Request;
useCore\Http\Response;
class AuthMiddleware implements MiddlewareInterface
{
publicfunctionhandle(Request$request): ?Response
{
if (!$request->header('Authorization')) {
return (newResponse())->status(401)->json(['error' => 'Unauthorized']);
}
returnnull; // Continue
}
}

Configuration

Database Configuration

Edit config/database.php to configure your database connection:

return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'your_database',
'username' => 'your_username',
'password' => 'your_password',
],
],
];

Application Configuration

Edit config/app.php for application settings:

return [
'name' => 'Your App Name',
'debug' => true,
'controllers' => [
\App\Controllers\HomeController::class,
\App\Controllers\UserController::class,
],
];

API Examples

GET /api/users

curl -X GET http://localhost:8000/api/users

POST /api/users

curl -X POST http://localhost:8000/api/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"name":"John Doe","email":"john@example.com"}'

Requirements

  • PHP 8.0 or higher
  • PDO extension
  • Composer

License

MIT License

Contributing

Pull requests are welcome. For major changes, please open an issue first.

About

a simple php mvc framework

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages