No description
When the same route file is registered on multiple domains (e.g. core.test, hub.core.test, core.localhost), Laravel's route:cache fails with "Another route has already been assigned name". Add deduplicateRouteNames() to strip names from duplicate routes, keeping only the first registration. Extract processViews(), processLivewire(), and refreshRoutes() helpers to reduce duplication across fire* methods. Co-Authored-By: Virgil <virgil@lethean.io> |
||
|---|---|---|
| .claude/skills | ||
| .claude-plugin | ||
| .core | ||
| .forgejo/workflows | ||
| .github | ||
| bin | ||
| changelog/2026/jan | ||
| cmd/core-php | ||
| config | ||
| database/migrations | ||
| docker | ||
| docs | ||
| pkg/php | ||
| src | ||
| stubs | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| composer.json | ||
| CONTRIBUTING.md | ||
| go.mod | ||
| go.sum | ||
| infection.json5 | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| phpstan.neon | ||
| phpunit.xml | ||
| psalm.xml | ||
| README.md | ||
| rector.php | ||
| ROADMAP.md | ||
| SECURITY.md | ||
| TODO.md | ||
Core PHP Framework
A modular monolith framework for Laravel with event-driven architecture, lazy module loading, and built-in multi-tenancy.
Documentation
📚 Read the full documentation →
Features
- Event-driven module system - Modules declare interest in lifecycle events and are only loaded when needed
- Lazy loading - Web requests don't load admin modules, API requests don't load web modules
- Multi-tenant isolation - Workspace-scoped data with automatic query filtering
- Actions pattern - Single-purpose business logic classes with dependency injection
- Activity logging - Built-in audit trails for model changes
- Seeder auto-discovery - Automatic ordering via priority and dependency attributes
- HLCRF Layout System - Hierarchical composable layouts (Header, Left, Content, Right, Footer)
Installation
composer require lthn/php
The service provider will be auto-discovered.
Quick Start
Creating a Module
php artisan make:mod Commerce
This creates a module at app/Mod/Commerce/ with a Boot.php entry point:
<?php
namespace Mod\Commerce;
use Core\Events\WebRoutesRegistering;
use Core\Events\AdminPanelBooting;
class Boot
{
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
AdminPanelBooting::class => 'onAdmin',
];
public function onWebRoutes(WebRoutesRegistering $event): void
{
$event->views('commerce', __DIR__.'/Views');
$event->routes(fn () => require __DIR__.'/Routes/web.php');
}
public function onAdmin(AdminPanelBooting $event): void
{
$event->routes(fn () => require __DIR__.'/Routes/admin.php');
}
}
Lifecycle Events
| Event | Purpose |
|---|---|
WebRoutesRegistering |
Public-facing web routes |
AdminPanelBooting |
Admin panel routes and navigation |
ApiRoutesRegistering |
REST API endpoints |
ClientRoutesRegistering |
Authenticated client routes |
ConsoleBooting |
Artisan commands |
McpToolsRegistering |
MCP tool handlers |
FrameworkBooted |
Late-stage initialisation |
Core Patterns
Actions
Extract business logic into testable, reusable classes:
use Core\Actions\Action;
class CreateOrder
{
use Action;
public function handle(User $user, array $data): Order
{
// Business logic here
return Order::create($data);
}
}
// Usage
$order = CreateOrder::run($user, $validated);
Multi-Tenant Isolation
Automatic workspace scoping for models:
use Core\Mod\Tenant\Concerns\BelongsToWorkspace;
class Product extends Model
{
use BelongsToWorkspace;
}
// Queries are automatically scoped to the current workspace
$products = Product::all();
// workspace_id is auto-assigned on create
$product = Product::create(['name' => 'Widget']);
Activity Logging
Track model changes with minimal setup:
use Core\Activity\Concerns\LogsActivity;
class Order extends Model
{
use LogsActivity;
protected array $activityLogAttributes = ['status', 'total'];
}
HLCRF Layout System
Data-driven layouts with infinite nesting:
use Core\Front\Components\Layout;
$page = Layout::make('HCF')
->h('<nav>Navigation</nav>')
->c('<article>Main content</article>')
->f('<footer>Footer</footer>');
echo $page;
Variant strings define structure: HCF (Header-Content-Footer), HLCRF (all five regions), H[LC]CF (nested layouts).
See HLCRF.md for full documentation.
Configuration
Publish the config file:
php artisan vendor:publish --tag=core-config
Configure module paths in config/core.php:
return [
'module_paths' => [
app_path('Core'),
app_path('Mod'),
],
];
Artisan Commands
php artisan make:mod Commerce # Create a module
php artisan make:website Marketing # Create a website module
php artisan make:plug Stripe # Create a plugin
Module Structure
app/Mod/Commerce/
├── Boot.php # Module entry point
├── Actions/ # Business logic
├── Models/ # Eloquent models
├── Routes/
│ ├── web.php
│ ├── admin.php
│ └── api.php
├── Views/
├── Migrations/
└── config.php
Documentation
- Patterns Guide - Detailed documentation for all framework patterns
- HLCRF Layout System - Composable layout documentation
Testing
composer test
Requirements
- PHP 8.2+
- Laravel 11+
License
EUPL-1.2 - See LICENSE for details.