feras_altaleb / mvc_php
Enterprise-Ready PHP MVC Framework Template
Package info
github.com/AltalebFeras/template_empty_mvc_for_any_new_project_php_native
Type:project
pkg:composer/feras_altaleb/mvc_php
Requires
- php: >=8.2
- monolog/monolog: ^3.0
- phpmailer/phpmailer: ^6.9
- predis/predis: ^2.0
- vlucas/phpdotenv: ^5.6
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^12.1
README
A hyper-secure, high-performance native PHP MVC application template designed for enterprise-grade production deployments. Zero framework dependencies — only essential, audited libraries.
📚 Detailed Technical Documentation: Full feature guides, API references, and architecture details are available in the docs/ folder.
Table of Contents
- Architecture
- Features
- Prerequisites
- Installation
- Configuration
- Database Migrations
- Request Lifecycle
- Security
- Authentication
- Authorization (RBAC/ABAC)
- API Development
- File Uploads
- Caching
- Background Jobs
- Cloudflare Turnstile
- Deployment (Shared Hosting vs Docker)
- Web Server Configuration
- Testing
- Maintenance
- Component Reference
- Directory Structure
Architecture
Request → Nginx/Apache → public/index.php → init.php (bootstrap)
├── Config::boot() Load .env
├── SecurityHeaders::send() Set CSP, HSTS, etc.
├── RequestLogger::log() Log request start
└── router.php Dispatch to controller
├── CSRF validation
├── Auth guard
├── RBAC role check
├── ABAC permission check
└── Controller::method()
├── Validator::validate()
├── Repository (PDO Singleton)
└── render() / ApiResponse::success()
Design Principles
- Defense-in-Depth: Multiple overlapping security layers
- PSR-4 Autoloading:
App\namespace mapped tosrc/ - Convention over Configuration: Repositories auto-map to tables and entities
- Fail-Fast: Required config validated on boot, strict PDO error mode
- Zero Trust: Every request authenticated, authorized, and rate-limited
Features
Security
- ✅ AES-256-GCM authenticated encryption (AEAD)
- ✅ Argon2id password hashing (64MB memory cost)
- ✅ Per-form CSRF tokens with expiry
- ✅ Hardened HTTP headers (CSP, HSTS, COOP, CORP)
- ✅ Session fingerprinting (IP + User-Agent binding)
- ✅ Automatic session regeneration & idle timeout
- ✅ Cloudflare Turnstile bot protection
- ✅ Sliding-window rate limiting
- ✅ Fail2Ban-compatible threat logging
- ✅ Secure file uploads (MIME verification, UUID rename, EXIF stripping)
- ✅ SVG sanitization (XSS prevention)
- ✅ IDOR prevention (ownership verification)
Performance
- ✅ Singleton PDO connection
- ✅ Generator-based streaming for large result sets
- ✅ Multi-tier caching (Memory → Redis → File)
- ✅ OPcache + JIT configuration
- ✅ Response compression (gzip)
- ✅ ETag + 304 Not Modified support
- ✅ Non-blocking sessions (
session_write_close())
Architecture
- ✅ RBAC + ABAC authorization middleware
- ✅ Attribute-based routing (
#[Route('/path')]) - ✅ Normalized API responses (
status,data,errors,meta) - ✅ Input validation engine (18+ rules)
- ✅ Background job queue with retry/backoff
- ✅ Circuit breaker for external APIs
- ✅ Database migration system (up/down)
- ✅ Structured JSON logging (PSR-3 / Monolog)
- ✅ Health check endpoints (
/health,/ready) - ✅ CORS middleware
- ✅ Docker-ready (PHP-FPM + Nginx + MySQL + Redis)
Prerequisites
| Requirement | Version |
|---|---|
| PHP | ≥ 8.2 |
| Composer | ≥ 2.0 |
| MySQL / PostgreSQL | 8.0+ / 16+ |
| Redis | 7+ (optional) |
Required PHP Extensions
pdo_mysql openssl mbstring curl
gd json fileinfo session
Optional: redis, imagick, apcu
Installation
1. Clone & Install Dependencies
git clone <repository-url> myproject cd myproject composer install
2. Configure Environment
cp .env.example .env
Edit .env with your settings. Generate an encryption key:
php -r "echo bin2hex(random_bytes(32));"
Paste the output as APP_KEY in .env.
3. Create Database & Run Migrations
# Create your database manually, then: composer migrate # or php bin/migrate.php up
4. Set Permissions
chmod -R 755 storage/ logs/ chown -R www-data:www-data storage/ logs/
5. Start Development Server
# Using PHP built-in server: php -S localhost:8080 -t public/ # Or using Docker: docker-compose up -d # App available at http://localhost:8080
Configuration
All configuration is managed via .env. See .env.example for all available keys.
| Key | Description | Default |
|---|---|---|
APP_ENV |
Environment (development/production/testing) | development |
APP_DEBUG |
Show debug info | true |
APP_KEY |
64-hex-char encryption key | (required) |
APP_URL |
Base application URL | http://localhost |
DB_HOST |
Database host | 127.0.0.1 |
DB_NAME |
Database name | (required) |
SESSION_IDLE_TIMEOUT |
Auto-logout idle time (seconds) | 1800 |
TURNSTILE_SITE_KEY |
Cloudflare Turnstile public key | |
RATE_LIMIT_LOGIN |
Max login attempts per window | 5 |
Access configuration in code:
use App\Services\Config; $debug = Config::isDebug(); $url = Config::baseUrl(); $host = Config::get('DB_HOST', '127.0.0.1'); $port = Config::getInt('DB_PORT', 3306);
Database Migrations
Migrations live in src/Migrations/ as PHP files returning ['up' => '...SQL...', 'down' => '...SQL...'].
# Run all pending migrations php bin/migrate.php up # Roll back the last migration php bin/migrate.php down # Show migration status php bin/migrate.php status
Creating a Migration
Create a file in src/Migrations/ with timestamp prefix:
<?php // src/Migrations/2024_004_create_posts_table.php return [ 'up' => " CREATE TABLE `posts` ( `post_id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `user_id` INT UNSIGNED NOT NULL, `title` VARCHAR(255) NOT NULL, `body` TEXT NOT NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ", 'down' => "DROP TABLE IF EXISTS `posts`;", ];
Request Lifecycle
public/index.php→ requiressrc/init.php- Config →
.envloaded via Dotenv - Error handler → exceptions logged, clean 500 page in production
- Session → secure init, idle timeout, periodic regeneration
- Security headers → CSP, HSTS, X-Frame-Options sent
- Request logging → method, path, timing captured
- Router →
#[Route]attributes scanned on controllers - CSRF → validated on POST/PUT/PATCH/DELETE
- Auth guard → session checked if
authRequired: true - RBAC/ABAC → roles and permissions verified
- Controller → business logic executed
- View/API → HTML rendered or JSON returned
Security
See SECURITY.md for the full security operations runbook.
Quick Reference
// Encrypt/decrypt sensitive data $enc = new Encryption(); $cipher = $enc->encrypt('sensitive-data'); $plain = $enc->decrypt($cipher); // Hash passwords $hash = PasswordHasher::hash($password); $ok = PasswordHasher::verify($password, $hash); // CSRF protection (in forms) <?= Csrf::inputField() ?> // CSRF for AJAX headers: { 'X-CSRF-Token': '<?= Csrf::getToken() ?>' } // Escape output <?= Validator::escape($userInput) ?>
Authentication
The UserController provides a complete authentication flow:
// Routes: #[Route('/login', methods: ['GET'])] // Display login form #[Route('/login', methods: ['POST'])] // Process login #[Route('/logout', methods: ['POST'])] // Logout #[Route('/dashboard', authRequired: true)] // Protected page
Login process:
- Rate limit check (5 attempts/minute)
- Cloudflare Turnstile verification
- Input validation
- Email lookup + Argon2id password verification
- Transparent password rehash if needed
- Session regeneration + CSRF refresh
- Session fingerprinting (IP + UA)
Authorization (RBAC/ABAC)
Route-Level Authorization
// Require authentication #[Route('/profile', authRequired: true)] // Require specific role #[Route('/admin', roles: ['admin'])] // Require specific permission #[Route('/posts/create', permissions: ['posts.write'])] // Combine #[Route('/users/delete', methods: ['DELETE'], authRequired: true, roles: ['admin'])]
Code-Level Authorization
use App\Services\Authorization; // Check role if (Authorization::hasRole('editor')) { ... } // Check permission if (Authorization::can('posts.delete')) { ... } // Check ownership (IDOR prevention) if (Authorization::canAccess($post->getUserId())) { ... } // Guard methods (throw 403) Authorization::requireRole('admin'); Authorization::requirePermission('users.write');
API Development
Response Format
use App\Services\ApiResponse; // Success (200) ApiResponse::success(['user' => $user]); // Created (201) ApiResponse::created(['id' => $newId]); // Error (422) ApiResponse::error(['Invalid email address.'], 422); // Paginated ApiResponse::paginated($items, $total, $page, $perPage); // No Content (204) ApiResponse::noContent();
All responses follow this structure:
{
"status": "success|error",
"data": {},
"errors": null,
"meta": { "pagination": { ... } }
}
Input Validation
$errors = Validator::validate($_POST, [ 'email' => ['required', 'email', 'max:255'], 'password' => ['required', 'min:8', 'confirmed'], 'role' => ['in:user,editor,admin'], 'website' => ['url'], 'slug' => ['slug'], ], [ 'email.required' => 'Email is mandatory.', ]); if (!empty($errors)) { ApiResponse::error($errors, 422); }
File Uploads
use App\Services\FileUpload; use App\Services\ImageProcessor; $upload = new FileUpload(); $result = $upload->store($_FILES['avatar']); // $result['name'] = 'a1b2c3d4...f0.jpg' // $result['path'] = '/storage/uploads/a1b2c3d4...f0.jpg' // Strip EXIF metadata ImageProcessor::sanitize($result['path']); // Generate thumbnail ImageProcessor::thumbnail($result['path'], 300, 300);
Caching
use App\Services\Cache; // Set/get Cache::set('user:42', $userData, 3600); $user = Cache::get('user:42'); // Remember pattern $result = Cache::remember('expensive:query', 600, function() { return $db->heavyQuery(); }); // Delete Cache::delete('user:42'); Cache::flush();
Background Jobs
use App\Services\JobQueue; // Enqueue JobQueue::dispatch('send_email', [ 'to' => 'user@example.com', 'subject' => 'Welcome!', 'body' => 'Your account is active.', ]); // Delayed job (run in 5 minutes) JobQueue::dispatch('send_reminder', $payload, delay: 300);
Run the worker:
php bin/worker.php # Run indefinitely php bin/worker.php --max=100 # Process 100 jobs then exit
Cloudflare Turnstile
1. Get Credentials
Register at Cloudflare Dashboard and obtain your Site Key + Secret Key.
2. Configure
TURNSTILE_SITE_KEY=0x4AAAAAAA... TURNSTILE_SECRET_KEY=0x4AAAAAAA...
3. Add Widget to Forms
<form method="POST" action="/login"> <?= Csrf::inputField() ?> <!-- form fields --> <?php include __DIR__ . '/../includes/turnstile.php'; ?> <button type="submit">Login</button> </form>
4. Verify Server-Side
$result = Turnstile::verify($_POST['cf-turnstile-response']); if ($result->failed()) { // Reject the request }
Docker Deployment
# Build and start all services docker-compose up -d # Services: # app — PHP 8.2 FPM (port 9000) # web — Nginx (port 8080) # db — MySQL 8.0 (port 3306) # redis — Redis 7 (port 6379) # mailpit — Mail catcher (SMTP 1025, UI 8025) # View logs docker-compose logs -f app # Run migrations inside container docker-compose exec app php bin/migrate.php up # Run tests docker-compose exec app composer test
Web Server Configuration
Nginx
See docker/nginx/default.conf for the production-ready configuration.
Key rules:
- Document root:
public/ - Deny access to:
.env,vendor/,src/,bin/,logs/,storage/ - Block PHP execution in
storage/ - Gzip compression enabled
- Static assets cached for 1 year
Apache
The public/.htaccess includes:
- URL rewriting (all routes →
index.php) - Security headers
- Gzip compression
- Browser caching rules
- HTTPS redirect (uncomment in production)
Critical: Ensure AllowOverride All is set in your Apache vhost.
Testing
# Run all tests composer test # Run specific suite vendor/bin/phpunit --testsuite Unit vendor/bin/phpunit --testsuite Integration # Static analysis (level 6) composer analyse # Security audit composer audit
Maintenance
Regular Tasks
| Task | Command | Frequency |
|---|---|---|
| Dependency audit | composer audit |
Every build |
| Update dependencies | composer update |
Monthly |
| Rotate APP_KEY | Manual (see SECURITY.md) | Bi-annually |
| Review security logs | tail logs/security-threats.log |
Weekly |
| Clear expired cache | Cache::flush() |
As needed |
| Prune old job records | SQL cleanup | Monthly |
Health Checks
# Application health curl http://localhost:8080/health # Readiness probe curl http://localhost:8080/ready
Component Reference
For complete method signatures, internal logic, and advanced usage, see docs/23-component-reference.md.
Key Components Overview
| Category | File | Description & Usage Summary |
|---|---|---|
| Controllers | FileController.php |
Serves uploaded files securely from /storage/uploads outside web root with MIME checking (finfo), path traversal protection (realpath), and inline/attachment dispositions. |
HealthController.php |
Operational probes (GET /health for database/Redis check, GET /ready for Kubernetes readiness probes). |
|
| Middleware | Cors.php |
Fine-grained Cross-Origin Resource Sharing handling allowed origins, headers, and preflight OPTIONS requests. |
RateLimiter.php |
Sliding window rate limiting using Redis sorted sets (with JSON file fallback) and automated threat logging. | |
RequestLogger.php |
Measures execution duration (ms) and memory usage (MB) for HTTP requests; logs on shutdown. |
|
SecurityHeaders.php |
Sends defense-in-depth headers (CSP, HSTS, X-Frame-Options, nosniff, Permissions-Policy, COOP/CORP). |
|
ThreatLogger.php |
Fail2Ban and WAF compatible security logger (logs/security-threats.log) with automatic PII masking. |
|
| Migrations | 2024_001_create_users_table.php |
Defines users database schema with Argon2id password comments and index structures. |
2024_002_create_roles_permissions_tables.php |
Creates RBAC schema (roles, permissions, role_permissions) and seeds default user roles. |
|
2024_003_create_jobs_table.php |
Schema for the background job queue table with status tracking and JSON payloads. | |
| Services | ApiResponse.php |
Standardized REST JSON responses (success, error, paginated, created, noContent). |
Authorization.php |
Role-Based (RBAC) and Attribute-Based (ABAC) access control supporting wildcards and ownership verification (owns). |
|
Cache.php |
Multi-tier cache (L1 Memory array → L2 Redis → L3 File fallback) with remember() pattern. |
|
CircuitBreaker.php |
Resiliency pattern for external API calls (CLOSED, OPEN, HALF_OPEN) preventing cascading failures. |
|
Config.php |
Singleton .env configuration loader enforcing required environment keys and typed getters. |
|
ConfigRouter.php |
Request utilities (_method HTTP spoofing, session IP/UA origin check, getClientIp, redirect). |
|
Csrf.php |
Cryptographic CSRF token generation, per-form token scoping, and constant-time validation (hash_equals). |
|
Database.php |
Singleton PDO manager configured with strict error mode (ERRMODE_EXCEPTION) and health checking (isHealthy). |
|
Encryption.php |
Symmetric AEAD encryption using AES-256-GCM with random 12-byte IVs and authentication tags. | |
FileUpload.php |
Secure upload handler with magic-byte MIME verification, extension whitelisting, UUID renaming, and memory bomb checks. | |
Hydration.php |
Trait for auto-hydrating entity properties (snake_case DB columns → setCamelCase setters) and serialization. |
|
ImageProcessor.php |
Privacy EXIF metadata stripper, aspect-ratio preserving thumbnail generator, and SVG XSS sanitizer. | |
JobQueue.php |
SQL task queue supporting FOR UPDATE SKIP LOCKED, atomic locks, and exponential backoff retries. |
|
Logger.php |
Monolog structured JSON logging wrapper with app, security, and db channels. |
|
Mail.php |
PHPMailer SMTP email dispatch service supporting HTML templates and attachments. | |
Migrator.php |
Migration runner handling up(), down(), and status() tracked via database table _migrations. |
|
PasswordHasher.php |
Hashing service using Argon2id (64MB memory cost, 4 time cost) with automatic rehash verification. | |
ResponseCompressor.php |
Output buffer wrapper generating ETag headers for 304 responses and transparent gzip compression. |
|
Route.php |
PHP 8 attribute (#[Route('/path', methods: [...])]) for declarative endpoint configuration. |
|
router.php |
Reflection route scanner and middleware pipeline dispatcher (CSRF → Auth → RBAC → ABAC → Controller). | |
Turnstile.php |
Cloudflare Turnstile CAPTCHA server-side token verification client. | |
Validator.php |
Input validation engine with 18+ rules (required, email, confirmed, slug, etc.) and HTML string escaping. |
|
| CLI Scripts | bin/migrate.php |
CLI entry point for running database migrations (`php bin/migrate.php up |
bin/worker.php |
CLI entry point for background job queue worker execution (php bin/worker.php [--max=N]). |
Directory Structure
project-root/
├── bin/ # CLI scripts
│ ├── migrate.php # Database migrations
│ └── worker.php # Job queue worker
├── docker/ # Docker configuration
│ └── nginx/default.conf # Nginx vhost config
├── logs/ # Application logs (gitignored)
├── public/ # Web root (document root)
│ ├── .htaccess # Apache rewrite rules
│ ├── index.php # Entry point
│ ├── assets/css/ # Stylesheets
│ ├── assets/js/ # JavaScript
│ ├── robots.txt
│ └── sitemap.xml
├── src/ # Application source (App\ namespace)
│ ├── Abstracts/ # Abstract base classes
│ │ ├── AbstractController # View rendering, redirects
│ │ └── AbstractRepository # CRUD, transactions, streaming
│ ├── Controllers/ # Request handlers
│ │ ├── FileController # Secure file serving
│ │ ├── HealthController # Health/readiness endpoints
│ │ ├── HomeController # Public pages, error pages
│ │ └── UserController # Authentication flows
│ ├── Entities/ # Data models
│ │ └── User
│ ├── Middleware/ # Request/response middleware
│ │ ├── Cors # Cross-origin resource sharing
│ │ ├── RateLimiter # Sliding-window rate limiting
│ │ ├── RequestLogger # Request timing/logging
│ │ ├── SecurityHeaders # CSP, HSTS, etc.
│ │ └── ThreatLogger # Fail2Ban-compatible logging
│ ├── Migrations/ # Database migration files
│ ├── Repositories/ # Data access layer
│ │ └── UserRepository
│ ├── Services/ # Core services
│ │ ├── ApiResponse # Normalized JSON responses
│ │ ├── Authorization # RBAC/ABAC engine
│ │ ├── Cache # Multi-tier caching
│ │ ├── CircuitBreaker # External API resilience
│ │ ├── Config # Environment configuration
│ │ ├── ConfigRouter # Request helpers
│ │ ├── Csrf # CSRF token management
│ │ ├── Database # Singleton PDO connection
│ │ ├── Encryption # AES-256-GCM encryption
│ │ ├── FileUpload # Secure file handling
│ │ ├── Hydration # Entity auto-hydration trait
│ │ ├── ImageProcessor # Image sanitization/thumbnails
│ │ ├── JobQueue # Background job processing
│ │ ├── Logger # Structured logging (Monolog)
│ │ ├── Mail # Email service (PHPMailer)
│ │ ├── Migrator # Migration engine
│ │ ├── PasswordHasher # Argon2id hashing
│ │ ├── ResponseCompressor # Gzip + ETag
│ │ ├── Route # Route attribute definition
│ │ ├── Turnstile # Cloudflare bot protection
│ │ └── Validator # Input validation engine
│ ├── Views/ # PHP templates
│ └── init.php # Application bootstrap
├── storage/ # Uploads & cache (gitignored)
│ ├── cache/
│ └── uploads/
├── tests/ # PHPUnit test suite
│ ├── Unit/
│ └── bootstrap.php
├── .env.example # Environment template
├── .gitignore
├── composer.json
├── docker-compose.yml
├── Dockerfile
├── opcache.ini
├── phpstan.neon
├── phpunit.xml
├── readme.md # This file
└── SECURITY.md # Security operations runbook
License
MIT License. See LICENSE for details.