ORM-agnostic declarative CRUD controller for PHP.
Auto-generates list views, edit forms, filters, and actions from field metadata — without coupling to any specific ORM, template engine, or framework.
- 🏗️ Declarative: Define fields and columns, get full CRUD automatically
- 🔌 ORM-Agnostic: Works with Eloquent, Doctrine, PDO, REST APIs, or any data source
- 🎨 UI Components: 15 field types, panels, tabs, filters — all serializable to JSON
- 🪝 Hook System: Extensible lifecycle (before/after save, form field injection)
- 🌍 i18n Ready: Pluggable translator contract
- 📦 Zero Dependencies: Only requires PHP 8.2
This package is the core of the Alxarafe Resource ecosystem. Use it with the adapters that fit your stack:
| Package | Purpose | Status |
|---|---|---|
| resource-controller | Core CRUD engine + UI components | ✅ Stable |
| resource-eloquent | Eloquent ORM adapter (Repository, Query, Transaction) | ✅ Stable |
| resource-pdo | Generic PDO adapter (Repository, Query, Transaction) | ✅ Stable |
composer require alxarafe/resource-controllerFor Eloquent support:
composer require alxarafe/resource-eloquentFor PDO support:
composer require alxarafe/resource-pdouse Alxarafe\ResourceController\AbstractResourceController;
use Alxarafe\ResourceController\Contracts\RepositoryContract;
use Alxarafe\ResourceController\Component\Fields\Text;
use Alxarafe\ResourceController\Component\Fields\Decimal;
use Alxarafe\ResourceController\Component\Fields\Boolean;
class ProductController extends AbstractResourceController
{
public static function getModuleName(): string { return 'Shop'; }
public static function getControllerName(): string { return 'Product'; }
public static function url(string $action = 'index', array $params = []): string
{
return '/products' . ($action !== 'index' ? "/{$action}" : '');
}
protected function getRepository(string $tabId = 'default'): RepositoryContract
{
return new EloquentRepository(Product::class); // or any adapter
}
protected function getListColumns(): array
{
return [
new Text('name', 'Name'),
new Decimal('price', 'Price', ['min' => 0]),
new Boolean('active', 'Active'),
];
}
protected function getEditFields(): array
{
return [
'general' => [
'label' => 'General',
'fields' => [
new Text('name', 'Name', ['required' => true]),
new Decimal('price', 'Price'),
new Boolean('active', 'Active'),
],
],
];
}
}┌──────────────────────────────────────────────┐
│ Your Controller │
│ getRepository() → RepositoryContract │
│ getListColumns() → Field[] │
│ getEditFields() → Field[] │
├──────────────────────────────────────────────┤
│ ResourceTrait (this package) │
│ buildConfiguration() │
│ handleRequest() │
│ fetchListData() / saveRecord() │
├──────────────────────────────────────────────┤
│ Contracts │
│ RepositoryContract TranslatorContract │
│ QueryContract MessageBagContract │
│ TransactionContract HookContract │
│ RendererContract │
└──────────────────────────────────────────────┘
↓ implemented by ↓
┌──────────────┐ ┌──────────────┐
│ Eloquent │ │ PDO │
│ Adapter │ │ Adapter │
└──────────────┘ └──────────────┘
| Contract | Purpose | Null Default |
|---|---|---|
RepositoryContract |
Data access (CRUD + query) | — (must implement) |
QueryContract |
Fluent query builder | — (from Repository) |
TransactionContract |
DB transactions | NullTransaction |
TranslatorContract |
i18n / translations | NullTranslator |
MessageBagContract |
Flash messages | NullMessageBag |
HookContract |
Plugin extensibility | NullHookService |
RendererContract |
Template rendering | — (optional) |
RelationContract |
Parent-child sync | — (optional) |
docker compose up -d
docker exec alxarafe-resources composer installbash bin/ci_local.shThis runs, in order: PHPCBF → PHPCS → PHPStan → Psalm → PHPUnit.
bash bin/run_tests.shGPL-3.0-or-later