This document defines the coding standards, patterns, and conventions used across these PHP/JavaScript web application projects. Use this document to ensure consistency when developing or refactoring.
Applies to: PHP/JavaScript web applications using this framework
- Project Structure
- PHP Standards
- JavaScript Standards
- CSS and Styling
- Configuration Patterns
- Security Standards
- API Design
- File and Content Conventions
- Documentation Standards
- Template Patterns
project-root/
├── src/ # PHP business logic classes
├── templates/ # PHP view templates
├── public/ # Web root (document root for server)
│ ├── index.php # Main entry point
│ ├── api.php # API entry point (if applicable)
│ ├── router.php # Development server router
│ ├── .htaccess # Apache rewrite rules
│ └── assets/ # Static assets
│ ├── css/ # Stylesheets (or *.css in assets/)
│ ├── js/ # JavaScript files (or *.js in assets/)
│ └── images/ # Image assets
├── content/ # User content (if applicable, gitignored)
├── vendor/ # Composer dependencies (gitignored)
├── composer.json # PHP dependencies
├── .env.example # Configuration template
├── .gitignore # Git ignore rules
├── README.md # Project documentation
└── STANDARDS.md # This file
- Separation of concerns: Business logic in
src/, presentation intemplates/, static files inpublic/assets/ - Web root isolation: Only
public/is web-accessible; sensitive files stay outside - One class per file: Each PHP class gets its own file (exception: helper functions)
- Flat structure preferred: Avoid deep nesting; keep directory depth minimal
Every PHP file MUST start with strict types declaration:
<?php
declare(strict_types=1);
namespace App;| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase | FileOperations, AdminAuth |
| Methods | camelCase | getContent(), validatePath() |
| Properties | camelCase | $contentDir, $isAuthenticated |
| Constants | UPPER_SNAKE_CASE | MAX_FILE_SIZE, DEFAULT_TIMEOUT |
| Files (classes) | PascalCase.php | Config.php, Content.php |
| Files (helpers) | lowercase.php | helpers.php |
Always use full type hints on parameters and return types:
public function getDocument(string $path): ?array
{
// Implementation
}
public function saveContent(string $path, string $content, bool $createBackup = true): bool
{
// Implementation
}class Config
{
private static ?Config $instance = null;
private array $config = [];
private function __construct()
{
$this->loadDefaults();
$this->loadFromEnv();
}
public static function getInstance(): Config
{
if (self::$instance === null) {
self::$instance = new Config();
}
return self::$instance;
}
public static function get(string $key, mixed $default = null): mixed
{
return self::getInstance()->config[$key] ?? $default;
}
}// In entry point (index.php or api.php)
$config = Config::getInstance();
$todoList = new TodoList($dataPath);
$api = new Api($todoList);Common utilities go in helpers.php without a class wrapper:
<?php
declare(strict_types=1);
// No namespace for global helper functions
/**
* Generate UUID v4
*/
function uuid(): string
{
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
/**
* Get current ISO 8601 timestamp
*/
function now(): string
{
return (new DateTime())->format(DateTime::ATOM);
}
/**
* Sanitize user input
*/
function sanitize(string $input): string
{
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
/**
* Get JSON input from request body
*/
function getJsonInput(): array
{
$input = file_get_contents('php://input');
return json_decode($input, true) ?? [];
}
/**
* Send JSON response
*/
function jsonResponse(array $data, int $code = 200): void
{
http_response_code($code);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
/**
* Send JSON error response
*/
function jsonError(string $message, int $code = 400): void
{
jsonResponse(['success' => false, 'error' => $message], $code);
}Organize functionality in a single object:
const AppName = {
// State
state: {
data: null,
currentItem: null
},
// Initialization
init() {
this.bindEvents();
this.loadData();
},
// API wrapper
async api(endpoint, options = {}) {
const defaults = {
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': this.csrfToken
}
};
const response = await fetch(endpoint, { ...defaults, ...options });
return response.json();
},
// Event handling
bindEvents() {
document.addEventListener('keydown', (e) => this.handleKeyboard(e));
},
handleKeyboard(e) {
if (e.key === 'Escape') this.closeModal();
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
this.save();
}
},
// Modal management
openModal(modalId) {
document.getElementById(modalId)?.classList.add('show');
},
closeModal() {
document.querySelectorAll('.modal.show').forEach(m => m.classList.remove('show'));
}
};
document.addEventListener('DOMContentLoaded', () => AppName.init());| Element | Convention | Example |
|---|---|---|
| Objects | PascalCase | TodoApp, AppController |
| Functions/Methods | camelCase | fetchData(), handleClick() |
| Variables | camelCase | currentItem, isLoading |
| Constants | UPPER_SNAKE_CASE | API_ENDPOINT, MAX_RETRIES |
| DOM IDs | kebab-case | item-modal, add-button |
| CSS Classes | kebab-case | modal-overlay, is-active |
:root {
/* Primary brand colors */
--primary-color: #0066cc;
--primary-hover: #0052a3;
/* Background colors */
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--bg-tertiary: #eeeeee;
/* Text colors */
--text-primary: #1a1a1a;
--text-secondary: #666666;
--text-muted: #999999;
/* Border colors */
--border-color: #e0e0e0;
/* Semantic colors */
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
}@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #404040;
--text-primary: #e5e5e5;
--text-secondary: #b0b0b0;
--text-muted: #808080;
--border-color: #404040;
}
}# Site Settings
SITE_NAME="App Name"
SITE_URL=""
# Security
ADMIN_PASSWORD=""
# Features
FEATURE_DARK_MODE=trueclass Config
{
private static ?Config $instance = null;
private array $config = [];
private function __construct()
{
$this->loadDefaults();
$this->loadFromEnv();
}
private function loadDefaults(): void
{
$this->config = [
'site_name' => 'App Name',
'feature_dark_mode' => true,
];
}
private function loadFromEnv(): void
{
$envMap = [
'SITE_NAME' => 'site_name',
'FEATURE_DARK_MODE' => 'feature_dark_mode',
];
foreach ($envMap as $envKey => $configKey) {
$value = $_ENV[$envKey] ?? null;
if ($value !== null) {
if ($value === 'true') $value = true;
if ($value === 'false') $value = false;
$this->config[$configKey] = $value;
}
}
}
public static function get(string $key, mixed $default = null): mixed
{
return self::getInstance()->config[$key] ?? $default;
}
public static function getInstance(): Config
{
if (self::$instance === null) {
self::$instance = new Config();
}
return self::$instance;
}
}class Auth
{
public function generateCsrfToken(): string
{
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public function validateCsrfToken(string $token): bool
{
if (empty($_SESSION['csrf_token'])) return false;
return hash_equals($_SESSION['csrf_token'], $token);
}
}function validateRequired(array $data, array $fields): ?string
{
foreach ($fields as $field) {
if (empty($data[$field])) {
return "Missing required field: {$field}";
}
}
return null;
}// In templates
<?= htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8') ?>| Method | Endpoint | Description |
|---|---|---|
| GET | /api/items |
List all items |
| GET | /api/items/{id} |
Get single item |
| POST | /api/items |
Create item |
| PUT | /api/items/{id} |
Update item |
| DELETE | /api/items/{id} |
Delete item |
{
"success": true,
"data": { ... }
}
{
"success": false,
"error": "Error message"
}| Code | Usage |
|---|---|
| 200 | Successful GET, PUT, DELETE |
| 201 | Successful POST (created) |
| 400 | Bad request (validation error) |
| 401 | Unauthorized |
| 404 | Resource not found |
| 500 | Internal server error |
- Keep files small and focused
- One class per file for PHP classes
- Group related functionality together
- Use clear, descriptive file names
- Use comments sparingly - code should be self-documenting
- Add comments for complex logic or non-obvious decisions
- Use PHPDoc blocks for public methods
# Project Name
Brief description.
## Features
- Feature list
## Requirements
- PHP 8.1+
- Composer
## Installation
1. Clone repository
2. Run `composer install`
3. Copy `.env.example` to `.env`
4. Configure settings
## Usage
Basic usage instructions.
## License
MIT License<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($pageTitle) ?></title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<?= $content ?>
<script src="/assets/app.js"></script>
</body>
</html>- Directory structure follows standard layout
- All PHP files have
declare(strict_types=1) - All classes use
namespace App; - Type hints on all method parameters and returns
- CSS uses custom properties for theming
- Dark mode support via media query
- Configuration via
.envwith.env.exampleprovided - CSRF protection on state-changing operations
- Output escaping in all templates
- RESTful API with consistent response format
- Comprehensive README.md