- Notifications
You must be signed in to change notification settings - Fork0
Neuron-PHP/mvc
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
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.
- Installation
- Quick Start
- Core Components
- Configuration
- Usage Examples
- Advanced Features
- CLI Commands
- API Reference
- Testing
- More Information
- PHP 8.4 or higher
- Composer
Install php composer fromhttps://getcomposer.org/
Install the neuron MVC component:
composer require neuron-php/mvc
Create apublic/index.php file:
<?phprequire_once'../vendor/autoload.php';// Bootstrap the application$app =boot('../config');// Dispatch the current requestdispatch($app);
Create apublic/.htaccess file to route all requests through the front controller:
IndexIgnore *Options +FollowSymlinksRewriteEngineon# Redirect all requests to index.php# except for actual files and directoriesRewriteCond%{REQUEST_FILENAME}!-dRewriteCond%{REQUEST_FILENAME}!-fRewriteRule^(.*)$index.php?route=$1 [L,QSA]
If using Nginx, add this to your server configuration:
location /{try_files$uri$uri/ /index.php?route=$uri&$args;}
Create aconfig/neuron.yaml file:
system:base_path:.routes_path:configviews:path:resources/views
Create aconfig/routes.yaml file:
routes:home:route:/method:GETcontroller:App\Controllers\HomeController@index
The main application class (Neuron\Mvc\Application) handles:
- Route configuration loading from YAML files
- Request routing and controller execution
- Event dispatching for HTTP errors
- Output capture for testing
- Cache management
Controllers handle incoming requests and return responses. All controllers should extendNeuron\Mvc\Controllers\Base and implement theIController interface.
namespaceApp\Controllers;useNeuron\Mvc\Controllers\Base;useNeuron\Mvc\Responses\HttpResponseStatus;class HomeControllerextends Base{publicfunctionindex():string {return$this->renderHtml( HttpResponseStatus::OK, ['title' =>'Welcome'],'index',// view file'default'// layout file ); }}
Available render methods:
renderHtml()- Render HTML views with layoutsrenderJson()- Return JSON responsesrenderXml()- Return XML responsesrenderMarkdown()- Render Markdown content with CommonMark
Views support multiple formats and are stored in the configured views directory:
// resources/views/home/index.php<h1><?phpecho$title;?></h1>
// resources/views/layouts/default.php<!DOCTYPE html><html><head> <title><?phpecho$title ??'My App';?></title></head><body><?phpecho$Content;?></body></html>
Routes are defined in YAML files and support various HTTP methods:
routes:user_profile:route:/user/{id}method:GETcontroller:App\Controllers\UserController@profilerequest:user_profile# optional request validationapi_users:route:/api/usersmethod:POSTcontroller:App\Controllers\Api\UserController@create
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;// ...}
The framework provides Rails-style URL helpers for generating URLs from named routes. This makes it easy to generate consistent URLs throughout your application.
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
Controllers can use URL helpers directly via magic methods:
class UserControllerextends 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])); }}
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();
URL helpers are automatically available in all views through the injected$urlHelper variable:
<!-- resources/views/user/profile.php --><divclass="user-profile"> <h1><?=$user->name ?></h1> <!-- Magic methods in views --> <a href="<?=$urlHelper->userEditPath(['id' =>$user->id])?>">Edit</a> <a href="<?=$urlHelper->userPostsPath(['user_id' =>$user->id])?>">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>
The magic methods follow Rails naming conventions:
| Route Name in YAML | Magic Method (Relative) | Magic Method (Absolute) | Generated URL |
|---|---|---|---|
user_profile | userProfilePath() | userProfileUrl() | /users/123 |
user_edit | userEditPath() | userEditUrl() | /users/123/edit |
admin_user_posts | adminUserPostsPath() | adminUserPostsUrl() | /admin/users/1/posts/2 |
blog_category | blogCategoryPath() | blogCategoryUrl() | /blog/category/tech |
| Method | Description | Example |
|---|---|---|
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]) |
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}
// 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);
All YAML config file parameters can be overridden by environment variables in the form of<CATEGORY>_<KEY>, e.g.SYSTEM_BASE_PATH.
# System settingssystem:timezone:US/Easternbase_path:.routes_path:config# 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
| Option | Description | Default |
|---|---|---|
enabled | Enable/disable caching globally | true |
storage | Storage type (currently only 'file') | file |
path | Directory for cache files | cache/views |
ttl | Default time-to-live in seconds | 3600 |
views.* | Enable caching per view type | varies |
gc_probability | Probability of running garbage collection | 0.01 |
gc_divisor | Divisor for probability calculation | 100 |
namespaceApp\Controllers;useNeuron\Mvc\Controllers\Base;useNeuron\Mvc\Requests\Request;useNeuron\Mvc\Responses\HttpResponseStatus;class ProductControllerextends 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' ); }}
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,booleanemail,url,uuiddate,date_time,timecurrency,us_phone_number,intl_phone_numberarray,objectip_address,ein,upc,name,numeric
The framework automatically handles 404 errors:
// Custom 404 handlerclass NotFoundControllerextends HttpCodes{publicfunctionrender404():string {return$this->renderHtml( HttpResponseStatus::NOT_FOUND, ['message' =>'Page not found'],'404','error' ); }}
The framework includes a sophisticated view caching system with multiple storage backends:
- File Storage (Default): Uses the local filesystem for cache storage
- Redis Storage: High-performance in-memory caching with Redis
- Automatic Cache Key Generation: Based on controller, view, and data
- Selective Caching: Enable/disable per view type
- TTL Support: Configure expiration times
- Garbage Collection: Automatic cleanup of expired entries
- Multiple Storage Backends: Choose between file or Redis storage
cache:enabled:truestorage:filepath:cache/viewsttl:3600views:html:truemarkdown:truejson:falsexml:false
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=redisCACHE_REDIS_HOST=127.0.0.1CACHE_REDIS_PORT=6379- etc.
// Cache is automatically used when enabled$html =$this->renderHtml( HttpResponseStatus::OK,$data,'cached-view','layout');
// Clear all expired cache entries (file storage only)$removed =ClearExpiredCache($app);echo"Removed$removed expired cache entries";// Clear all cache$app->getViewCache()->clear();
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. SeeCLI Commands section for details.
Create custom view types by implementingIView:
namespaceApp\Views;useNeuron\Mvc\Views\IView;class PdfViewimplements IView{publicfunctionrender(array$Data):string {// Generate PDF contentreturn$pdfContent; }}
Listen for HTTP events:
# config/events.yamllisteners:http_404:class:App\Listeners\NotFoundLoggermethod:logNotFound
The MVC component includes several CLI commands for managing cache and routes. These commands are available when using the Neuron CLI tool.
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 entriesneuron mvc:cache:clear --expired# Clear specific cache typeneuron mvc:cache:clear --type=html# Force clear without confirmationneuron mvc:cache:clear --force# Specify custom config pathneuron mvc:cache:clear --config=/path/to/config
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 statisticsneuron mvc:cache:stats# Show detailed statistics with view type breakdownneuron mvc:cache:stats --detailed# Output as JSON for scriptingneuron mvc:cache:stats --json# Detailed JSON outputneuron mvc:cache:stats --detailed --json
Sample Output:
MVC View Cache Statistics==================================================Configuration:Cache Path: /path/to/cache/viewsCache Enabled: YesDefault TTL: 3600 seconds (1 hour)Overall Statistics:Total Cache Entries: 247Valid Entries: 189Expired Entries: 58Total Cache Size: 2.4 MBAverage Entry Size: 10.2 KBOldest Entry: 2025-08-10 14:23:15Newest Entry: 2025-08-13 09:45:32Recommendations:- 58 expired entries can be cleared (saving ~580 KB) Run: neuron mvc:cache:clear --expiredThe MVC component includes integrated rate limiting support through the routing component. Rate limiting helps protect your application from abuse and ensures fair resource usage.
Rate limiting is configured in yourneuron.yaml file using two categories:
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_limit:enabled:falsestorage:filerequests:1000# 1000 requests per hourwindow:3600file_path:cache/api_limits
Configuration maps to environment variables using the{category}_{name} pattern:
RATE_LIMIT_ENABLED=trueRATE_LIMIT_STORAGE=redisRATE_LIMIT_REQUESTS=100API_LIMIT_ENABLED=trueAPI_LIMIT_REQUESTS=1000
Setglobal: true in configuration to apply rate limiting to all routes:
rate_limit:enabled:trueglobal:truerequests:100window:3600
Apply rate limiting to specific routes using thefilter parameter inroutes.yaml:
routes:# Public page - no rate limiting -name:homemethod:GETroute:/controller:HomeController@index# Standard protected route with rate limiting -name:user_profilemethod:GETroute:/user/profilecontroller:UserController@profilefilter:rate_limit# Apply rate_limit (100/hour)# API endpoint with higher limits -name:api_usersmethod:GETroute:/api/userscontroller:ApiController@usersfilter:api_limit# Apply api_limit (1000/hour)
Best for single-server deployments:
rate_limit:storage:filefile_path:cache/rate_limits# Directory for rate limit files
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
For unit tests and development. Data is lost when PHP process ends:
rate_limit:storage:memory
When rate limiting is active, the following headers are included in responses:
X-RateLimit-Limit: Maximum requests allowedX-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when limit resets
When limit is exceeded (HTTP 429):
Retry-After: Seconds until retry is allowed
- 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
- Apply to routes in
routes.yaml:
routes: -name:loginmethod:POSTroute:/auth/logincontroller:AuthController@loginfilter:rate_limit# Strict limit for login attempts -name:api_datamethod:GETroute:/api/datacontroller:ApiController@getDatafilter:api_limit# Higher limit for API access
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.
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 routesneuron mvc:routes:list# Filter by controllerneuron mvc:routes:list --controller=UserController# Filter by HTTP methodneuron mvc:routes:list --method=POST# Search by patternneuron mvc:routes:list --pattern=/api/# Combine filtersneuron mvc:routes:list --controller=Api --method=GET# Output as JSON for processingneuron mvc:routes:list --json
Sample Output:
MVC Routes======================================================================================Name | Pattern | Method | Controller | Action--------------------------------------------------------------------------------------home | / | GET | HomeController | indexuser_profile | /user/{id} | GET | UserController | profileapi_users_list | /api/users | GET | Api\UserController | listapi_users_create | /api/users | POST | Api\UserController | createproducts_list | /products | GET | ProductController | listproduct_details | /products/{id} | GET | ProductController | detailsTotal routes: 6Named routes: 6Methods: GET: 4, POST: 2Initialize the application with configuration.
$app =Boot('/path/to/config');
Process the current HTTP request.
Dispatch($app);
Remove expired cache entries.
$removed =ClearExpiredCache($app);
All controllers must implement this interface:
renderHtml()- Render HTML with layoutrenderJson()- Render JSON responserenderXml()- Render XML response
Views must implement:
render(array $Data): string- Render the view
Cache storage implementations must provide:
read(),write(),exists(),delete()clear()- Clear all entriesisExpired()- Check expirationgc()- Garbage collection
Run the test suite:
# Run all testsvendor/bin/phpunit -c tests/phpunit.xml# Run with coveragevendor/bin/phpunit -c tests/phpunit.xml --coverage-html coverage# Run specific test filevendor/bin/phpunit -c tests/phpunit.xml tests/Mvc/ApplicationTest.php
You can read more about the Neuron components atneuronphp.com
About
Lightweight MVC framework.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.