Skip to content

Marshud/frozen-in-time

Repository files navigation

Frozen In Time - API Structure

A PHP sample API Structure with basic fundamentals an API needs to get up and running. This codebase demonstrates modern PHP development practices and serves as a foundation for scalable web applications incuding SOLID principles, dependency injection, and enterprise architecture patterns.

📋 Table of Contents


🏗️ Architecture Overview

This API follows Clean Architecture principles with a layered approach:

┌─────────────────────────────────────────────────────────────┐
│                    HTTP Layer                               │
│  Controllers • Middleware • Routes • Request/Response      │
├─────────────────────────────────────────────────────────────┤
│                  Business Layer                             │
│     Services • Domain Logic • Validation • Events         │
├─────────────────────────────────────────────────────────────┤
│                 Persistence Layer                           │
│        Repositories • Database • Data Access               │
├─────────────────────────────────────────────────────────────┤
│                Infrastructure Layer                         │
│    Configuration • Logging • External Services             │
└─────────────────────────────────────────────────────────────┘

🎯 Key Features

  • SOLID Principles - Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion
  • Dependency Injection - Custom DI container with automatic resolution
  • Interface-Driven Design - Testable and extensible architecture
  • Service Layer - Clean separation of business logic
  • Repository Pattern - Abstracted data access
  • Middleware Support - Rate limiting, CORS, JWT authentication
  • Comprehensive Logging - Structured logging with context
  • Environment Configuration - 12-factor app compliance
  • Security Best Practices - Authentication, authorization, input validation

⚡ Quick Start

Prerequisites

  • PHP 8.2+
  • MySQL 8.0+
  • Composer
  • Docker (optional but recommended)

🐳 Docker Setup (Recommended)

  1. Clone the repository:
git clone <repository-url>
cd frozen-in-time
  1. Start with Docker Compose:
docker-compose up -d
  1. Verify installation:
curl http://api.frozen_in_time.local/v1/health

🔧 Manual Setup

  1. Install dependencies:
composer install
  1. Configure environment:
cp .env.example .env
# Edit .env with your database credentials
  1. Set up database:
# Create database and tables using provided SQL files
mysql -u root -p < database/init-db.sql
mysql -u root -p EmployeeManagement < database/seeds/sample_data.sql
  1. Configure web server:
    • Document root: public/
    • Rewrite all requests to index.php

🔧 Configuration

Environment Variables

# Database Configuration
DB_HOST=mysql
DB_NAME=EmployeeManagement
DB_USERNAME=root
DB_PASSWORD=FrozenInTime2024!
DB_PORT=3306

# Application Configuration
APP_ENV=development
APP_TIMEZONE=UTC
API_VERSION=v1
LOG_LEVEL=INFO

# Security Configuration
JWT_SECRET=your_jwt_secret_here
API_KEY=your_api_key_here

Department Authentication

The API uses JWT-based authentication with department-specific API keys for token generation:

Department API Key Permissions
Engineering eng_secure_key_2025 create, read, update, delete
Human Resources hr_secure_key_2025 create, read, update, delete
Finance fin_secure_key_2025 read, update
Marketing mkt_secure_key_2025 read
Sales sales_secure_key_2025 read

Authentication Flow:

  1. Use department + API key to get JWT token via /v1/auth/token
  2. Include JWT token in Authorization header for all subsequent requests
  3. Token expires after 1 hour (3600 seconds) - use /v1/auth/refresh to renew

📚 API Documentation

Base URL (Docker)

http://api.frozen_in_time.local/v1

Authentication

The API uses JWT (JSON Web Token) authentication. First obtain a token, then use it in the Authorization header:

Authorization: Bearer <your_jwt_token>

Response Format

All responses follow this structure:

{
    "success": true|false,
    "message": "Readable message",
    "data": {...},
    "timestamp": "2025-07-18 23:29:09"
}

🔐 Authentication

POST /v1/auth/token

Generate JWT token for authentication.

No authentication required for this endpoint.

Request Body:

{
    "department": "Engineering",
    "api_key": "eng_secure_key_2025"
}

Response:

{
    "success": true,
    "message": "Token generated successfully",
    "data": {
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "expires_in": 3600,
        "token_type": "Bearer",
        "department": "Engineering",
        "permissions": ["create", "read", "update", "delete"]
    }
}

Example:

curl -X POST "http://localhost/v1/auth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "department": "Engineering",
    "api_key": "eng_secure_key_2025"
  }'

POST /v1/auth/refresh

Refresh an existing JWT token.

Headers:

Authorization: Bearer <your_current_token>

Example:

curl -X POST "http://localhost/v1/auth/refresh" \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."

👥 Employee Endpoints

GET /v1/employees

Retrieve all employees with pagination.

Headers:

Authorization: Bearer <your_jwt_token>

Query Parameters:

  • page (int, optional): Page number (default: 1)
  • limit (int, optional): Items per page (default: 50, max: 100)

Response:

{
    "success": true,
    "message": "Employees retrieved successfully",
    "data": {
        "data": [
            {
                "employeeId": "EMP001",
                "firstName": "Richard",
                "lastName": "Hendricks",
                "email": "richard.hendricks@piedpiper.com",
                "phone": "+1-650-555-0101",
                "department": "Engineering",
                "position": "CEO & Founder",
                "salary": "150000.00",
                "dateOfHire": "2023-01-15",
                "status": "active",
                "address": "5230 Newell Rd, Palo Alto, CA 94301",
                "dateOfBirth": "1985-03-15",
                "createdAt": "2025-07-18 18:53:30",
                "updatedAt": "2025-07-18 18:53:30"
            }
        ],
        "pagination": {
            "page": 1,
            "limit": 50,
            "total": 13,
            "pages": 1
        }
    }
}

GET /v1/employees/{employeeId}

Retrieve a specific employee by ID.

Example:

curl -X GET "http://localhost/v1/employees/EMP001" \
  -H "Authorization: Bearer <your_jwt_token>"

Response:

{
    "success": true,
    "message": "Employee retrieved successfully",
    "data": {
        "employeeId": "EMP001",
        "firstName": "Richard",
        "lastName": "Hendricks",
        "email": "richard.hendricks@piedpiper.com",
        "department": "Engineering",
        "position": "CEO & Founder",
        "salary": "150000.00",
        "status": "active"
    }
}

POST /v1/employees

Create a new employee.

Required Permission: create

Headers:

Content-Type: application/json
Authorization: Bearer <your_jwt_token>

Request Body:

{
    "employeeId": "EMP014",
    "firstName": "Steve",
    "lastName": "Wozniak",
    "email": "steve.wozniak@apple.com",
    "phone": "+1-555-0123",
    "department": "Engineering",
    "position": "Software Engineer",
    "salary": 95000,
    "dateOfHire": "2025-07-18",
    "address": "123 Tech Street, San Francisco, CA",
    "dateOfBirth": "1990-05-15"
}

Validation Rules:

  • employeeId: Required, unique, max 50 characters
  • firstName: Required, max 100 characters
  • lastName: Required, max 100 characters
  • email: Required, valid email format, max 150 characters
  • phone: Optional, valid phone format, max 20 characters
  • department: Required, max 100 characters
  • position: Required, max 100 characters
  • salary: Required, numeric, greater than 0
  • dateOfHire: Required, YYYY-MM-DD format, not in future
  • status: Optional, one of: active, inactive, terminated
  • address: Optional, max 500 characters
  • dateOfBirth: Optional, YYYY-MM-DD format, not in future

Example:

curl -X POST "http://localhost/v1/employees" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your_jwt_token>" \
  -d '{
    "employeeId": "EMP014",
    "firstName": "Steve",
    "lastName": "Wozniak",
    "email": "steve.wozniak@apple.com",
    "department": "Engineering",
    "position": "Software Engineer",
    "salary": 95000,
    "dateOfHire": "2025-07-18"
  }'

PUT /v1/employees/{employeeId}

Update an existing employee.

Required Permission: update

Headers:

Content-Type: application/json
Authorization: Bearer <your_jwt_token>

Request Body: (partial update supported)

{
    "salary": 105000,
    "position": "Senior Software Engineer"
}

Example:

curl -X PUT "http://localhost/v1/employees/EMP014" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your_jwt_token>" \
  -d '{
    "salary": 105000,
    "position": "Senior Software Engineer"
  }'

DELETE /v1/employees/{employeeId}

Soft delete an employee (sets status to 'deleted').

Required Permission: delete

Example:

curl -X DELETE "http://localhost/v1/employees/EMP014" \
  -H "Authorization: Bearer <your_jwt_token>"

Response:

{
    "success": true,
    "message": "Employee deleted successfully",
    "data": []
}

GET /v1/employees/search

Search employees with multiple criteria.

Query Parameters:

  • firstName: Partial match on first name
  • lastName: Partial match on last name
  • email: Partial match on email
  • department: Partial match on department
  • position: Partial match on position
  • status: Exact match on status

Example:

curl -X GET "http://localhost/v1/employees/search?department=Engineering&status=active" \
  -H "Authorization: Bearer <your_jwt_token>"

🏥 Health Check

GET /v1/health

Check API health status.

No authentication required.

Response:

{
    "success": true,
    "message": "Service is healthy",
    "data": {
        "status": "healthy",
        "timestamp": "2025-07-18 23:28:35",
        "version": "1.0.0"
    }
}

❌ Error Responses

400 Bad Request

{
    "success": false,
    "message": "Invalid JSON - check your request format",
    "timestamp": "2025-07-18 23:29:21"
}

401 Unauthorized

{
    "success": false,
    "message": "Department and API key are required",
    "timestamp": "2025-07-18 23:29:21"
}

403 Forbidden

{
    "success": false,
    "message": "Insufficient permissions",
    "timestamp": "2025-07-18 23:29:21"
}

404 Not Found

{
    "success": false,
    "message": "Employee not found",
    "timestamp": "2025-07-18 23:29:21"
}

422 Validation Error

{
    "success": false,
    "message": "Validation failed",
    "details": {
        "validation_errors": [
            "Field 'email' is required",
            "Salary must be greater than 0"
        ]
    },
    "timestamp": "2025-07-18 23:29:21"
}

429 Rate Limit Exceeded

{
    "success": false,
    "message": "Rate limit exceeded. Too many requests.",
    "details": {
        "reset_time": 1642534800,
        "retry_after": 3600
    },
    "timestamp": "2025-07-18 23:29:21"
}

Headers:

X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1642534800
Retry-After: 3600

🏛️ Architecture Deep Dive

🧩 Directory Structure

src/
├── bootstrap/          # Application initialization
│   └── app.php        # DI container configuration
├── controllers/        # HTTP request handlers
│   └── EmployeeController.php
├── core/              # Framework components
│   └── Container.php  # Dependency injection container
├── interfaces/        # Contracts and abstractions
│   ├── EmployeeRepositoryInterface.php
│   ├── EmployeeServiceInterface.php
│   ├── AuthServiceInterface.php
│   ├── ValidatorInterface.php
│   ├── LoggerInterface.php
│   ├── ConfigInterface.php
│   ├── ResponseInterface.php
│   └── RateLimiterInterface.php
├── middleware/        # HTTP middleware
│   └── RateLimitMiddleware.php
├── repositories/      # Data access layer
│   └── EmployeeRepository.php
├── routes/           # URL routing
│   └── api.php
└── services/         # Business logic layer
    ├── EmployeeService.php
    ├── AuthService.php
    ├── ValidatorService.php
    ├── LoggerService.php
    ├── ConfigService.php
    ├── ResponseService.php
    └── RateLimiterService.php

🔄 Request Flow

1. HTTP Request → public/index.php
2. Bootstrap → src/bootstrap/app.php
3. Container → Dependency resolution
4. Router → src/routes/api.php
5. Middleware → Rate limiting, CORS
6. Controller → HTTP handling
7. Service → Business logic
8. Repository → Database operations
9. Response → JSON output

🏗️ Dependency Injection Container

The custom DI container provides automatic dependency resolution:

Container Bindings

// Interface to implementation mapping
$container->singleton(EmployeeServiceInterface::class, EmployeeService::class);
$container->singleton(LoggerInterface::class, LoggerService::class);

// Factory functions for complex objects
$container->singleton(PDO::class, function ($container) {
    $config = $container->resolve(ConfigInterface::class);
    return new PDO($dsn, $username, $password, $options);
});

Automatic Resolution

// Container automatically resolves all dependencies
$controller = $container->resolve(EmployeeController::class);

// Equivalent to:
$config = new ConfigService();
$logger = new LoggerService($config);
$pdo = new PDO(/* config values */);
$repository = new EmployeeRepository($pdo, $logger);
$validator = new ValidatorService();
$employeeService = new EmployeeService($repository, $validator, $logger);
$responseService = new ResponseService();
$authService = new AuthService($config, $logger);
$controller = new EmployeeController($employeeService, $responseService, $logger, $authService);

🎯 SOLID Principles Implementation

Single Responsibility Principle (SRP)

Each class has one reason to change:

  • EmployeeController: HTTP handling
  • EmployeeService: Business logic
  • EmployeeRepository: Data access
  • ValidatorService: Input validation
  • LoggerService: Logging

Open/Closed Principle (OCP)

Classes are open for extension, closed for modification:

// Add new authentication method without changing existing code
class LdapAuthService implements AuthServiceInterface {
    public function authenticate(string $department, string $apiKey): bool {
        // LDAP authentication logic
    }
}

// Register in container
$container->singleton(AuthServiceInterface::class, LdapAuthService::class);

Liskov Substitution Principle (LSP)

Implementations can be substituted without breaking functionality:

// Any implementation of LoggerInterface can be used
interface LoggerInterface {
    public function info(string $message, array $context = []): void;
}

class FileLogger implements LoggerInterface { /* ... */ }
class DatabaseLogger implements LoggerInterface { /* ... */ }
class CloudLogger implements LoggerInterface { /* ... */ }

Interface Segregation Principle (ISP)

Clients depend only on interfaces they use:

// Specific, focused interfaces
interface EmployeeRepositoryInterface {
    public function create(array $data): bool;
    public function getByEmployeeId(string $employeeId): ?array;
    // Only repository-specific methods
}

interface ValidatorInterface {
    public function validateEmployee(array $data): array;
    // Only validation-specific methods
}

Dependency Inversion Principle (DIP)

High-level modules depend on abstractions:

class EmployeeController {
    public function __construct(
        private EmployeeServiceInterface $service,  // ← Abstract
        private ResponseInterface $response,        // ← Abstract
        private LoggerInterface $logger             // ← Abstract
    ) {}
}

🔌 Extension Guide

📝 Adding a New Service

  1. Create Interface:
// src/interfaces/NotificationServiceInterface.php
interface NotificationServiceInterface {
    public function sendEmail(string $to, string $subject, string $body): bool;
    public function sendSms(string $to, string $message): bool;
}
  1. Implement Service:
// src/services/EmailNotificationService.php
class EmailNotificationService implements NotificationServiceInterface {
    public function __construct(
        private ConfigInterface $config,
        private LoggerInterface $logger
    ) {}

    public function sendEmail(string $to, string $subject, string $body): bool {
        // Implementation
    }

    public function sendSms(string $to, string $message): bool {
        // Implementation
    }
}
  1. Register in Container:
// src/bootstrap/app.php
$container->singleton(NotificationServiceInterface::class, EmailNotificationService::class);
  1. Use in Controllers:
class EmployeeController {
    public function __construct(
        // ... existing dependencies
        private NotificationServiceInterface $notifications
    ) {}

    public function create(): void {
        $employee = $this->employeeService->createEmployee($data);
        
        // Send welcome email
        $this->notifications->sendEmail(
            $employee['email'],
            'Welcome to the team!',
            "Welcome {$employee['firstName']}!"
        );
    }
}

🚪 Adding New Endpoints

  1. Add Controller Method:
// src/controllers/EmployeeController.php
public function getByDepartment(string $department): void {
    try {
        if (!$this->checkPermission('read')) {
            return;
        }

        $employees = $this->employeeService->getByDepartment($department);
        $this->response->success($employees, 'Employees retrieved successfully');
        
    } catch (Exception $e) {
        $this->logger->error("Failed to get employees by department", ['error' => $e->getMessage()]);
        $this->response->error('Failed to retrieve employees', 500);
    }
}
  1. Add Service Method:
// src/services/EmployeeService.php
public function getByDepartment(string $department): array {
    return $this->repository->search(['department' => $department]);
}
  1. Add Route:
// src/routes/api.php
case 'GET':
    if (count($pathSegments) === 3 && $pathSegments[1] === 'department') {
        $controller->getByDepartment($pathSegments[2]);
    }
    break;

🔐 Adding Authentication Methods

  1. Create New Auth Service:
interface JwtAuthServiceInterface {
    public function generateToken(array $claims): string;
    public function validateToken(string $token): ?array;
}

class JwtAuthService implements JwtAuthServiceInterface {
    public function __construct(
        private ConfigInterface $config,
        private LoggerInterface $logger
    ) {}

    public function generateToken(array $claims): string {
        // JWT implementation
    }

    public function validateToken(string $token): ?array {
        // JWT validation
    }
}
  1. Register in Container:
$container->singleton(JwtAuthServiceInterface::class, JwtAuthService::class);
  1. Create Middleware:
class JwtMiddleware {
    public function __construct(
        private JwtAuthServiceInterface $jwt,
        private ResponseInterface $response
    ) {}

    public function handle(): bool {
        $token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
        
        if (!$token || !$this->jwt->validateToken($token)) {
            $this->response->unauthorized('Invalid token');
            return false;
        }
        
        return true;
    }
}

📊 Adding Database Migrations

  1. Create Migration File:
-- database/migrations/2025_07_18_add_employee_notes.sql
ALTER TABLE Employees 
ADD COLUMN notes TEXT NULL 
AFTER address;
  1. Update Repository:
// Add to EmployeeRepository::create()
$sql = "INSERT INTO Employees (..., notes) VALUES (..., :notes)";
  1. Update Service Validation:
// Add to ValidatorService::validateEmployee()
if (isset($data['notes']) && strlen($data['notes']) > 1000) {
    $errors[] = "Notes must be 1000 characters or less";
}

🎯 Adding Events

  1. Create Event System:
interface EventDispatcherInterface {
    public function dispatch(object $event): void;
    public function listen(string $eventClass, callable $listener): void;
}

class EventDispatcher implements EventDispatcherInterface {
    private array $listeners = [];

    public function dispatch(object $event): void {
        $eventClass = get_class($event);
        
        foreach ($this->listeners[$eventClass] ?? [] as $listener) {
            $listener($event);
        }
    }

    public function listen(string $eventClass, callable $listener): void {
        $this->listeners[$eventClass][] = $listener;
    }
}
  1. Create Events:
class EmployeeCreatedEvent {
    public function __construct(
        public readonly array $employee,
        public readonly string $createdBy
    ) {}
}
  1. Dispatch Events:
// In EmployeeService::createEmployee()
$employee = $this->repository->create($data);
$this->eventDispatcher->dispatch(new EmployeeCreatedEvent($employee, $department));
  1. Register Listeners:
// In bootstrap/app.php
$eventDispatcher = $container->resolve(EventDispatcherInterface::class);
$eventDispatcher->listen(EmployeeCreatedEvent::class, function($event) use ($container) {
    $notifications = $container->resolve(NotificationServiceInterface::class);
    $notifications->sendEmail($event->employee['email'], 'Welcome!', '...');
});

🧪 Testing

Our comprehensive testing suite features using Silicon Valley characters, making tests both technically robust and entertaining to read!

🎭 Test Philosophy

Instead of boring technical tests, we shall use relatable scenarios:

  • Richard Hendricks creating employee records at Pied Piper
  • Gavin Belson trying (and failing) to access Pied Piper data
  • Dinesh Chugtai getting promoted to Senior Engineer
  • HR department managing team permissions
  • Marketing team discovering their read-only limitations

🚀 Running Tests

# Run all tests with human-readable output
vendor/bin/phpunit --testdox

# Run unit tests only (recommended for development)
vendor/bin/phpunit tests/Unit --testdox

# Run integration tests only  
vendor/bin/phpunit tests/Integration --testdox

# Run specific test class
vendor/bin/phpunit tests/Unit/Services/ValidatorServiceTest.php

# Run specific test method
vendor/bin/phpunit tests/Unit/Services/ValidatorServiceTest.php --filter=it_validates_phone_numbers_properly

# Run with coverage (if xdebug installed)
vendor/bin/phpunit --coverage-html tests/results/coverage

# Generate test documentation in HTML format
vendor/bin/phpunit --testdox-html tests/results/testdox.html

# Run tests and generate both coverage and documentation
vendor/bin/phpunit --testdox-html tests/results/testdox.html --coverage-html tests/results/coverage

📊 Test Results Summary

✅ Unit Tests: 49 tests, 125 assertions - ALL PASSING
   📋 AuthService: 12 tests (authentication & permissions)
   👥 EmployeeService: 11 tests (business logic)
   ✓ ValidatorService: 14 tests (input validation & XSS prevention)
   🗄️ EmployeeRepository: 12 tests (database operations)

⚙️ Integration Tests: Controller behavior testing
🎯 Human-Readable: TestDox format for clear documentation

🏗️ Test Architecture

tests/
├── Unit/                          # Isolated component testing
│   ├── Services/
│   │   ├── AuthServiceTest.php           # 🔐 Department permissions
│   │   ├── EmployeeServiceTest.php       # 👥 Business logic
│   │   └── ValidatorServiceTest.php      # ✅ Input validation
│   └── Repositories/
│       └── EmployeeRepositoryTest.php    # 🗄️ Database operations
├── Integration/                   # End-to-end behavior testing
│   ├── Controllers/
│   │   └── EmployeeControllerTest.php    # 🎮 Controller integration
│   └── ApiRoutesIntegrationTest.php      # 🌐 Full API testing
└── bootstrap.php                  # Test environment setup

🎬 Example Test

/** @test */
public function richard_can_successfully_add_jared_to_the_team(): void
{
    // Given Richard (Engineering) wants to add Jared to the team
    $jaredData = [
        'firstName' => 'Jared',
        'lastName' => 'Dunn', 
        'email' => 'jared.dunn@piedpiper.com',
        'department' => 'Operations',
        'position' => 'Head of Business Development',
        'salary' => 110000,
        'dateOfHire' => '2023-07-19'
    ];

    // And authentication succeeds
    $this->mockAuthService->shouldReceive('authenticate')
        ->with('Engineering', 'eng_secure_key_2025')
        ->andReturn(true);

    // And the service successfully creates Jared
    $this->mockEmployeeService->shouldReceive('createEmployee')
        ->with($jaredData)
        ->andReturn(array_merge($jaredData, [
            'employeeId' => 'EMP005',
            'status' => 'active'
        ]));

    // When Richard creates Jared's employee record
    $this->controller->create();

    // Then all expectations should be met ✅
}

🔐 Security Test Examples

/** @test */
public function gavin_cannot_access_pied_piper_data_with_wrong_credentials(): void
{
    // Given Gavin from Hooli tries to spy on our employee data
    // When he uses wrong credentials  
    // Then he should be denied access 🚫
}

/** @test */
public function it_sanitizes_input_to_prevent_xss_attacks(): void
{
    // Given someone tries XSS injection
    $maliciousInput = '<script>alert("Gavin was here")</script>Richard';
    
    // When we sanitize the input
    $result = $this->validator->sanitizeInput($maliciousInput);
    
    // Then the script should be stripped
    $this->assertEquals('Richard', $result);
}

🛠️ Test Environment Setup

The test environment automatically:

  • ✅ Sets up proper test database configuration
  • ✅ Initializes dependency injection container for integration tests
  • ✅ Creates Silicon Valley test employee data
  • ✅ Handles test isolation and cleanup
  • ✅ Provides structured logging for debugging

🎯 Test Data - Silicon Valley Team

// Test employees automatically created for integration tests:
[
    'TEST001' => 'Richard Hendricks - CEO & Founder',
    'TEST002' => 'Bertram Gilfoyle - System Architect', 
    'TEST003' => 'Dinesh Chugtai - Senior Software Engineer'
]

📱 Database Testing

# Test database connection with Docker
docker exec frozen_in_time php run-tests.php

# Expected output:
# 🧪 Test environment initialized for SOLID architecture
# ✅ All 49 unit tests passing
# 🎭 Human-like scenarios verified
# 🔐 Security validations confirmed

🏆 Testing Best Practices Implemented

  • 🎭 Human-Readable Tests - Scenarios anyone can understand
  • 🔒 Security-First - XSS prevention, authentication testing
  • ⚡ Fast Execution - Unit tests run in milliseconds
  • 🧩 Isolated Components - True unit testing with mocks
  • 📊 Comprehensive Coverage - All SOLID components tested
  • 🛡️ Error Handling - Graceful failure testing
  • 📝 Documentation - TestDox output serves as living docs

🛡️ Security

Authentication & Authorization

  • Department-based access control
  • API key authentication
  • Permission-based operations (create, read, update, delete)
  • Rate limiting per endpoint and client

Input Validation

  • Comprehensive validation for all employee fields
  • SQL injection prevention using prepared statements
  • XSS protection through input sanitization
  • Type checking with PHP 8.2+ strict types

Security Headers

// CORS headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-Department, X-API-Key

// Rate limiting headers  
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1642534800

Environment Security

  • Sensitive data stored in environment variables
  • Database passwords not logged (security fix applied)
  • API keys configurable per environment
  • Debug mode disabled in production

📦 Deployment

Docker Production Deployment

  1. Build production image:
docker build -t frozen-in-time:production .
  1. Run with production environment:
docker run -d \
  --name frozen-in-time-prod \
  -p 80:80 \
  -e APP_ENV=production \
  -e LOG_LEVEL=ERROR \
  frozen-in-time:production

Environment Configurations

Development

APP_ENV=development
LOG_LEVEL=DEBUG
DB_HOST=localhost

Staging

APP_ENV=staging
LOG_LEVEL=INFO
DB_HOST=staging-db.company.com

Production

APP_ENV=production
LOG_LEVEL=ERROR
DB_HOST=prod-db.company.com

Health Checks

# Application health
curl http://api.company.com/v1/health

# Database connectivity
curl http://api.company.com/v1/health/database

Monitoring

  • Application logs: /var/www/html/logs/app.log
  • Error tracking: Structured JSON logs with context
  • Performance metrics: Request timing and memory usage
  • Rate limiting: Monitor API usage patterns

🤝 Contributing

Code Standards

  • PSR-4 autoloading
  • PSR-12 coding style
  • PHPDoc comments for all public methods
  • Type declarations for all parameters and return values
  • SOLID principles adherence

Pull Request Process

  1. Create feature branch from main
  2. Write tests for new functionality
  3. Ensure all tests pass
  4. Update documentation
  5. Submit pull request with clear description

Development Tools

# Install development dependencies
composer install --dev

# Run code style checks
vendor/bin/phpcs src/

# Run static analysis
vendor/bin/phpstan analyse src/

# Format code
vendor/bin/phpcbf src/

📞 Support

  • Documentation: This README
  • Issues: GitHub Issues
  • Architecture Questions: See "Architecture Deep Dive" section
  • API Questions: See "API Documentation" section

📄 License

MIT License - see LICENSE file for details.


Happy Coding 🥳

About

A PHP boiler plate code for APIs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages