No description
Find a file
Snider be304e7b1a
Some checks failed
CI / PHP 8.4 (pull_request) Failing after 2m3s
CI / PHP 8.3 (pull_request) Failing after 2m18s
CI / PHP 8.4 (push) Failing after 2m2s
CI / PHP 8.3 (push) Failing after 2m20s
Publish Composer Package / publish (push) Failing after 10s
fix(lifecycle): deduplicate route names from multi-domain registrations
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>
2026-03-21 23:19:30 +00:00
.claude/skills feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
.claude-plugin fix(plugin): remove invalid commands/repository schema from plugin.json 2026-03-13 10:54:42 +00:00
.core refactor: move config YAMLs into .core/ convention 2026-03-13 10:06:58 +00:00
.forgejo/workflows feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
.github feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
bin feat: promote PHP commands to root in core-php binary 2026-03-09 18:47:55 +00:00
changelog/2026/jan feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
cmd/core-php refactor: move Go source files to pkg/php/ 2026-03-12 19:14:14 +00:00
config feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
database/migrations feat(webhook): add WebhookCall model, migration, event, verifier interface 2026-03-12 14:21:20 +00:00
docker feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
docs docs: add human-friendly documentation 2026-03-11 13:02:41 +00:00
pkg/php refactor: move Go source files to pkg/php/ 2026-03-12 19:14:14 +00:00
src fix(lifecycle): deduplicate route names from multi-domain registrations 2026-03-21 23:19:30 +00:00
stubs feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
tests fix(lifecycle): deduplicate route names from multi-domain registrations 2026-03-21 23:19:30 +00:00
.gitattributes refactor: move config YAMLs into .core/ convention 2026-03-13 10:06:58 +00:00
.gitignore chore: add .core/ and .idea/ to .gitignore 2026-03-15 10:17:50 +00:00
CLAUDE.md fix(plugin): add commands/ auto-discover directory, fix plugin.json schema 2026-03-13 10:18:41 +00:00
composer.json feat: rename package to lthn/php for Packagist distribution 2026-03-09 17:56:14 +00:00
CONTRIBUTING.md feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
go.mod chore: sync dependencies for v0.0.3 2026-03-17 17:53:15 +00:00
go.sum chore: sync dependencies for v0.0.3 2026-03-17 17:53:15 +00:00
infection.json5 feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
LICENSE feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
package-lock.json feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
package.json feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
phpstan.neon feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
phpunit.xml feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
psalm.xml feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
README.md docs: update README badges and install command to lthn/php 2026-03-09 18:00:12 +00:00
rector.php feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
ROADMAP.md feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
SECURITY.md feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00
TODO.md feat: replace Go CLI with PHP framework 2026-03-06 08:49:51 +00:00

Core PHP Framework

Latest Stable Version License PHP Version Laravel Version

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

Testing

composer test

Requirements

  • PHP 8.2+
  • Laravel 11+

License

EUPL-1.2 - See LICENSE for details.