kalimeromk/slim-mvc-starter

A production-ready Slim 4 MVC Starter Kit with Eloquent ORM, database migrations, JWT authentication, role-based permissions, and modular architecture

Maintainers

Package info

github.com/KalimeroMK/Slim4MVC

Type:project

pkg:composer/kalimeromk/slim-mvc-starter

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 3

Open Issues: 0

v2.1.1 2026-03-26 22:44 UTC

This package is auto-updated.

Last update: 2026-03-26 22:48:29 UTC


README

A modern, production-ready starter kit for building web applications with Slim Framework 4, featuring Eloquent ORM, Blade templating, comprehensive authentication, and a robust testing suite.

๐Ÿš€ Features

  • MVC Architecture - Clean separation of concerns with Slim 4
  • Eloquent ORM - Laravel's powerful database toolkit
  • Blade Templating - Lightweight BladeOne engine with custom directives (@auth, @guest, @csrf, @method, @can, @role)
  • Authentication System - JWT-based API auth and session-based web auth
  • Authorization - Role and permission-based access control with middleware and policies
  • Form Request Validation - Laravel-style validation with automatic error handling
  • Rate Limiting - Built-in protection against brute force attacks
  • CORS Support - Configurable CORS middleware for API endpoints
  • Error Logging - PSR-3 compatible logging with Monolog
  • API Resources - Consistent API response formatting with Resource classes
  • Consistent API Responses - Standardized JSON responses with enums and helper methods
  • Repository Pattern - Clean data access layer abstraction for better testability and maintainability
  • Exception Handling - Custom exception classes with centralized exception handling middleware
  • Caching Layer - Multi-driver cache system (File, Redis, Null) with helper functions
  • Cookie Helper - Encrypted cookie management with AES-256-CBC
  • API Query Builder - Filter, sort, search with operators and pagination
  • Testing Suite - Comprehensive test coverage with PHPUnit (286+ tests, 599+ assertions)
  • CLI Commands - Artisan-like commands for scaffolding (modules, models, controllers, requests)
  • Modular Architecture - Feature-based module organization for better scalability
  • Automatic Dependency Registration - Dependencies automatically registered when creating modules
  • PHP 8.4 Ready - Modern PHP features and Rector automated refactoring
  • Docker Ready - Complete Docker setup for development
  • Environment Validation - Fail-fast configuration validation
  • Auto-Discovery - Automatic DI registration with caching
  • Generic CRUD - Reusable CRUD with 87% less code

๐Ÿ“‹ Requirements

  • PHP >= 8.4
  • Composer
  • Docker & Docker Compose (for development)
  • MySQL/MariaDB (or SQLite for testing)

๐Ÿ› ๏ธ Installation

  1. Clone the repository:

    git clone https://github.com/KalimeroMK/Slim4MVC
    cd Slim4MVC
  2. Install dependencies:

    composer install
  3. Configure environment:

    cp .env.example .env

    Edit .env and configure:

    • Database credentials
    • JWT_SECRET (generate a strong secret key)
    • Mail settings
    • CORS origins (if needed)
    • Cache driver (file/redis) and Redis settings (if using Redis)
    • Cookie settings (encryption, secure flags for production)
  4. Start Docker containers:

    docker-compose up -d
  5. Clear cache (if you encounter namespace errors):

    php clear-cache.php
    docker compose restart
  6. Run migrations:

    php run_migrations.php
  7. Seed database (optional):

    php slim seed:database
  8. Setup storage permissions:

    # Using PHP script (recommended)
    php setup-storage.php
    
    # Or using bash script
    ./setup-storage.sh
    
    # Or manually
    chmod -R 775 storage
    chown -R www-data:www-data storage  # Adjust user/group as needed

The application will be available at http://localhost:81

๐Ÿ“ Project Structure

The project uses a modular architecture where each feature is organized as an independent module:

โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ Console/               # CLI Commands
โ”‚   โ”‚   โ””โ”€โ”€ Commands/         # Console commands (make:module, make:request, etc.)
โ”‚   โ”œโ”€โ”€ Modules/              # Feature modules
โ”‚   โ”‚   โ”œโ”€โ”€ Core/             # Core module (base classes, middleware, support)
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Application/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Actions/  # Core actions (Auth actions)
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ DTOs/     # Core DTOs
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Enums/    # Enums (HttpStatusCode, ApiResponseStatus)
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Interfaces/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Infrastructure/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Events/   # Event system
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Exceptions/ # Custom exceptions
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Http/
โ”‚   โ”‚   โ”‚       โ”‚   โ”œโ”€โ”€ Controllers/ # Base controllers
โ”‚   โ”‚   โ”‚       โ”‚   โ”œโ”€โ”€ Middleware/  # Middleware
โ”‚   โ”‚   โ”‚       โ”‚   โ”œโ”€โ”€ Requests/    # Base FormRequest
โ”‚   โ”‚   โ”‚       โ”‚   โ””โ”€โ”€ Resources/   # Base Resource
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Jobs/     # Queue jobs
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Policies/ # Base Policy
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Providers/ # Service providers
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Queue/    # Queue system
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Repositories/ # Base repositories
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Support/  # Helper classes (Auth, Logger, Mailer, Cookie)
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Cache/    # Cache system (File, Redis, Null drivers)
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Query/    # API Query Builder (filter, sort, search)
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ View/     # Blade integration
โ”‚   โ”‚   โ”œโ”€โ”€ Auth/             # Authentication module
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Application/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Actions/Auth/  # Login, Register, PasswordRecovery, etc.
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ DTOs/Auth/    # Auth DTOs
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Interfaces/Auth/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Infrastructure/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Http/
โ”‚   โ”‚   โ”‚       โ”‚   โ”œโ”€โ”€ Controllers/
โ”‚   โ”‚   โ”‚       โ”‚   โ”‚   โ”œโ”€โ”€ Api/    # API AuthController (JWT)
โ”‚   โ”‚   โ”‚       โ”‚   โ”‚   โ””โ”€โ”€ Web/    # Web AuthController (Session)
โ”‚   โ”‚   โ”‚       โ”‚   โ””โ”€โ”€ Requests/Auth/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ Providers/ # AuthServiceProvider
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ Routes/    # API and Web routes
โ”‚   โ”‚   โ”œโ”€โ”€ User/             # User module
โ”‚   โ”‚   โ”œโ”€โ”€ Role/             # Role module
โ”‚   โ”‚   โ””โ”€โ”€ Permission/      # Permission module
โ”‚   โ””โ”€โ”€ Support/             # Legacy support (backward compatibility)
โ”œโ”€โ”€ bootstrap/                # Application bootstrap files
โ”‚   โ”œโ”€โ”€ modules.php          # Module loader
โ”‚   โ””โ”€โ”€ modules-register.php # Module registration
โ”œโ”€โ”€ database/
โ”‚   โ”œโ”€โ”€ migrations/           # Database migrations
โ”‚   โ””โ”€โ”€ seed/                 # Database seeders
โ”œโ”€โ”€ public/                   # Web root
โ”œโ”€โ”€ resources/
โ”‚   โ”œโ”€โ”€ views/                # Blade templates
โ”‚   โ””โ”€โ”€ lang/                 # Translation files
โ”œโ”€โ”€ routes/                   # Main route files (web.php, api.php)
โ”œโ”€โ”€ stubs/                    # Code generation stubs
โ”‚   โ””โ”€โ”€ Module/              # Module structure stubs
โ”œโ”€โ”€ storage/
โ”‚   โ”œโ”€โ”€ cache/                # Application cache
โ”‚   โ”‚   โ”œโ”€โ”€ data/             # File cache storage
โ”‚   โ”‚   โ””โ”€โ”€ view/             # Blade compiled views
โ”‚   โ”œโ”€โ”€ logs/                 # Application logs
โ”‚   โ”œโ”€โ”€ queue/                # Queue storage (file driver)
โ”‚   โ””โ”€โ”€ sessions/             # Session storage (file driver)
โ””โ”€โ”€ tests/                    # PHPUnit tests
    โ”œโ”€โ”€ Unit/                  # Unit tests
    โ””โ”€โ”€ Feature/               # Feature tests

Module Structure

Each module follows a consistent structure:

app/Modules/Example/
โ”œโ”€โ”€ Application/              # Business logic layer
โ”‚   โ”œโ”€โ”€ Actions/             # Business logic actions
โ”‚   โ”œโ”€โ”€ DTOs/                # Data Transfer Objects
โ”‚   โ”œโ”€โ”€ Services/            # Service classes
โ”‚   โ””โ”€โ”€ Interfaces/          # Service contracts
โ”œโ”€โ”€ Infrastructure/          # Infrastructure layer
โ”‚   โ”œโ”€โ”€ Models/              # Eloquent models
โ”‚   โ”œโ”€โ”€ Repositories/        # Data access layer
โ”‚   โ”œโ”€โ”€ Http/
โ”‚   โ”‚   โ”œโ”€โ”€ Controllers/     # Request handlers
โ”‚   โ”‚   โ”œโ”€โ”€ Requests/       # Form request validation
โ”‚   โ”‚   โ””โ”€โ”€ Resources/      # API resource transformers
โ”‚   โ”œโ”€โ”€ Providers/          # Service providers
โ”‚   โ””โ”€โ”€ Routes/             # Module routes (api.php, web.php)
โ”œโ”€โ”€ Exceptions/             # Module-specific exceptions
โ”œโ”€โ”€ Observers/              # Eloquent observers
โ””โ”€โ”€ Policies/               # Authorization policies

๐ŸŽฏ Usage

Creating Modules

The recommended way to create new features is using the modular architecture:

# Create a new module
php slim make:module Product

# Create module with custom model name
php slim make:module Product --model=Item

# Create module with migration
php slim make:module Product --migration

This will automatically create:

  • Complete module structure (Application, Infrastructure layers)
  • Actions (Create, Update, Delete, Get, List)
  • DTOs (Create, Update)
  • Interfaces (CreateActionInterface, UpdateActionInterface)
  • Model and Repository
  • Controller with CRUD methods
  • Form Requests (Create, Update)
  • API Resource
  • Policy
  • Service Provider
  • API Routes
  • Automatic dependency registration in bootstrap/dependencies.php
  • Automatic module registration in bootstrap/modules-register.php

Example:

php slim make:module Blog --model=Post --migration

This creates:

  • app/Modules/Blog/ with complete structure
  • CreatePostAction, UpdatePostAction, etc.
  • PostRepository automatically registered in Service Provider
  • CreatePostActionInterface and UpdatePostActionInterface automatically registered in bootstrap/dependencies.php
  • Module automatically registered in bootstrap/modules-register.php

Creating Models and Migrations

# Create a model
php slim make:model Product

# Create a model with migration
php slim make:model Product -m

Creating Controllers

php slim make:controller Product

This will create:

  • Controller
  • Actions (Create, Update, Delete, Get, List)
  • DTOs (Create, Update)
  • Form Requests (Create, Update)

Creating Form Requests

# Create a basic request
php slim make:request User/CreateUserRequest

# Create request with auto-generated rules from model
php slim make:request User/CreateUserRequest --model=User

# Create update request with model
php slim make:request User/UpdateUserRequest --model=User --type=update

# Short syntax
php slim make:request User/CreateUserRequest -m User -t create

The --model option automatically generates validation rules based on the model's $fillable fields and $casts.

Running Migrations

# Run migrations
php run_migrations.php

# Rollback last migration
php run_migrations.php rollback

# Refresh all migrations
php run_migrations.php refresh

Available Commands

# Module creation
php slim make:module <ModuleName> [--model=<ModelName>] [--migration]

# Model creation
php slim make:model <ModelName> [-m]

# Controller creation
php slim make:controller <ControllerName>

# Request creation
php slim make:request <Namespace/RequestName> [--model=<ModelName>] [--type=<create|update>]

# Database seeding
php slim seed:database

# Queue processing
php slim queue:work [--stop-when-empty] [--max-jobs=<number>]

# Cache management
php slim cache:clear [--tag=<tag>] [--driver=<driver>]

# List all routes
php slim list-routes

Running Tests

# Run all tests
./vendor/bin/phpunit
# or
composer test

# Run with detailed output
./vendor/bin/phpunit --testdox

# Run specific test suite
./vendor/bin/phpunit tests/Unit

๐Ÿ” Authentication

The application includes a dedicated Auth module that handles both API and Web authentication.

Auth Module

The Auth module (app/Modules/Auth/) provides:

  • API Authentication - JWT-based authentication for API endpoints
  • Web Authentication - Session-based authentication for web routes
  • Password Recovery - Token-based password reset functionality
  • Event-driven - Dispatches events for user registration and password reset

API Authentication (JWT)

The API uses JWT tokens for authentication. After successful login, include the token in requests:

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" http://localhost:81/api/v1/users

Web Authentication (Session)

Web routes use session-based authentication. The AuthWebMiddleware handles authentication for web routes.

๐Ÿ›ก๏ธ Authorization

Using Middleware

Role-based access:

$app->get('/admin/dashboard', [DashboardController::class, 'index'])
    ->add(new CheckRoleMiddleware())
    ->setArgument('roles', 'admin');

// Multiple roles (user needs one of these)
$app->get('/reports', [ReportController::class, 'index'])
    ->add(new CheckRoleMiddleware())
    ->setArgument('roles', ['admin', 'manager']);

Permission-based access:

$app->post('/users', [UserController::class, 'store'])
    ->add(new CheckPermissionMiddleware())
    ->setArgument('permissions', 'create-users');

// Multiple permissions
$app->put('/posts/{id}', [PostController::class, 'update'])
    ->add(new CheckPermissionMiddleware())
    ->setArgument('permissions', ['edit-posts', 'publish-posts']);

Using Policies

Create a policy:

// app/Policies/PostPolicy.php
class PostPolicy extends Policy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || 
               $user->hasPermission('edit-posts');
    }
}

Use in controller:

public function update(Request $request, Response $response, int $id): Response
{
    $post = Post::find($id);
    
    if (!$this->authorize('update', $post)) {
        return $this->respondUnauthorized();
    }
    
    // Update logic...
}

โšก Rate Limiting

Rate limiting is automatically applied to authentication endpoints (5 requests per minute). You can apply it to any route:

use App\Modules\Core\Infrastructure\Http\Middleware\RateLimitMiddleware;

$rateLimit = new RateLimitMiddleware(10, 60); // 10 requests per 60 seconds
$app->post('/api/endpoint', [Controller::class, 'method'])
    ->add($rateLimit);

๐ŸŒ CORS Configuration

CORS is configured globally in bootstrap/middleware.php. Configure allowed origins in .env:

CORS_ORIGINS=*
# or specific origins
CORS_ORIGINS=http://localhost:3000,https://example.com

๐Ÿ“ Logging

The application uses Monolog for logging. Use the Logger helper:

use App\Modules\Core\Infrastructure\Support\Logger;

Logger::error('Something went wrong', ['user_id' => 123]);
Logger::warning('Suspicious activity detected');
Logger::info('User logged in', ['email' => $email]);
Logger::debug('Debug information', $data);

Logs are written to storage/logs/slim.log. Log level is automatically set based on APP_ENV:

  • production: Warning and above
  • local/development: Debug and above

๐ŸŽจ Blade Templating

This project uses BladeOne - a lightweight, standalone Blade engine optimized for Slim 4. It provides the same Blade syntax as Laravel but without the heavy dependencies.

Custom Blade Directives

The following Laravel-like directives are available:

Authentication Directives

@guest
    <a href="/login">Login</a>
    <a href="/register">Register</a>
@endguest

@auth
    <p>Welcome, {{ AuthHelper::user()['name'] }}!</p>
    <form method="POST" action="/logout">
        @csrf
        <button type="submit">Logout</button>
    </form>
@endauth

CSRF Protection

<form method="POST" action="/profile">
    @csrf
    <!-- or manually: -->
    <input type="hidden" name="_token" value="{{ AuthHelper::csrfToken() }}">
</form>

HTTP Method Spoofing

<form method="POST" action="/users/1">
    @csrf
    @method('DELETE')
    <button type="submit">Delete User</button>
</form>

Authorization Directives

@can('edit-posts')
    <a href="/posts/1/edit">Edit Post</a>
@endcan

@role('admin')
    <a href="/admin">Admin Panel</a>
@endrole

AuthHelper Class

The AuthHelper class provides static methods for authentication:

use App\Modules\Core\Infrastructure\Support\AuthHelper;

// Check authentication
if (AuthHelper::check()) {
    // User is logged in
}

// Check if guest
if (AuthHelper::guest()) {
    // User is not logged in
}

// Get current user
$user = AuthHelper::user();
echo $user['name'];

// Get user ID
$userId = AuthHelper::id();

// Check permissions and roles
if (AuthHelper::can('create-posts')) {
    // User can create posts
}

if (AuthHelper::hasRole('admin')) {
    // User is admin
}

// Set user data after login
AuthHelper::setUser([
    'id' => $user->id,
    'name' => $user->name,
    'email' => $user->email,
    'roles' => ['user', 'editor'],
    'permissions' => ['view-posts', 'create-posts']
]);

// Logout user
AuthHelper::logout();

Standard Blade Syntax

All standard Blade directives work as expected:

@extends('layouts.app')

@section('content')
    <h1>{{ $title }}</h1>
    
    @foreach($users as $user)
        <p>{{ $user->name }}</p>
    @endforeach
    
    @if($condition)
        <span>{{ $message }}</span>
    @elseif($otherCondition)
        <span>Other message</span>
    @else
        <span>Default message</span>
    @endif
    
    @include('partials.footer')
@endsection

๐Ÿงช Testing

The project includes a comprehensive test suite covering:

Test Suites

Suite Tests Description
Unit 404+ Isolated component tests
Integration 63+ Database and service integration
Feature 21+ End-to-end API tests
Edge Cases 18+ Boundary and unusual scenarios

New in v2.0 (173 additional tests)

Environment Validation Tests (48 tests)

  • Production environment validation
  • Local/development environment validation
  • Weak secret detection
  • Missing configuration detection
  • Edge cases (unicode, special chars, boundary values)

JWT Service Tests (51 tests)

  • Token generation and validation
  • Refresh token rotation
  • Fingerprint-based security
  • Token theft detection
  • Multiple algorithms (HS256, HS384, HS512)
  • Edge cases (expired tokens, tampered tokens)

Auto-Discovery Tests (35 tests)

  • Module scanning
  • Cache warming and clearing
  • Production vs development behavior
  • Statistics generation

Generic CRUD Tests (39 tests)

  • Create, Read, Update, Delete operations
  • Pagination
  • Action factory
  • Integration with real database

Running Tests

# Run all tests
composer test

# Run specific test suites
./vendor/bin/phpunit --testsuite Unit
./vendor/bin/phpunit --testsuite Integration
./vendor/bin/phpunit --testsuite Feature
./vendor/bin/phpunit --testsuite EdgeCases

# Run with coverage
./vendor/bin/phpunit --coverage-html coverage

# Run specific test file
./vendor/bin/phpunit tests/Unit/EnvironmentValidatorTest.php

Test Coverage

  • โœ… 475+ tests
  • โœ… 900+ assertions
  • โœ… All new features tested
  • โœ… Edge cases covered
  • โœ… Integration with real database
  • โœ… PHP 8.4 optimized with Rector

Code Quality Tools

Laravel Pint (Code formatting):

# Check code style
./vendor/bin/pint --test

# Fix code style
./vendor/bin/pint

Rector (Automated refactoring):

# Check what would be changed
./vendor/bin/rector process --dry-run

# Apply refactoring
./vendor/bin/rector process

Combined (CI/CD ready):

./vendor/bin/pint && ./vendor/bin/rector process && ./vendor/bin/phpunit

๐Ÿ”ง Configuration

Environment Variables

Key environment variables in .env:

# Application
APP_ENV=local
APP_URL=http://localhost:81

# Database
DB_CONNECTION=mysql
DB_HOST=slim_db
DB_PORT=3306
DB_DATABASE=slim
DB_USERNAME=slim
DB_PASSWORD=secret

# JWT
JWT_SECRET=your-secret-key-here

# Mail
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your_email@example.com
MAIL_PASSWORD=your_password
MAIL_FROM_ADDRESS=noreply@example.com
MAIL_FROM_NAME="Your App Name"
MAIL_ENCRYPTION=tls

# CORS
CORS_ORIGINS=*

# Cache
CACHE_DRIVER=redis
CACHE_PREFIX=slim_cache
CACHE_PATH=/var/www/html/storage/cache/data
REDIS_CACHE_DATABASE=1

# Queue
QUEUE_DRIVER=redis
QUEUE_RETRY_AFTER=90

# Session
SESSION_DRIVER=redis
SESSION_LIFETIME=120

# Cookies
COOKIE_TTL=3600
COOKIE_PATH=/
COOKIE_DOMAIN=
COOKIE_SECURE=true
COOKIE_HTTP_ONLY=true
COOKIE_SAME_SITE=Lax
COOKIE_ENCRYPT=true

๐Ÿ“ฆ API Resources

The application uses Resource classes to format API responses consistently:

use App\Modules\User\Infrastructure\Http\Resources\UserResource;

// Single resource
return ApiResponse::success(UserResource::make($user));

// Collection
return ApiResponse::success(UserResource::collection($users));

Available Resources:

  • UserResource - Formats user data (hides password, includes roles)
  • RoleResource - Formats role data (includes permissions)
  • PermissionResource - Formats permission data (includes roles)

๐Ÿ“Š API Response Format

All API responses follow a consistent format using the ApiResponse helper:

Success Response:

{
  "status": "success",
  "data": {...},
  "message": "Optional message"
}

Error Response:

{
  "status": "error",
  "message": "Error message",
  "code": "ERROR_CODE",
  "errors": {
    "field": ["Error message"]
  }
}

Usage in Controllers:

use App\Support\ApiResponse;
use App\Enums\HttpStatusCode;

// Success
return ApiResponse::success($data);
return ApiResponse::success($user, HttpStatusCode::CREATED);

// Errors
return ApiResponse::error('Error message');
return ApiResponse::unauthorized();
return ApiResponse::notFound('User not found');
return ApiResponse::validationError(['email' => ['Invalid email']]);

๐Ÿ“š API Endpoints

Authentication

  • POST /api/v1/register - Register new user
  • POST /api/v1/login - Login and get JWT token
  • POST /api/v1/password-recovery - Request password reset
  • POST /api/v1/reset-password - Reset password with token

Users (requires authentication)

  • GET /api/v1/users - List all users
  • POST /api/v1/users - Create user
  • GET /api/v1/users/{id} - Get user
  • PUT /api/v1/users/{id} - Update user
  • DELETE /api/v1/users/{id} - Delete user

Roles (requires authentication)

  • GET /api/v1/roles - List all roles
  • POST /api/v1/roles - Create role
  • GET /api/v1/roles/{id} - Get role
  • PUT /api/v1/roles/{id} - Update role
  • DELETE /api/v1/roles/{id} - Delete role

Permissions (requires authentication)

  • GET /api/v1/permissions - List all permissions
  • POST /api/v1/permissions - Create permission
  • GET /api/v1/permissions/{id} - Get permission
  • PUT /api/v1/permissions/{id} - Update permission
  • DELETE /api/v1/permissions/{id} - Delete permission

๐Ÿ—๏ธ Architecture

The project follows a modular clean architecture pattern:

Modular Architecture

The application is organized into independent modules, each containing:

  1. Application Layer - Business logic

    • Actions - Business logic operations
    • DTOs - Data Transfer Objects for type-safe data handling
    • Interfaces - Service contracts for dependency injection
    • Services - Complex business logic services
  2. Infrastructure Layer - Technical implementation

    • Models - Eloquent models for database interaction
    • Repositories - Data access layer abstraction (Repository pattern)
    • Controllers - Thin controllers that delegate to Actions
    • Requests - Form request validation
    • Resources - API response transformers
    • Providers - Service providers for dependency registration
    • Routes - Module-specific routes
  3. Cross-cutting Concerns

    • Middleware - Request/response processing
    • Policies - Authorization logic
    • Exceptions - Custom exception classes for better error handling
    • Events - Event-driven architecture
    • Jobs - Asynchronous task processing

Module Registration

Modules are automatically registered via bootstrap/modules-register.php:

return [
    // Core module must be loaded first
    App\Modules\Core\Infrastructure\Providers\CoreServiceProvider::class,
    
    // Auth module
    App\Modules\Auth\Infrastructure\Providers\AuthServiceProvider::class,
    
    // Feature modules
    App\Modules\User\Infrastructure\Providers\UserServiceProvider::class,
    App\Modules\Role\Infrastructure\Providers\RoleServiceProvider::class,
    App\Modules\Permission\Infrastructure\Providers\PermissionServiceProvider::class,
];

Dependency Injection

The application uses PHP-DI with automatic dependency registration:

  • Repositories - Automatically registered in Service Providers
  • Action Interfaces - Automatically registered in bootstrap/dependencies.php when using make:module
  • Autowiring - PHP-DI automatically resolves constructor dependencies

How it works:

  1. When creating a module with make:module:

    • Repository is registered in ServiceProvider::register()
    • Action Interfaces are registered in bootstrap/dependencies.php
    • Use statements are automatically added
  2. PHP-DI Autowiring:

    • Automatically resolves concrete classes (no registration needed)
    • Resolves constructor dependencies via type hints
    • Example: LoginAction needs UserRepository โ†’ automatically injected
  3. Interface-based injection:

    • Controllers use interfaces (e.g., CreateUserActionInterface)
    • PHP-DI resolves to implementation via dependencies.php
    • Allows for easy mocking in tests

Repository Pattern

The application uses the Repository pattern to abstract data access logic:

use App\Modules\User\Infrastructure\Repositories\UserRepository;

class UserController extends Controller
{
    public function __construct(
        private readonly UserRepository $repository
    ) {}
    
    public function show(int $id): Response
    {
        $user = $this->repository->findOrFail($id);
        return ApiResponse::success(UserResource::make($user));
    }
}

Automatic Registration: When you create a module with make:module, the Repository is automatically registered in the Service Provider:

// ServiceProvider::register()
$container->set(UserRepository::class, \DI\autowire(UserRepository::class));

Available Repositories:

  • UserRepository - User data access with methods like findByEmail(), findByPasswordResetToken()
  • RoleRepository - Role data access with methods like findByName(), paginateWithPermissions()
  • PermissionRepository - Permission data access with methods like findByName(), paginateWithRoles()

Action Pattern with Interfaces

Actions implement interfaces for better testability and flexibility:

// Interface
interface CreateUserActionInterface
{
    public function execute(CreateUserDTO $dto): User;
}

// Implementation
final class CreateUserAction implements CreateUserActionInterface
{
    public function __construct(
        private readonly UserRepository $repository
    ) {}
    
    public function execute(CreateUserDTO $dto): User
    {
        return $this->repository->create([...]);
    }
}

Automatic Registration: When you create a module, Action Interfaces are automatically registered in bootstrap/dependencies.php:

CreateUserActionInterface::class => \DI\autowire(CreateUserAction::class),

Working with Modules

Creating a new module:

php slim make:module Product --migration

This creates a complete module structure. After creation:

  1. Update the Model (app/Modules/Product/Infrastructure/Models/Product.php):

    • Add $fillable fields
    • Add $casts for type casting
    • Add relationships if needed
  2. Update the DTOs (app/Modules/Product/Application/DTOs/):

    • Add properties to CreateProductDTO
    • Add optional properties to UpdateProductDTO
  3. Update the Actions (app/Modules/Product/Application/Actions/):

    • Map DTO properties to model attributes in CreateProductAction
    • Add business logic as needed
  4. Update the Controller (app/Modules/Product/Infrastructure/Http/Controllers/ProductController.php):

    • Map request data to DTOs in store() and update() methods
  5. Update the Resource (app/Modules/Product/Infrastructure/Http/Resources/ProductResource.php):

    • Add fields to the resource output
  6. Update the Requests (app/Modules/Product/Infrastructure/Http/Requests/):

    • Add validation rules as needed
  7. Update the Policy (app/Modules/Product/Policies/ProductPolicy.php):

    • Add authorization logic

Module is ready to use! Routes are automatically loaded from app/Modules/Product/Infrastructure/Routes/api.php.

Exception Handling

Custom exception classes provide consistent error handling:

use App\Modules\Core\Infrastructure\Exceptions\NotFoundException;
use App\Modules\Core\Infrastructure\Exceptions\InvalidCredentialsException;
use App\Modules\Core\Infrastructure\Exceptions\UnauthorizedException;
use App\Modules\Core\Infrastructure\Exceptions\ForbiddenException;
use App\Modules\Core\Infrastructure\Exceptions\BadRequestException;

// In Actions
throw new NotFoundException('User not found');
throw new InvalidCredentialsException('Invalid email or password');

The ExceptionHandlerMiddleware automatically converts exceptions to appropriate API responses.

๐Ÿ†• New Features

Environment Validation (Fail-Fast)

Environment validation runs automatically at application startup to catch configuration errors early.

# Validate configuration
php slim discovery --validate

Features:

  • Validates JWT_SECRET length (minimum 32 characters)
  • Checks database connectivity
  • Validates APP_ENV settings
  • Production-specific checks (Redis, Mail, Cache)

Example Output:

๐Ÿ”’ Environment Validation
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

โœ… Environment configuration is valid

 ------------------- ---------- 
  Setting             Value     
 ------------------- ---------- 
  Environment         local     
  JWT Configured      Yes       
  JWT Secret Length   42 chars  
  DB Connection       mysql     
 ------------------- ---------- 

Auto-Discovery for Dependency Injection

Automatically discovers and registers Interface โ†’ Implementation bindings.

# View discovery statistics
php slim discovery --stats

# Warm cache for production
php slim discovery --warm

# Refresh cache
php slim discovery --refresh

Before (Manual Registration):

// bootstrap/dependencies.php
return [
    CreateUserActionInterface::class => autowire(CreateUserAction::class),
    UpdateUserActionInterface::class => autowire(UpdateUserAction::class),
    // ... 80+ more lines
];

After (Auto-Discovery):

// bootstrap/dependencies.php
$discovery = new OptimizedDiscovery();
return $discovery->buildDefinitions();

JWT Service with Refresh Tokens

Enhanced JWT implementation with security best practices.

use App\Modules\Core\Infrastructure\Support\AdvancedJwtService;

$jwtService = new AdvancedJwtService(
    secret: $_ENV['JWT_SECRET'],
    algorithm: 'HS256',
    issuer: 'my-app',
    audience: 'my-api'
);

// Generate access token
$token = $jwtService->generateAccessToken(
    userId: 123,
    claims: ['role' => 'admin'],
    ttl: 3600 // 1 hour
);

// Generate token pair (access + refresh)
$tokenPair = $jwtService->generateRefreshToken(userId: 123);
// Returns: access_token, refresh_token, expires_in

// Rotate refresh token (security best practice)
$newPair = $jwtService->rotateRefreshToken($tokenPair->getRefreshToken());

// Validate token
if ($jwtService->verify($token)) {
    $payload = $jwtService->decode($token);
    $userId = $payload->sub;
}

// Get token info
$info = $jwtService->getTokenInfo($token);
// Returns: algorithm, type, issuer, expires_at, is_expired, etc.

Features:

  • Access tokens (short-lived)
  • Refresh tokens (long-lived with rotation)
  • Fingerprint-based theft detection
  • Issuer and audience validation
  • Token blacklisting support

Generic CRUD Controller

Build complete CRUD APIs with minimal code.

Before (150 lines):

class UserController extends Controller
{
    public function __construct(
        private CreateUserAction $createAction,
        private UpdateUserAction $updateAction,
        // ... 5 more actions
    ) {}
    
    public function index(Request $request): Response { /* 20 lines */ }
    public function store(Request $request): Response { /* 15 lines */ }
    public function show(Request $request, array $args): Response { /* 10 lines */ }
    public function update(Request $request, array $args): Response { /* 15 lines */ }
    public function destroy(Request $request, array $args): Response { /* 10 lines */ }
}

After (20 lines):

class UserController extends GenericCrudController
{
    protected string $repositoryClass = UserRepository::class;
    protected ?string $resourceClass = UserResource::class;
    protected array $defaultRelations = ['roles'];
    protected array $fillable = ['name', 'email', 'password'];
}

Benefits:

  • 87% less code
  • 67% faster development
  • Automatic pagination, relations, and validation
  • Backwards compatible with existing controllers

See Migration Guide for detailed migration instructions.

๐Ÿ”’ Security Features

  • โœ… Password hashing with bcrypt
  • โœ… JWT token authentication (API)
  • โœ… Session-based authentication (Web)
  • โœ… CSRF protection for web routes
  • โœ… Rate limiting on auth endpoints (5 requests/minute)
  • โœ… Rate limiting on all API endpoints (configurable)
  • โœ… Input validation with FormRequest
  • โœ… SQL injection protection (Eloquent ORM)
  • โœ… XSS protection (Blade templating)
  • โœ… Secure session handling
  • โœ… Cookie encryption (AES-256-CBC) for sensitive data
  • โœ… Centralized exception handling with proper error responses
  • โœ… Modular architecture for better code organization
  • โœ… Automatic dependency registration
  • โœ… Custom Blade directives (@auth, @guest, @csrf, @method, @can, @role)

๐Ÿ’พ Caching

The application includes a powerful caching layer with multiple driver support:

Features

  • Multiple Drivers: File, Redis, and Null (for testing)
  • Simple API: Laravel-like helper functions
  • Repository Integration: Built-in trait for easy model caching
  • Tag-based Cache Clearing: Flush cache by tags
  • PSR-16 Compatible: Standard caching interface

Configuration

Configure caching in your .env file:

# Cache Driver: file, redis, null
CACHE_DRIVER=redis
CACHE_PREFIX=slim_cache
CACHE_PATH=/var/www/html/storage/cache/data

# Redis Cache Database (separate from queue/session)
REDIS_CACHE_DATABASE=1

Usage

Helper Functions

// Store a value
cache_put('key', 'value', 3600); // TTL in seconds (null = forever)

// Retrieve a value
$value = cache('key');
$value = cache('key', 'default'); // With default

// Remember pattern (cache or compute)
$users = cache_remember('users', 300, function () {
    return User::all();
});

// Check existence
if (cache_has('key')) {
    // ...
}

// Delete a value
cache_forget('key');

// Clear all cache
cache_flush();

// Clear by tag
cache_flush('users');
cache_flush(['users', 'posts']);

Dependency Injection

use App\Modules\Core\Infrastructure\Cache\CacheInterface;

class UserController extends Controller
{
    public function __construct(
        private readonly CacheInterface $cache
    ) {}
    
    public function index(): Response
    {
        $users = $this->cache->remember('users', 300, function () {
            return User::all();
        });
        
        return ApiResponse::success($users);
    }
}

Repository Caching

use App\Modules\Core\Infrastructure\Cache\RepositoryCacheExample;
use App\Modules\Core\Infrastructure\Repositories\EloquentRepository;

class UserRepository extends EloquentRepository
{
    use RepositoryCacheExample;
    
    protected string $cacheTag = 'users';
    protected int $repositoryCacheTtl = 600; // 10 minutes
    
    protected function model(): string
    {
        return User::class;
    }
    
    // Use cached methods
    public function findCached(int $id): ?User
    {
        return $this->findCached($id);
    }
    
    // Custom cached query
    public function findByEmailCached(string $email): ?User
    {
        return $this->remember("email:{$email}", function () use ($email) {
            return $this->findByEmail($email);
        });
    }
}

Cacheable Trait

use App\Modules\Core\Infrastructure\Cache\Cacheable;

class ProductService
{
    use Cacheable;
    
    protected string $cacheTag = 'products';
    protected int $cacheTtl = 3600;
    
    public function getFeaturedProducts()
    {
        return $this->remember('featured', function () {
            // Expensive query
            return Product::featured()->with('category')->get();
        });
    }
    
    public function clearProductCache()
    {
        $this->cacheFlush();
    }
}

Cache Drivers

File Driver

Stores cache in filesystem. Good for single-server setups.

CACHE_DRIVER=file
CACHE_PATH=/var/www/html/storage/cache/data

Redis Driver

Recommended for production and multi-server setups.

CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_CACHE_DATABASE=1

Null Driver

Disables caching. Useful for testing.

CACHE_DRIVER=null

CLI Commands

# Clear application cache
php slim cache:clear

# Clear cache by tag
php slim cache:clear --tag=users

๐Ÿช Cookies

The application includes a powerful Cookie Helper with encryption support:

Features

  • Easy API: Simple get/set methods with helper functions
  • Encryption: AES-256-CBC encryption for sensitive data
  • Flexible: Support for arrays, objects, and primitives
  • Secure: Configurable secure, httpOnly, and SameSite options
  • Framework Agnostic: Works with any PHP application

Configuration

Configure cookies in your .env file:

# Cookie Settings
COOKIE_TTL=3600
COOKIE_PATH=/
COOKIE_DOMAIN=
COOKIE_SECURE=true
COOKIE_HTTP_ONLY=true
COOKIE_SAME_SITE=Lax
COOKIE_ENCRYPT=true

Usage

Helper Functions

// Set a cookie
cookie_set('user_id', 123, 3600);
cookie_set('preferences', ['theme' => 'dark'], 86400);

// Get a cookie
$userId = cookie_get('user_id');
$userId = cookie_get('user_id', 0); // With default

// Check if exists
if (cookie_has('user_id')) {
    // ...
}

// Delete a cookie
cookie_delete('user_id');

// Delete all cookies
cookie_flush();

// Forever cookie (10 years)
cookie_forever('remember_me', 'yes');

// Remember pattern (get or set)
$settings = cookie_remember('app_settings', 3600, function () {
    return Settings::getDefaults();
});

Using the Class Directly

use App\Modules\Core\Infrastructure\Support\CookieHelper;

$cookie = CookieHelper::getInstance();

// Set with custom options
$cookie->set('session', $data, 3600, [
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);

// Get value
$value = $cookie->get('session');

// Make cookie for Slim Response
$cookieData = $cookie->make('name', 'value', 3600);
$response = $response->withHeader('Set-Cookie', 
    'name=' . $cookieData['value'] . '; Path=' . $cookieData['path']
);

In Controllers

class UserController extends Controller
{
    public function login(Request $request, Response $response): Response
    {
        // Set user preference cookie
        cookie_set('last_login', date('Y-m-d H:i:s'), 86400 * 30);
        
        // Set encrypted session data
        cookie_set('user_prefs', [
            'theme' => 'dark',
            'lang' => 'en'
        ], 3600);
        
        return $response;
    }
    
    public function logout(Request $request, Response $response): Response
    {
        // Clear user cookies
        cookie_delete('user_prefs');
        cookie_delete('last_login');
        
        return $response;
    }
}

Cookie Encryption

Cookies are automatically encrypted using AES-256-CBC when COOKIE_ENCRYPT=true:

// Encryption is transparent - works the same way
$helper = new CookieHelper(encryptionEnabled: true);
$helper->set('secret', 'sensitive data');

// Automatically decrypted on get
$data = $helper->get('secret'); // 'sensitive data'

Security Best Practices

  1. Always use encryption for sensitive data
  2. Set httpOnly to prevent XSS attacks
  3. Use secure flag in production (HTTPS only)
  4. SameSite=Lax or Strict for CSRF protection
  5. Reasonable TTL - don't make cookies live forever

๐Ÿ” API Query Builder

Powerful query parameter parsing for REST API filtering, sorting, and searching.

Features

  • Filtering - Filter by any field with operators (eq, ne, gt, gte, lt, lte, like, in, etc.)
  • Sorting - Multi-column sort with direction (asc/desc)
  • Searching - Full-text search across multiple fields
  • Field Selection - Select only needed fields (sparse fieldsets)
  • Relationships - Eager load related data (include)
  • Ranges - Filter by numeric ranges (price, dates, etc.)
  • Pagination - Automatic pagination with configurable limits

Usage

Basic Filtering

// URL: GET /api/users?filter[role]=admin&filter[active]=true
$users = User::filter($request)->paginate();

Sorting

// URL: GET /api/users?sort=-created_at,name
// Sort by created_at DESC, then name ASC
$users = User::filter($request)->paginate();

Searching

// In User model
protected array $searchable = ['name', 'email'];

// URL: GET /api/users?search=john
// Searches in name and email fields
$users = User::filter($request)->paginate();

Field Selection

// URL: GET /api/users?fields=id,name,email
// Returns only id, name, and email
$users = User::filter($request)->paginate();

Eager Loading

// URL: GET /api/users?include=posts,comments
$users = User::filter($request)->paginate();

Ranges

// URL: GET /api/products?range[price]=10,100
$products = Product::filter($request)->paginate();

Advanced Filters with Operators

// URL: GET /api/users?filter[age]=gt:18&filter[name]=like:john
// Operators: eq, ne, gt, gte, lt, lte, like, starts, ends, in, nin, null

Using in Controllers

use App\Modules\Core\Infrastructure\Query\Filterable;

class UserController extends Controller
{
    public function index(Request $request): Response
    {
        // Using the Filterable trait
        $result = User::filterPaginate($request);
        
        return ApiResponse::success($result['items'], HttpStatusCode::OK, null, $result['pagination']);
    }
}

Model Configuration

use App\Modules\Core\Infrastructure\Query\Filterable;

class User extends Model
{
    use Filterable;
    
    // Fields allowed for filtering
    protected array $filterable = ['name', 'email', 'role', 'active'];
    
    // Fields allowed for sorting
    protected array $sortable = ['id', 'name', 'created_at', 'updated_at'];
    
    // Fields allowed for searching
    protected array $searchable = ['name', 'email'];
    
    // Default sort order
    protected array $defaultSort = ['created_at' => 'desc'];
}

Using QueryBuilder Directly

use App\Modules\Core\Infrastructure\Query\QueryBuilder;

// Simple usage
$users = (new QueryBuilder($request))->paginate(User::class);

// With configuration
$builder = new QueryBuilder($request, [
    'filterable' => ['name', 'email'],
    'sortable' => ['id', 'name'],
    'searchable' => ['name', 'email'],
]);

$users = $builder->paginate(User::class);

Helper Functions

// Parse query parameters
$parser = query_parser($request);
$filters = $parser->filters();
$sorts = $parser->sorts();
$search = $parser->search();

// Apply filters to query
$query = query_filter(User::class, $request);
$users = $query->paginate();

// Get paginated results directly
$result = query_paginate(User::class, $request);
// Returns: ['items' => [...], 'pagination' => [...]]

URL Parameter Reference

Parameter Example Description
filter[field] filter[status]=active Filter by field value
filter[field]=op:value filter[age]=gt:18 Filter with operator
sort sort=-created_at,name Sort by fields
search search=john Full-text search
fields fields=id,name,email Select specific fields
include include=posts,comments Eager load relations
range[field] range[price]=10,100 Filter by range
page page=2 Page number
per_page per_page=25 Items per page (max 100)

Filter Operators

Operator Example Description
eq filter[id]=eq:1 Equal (default)
ne filter[status]=ne:draft Not equal
gt filter[age]=gt:18 Greater than
gte filter[price]=gte:10 Greater than or equal
lt filter[age]=lt:65 Less than
lte filter[price]=lte:100 Less than or equal
like filter[name]=like:john Contains substring
starts filter[name]=starts:John Starts with
ends filter[name]=ends:Doe Ends with
in filter[id]=in:1,2,3 In array
nin filter[status]=nin:deleted Not in array
null filter[deleted_at]=null:true Is null / not null

๐Ÿ“ฆ Dependencies

Core

  • slim/slim - Slim Framework 4
  • illuminate/database - Eloquent ORM
  • illuminate/validation - Validation
  • eftec/bladeone - Lightweight Blade templating engine
  • php-di/slim-bridge - Dependency injection

Authentication & Security

  • firebase/php-jwt - JWT tokens
  • slim/csrf - CSRF protection
  • tuupola/cors-middleware - CORS support

Utilities

  • monolog/monolog - Logging
  • phpmailer/phpmailer - Email sending
  • vlucas/phpdotenv - Environment variables
  • illuminate/pagination - Pagination support
  • illuminate/support - Laravel support package
  • predis/predis - Redis client (cache, queue, sessions)

Development

  • phpunit/phpunit - Testing
  • laravel/pint - Code formatting
  • rector/rector - Code refactoring

๐Ÿš€ Deployment

Storage Directory Setup

The application requires write access to the storage/ directory for logs, cache, and queue files.

Setup

For Nginx:

# Set ownership
sudo chown -R www-data:www-data storage

# Set permissions
sudo chmod -R 775 storage

For Apache:

# Set ownership
sudo chown -R www-data:www-data storage

# Set permissions
sudo chmod -R 775 storage

For Docker: The storage directory is automatically mounted and permissions are handled by the container.

Web Server Configuration

Nginx Configuration

The nginx configuration is located in docker/nginx/app.conf. For production, ensure:

  1. Document root points to /var/www/html/public
  2. PHP-FPM is configured correctly
  3. Storage directory is protected (denied access in nginx config)
  4. Security headers are set

Example production nginx configuration:

server {
    listen 80;
    server_name your-domain.com;
    root /var/www/html/public;
    
    # Deny access to storage and cache
    location ~ ^/(storage|cache)/ {
        deny all;
    }
    
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Apache Configuration

The .htaccess file in public/ directory handles URL rewriting. Ensure:

  1. mod_rewrite is enabled:

    sudo a2enmod rewrite
    sudo systemctl restart apache2
  2. AllowOverride is set to All in Apache config:

    <Directory /var/www/html/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
  3. Storage directory is protected (already configured in .htaccess)

Permissions Reference

Directory Permissions Owner Purpose
storage/ 775 www-data Root storage directory
storage/logs/ 775 www-data Application logs
storage/cache/ 775 www-data Blade view cache
storage/queue/ 775 www-data Queue job files

Note: Adjust user/group (www-data, nginx, apache) based on your server configuration.

Security Considerations

  1. Storage directory should never be accessible via web browser
  2. Environment files (.env) should be protected
  3. Composer files should not be accessible
  4. Git files should not be accessible
  5. File uploads (if implemented) should be stored outside public/ directory

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

๐Ÿ“„ License

This project is licensed under the MIT License.

๐Ÿ™ Acknowledgments

Made with โค๏ธ for the PHP community