Skip to content

Repository files navigation

CIcodecov

Neuron-PHP MVC

A lightweight MVC (Model-View-Controller) framework component for PHP 8.4+ that provides core MVC functionality including controllers, views, routing integration, request handling, and a powerful view caching system.

Table of Contents

Installation

Requirements

  • PHP 8.4 or higher
  • Composer

Install via Composer

Install php composer from https://getcomposer.org/

Install the neuron MVC component:

composer require neuron-php/mvc

Quick Start

1. Create the Front Controller

Create a public/index.php file:

<?phprequire_once'../vendor/autoload.php';
// Bootstrap the application$app = boot('../config');
// Dispatch the current requestdispatch($app);

2. Configure Apache (.htaccess)

Create a public/.htaccess file to route all requests through the front controller:

IndexIgnore *
Options +FollowSymlinks
RewriteEngineon# Redirect all requests to index.php# except for actual files and directoriesRewriteCond%{REQUEST_FILENAME}!-dRewriteCond%{REQUEST_FILENAME}!-fRewriteRule^(.*)$index.php?route=$1 [L,QSA]

For Nginx

If using Nginx, add this to your server configuration:

location / {try_files$uri$uri/ /index.php?route=$uri&$args;}

3. Minimal Configuration

Create a config/neuron.yaml file:

system:
base_path: .views:
path: resources/viewsrouting:
controller_paths:
- path: 'app/Controllers'namespace: 'App\Controllers'

Core Components

Application

The main application class (Neuron\Mvc\Application) handles:

  • Route discovery from controller attributes
  • Request routing and controller execution
  • Event dispatching for HTTP errors
  • Output capture for testing
  • Cache management

Controllers

Controllers handle incoming requests and return responses. All controllers should extend Neuron\Mvc\Controllers\Base and implement the IController interface.

namespaceApp\Controllers;
useNeuron\Mvc\Controllers\Base;
useNeuron\Mvc\Responses\HttpResponseStatus;
class HomeController extends Base
{
publicfunctionindex(): string
{
return$this->renderHtml(
HttpResponseStatus::OK,
['title' => 'Welcome'],
'index', // view file'default'// layout file
);
}
}

Available render methods:

  • renderHtml() - Render HTML views with layouts
  • renderJson() - Return JSON responses
  • renderXml() - Return XML responses
  • renderMarkdown() - Render Markdown content with CommonMark

Views

Views support multiple formats and are stored in the configured views directory:

HTML Views

// resources/views/home/index.php
<h1><?phpecho$title; ?></h1>

Layouts

// resources/views/layouts/default.php
<!DOCTYPE html>
<html>
<head>
<title><?phpecho$title ?? 'My App'; ?></title>
</head>
<body>
<?phpecho$Content; ?>
</body>
</html>

Routing

Routes are defined using PHP attributes on controller methods:

useNeuron\Routing\Attributes\Get;
useNeuron\Routing\Attributes\Post;
useNeuron\Routing\Attributes\RouteGroup;
#[RouteGroup(prefix: '/api', filters: ['auth'])]
class UserController extends Base
{
#[Get('/user/:id', name: 'user_profile')]
publicfunctionprofile(Request$request): string
{
$userId = $request->getRouteParameter('id');
// ...
}
#[Post('/users', name: 'api_users', filters: ['csrf'])]
publicfunctioncreate(Request$request): string
{
// ...
}
}

Request Handling

Create request DTO definitions for validation. You can define DTOs inline or reference external DTO files:

Inline DTO Definition:

# config/requests/user_profile.yamlrequest:
method: GETproperties:
id:
type: integerrequired: truerange:
min: 1

Referenced DTO:

# config/requests/user_create.yamlrequest:
method: POSTdto: user # References config/Dtos/user.yaml or src/Dtos/user.yaml
# config/Dtos/user.yaml (or src/Dtos/user.yaml)dto:
username:
type: stringrequired: truelength:
min: 3max: 20email:
type: emailrequired: true

Access validated data in controllers:

publicfunctionprofile(Request$request): string
{
$dto = $request->getDto();
$userId = $dto->id;
// ...
}

URL Helpers

The framework provides Rails-style URL helpers for generating URLs from named routes. This makes it easy to generate consistent URLs throughout your application.

Route Naming

Routes are automatically named based on their configuration key in the YAML file:

routes:
user_profile: # This becomes the route nameroute: /users/{id}method: GETcontroller: App\Controllers\UserController@profileadmin_user_posts:
route: /admin/users/{user_id}/posts/{post_id}method: GETcontroller: App\Controllers\AdminController@userPosts

Using URL Helpers in Controllers

Controllers can use URL helpers directly via magic methods:

class UserController extends Base
{
publicfunctionshow($id): string
{
$user = User::find($id);
// Magic methods for URL generation$editUrl = $this->userEditPath(['id' => $id]);
$absoluteUrl = $this->userProfileUrl(['id' => $id]);
// Use in redirectsif (!$user) {
returnredirect($this->userIndexPath());
}
return$this->renderHtml(HttpResponseStatus::OK, [
'user' => $user,
'edit_url' => $editUrl
]);
}
publicfunctioncreate(): string
{
// After creating user, redirect using magic method$user = newUser($request->all());
$user->save();
returnredirect($this->userProfilePath(['id' => $user->id]));
}
}

Direct URL Helper Methods

Controllers also provide direct helper methods:

// Generate relative URLs$profileUrl = $this->urlFor('user_profile', ['id' => 123]);
// Generate absolute URLs $absoluteUrl = $this->urlForAbsolute('user_profile', ['id' => 123]);
// Check if route existsif ($this->routeExists('user_profile')) {
// Route is available
}
// Get UrlHelper instance for advanced usage$urlHelper = $this->urlHelper();

Using URL Helpers in Views

URL helpers are automatically available in all views through the injected $urlHelper variable:

<!-- resources/views/user/profile.php -->
<div class="user-profile"> <h1><?= $user->name ?></h1> <!-- Magic methods in views --> <a href="<?= $urlHelper->userEditPath(['id' => $user->id]) ?>" class="btn">Edit</a>
<a href="<?=$urlHelper->userPostsPath(['user_id' => $user->id]) ?>" class="btn">View Posts</a>
<!-- Complex routes work too -->
<a href="<?=$urlHelper->adminUserReportsPath(['id' => $user->id, 'year' => date('Y')]) ?>">
Admin Reports
</a>
<!-- Direct method calls -->
<a href="<?=$urlHelper->routePath('user_profile', ['id' => $user->id]) ?>">Profile</a>
<a href="<?=$urlHelper->routeUrl('user_profile', ['id' => $user->id]) ?>">Share Link</a>
</div>

Magic Method Conventions

The magic methods follow Rails naming conventions:

Route Name in YAMLMagic Method (Relative)Magic Method (Absolute)Generated URL
user_profileuserProfilePath()userProfileUrl()/users/123
user_edituserEditPath()userEditUrl()/users/123/edit
admin_user_postsadminUserPostsPath()adminUserPostsUrl()/admin/users/1/posts/2
blog_categoryblogCategoryPath()blogCategoryUrl()/blog/category/tech

URL Helper Methods

MethodDescriptionExample
routePath($name, $params)Generate relative URL$urlHelper->routePath('user_profile', ['id' => 123])
routeUrl($name, $params)Generate absolute URL$urlHelper->routeUrl('user_profile', ['id' => 123])
routeExists($name)Check if route exists$urlHelper->routeExists('user_profile')
getAvailableRoutes()List all named routes$urlHelper->getAvailableRoutes()
{routeName}Path($params)Magic method for relative URL$urlHelper->userProfilePath(['id' => 123])
{routeName}Url($params)Magic method for absolute URL$urlHelper->userProfileUrl(['id' => 123])

Error Handling

URL helpers gracefully handle missing routes:

// Returns null if route doesn't exist$url = $urlHelper->nonExistentRoutePath(['id' => 123]);
if ($url === null) {
// Handle missing route$url = $urlHelper->userIndexPath(); // fallback
}

Advanced Usage

// Get all available routes for debugging$routes = $urlHelper->getAvailableRoutes();
foreach ($routesas$route) {
echo"Route: {$route['name']} -> {$route['method']}{$route['path']}\n";
}
// Custom UrlHelper instance$customHelper = newUrlHelper($customRouter);

Configuration

All YAML config file parameters can be overridden by environment variables in the form of <CATEGORY>_<KEY>, e.g. SYSTEM_BASE_PATH.

Main Configuration (neuron.yaml)

# System settingssystem:
timezone: US/Easternbase_path: .# View settingsviews:
path: resources/views# Logginglogging:
destination: \Neuron\Log\Destination\Fileformat: \Neuron\Log\Format\PlainTextfile: app.loglevel: debug# Cache configurationcache:
enabled: truestorage: filepath: cache/viewsttl: 3600# Default TTL in secondshtml: true # Enable HTML view cachingmarkdown: true # Enable Markdown view cachingjson: false # Disable JSON response cachingxml: false # Disable XML response caching# Garbage collection settings (optional)gc_probability: 0.01# 1% chance to run GC on cache writegc_divisor: 100# Fine-tune probability calculation

Routing Configuration (routing.yaml)

Routing configuration is now handled in a dedicated config/routing.yaml file. This separates routing concerns from the main application configuration.

# config/routing.yaml# URL Rewrites (transparent, no HTTP redirect)rewrites:
'/': '/home'# Root goes to homepage'/index': '/home'# Legacy URL support'/index.php': '/home'# Handle old PHP URLs# Controller paths for route scanningcontroller_paths:
- path: 'app/Controllers'namespace: 'App\Controllers'
- path: 'app/Admin/Controllers'namespace: 'App\Admin\Controllers'

Key Features:

  1. URL Rewrites: Transparently rewrite URLs before route matching

    • No HTTP redirects (faster, invisible to client)
    • Override package-provided routes
    • Support legacy URLs without duplicate routes
  2. Controller Paths: Specify where to scan for route attributes

    • Order matters: first paths take precedence
    • Allows overriding routes from packages

Backward Compatibility:

For backward compatibility, controller_paths can still be configured in neuron.yaml:

routing:
controller_paths:
- path: 'app/Controllers'namespace: 'App\Controllers'

If both files exist, routing.yaml takes precedence.

Cache Configuration Options

OptionDescriptionDefault
enabledEnable/disable caching globallytrue
storageStorage type (currently only 'file')file
pathDirectory for cache filescache/views
ttlDefault time-to-live in seconds3600
views.*Enable caching per view typevaries
gc_probabilityProbability of running garbage collection0.01
gc_divisorDivisor for probability calculation100

Usage Examples

Creating a Controller

namespaceApp\Controllers;
useNeuron\Mvc\Controllers\Base;
useNeuron\Mvc\Requests\Request;
useNeuron\Mvc\Responses\HttpResponseStatus;
class ProductController extends Base
{
publicfunctionlist(): string
{
$products = $this->getProducts();
return$this->renderHtml(
HttpResponseStatus::OK,
['products' => $products],
'list',
'default'
);
}
publicfunctionapiList(): string
{
$products = $this->getProducts();
return$this->renderJson(
HttpResponseStatus::OK,
['products' => $products]
);
}
publicfunctiondetails(Request$request): string
{
$id = $request->getRouteParameter('id');
$product = $this->getProduct($id);
if (!$product) {
return$this->renderHtml(
HttpResponseStatus::NOT_FOUND,
['message' => 'Product not found'],
'error',
'default'
);
}
return$this->renderHtml(
HttpResponseStatus::OK,
['product' => $product],
'details',
'default'
);
}
}

Request Validation with DTOs

Define request DTOs in YAML:

# config/requests/product_create.yamlrequest:
method: POSTheaders:
Content-Type: application/jsonproperties:
name:
type: stringrequired: truelength:
min: 3max: 100price:
type: currencyrequired: truerange:
min: 0category_id:
type: integerrequired: truedescription:
type: stringrequired: falselength:
max: 1000

Available property types:

  • string, integer, float, boolean
  • email, url, uuid
  • date, date_time, time
  • currency, us_phone_number, intl_phone_number
  • array, object
  • ip_address, ein, upc, name, numeric

Error Handling

The framework automatically handles 404 errors:

// Custom 404 handlerclass NotFoundController extends HttpCodes
{
publicfunctionrender404(): string
{
return$this->renderHtml(
HttpResponseStatus::NOT_FOUND,
['message' => 'Page not found'],
'404',
'error'
);
}
}

Advanced Features

View Caching

The framework includes a sophisticated view caching system with multiple storage backends:

Storage Backends

  1. File Storage (Default): Uses the local filesystem for cache storage
  2. Redis Storage: High-performance in-memory caching with Redis

Features

  1. Automatic Cache Key Generation: Based on controller, view, and data
  2. Selective Caching: Enable/disable per view type
  3. TTL Support: Configure expiration times
  4. Garbage Collection: Automatic cleanup of expired entries
  5. Multiple Storage Backends: Choose between file or Redis storage

Configuration

File Storage Configuration
cache:
enabled: truestorage: filepath: cache/viewsttl: 3600views:
html: truemarkdown: truejson: falsexml: false
Redis Storage Configuration
cache:
enabled: truestorage: redis # Use Redis instead of file storagettl: 3600# Redis configuration (flat structure for env variable compatibility)redis_host: 127.0.0.1redis_port: 6379redis_database: 0redis_prefix: neuron_cache_redis_timeout: 2.0redis_auth: null # Optional: Redis passwordredis_persistent: false # Optional: Use persistent connections# View-specific cache settingshtml: truemarkdown: truejson: falsexml: false

This flat structure ensures compatibility with environment variables:

  • CACHE_STORAGE=redis
  • CACHE_REDIS_HOST=127.0.0.1
  • CACHE_REDIS_PORT=6379
  • etc.

Programmatic Usage

// Cache is automatically used when enabled$html = $this->renderHtml(
HttpResponseStatus::OK,
$data,
'cached-view',
'layout'
);

Manual Cache Management

// Clear all expired cache entries (file storage only)$removed = ClearExpiredCache($app);
echo"Removed $removed expired cache entries";
// Clear all cache$app->getViewCache()->clear();

Using CacheStorageFactory

useNeuron\Mvc\Cache\Storage\CacheStorageFactory;
// Create storage based on configuration$storage = CacheStorageFactory::create([
'storage' => 'redis',
'redis_host' => 'localhost',
'redis_port' => 6379,
'redis_database' => 0,
'redis_prefix' => 'neuron_cache_'
]);
// Auto-detect best available storage$storage = CacheStorageFactory::createAutoDetect();
// Check storage availabilityif (CacheStorageFactory::isAvailable('redis')) {
echo"Redis cache is available";
}

You can also manage cache using the CLI commands. See CLI Commands section for details.

Custom View Implementations

Create custom view types by implementing IView:

namespaceApp\Views;
useNeuron\Mvc\Views\IView;
class PdfView implements IView
{
publicfunctionrender(array$Data): string
{
// Generate PDF contentreturn$pdfContent;
}
}

Event System

Listen for HTTP events:

# config/events.yamllisteners:
http_404:
class: App\Listeners\NotFoundLoggermethod: logNotFound

CLI Commands

The MVC component includes several CLI commands for managing cache and routes. These commands are available when using the Neuron CLI tool.

Cache Management Commands

mvc:cache:clear

Clear view cache entries.

Options:

  • --type, -t VALUE - Clear specific cache type (html, json, xml, markdown)
  • --expired, -e - Only clear expired entries
  • --force, -f - Clear without confirmation
  • --config, -c PATH - Path to configuration directory

Examples:

# Clear all cache entries (with confirmation)
neuron mvc:cache:clear
# Clear only expired entries
neuron mvc:cache:clear --expired
# Clear specific cache type
neuron mvc:cache:clear --type=html
# Force clear without confirmation
neuron mvc:cache:clear --force
# Specify custom config path
neuron mvc:cache:clear --config=/path/to/config

mvc:cache:stats

Display comprehensive cache statistics.

Options:

  • --config, -c PATH - Path to configuration directory
  • --json, -j - Output statistics in JSON format
  • --detailed, -d - Show detailed breakdown by view type

Examples:

# Display cache statistics
neuron mvc:cache:stats
# Show detailed statistics with view type breakdown
neuron mvc:cache:stats --detailed
# Output as JSON for scripting
neuron mvc:cache:stats --json
# Detailed JSON output
neuron mvc:cache:stats --detailed --json

Sample Output:

MVC View Cache Statistics
==================================================
Configuration:
Cache Path: /path/to/cache/views
Cache Enabled: Yes
Default TTL: 3600 seconds (1 hour)
Overall Statistics:
Total Cache Entries: 247
Valid Entries: 189
Expired Entries: 58
Total Cache Size: 2.4 MB
Average Entry Size: 10.2 KB
Oldest Entry: 2025-08-10 14:23:15
Newest Entry: 2025-08-13 09:45:32
Recommendations:
- 58 expired entries can be cleared (saving ~580 KB)
Run: neuron mvc:cache:clear --expired

Rate Limiting

The MVC component includes integrated rate limiting support through the routing component. Rate limiting helps protect your application from abuse and ensures fair resource usage.

Configuration

Rate limiting is configured in your neuron.yaml file using two categories:

Standard Rate Limiting

rate_limit:
enabled: false # Enable/disable rate limitingglobal: false # Apply to all routes globallystorage: file # Storage backend: file, redis, memory (testing only)requests: 100# Maximum requests per windowwindow: 3600# Time window in seconds (1 hour)file_path: cache/rate_limits# Redis configuration (if storage: redis)# redis_host: 127.0.0.1# redis_port: 6379

API Rate Limiting (Higher Limits)

api_limit:
enabled: falsestorage: filerequests: 1000# 1000 requests per hourwindow: 3600file_path: cache/api_limits

Environment Variables

Configuration maps to environment variables using the {category}_{name} pattern:

  • RATE_LIMIT_ENABLED=true
  • RATE_LIMIT_STORAGE=redis
  • RATE_LIMIT_REQUESTS=100
  • API_LIMIT_ENABLED=true
  • API_LIMIT_REQUESTS=1000

Usage in Routes

Global Application

Set global: true in configuration to apply rate limiting to all routes:

rate_limit:
enabled: trueglobal: truerequests: 100window: 3600

Per-Route Application

Apply rate limiting to specific routes using the filters parameter in route attributes:

useNeuron\Routing\Attributes\Get;
class HomeController extends Base
{
// Public page - no rate limiting
#[Get('/', name: 'home')]
publicfunctionindex(Request$request): string
{
// ...
}
}
class UserController extends Base
{
// Standard protected route with rate limiting
#[Get('/user/profile', name: 'user_profile', filters: ['rate_limit'])]
publicfunctionprofile(Request$request): string
{
// Apply rate_limit (100/hour)// ...
}
}
class ApiController extends Base
{
// API endpoint with higher limits
#[Get('/api/users', name: 'api_users', filters: ['api_limit'])]
publicfunctionusers(Request$request): string
{
// Apply api_limit (1000/hour)// ...
}
}

Storage Backends

File Storage (Default)

Best for single-server deployments:

rate_limit:
storage: filefile_path: cache/rate_limits # Directory for rate limit files

Redis Storage (Recommended for Production)

Best for distributed systems and high traffic:

rate_limit:
storage: redisredis_host: 127.0.0.1redis_port: 6379redis_database: 0redis_prefix: rate_limit_redis_auth: password # Optionalredis_persistent: true # Use persistent connections

Memory Storage (Testing Only)

For unit tests and development. Data is lost when PHP process ends:

rate_limit:
storage: memory

Rate Limit Headers

When rate limiting is active, the following headers are included in responses:

  • X-RateLimit-Limit: Maximum requests allowed
  • X-RateLimit-Remaining: Requests remaining in current window
  • X-RateLimit-Reset: Unix timestamp when limit resets

When limit is exceeded (HTTP 429):

  • Retry-After: Seconds until retry is allowed

Example Implementation

  1. Enable rate limiting in neuron.yaml:
rate_limit:
enabled: trueglobal: falsestorage: redisrequests: 100window: 3600redis_host: 127.0.0.1api_limit:
enabled: truestorage: redisrequests: 1000window: 3600redis_host: 127.0.0.1
  1. Apply to routes using attributes:
useNeuron\Routing\Attributes\Post;
useNeuron\Routing\Attributes\Get;
class AuthController extends Base
{
#[Post('/auth/login', name: 'login', filters: ['rate_limit'])]
publicfunctionlogin(Request$request): string
{
// Strict limit for login attempts// ...
}
}
class ApiController extends Base
{
#[Get('/api/data', name: 'api_data', filters: ['api_limit'])]
publicfunctiongetData(Request$request): string
{
// Higher limit for API access// ...
}
}

Customization

For advanced use cases, you can extend the rate limiting system by creating custom filters in your application. The rate limiting system automatically detects if the routing component version supports it and gracefully degrades if not available.

Route Management Commands

mvc:routes:list

List all registered routes with filtering options.

Options:

  • --config, -c PATH - Path to configuration directory
  • --controller VALUE - Filter by controller name
  • --method, -m VALUE - Filter by HTTP method (GET, POST, PUT, DELETE, etc.)
  • --pattern, -p VALUE - Search routes by pattern
  • --json, -j - Output routes in JSON format

Examples:

# List all routes
neuron mvc:routes:list
# Filter by controller
neuron mvc:routes:list --controller=UserController
# Filter by HTTP method
neuron mvc:routes:list --method=POST
# Search by pattern
neuron mvc:routes:list --pattern=/api/
# Combine filters
neuron mvc:routes:list --controller=Api --method=GET
# Output as JSON for processing
neuron mvc:routes:list --json

Sample Output:

MVC Routes
======================================================================================
Name | Pattern | Method | Controller | Action
--------------------------------------------------------------------------------------
home | / | GET | HomeController | index
user_profile | /user/{id} | GET | UserController | profile
api_users_list | /api/users | GET | Api\UserController | list
api_users_create | /api/users | POST | Api\UserController | create
products_list | /products | GET | ProductController | list
product_details | /products/{id} | GET | ProductController | details
Total routes: 6
Named routes: 6
Methods: GET: 4, POST: 2

API Reference

Bootstrap Functions

Boot(string $ConfigPath): Application

Initialize the application with configuration.

$app = Boot('/path/to/config');

Dispatch(Application $App): void

Process the current HTTP request.

Dispatch($app);

ClearExpiredCache(Application $App): int

Remove expired cache entries.

$removed = ClearExpiredCache($app);

Key Interfaces

IController

All controllers must implement this interface:

  • renderHtml() - Render HTML with layout
  • renderJson() - Render JSON response
  • renderXml() - Render XML response

IView

Views must implement:

  • render(array $Data): string - Render the view

ICacheStorage

Cache storage implementations must provide:

  • read(), write(), exists(), delete()
  • clear() - Clear all entries
  • isExpired() - Check expiration
  • gc() - Garbage collection

Testing

Run the test suite:

# Run all tests
vendor/bin/phpunit -c tests/phpunit.xml
# Run with coverage
vendor/bin/phpunit -c tests/phpunit.xml --coverage-html coverage
# Run specific test file
vendor/bin/phpunit -c tests/phpunit.xml tests/Mvc/ApplicationTest.php

More Information

You can read more about the Neuron components at neuronphp.com

Releases

Packages

Used by

Contributors

Languages