EasyAPP follows the Model-View-Controller (MVC) architectural pattern with additional layers for enhanced flexibility and maintainability.
- Overview
- MVC Pattern
- Application Layers
- Component Communication
- Registry Pattern
- Event System
- Design Patterns
- Best Practices
EasyAPP architecture is designed to be:
- Lightweight - Minimal overhead and fast execution
- Flexible - Easy to extend and customize
- Maintainable - Clear separation of concerns
- Testable - Components are loosely coupled
┌─────────────────────────────────────────────────────────────┐
│ HTTP Request │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ index.php (Entry Point) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Framework │ │
│ │ • Load Configuration │ │
│ │ • Initialize Registry │ │
│ │ • Setup Error Handling │ │
│ │ • Route Request │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Router │
│ • Parse URL │
│ • Match Route │
│ • Load Controller │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Controller │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Request │ │ Response │ │ Load │ │
│ │ │ │ │ │ │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Business Logic │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────┬──────────────────────┬──────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ Services │ │ Models │
│ • EmailService │ │ • User.php │
│ • PaymentService │ │ • Product.php │
│ • UserService │ │ • Order.php │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
│ ▼
│ ┌──────────────────────┐
│ │ Database │
│ │ ┌───────────────┐ │
│ │ │ ORM │ │
│ │ └───────────────┘ │
│ │ ┌───────────────┐ │
│ │ │ Query API │ │
│ │ └───────────────┘ │
│ └──────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Libraries │
│ • Upload • Pagination • Pdf │
│ • Image • Csv • Cache │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ View │
│ • Load Template │
│ • Render HTML │
│ • Output Response │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ HTTP Response │
└──────────────────────────────────────────────────────────────┘
EasyAPP implements the classic MVC pattern with clear responsibilities for each layer.
Responsibility: Data access, business logic, validation
Location: app/model/
class ModelUser extends Model {
// Data access
public function getById($id) {
return $this->db->query("SELECT * FROM users WHERE id = ?", [$id])->row;
}
// Business logic
public function isEmailAvailable($email) {
$result = $this->db->query("SELECT id FROM users WHERE email = ?", [$email]);
return empty($result->row);
}
// Validation
public function validate($data) {
$errors = [];
if (empty($data['email'])) {
$errors[] = 'Email is required';
}
return $errors;
}
}Responsibility: Presentation, HTML rendering
Location: app/view/
<!DOCTYPE html>
<html>
<head>
<title><?php echo $title; ?></title>
</head>
<body>
<h1><?php echo $heading; ?></h1>
<ul>
<?php foreach ($users as $user): ?>
<li><?php echo htmlspecialchars($user['name']); ?></li>
<?php endforeach; ?>
</ul>
</body>
</html>Responsibility: Request handling, coordination, response generation
Location: app/controller/
class ControllerUser extends Controller {
public function index() {
// Load resources
$userModel = $this->load->model('user');
// Get data
$data['users'] = $userModel->getAll();
// Load view
$output = $this->load->view('user/list.html', $data);
// Send response
$this->response->setOutput($output);
}
}EasyAPP extends the basic MVC pattern with additional layers for better organization.
Purpose: Display data to users
Components:
- HTML templates
- CSS stylesheets
- JavaScript
Rules:
- Minimal logic (loops, conditionals only)
- Always escape output
- No database queries
Purpose: Handle HTTP requests and coordinate responses
Components:
- Request handling
- Input validation
- Response generation
- Resource loading
Rules:
- Keep thin (minimal business logic)
- Single responsibility
- No direct database queries
Purpose: Business logic and external integrations
Components:
- Complex business operations
- API integrations
- Email sending
- Payment processing
Example:
class ServiceOrderService extends Service {
public function processOrder($orderData) {
// Validate inventory
$this->validateInventory($orderData);
// Process payment
$payment = $this->processPayment($orderData);
// Create order
$orderId = $this->createOrder($orderData, $payment);
// Send confirmation
$this->sendConfirmation($orderId);
return $orderId;
}
}Purpose: Database operations and data logic
Components:
- Database queries
- ORM models
- Data validation
- Relationships
Traditional Model:
class ModelProduct extends Model {
public function getAll() {
return $this->db->query("SELECT * FROM products")->rows;
}
}ORM Model:
class Product extends System\Framework\Orm {
protected $table = 'products';
protected $fillable = ['name', 'price', 'stock'];
public function category() {
return $this->belongsTo(Category::class);
}
}Purpose: Reusable utility components
Components:
- File upload
- Image processing
- PDF generation
- Pagination
Example:
class LibraryUpload extends Library {
public function upload($file, $path) {
// Upload logic
}
}Request → Router → Controller → Service → Model → Database
↓
Libraries
↓
Response ← View ← Controller ←──────────────────────┘
Controllers can access:
- Models (data access)
- Services (business logic)
- Libraries (utilities)
- Views (templates)
Services can access:
- Models
- Libraries
- Other Services
Models can access:
- Database
- Libraries
- Other Models
Libraries should be independent:
- No access to Models or Services
- Utility functions only
// User registration flow
// 1. Controller receives request
class ControllerUser extends Controller {
public function register() {
$data = $this->request->post;
// 2. Delegate to service
$this->load->service('UserService');
$userId = $this->UserService->register($data);
// 3. Return response
$this->response->redirect('/user/profile');
}
}
// 2. Service handles business logic
class ServiceUserService extends Service {
public function register($data) {
// Load model and get instance
$userModel = $this->load->model('user');
// Validate
if (!$this->validate($data)) {
throw new Exception('Invalid data');
}
// Hash password
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
// Create user
$userId = $userModel->create($data);
// Send email
$this->load->service('EmailService');
$this->EmailService->sendWelcome($data);
return $userId;
}
}
// 3. Model handles data
class ModelUser extends Model {
public function create($data) {
$this->db->query(
"INSERT INTO users (email, password, name) VALUES (?, ?, ?)",
[$data['email'], $data['password'], $data['name']]
);
return $this->db->getLastId();
}
}EasyAPP uses the Registry pattern for dependency injection.
class Framework {
private $registry;
public function __construct() {
$this->registry = new System\Framework\Registry();
// Register core components
$this->registry->set('db', new System\Framework\Db());
$this->registry->set('request', new System\Framework\Request());
$this->registry->set('response', new System\Framework\Response());
$this->registry->set('cache', new System\Framework\Cache());
$this->registry->set('logger', new System\Framework\Logger());
}
}All base classes (Controller, Model, Service, Library) have access to the registry:
class Controller {
protected $registry;
public function __construct($registry) {
$this->registry = $registry;
}
// Magic method for easy access
public function __get($name) {
return $this->registry->get($name);
}
}Usage:
class ControllerUser extends Controller {
public function index() {
// Access registry components
$users = $this->db->query("SELECT * FROM users")->rows;
$postData = $this->request->post;
$this->cache->set('users', $users);
$this->logger->info('Users loaded');
$this->response->setOutput($output);
}
}EasyAPP includes an event system for decoupled communication.
Component A Event Bus Component B
│ │ │
│ trigger('user.created') │ │
├───────────────────────────>│ │
│ │ listen('user.created') │
│ │<───────────────────────────┤
│ │ │
│ │ callback($data) │
│ ├───────────────────────────>│
│ │ │
// In controller or model
$this->event->register('user.created', function($data) {
// Log event
$this->logger->info('User created', $data);
// Send welcome email
$this->load->service('EmailService');
$this->EmailService->sendWelcome($data);
});public function create($userData) {
// Create user
$userId = $this->db->query("INSERT INTO users ...")->getLastId();
// Trigger event
$this->event->trigger('user.created', [
'user_id' => $userId,
'email' => $userData['email']
]);
return $userId;
}EasyAPP implements several design patterns:
Separates presentation, business logic, and data access.
Central storage for shared objects and services.
$this->registry->set('cache', new Cache());
$cache = $this->registry->get('cache');Dynamic creation of components based on configuration.
// Database factory
$db = DbFactory::create([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'myapp'
]);ORM models represent database records as objects.
$user = User::find(1);
$user->name = 'John Doe';
$user->save();Method interception for cross-cutting concerns.
class Proxy {
public function __call($method, $args) {
// Before method
$this->beforeMethod($method);
// Execute method
$result = $this->object->$method(...$args);
// After method
$this->afterMethod($method, $result);
return $result;
}
}Decoupled communication between components.
$this->event->register('order.created', $callback);
$this->event->trigger('order.created', $data);Interchangeable algorithms (cache drivers, email drivers).
// File cache strategy
$cache = new FileCache();
// Redis cache strategy
$cache = new RedisCache();
// Same interface
$cache->get('key');
$cache->set('key', 'value');Simplified interface to complex subsystems.
// Complex subsystem
$db->query(...);
$db->prepare(...);
$db->execute(...);
// Facade
$this->load->model('user')->getAll();// Good: Controller delegates to service
class ControllerOrder extends Controller {
public function create() {
$this->load->service('OrderService');
$orderId = $this->OrderService->create($this->request->post);
$this->response->redirect('/order/view?id=' . $orderId);
}
}
// Bad: Controller contains business logic
class ControllerOrder extends Controller {
public function create() {
$data = $this->request->post;
// Validate inventory (should be in service)
$product = $this->load->model('product')->getById($data['product_id']);
if ($product['stock'] < $data['quantity']) {
throw new Exception('Out of stock');
}
// Process payment (should be in service)
$payment = $this->processPayment($data);
// Create order (should be in service)
$orderId = $this->load->model('order')->create($data);
// Send email (should be in service)
$this->sendConfirmation($orderId);
}
}// Good: Dependencies injected
class ServiceOrderService extends Service {
private $paymentService;
private $emailService;
public function __construct($registry) {
parent::__construct($registry);
$this->load->service('PaymentService');
$this->load->service('EmailService');
$this->paymentService = $this->PaymentService;
$this->emailService = $this->EmailService;
}
}
// Bad: Hard-coded dependencies
class ServiceOrderService extends Service {
public function processPayment() {
$gateway = new StripeGateway(); // Hard-coded
$gateway->charge(...);
}
}// Good: Thin controller
public function create() {
$this->load->service('UserService');
$userId = $this->UserService->register($this->request->post);
$this->response->redirect('/user/profile');
}
// Bad: Fat controller
public function create() {
// 50 lines of validation
// 30 lines of business logic
// 20 lines of database operations
// 15 lines of email sending
}// Good: Event-driven
public function create($userData) {
$userId = $this->createUser($userData);
$this->event->trigger('user.created', ['user_id' => $userId]);
return $userId;
}
// Listeners are registered separately
$this->event->register('user.created', function($data) {
$this->sendWelcomeEmail($data['user_id']);
});
$this->event->register('user.created', function($data) {
$this->logUserCreation($data['user_id']);
});
$this->event->register('user.created', function($data) {
$this->trackAnalytics($data['user_id']);
});Good:
app/
├── User/
│ ├── UserController.php
│ ├── UserService.php
│ ├── User.php (model)
│ └── views/
Bad:
app/
├── controllers/
│ └── UserController.php
├── services/
│ └── UserService.php
└── models/
└── User.php
- Request Lifecycle - How requests are processed
- Dependency Injection - Registry and DI
- Controllers - Controller layer
- Models - Model layer
- Services - Service layer
Previous: Directory Structure
Next: Request Lifecycle