feat: add initial framework files including API, console, and web routes; set up testing structure

This commit is contained in:
Snider 2026-01-26 14:25:55 +00:00
parent e498a1701e
commit f1c4c8f46d
13 changed files with 2257 additions and 133 deletions

View file

@ -0,0 +1,455 @@
---
name: core-patterns
description: Scaffold Core PHP Framework patterns (Actions, Multi-tenant, Activity Logging, Modules, Seeders)
---
# Core Patterns Scaffolding
You are helping the user scaffold common Core PHP Framework patterns. This is an interactive skill - gather information through conversation before generating code.
## Start by asking what the user wants to create
Present these options:
1. **Action class** - Single-purpose business logic class
2. **Multi-tenant model** - Add workspace isolation to a model
3. **Activity logging** - Add change tracking to a model
4. **Module** - Create a new module with Boot class
5. **Seeder** - Create a seeder with dependency ordering
Ask: "What would you like to scaffold? (1-5 or describe what you need)"
---
## Option 1: Action Class
Actions are small, focused classes that do one thing well. They extract complex logic from controllers and Livewire components.
### Gather information
Ask the user for:
- **Action name** (e.g., `CreateInvoice`, `PublishPost`, `SendNotification`)
- **Module** (e.g., `Billing`, `Content`, `Notification`)
- **What it does** (brief description to understand parameters needed)
### Generate the Action
Location: `packages/core-php/src/Mod/{Module}/Actions/{ActionName}.php`
```php
<?php
declare(strict_types=1);
namespace Core\Mod\{Module}\Actions;
use Core\Actions\Action;
/**
* {Description}
*
* Usage:
* $action = app({ActionName}::class);
* $result = $action->handle($param1, $param2);
*
* // Or via static helper:
* $result = {ActionName}::run($param1, $param2);
*/
class {ActionName}
{
use Action;
public function __construct(
// Inject dependencies here
) {}
/**
* Execute the action.
*/
public function handle(/* parameters */): mixed
{
// Implementation
}
}
```
### Key points to explain
- Actions use the `Core\Actions\Action` trait for the static `run()` helper
- Dependencies are constructor-injected
- The `handle()` method contains the business logic
- Can optionally implement `Core\Actions\Actionable` for type-hinting
- Naming convention: verb + noun (CreateThing, UpdateThing, DeleteThing)
---
## Option 2: Multi-tenant Model
The `BelongsToWorkspace` trait enforces workspace isolation with automatic scoping and caching.
### Gather information
Ask the user for:
- **Model name** (e.g., `Invoice`, `Project`)
- **Whether workspace context is always required** (default: yes)
### Migration requirement
Ensure the model's table has a `workspace_id` column:
```php
$table->foreignId('workspace_id')->constrained()->cascadeOnDelete();
```
### Add the trait
```php
<?php
declare(strict_types=1);
namespace Core\Mod\{Module}\Models;
use Core\Mod\Tenant\Concerns\BelongsToWorkspace;
use Illuminate\Database\Eloquent\Model;
class {ModelName} extends Model
{
use BelongsToWorkspace;
protected $fillable = [
'workspace_id',
// other fields...
];
// Optional: Disable strict mode (not recommended)
// protected bool $workspaceContextRequired = false;
}
```
### Key points to explain
- **Auto-assignment**: `workspace_id` is automatically set from the current workspace context on create
- **Query scoping**: Use `Model::ownedByCurrentWorkspace()` to scope queries
- **Caching**: Use `Model::ownedByCurrentWorkspaceCached()` for cached collections
- **Security**: Throws `MissingWorkspaceContextException` if no workspace context and strict mode is enabled
- **Relation**: Provides `workspace()` belongsTo relationship
### Usage examples
```php
// Query scoped to current workspace
$invoices = Invoice::ownedByCurrentWorkspace()->where('status', 'paid')->get();
// Cached collection for current workspace
$invoices = Invoice::ownedByCurrentWorkspaceCached();
// Query for specific workspace
$invoices = Invoice::forWorkspace($workspace)->get();
// Check ownership
if ($invoice->belongsToCurrentWorkspace()) {
// safe to display
}
```
---
## Option 3: Activity Logging
The `LogsActivity` trait wraps spatie/laravel-activitylog with framework defaults and workspace tagging.
### Gather information
Ask the user for:
- **Model name** to add logging to
- **Which attributes to log** (all, or specific ones)
- **Which events to log** (created, updated, deleted - default: all)
### Add the trait
```php
<?php
declare(strict_types=1);
namespace Core\Mod\{Module}\Models;
use Core\Activity\Concerns\LogsActivity;
use Illuminate\Database\Eloquent\Model;
class {ModelName} extends Model
{
use LogsActivity;
// Optional configuration via properties:
// Log only specific attributes (default: all)
// protected array $activityLogAttributes = ['status', 'amount'];
// Custom log name (default: from config)
// protected string $activityLogName = 'invoices';
// Events to log (default: created, updated, deleted)
// protected array $activityLogEvents = ['created', 'updated'];
// Include workspace_id in properties (default: true)
// protected bool $activityLogWorkspace = true;
// Only log dirty attributes (default: true)
// protected bool $activityLogOnlyDirty = true;
}
```
### Custom activity tap (optional)
```php
/**
* Customize activity before saving.
*/
protected function customizeActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName): void
{
$activity->properties = $activity->properties->merge([
'custom_field' => $this->some_field,
]);
}
```
### Key points to explain
- Automatically includes `workspace_id` in activity properties
- Empty logs are not submitted
- Uses sensible defaults that can be overridden via model properties
- Can temporarily disable logging with `Model::withoutActivityLogging(fn() => ...)`
---
## Option 4: Module
Modules are the core organizational unit. Each module has a Boot class that declares which lifecycle events it listens to.
### Gather information
Ask the user for:
- **Module name** (e.g., `Billing`, `Notifications`)
- **What the module provides** (web routes, admin panel, API, console commands)
### Create the directory structure
```
packages/core-php/src/Mod/{ModuleName}/
├── Boot.php # Module entry point
├── Models/ # Eloquent models
├── Actions/ # Business logic
├── Routes/
│ ├── web.php # Web routes
│ └── api.php # API routes
├── View/
│ └── Blade/ # Blade views
├── Console/ # Artisan commands
├── Database/
│ ├── Migrations/ # Database migrations
│ └── Seeders/ # Database seeders
└── Lang/
└── en_GB/ # Translations
```
### Generate Boot.php
```php
<?php
declare(strict_types=1);
namespace Core\Mod\{ModuleName};
use Core\Events\AdminPanelBooting;
use Core\Events\ApiRoutesRegistering;
use Core\Events\ConsoleBooting;
use Core\Events\WebRoutesRegistering;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
/**
* {ModuleName} Module Boot.
*
* {Description of what this module handles}
*/
class Boot extends ServiceProvider
{
protected string $moduleName = '{module_slug}';
/**
* Events this module listens to for lazy loading.
*
* @var array<class-string, string>
*/
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
ApiRoutesRegistering::class => 'onApiRoutes',
AdminPanelBooting::class => 'onAdminPanel',
ConsoleBooting::class => 'onConsole',
];
public function register(): void
{
// Register singletons and bindings
}
public function boot(): void
{
$this->loadMigrationsFrom(__DIR__.'/Database/Migrations');
$this->loadTranslationsFrom(__DIR__.'/Lang/en_GB', $this->moduleName);
}
// -------------------------------------------------------------------------
// Event-driven handlers
// -------------------------------------------------------------------------
public function onWebRoutes(WebRoutesRegistering $event): void
{
$event->views($this->moduleName, __DIR__.'/View/Blade');
if (file_exists(__DIR__.'/Routes/web.php')) {
$event->routes(fn () => Route::middleware('web')->group(__DIR__.'/Routes/web.php'));
}
// Register Livewire components
// $event->livewire('{module}.component-name', View\Components\ComponentName::class);
}
public function onApiRoutes(ApiRoutesRegistering $event): void
{
if (file_exists(__DIR__.'/Routes/api.php')) {
$event->routes(fn () => Route::middleware('api')->group(__DIR__.'/Routes/api.php'));
}
}
public function onAdminPanel(AdminPanelBooting $event): void
{
$event->views($this->moduleName, __DIR__.'/View/Blade');
}
public function onConsole(ConsoleBooting $event): void
{
// Register commands
// $event->command(Console\MyCommand::class);
}
}
```
### Available lifecycle events
| Event | Purpose | Handler receives |
|-------|---------|------------------|
| `WebRoutesRegistering` | Public web routes | views, routes, livewire |
| `AdminPanelBooting` | Admin panel setup | views, routes |
| `ApiRoutesRegistering` | REST API routes | routes |
| `ClientRoutesRegistering` | Authenticated client routes | routes |
| `ConsoleBooting` | Artisan commands | command, middleware |
| `McpToolsRegistering` | MCP tools | tools |
| `FrameworkBooted` | Late initialization | - |
### Key points to explain
- The `$listens` array declares which events trigger which methods
- Modules are lazy-loaded - only instantiated when their events fire
- Keep Boot classes thin - delegate to services and actions
- Use the `$moduleName` for consistent view namespace and translations
---
## Option 5: Seeder with Dependencies
Seeders can declare ordering via attributes for dependencies between seeders.
### Gather information
Ask the user for:
- **Seeder name** (e.g., `PackageSeeder`, `DemoDataSeeder`)
- **Module** it belongs to
- **Dependencies** - which seeders must run before this one
- **Priority** (optional) - lower numbers run first (default: 50)
### Generate the Seeder
Location: `packages/core-php/src/Mod/{Module}/Database/Seeders/{SeederName}.php`
```php
<?php
declare(strict_types=1);
namespace Core\Mod\{Module}\Database\Seeders;
use Core\Database\Seeders\Attributes\SeederAfter;
use Core\Database\Seeders\Attributes\SeederPriority;
use Core\Mod\Tenant\Database\Seeders\FeatureSeeder;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Schema;
/**
* Seeds {description}.
*/
#[SeederPriority(50)]
#[SeederAfter(FeatureSeeder::class)]
class {SeederName} extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Guard against missing tables
if (! Schema::hasTable('your_table')) {
return;
}
// Seeding logic here
}
}
```
### Available attributes
```php
// Set priority (lower runs first, default 50)
#[SeederPriority(10)]
// Must run after these seeders
#[SeederAfter(FeatureSeeder::class)]
#[SeederAfter(FeatureSeeder::class, PackageSeeder::class)]
// Must run before these seeders
#[SeederBefore(DemoDataSeeder::class)]
```
### Priority guidelines
| Range | Use case |
|-------|----------|
| 0-20 | Foundation seeders (features, configuration) |
| 20-40 | Core data (packages, workspaces) |
| 40-60 | Default priority (general seeders) |
| 60-80 | Content seeders (pages, posts) |
| 80-100 | Demo/test data seeders |
### Key points to explain
- Always guard against missing tables with `Schema::hasTable()`
- Use `updateOrCreate()` to make seeders idempotent
- Seeders are auto-discovered from `Database/Seeders/` directories
- The framework detects circular dependencies and throws `CircularDependencyException`
---
## After generating code
Always:
1. Show the generated code with proper file paths
2. Explain what was created and why
3. Provide usage examples
4. Mention any follow-up steps (migrations, route registration, etc.)
5. Ask if they need any modifications or have questions
Remember: This is pair programming. Be helpful, explain decisions, and adapt to what the user needs.

248
README.md
View file

@ -1,8 +1,16 @@
# Core PHP Framework # Core PHP Framework
A modular monolith framework for Laravel with event-driven architecture, lazy module loading, and built-in multi-tenancy.
## Features
A modular monolith framework for Laravel with event-driven architecture and lazy module loading. - **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 ## Installation
@ -12,58 +20,15 @@ composer require host-uk/core
The service provider will be auto-discovered. The service provider will be auto-discovered.
## Configuration ## Quick Start
Publish the config file: ### Creating a Module
```bash ```bash
php artisan vendor:publish --tag=core-config
```
Configure your module paths in `config/core.php`:
```php
return [
'module_paths' => [
app_path('Core'),
app_path('Mod'),
],
];
```
## Creating Modules
Use the artisan commands to scaffold modules:
```bash
# Create a full module
php artisan make:mod Commerce php artisan make:mod Commerce
# Create a website module (domain-scoped)
php artisan make:website Marketing
# Create a plugin
php artisan make:plug Stripe
``` ```
## Module Structure This creates a module at `app/Mod/Commerce/` with a `Boot.php` entry point:
Modules are organised with a `Boot.php` entry point:
```
app/Mod/Commerce/
├── Boot.php
├── Routes/
│ ├── web.php
│ ├── admin.php
│ └── api.php
├── Views/
└── config.php
```
## Lifecycle Events
Modules declare interest in lifecycle events via a static `$listens` array:
```php ```php
<?php <?php
@ -93,137 +58,154 @@ class Boot
} }
``` ```
### Available Events ### Lifecycle Events
| Event | Purpose | | Event | Purpose |
|-------|---------| |-------|---------|
| `WebRoutesRegistering` | Public-facing web routes | | `WebRoutesRegistering` | Public-facing web routes |
| `AdminPanelBooting` | Admin panel routes and navigation | | `AdminPanelBooting` | Admin panel routes and navigation |
| `ApiRoutesRegistering` | REST API endpoints | | `ApiRoutesRegistering` | REST API endpoints |
| `ClientRoutesRegistering` | Authenticated client/workspace routes | | `ClientRoutesRegistering` | Authenticated client routes |
| `ConsoleBooting` | Artisan commands | | `ConsoleBooting` | Artisan commands |
| `McpToolsRegistering` | MCP tool handlers | | `McpToolsRegistering` | MCP tool handlers |
| `FrameworkBooted` | Late-stage initialisation | | `FrameworkBooted` | Late-stage initialisation |
### Event Methods ## Core Patterns
Events collect requests from modules: ### Actions
Extract business logic into testable, reusable classes:
```php ```php
// Register routes use Core\Actions\Action;
$event->routes(fn () => require __DIR__.'/routes.php');
// Register view namespace class CreateOrder
$event->views('namespace', __DIR__.'/Views'); {
use Action;
// Register Livewire component public function handle(User $user, array $data): Order
$event->livewire('alias', ComponentClass::class); {
// Business logic here
return Order::create($data);
}
}
// Register navigation item // Usage
$event->navigation(['label' => 'Products', 'icon' => 'box']); $order = CreateOrder::run($user, $validated);
// Register Artisan command (ConsoleBooting)
$event->command(MyCommand::class);
// Register middleware alias
$event->middleware('alias', MiddlewareClass::class);
// Register translations
$event->translations('namespace', __DIR__.'/lang');
// Register Blade component path
$event->bladeComponentPath(__DIR__.'/components', 'prefix');
// Register policy
$event->policy(Model::class, Policy::class);
``` ```
## Firing Events ### Multi-Tenant Isolation
Create frontage service providers to fire events at appropriate times: Automatic workspace scoping for models:
```php ```php
use Core\LifecycleEventProvider; use Core\Mod\Tenant\Concerns\BelongsToWorkspace;
class WebServiceProvider extends ServiceProvider class Product extends Model
{ {
public function boot(): void use BelongsToWorkspace;
{ }
LifecycleEventProvider::fireWebRoutes();
} // 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:
```php
use Core\Activity\Concerns\LogsActivity;
class Order extends Model
{
use LogsActivity;
protected array $activityLogAttributes = ['status', 'total'];
} }
``` ```
## Lazy Loading ### HLCRF Layout System
Modules are only instantiated when their subscribed events fire. A web request doesn't load admin-only modules. An API request doesn't load web modules. This keeps your application fast. Data-driven layouts with infinite nesting:
## Custom Namespace Mapping
For non-standard directory structures:
```php ```php
$scanner = app(ModuleScanner::class); use Core\Front\Components\Layout;
$scanner->setNamespaceMap([
'CustomMod' => 'App\\CustomMod', $page = Layout::make('HCF')
]); ->h('<nav>Navigation</nav>')
->c('<article>Main content</article>')
->f('<footer>Footer</footer>');
echo $page;
``` ```
## Contracts Variant strings define structure: `HCF` (Header-Content-Footer), `HLCRF` (all five regions), `H[LC]CF` (nested layouts).
### AdminMenuProvider See [HLCRF.md](packages/core-php/src/Core/Front/HLCRF.md) for full documentation.
Implement for admin navigation: ## Configuration
Publish the config file:
```bash
php artisan vendor:publish --tag=core-config
```
Configure module paths in `config/core.php`:
```php ```php
use Core\Front\Admin\Contracts\AdminMenuProvider; return [
'module_paths' => [
class Boot implements AdminMenuProvider app_path('Core'),
{ app_path('Mod'),
public function adminMenuItems(): array ],
{ ];
return [
[
'group' => 'services',
'priority' => 20,
'item' => fn () => [
'label' => 'Products',
'icon' => 'box',
'href' => route('admin.products.index'),
],
],
];
}
}
``` ```
### ServiceDefinition ## Artisan Commands
For SaaS service registration: ```bash
php artisan make:mod Commerce # Create a module
```php php artisan make:website Marketing # Create a website module
use Core\Service\Contracts\ServiceDefinition; php artisan make:plug Stripe # Create a plugin
class Boot implements ServiceDefinition
{
public static function definition(): array
{
return [
'code' => 'commerce',
'module' => 'Commerce',
'name' => 'Commerce',
'tagline' => 'E-commerce platform',
];
}
}
``` ```
## 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](docs/patterns.md) - Detailed documentation for all framework patterns
- [HLCRF Layout System](packages/core-php/src/Core/Front/HLCRF.md) - Composable layout documentation
## Testing ## Testing
```bash ```bash
composer test composer test
``` ```
## Requirements
- PHP 8.2+
- Laravel 11+
## License ## License
EUPL-1.2. See [LICENSE](LICENSE) for details. EUPL-1.2 - See [LICENSE](LICENSE) for details.

45
TODO.md Normal file
View file

@ -0,0 +1,45 @@
# Core PHP Framework - TODO
## Code Cleanup
- [ ] **ApiExplorer** - Update biolinks endpoint examples
---
## Completed (January 2026)
### Security Fixes
- [x] **MCP: Database Connection Fallback** - Fixed to throw exception instead of silently falling back to default connection
- See: `packages/core-mcp/changelog/2026/jan/security.md`
- [x] **MCP: SQL Validator Regex** - Strengthened WHERE clause patterns to prevent SQL injection vectors
- See: `packages/core-mcp/changelog/2026/jan/security.md`
### Features
- [x] **MCP: EXPLAIN Plan** - Added query optimization analysis with human-readable performance insights
- See: `packages/core-mcp/changelog/2026/jan/features.md`
- [x] **CDN: Integration Tests** - Comprehensive test suite for CDN operations and asset pipeline
- See: `packages/core-php/changelog/2026/jan/features.md`
### Documentation & Code Quality
- [x] **API docs** - Genericized vendor-specific content (removed Host UK branding, lt.hn references)
- See: `packages/core-api/changelog/2026/jan/features.md`
- [x] **Admin: Route Audit** - Verified admin routes use Livewire modals instead of traditional controllers; #[Action] attributes not applicable
- [x] **ServicesAdmin** - Reviewed stubbed bio service methods; intentionally stubbed pending module extraction (documented with TODO comments)
---
## Package Changelogs
For complete feature lists and implementation details:
- `packages/core-php/changelog/2026/jan/features.md`
- `packages/core-admin/changelog/2026/jan/features.md`
- `packages/core-api/changelog/2026/jan/features.md`
- `packages/core-mcp/changelog/2026/jan/features.md`
- `packages/core-mcp/changelog/2026/jan/security.md` ⚠️ Security fixes

19
bootstrap/app.php Normal file
View file

@ -0,0 +1,19 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
//
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();

View file

@ -39,6 +39,8 @@
"psr-4": { "psr-4": {
"Tests\\": "tests/", "Tests\\": "tests/",
"Core\\Tests\\": "packages/core-php/tests/", "Core\\Tests\\": "packages/core-php/tests/",
"Core\\Mod\\Mcp\\Tests\\": "packages/core-mcp/tests/",
"Core\\Mod\\Tenant\\Tests\\": "packages/core-php/src/Mod/Tenant/Tests/",
"Mod\\": "packages/core-php/tests/Fixtures/Mod/", "Mod\\": "packages/core-php/tests/Fixtures/Mod/",
"Plug\\": "packages/core-php/tests/Fixtures/Plug/", "Plug\\": "packages/core-php/tests/Fixtures/Plug/",
"Website\\": "packages/core-php/tests/Fixtures/Website/" "Website\\": "packages/core-php/tests/Fixtures/Website/"

View file

@ -113,6 +113,43 @@ return [
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
], ],
/*
|--------------------------------------------------------------------------
| MCP Read-Only Connection
|--------------------------------------------------------------------------
|
| This connection is used by the MCP QueryDatabase tool. It should be
| configured with a database user that has SELECT-only permissions.
|
| For MySQL, create a read-only user:
| CREATE USER 'mcp_readonly'@'localhost' IDENTIFIED BY 'password';
| GRANT SELECT ON your_database.* TO 'mcp_readonly'@'localhost';
| FLUSH PRIVILEGES;
|
| If MCP_DB_CONNECTION is not set, this falls back to the default connection.
| In production, always configure a dedicated read-only user.
|
*/
'mcp_readonly' => [
'driver' => env('MCP_DB_DRIVER', env('DB_CONNECTION', 'mysql')),
'url' => env('MCP_DB_URL'),
'host' => env('MCP_DB_HOST', env('DB_HOST', '127.0.0.1')),
'port' => env('MCP_DB_PORT', env('DB_PORT', '3306')),
'database' => env('MCP_DB_DATABASE', env('DB_DATABASE', 'laravel')),
'username' => env('MCP_DB_USERNAME', env('DB_USERNAME', 'root')),
'password' => env('MCP_DB_PASSWORD', env('DB_PASSWORD', '')),
'unix_socket' => env('MCP_DB_SOCKET', env('DB_SOCKET', '')),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
], ],
/* /*

160
config/mcp.php Normal file
View file

@ -0,0 +1,160 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| MCP Database Security
|--------------------------------------------------------------------------
|
| Configuration for the MCP QueryDatabase tool security measures.
|
*/
'database' => [
/*
|--------------------------------------------------------------------------
| Read-Only Connection
|--------------------------------------------------------------------------
|
| The database connection to use for MCP query execution. This should
| be configured with a read-only database user for defence in depth.
|
| Set to null to use the default connection (not recommended for production).
|
*/
'connection' => env('MCP_DB_CONNECTION', 'mcp_readonly'),
/*
|--------------------------------------------------------------------------
| Query Whitelist
|--------------------------------------------------------------------------
|
| Enable or disable whitelist-based query validation. When enabled,
| queries must match at least one pattern in the whitelist to execute.
|
*/
'use_whitelist' => env('MCP_DB_USE_WHITELIST', true),
/*
|--------------------------------------------------------------------------
| Custom Whitelist Patterns
|--------------------------------------------------------------------------
|
| Additional regex patterns to allow. The default whitelist allows basic
| SELECT queries. Add patterns here for application-specific queries.
|
| Example:
| '/^\s*SELECT\s+.*\s+FROM\s+`?users`?\s+WHERE\s+id\s*=\s*\d+;?\s*$/i'
|
*/
'whitelist_patterns' => [
// Add custom patterns here
],
/*
|--------------------------------------------------------------------------
| Blocked Tables
|--------------------------------------------------------------------------
|
| Tables that cannot be queried even with valid SELECT queries.
| Use this to protect sensitive tables from MCP access.
|
*/
'blocked_tables' => [
'users',
'password_reset_tokens',
'sessions',
'personal_access_tokens',
'failed_jobs',
],
/*
|--------------------------------------------------------------------------
| Row Limit
|--------------------------------------------------------------------------
|
| Maximum number of rows that can be returned from a query.
| This prevents accidentally returning huge result sets.
|
*/
'max_rows' => env('MCP_DB_MAX_ROWS', 1000),
],
/*
|--------------------------------------------------------------------------
| Tool Usage Analytics
|--------------------------------------------------------------------------
|
| Configuration for MCP tool usage analytics and metrics tracking.
|
*/
'analytics' => [
/*
|--------------------------------------------------------------------------
| Enable Analytics
|--------------------------------------------------------------------------
|
| Enable or disable tool usage analytics. When disabled, no metrics
| will be recorded for tool executions.
|
*/
'enabled' => env('MCP_ANALYTICS_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Data Retention
|--------------------------------------------------------------------------
|
| Number of days to retain analytics data before pruning.
| Use the mcp:prune-metrics command to clean up old data.
|
*/
'retention_days' => env('MCP_ANALYTICS_RETENTION_DAYS', 90),
/*
|--------------------------------------------------------------------------
| Batch Size
|--------------------------------------------------------------------------
|
| Number of metrics to accumulate before flushing to the database.
| Higher values improve write performance but may lose data on crashes.
|
*/
'batch_size' => env('MCP_ANALYTICS_BATCH_SIZE', 100),
],
/*
|--------------------------------------------------------------------------
| Log Retention
|--------------------------------------------------------------------------
|
| Configuration for MCP log retention and cleanup.
|
*/
'log_retention' => [
/*
|--------------------------------------------------------------------------
| Detailed Logs Retention
|--------------------------------------------------------------------------
|
| Number of days to retain detailed tool call logs.
|
*/
'days' => env('MCP_LOG_RETENTION_DAYS', 90),
/*
|--------------------------------------------------------------------------
| Statistics Retention
|--------------------------------------------------------------------------
|
| Number of days to retain aggregated statistics.
| Should typically be longer than detailed logs.
|
*/
'stats_days' => env('MCP_LOG_RETENTION_STATS_DAYS', 365),
],
];

1336
docs/patterns.md Normal file

File diff suppressed because it is too large Load diff

55
phpunit.xml Normal file
View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheDirectory=".phpunit.cache"
executionOrder="random"
requireCoverageMetadata="false"
beStrictAboutCoverageMetadata="false"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="Feature">
<directory>tests/Feature</directory>
<directory>packages/core-php/tests/Feature</directory>
<directory>packages/core-php/src/Core/**/Tests/Feature</directory>
<directory>packages/core-php/src/Mod/**/Tests/Feature</directory>
<directory>packages/core-admin/tests/Feature</directory>
<directory>packages/core-api/tests/Feature</directory>
<directory>packages/core-mcp/tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory>tests/Unit</directory>
<directory>packages/core-php/tests/Unit</directory>
<directory>packages/core-php/src/Core/**/Tests/Unit</directory>
<directory>packages/core-php/src/Mod/**/Tests/Unit</directory>
<directory>packages/core-admin/tests/Unit</directory>
<directory>packages/core-api/tests/Unit</directory>
<directory>packages/core-mcp/tests/Unit</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
<directory>packages/core-php/src</directory>
<directory>packages/core-admin/src</directory>
<directory>packages/core-api/src</directory>
<directory>packages/core-mcp/src</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_DEBUG" value="true"/>
<env name="APP_KEY" value="base64:Kx0qLJZJAQcDSFE2gMpuOlwrJcC6kXHM0j0KJdMGqzQ="/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>

3
routes/api.php Normal file
View file

@ -0,0 +1,3 @@
<?php
// API routes are registered via core-api package

3
routes/console.php Normal file
View file

@ -0,0 +1,3 @@
<?php
// Console commands are registered via core-php module system

7
routes/web.php Normal file
View file

@ -0,0 +1,7 @@
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return 'Core PHP Framework';
});

20
tests/TestCase.php Normal file
View file

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
/**
* Automatically load migrations from packages.
*/
protected function defineDatabaseMigrations(): void
{
// Load core-php migrations
$this->loadMigrationsFrom(__DIR__.'/../packages/core-php/src/Mod/Tenant/Migrations');
$this->loadMigrationsFrom(__DIR__.'/../packages/core-php/src/Mod/Social/Migrations');
}
}