obelaw / ium
IUM Core
Requires
- laravel/framework: ^12.0|^13.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-08-27 11:28:01 UTC
README
A decoupled, modular ERP framework for Laravel. Business logic is isolated from presentation — domains like EAM, PIM, and WMS are accessed through a single fluent API. No HTTP routes, JSON schemas, or frontend contracts. Pure backend domain orchestration.
Core Philosophy: Fluent API DSL
ium() → domain() → service() → method()
| Segment | Role |
|---|---|
ium() |
Global helper → ObelawiumManager singleton. Single entry point into the ERP domain mesh. |
domain() |
Registered domain (eam, pim, url, wms). Resolves to its own manager, keeping domains isolated. |
service() |
Capability within the domain (assets, categories, reports, products). Returns a scoped service or sub-manager. |
method() |
Operation — terminal (create, find, get) materializes results; intermediate (query, whereType) returns $this for chaining. |
$assets = ium()->eam()->assets()->available(); $products = ium()->pim()->products()->list(); $url = ium()->url()->shorten(ShortenUrlData::from([...]));
Domain Registration
Domains register at boot through registerDomain() on the manager. The domain class must extend Obelaw\Ium\Abstracts\Domain and receives the shared config manager at instantiation:
use Obelaw\Ium\Facades\Ium; // In your domain's service provider boot/register: Ium::registerDomain('url', UrlService::class); Ium::registerDomain('pim', PIMService::class);
Once registered, the domain resolves through the manager — either fluently via magic accessors or through the unified gateway:
// Fluent access — instantiates the domain with the shared config ium()->url()->shorten(ShortenUrlData::from([...])); // Unified gateway — same call, dispatchable from PHP or an API payload Ium::call(domain: 'url', service: 'shorten', data: ShortenUrlData::from([...]));
Laravel auto-discovers providers via extra.laravel.providers in composer.json.
Unified Gateway & Smart Hydration
Ium::call() is the single dispatch point for both internal PHP calls and external API requests. It resolves domain → service → method, inspects the terminal method's signature via reflection, and hydrates the payload into the expected types.
An API payload maps one-to-one onto the gateway arguments:
{
"domain": "url",
"service": "records",
"method": "store",
"data": {
"key": "setting_name",
"value": "some_value"
}
}
Ium::call('url', 'records', 'store', [ 'key' => 'setting_name', 'value' => 'some_value', ]);
The dispatcher supports four payload shapes:
| Payload shape | Behavior |
|---|---|
| DTO instance | Passed through as-is (internal PHP calls). |
| Associative array + single DTO parameter | The whole array is hydrated into the DTO (fromArray() when defined, otherwise new DTO($data)). |
| Associative array + multiple parameters | Treated as named arguments; each value is hydrated against its parameter type. Unknown keys throw a TypeError. |
| List array | Values are matched to parameters by type compatibility, so order-independent mixes of scalars and DTOs just work. |
Backed enums are resolved automatically via Enum::from(). Union/nullable types (?RecordData) resolve to their first class member. Reflection signatures are cached in memory for the lifetime of the process — flush with Ium::flushSignatureCache() or Ium::reset().
Helpers & Configuration
ium(); // ObelawiumManager singleton ium(['key' => 'value']); // merge config, then return the manager ium_config('key', $default); // read a config value ium_config(IumConfigEnumCase::KEY); // enum-backed keys are supported ium_set_config('key', 'value'); // set a config value ium_set_config('key', 'value', global: true); // also mirror to the global store
Values set with global: true are readable from static contexts (e.g. ModelBase resolving its database connection) through GlobalConfigManager.
Domain Layout
Each domain follows a DDD-aligned structure with strict segregation between write mutations and read pipelines:
| Directory | Concern |
|---|---|
Actions/ |
Write mutations. Receives a validated DTO, performs one unit of work. |
Queries/ |
Read pipelines. Intermediate methods return $this; terminal methods materialise results. |
Data/ |
Immutable DTOs. Type-safe, structured input at every domain boundary. |
Models/ |
Eloquent definitions. Persistence only — no business logic. Extends ModelBase (auto-prefixes ium_ tables, binds configured connection). |
Managers/ |
Domain entry points. Orchestrates sub-services (AssetManager, CategoryManager, ReportsManager). |
Events/ |
Domain events fired during lifecycle transitions. |
Exceptions/ |
Domain-specific exceptions for invalid states. |
Traits/ |
Cross-cutting model behaviors (HasAssets, HasMedia, HasStock). |
src/
├── Actions/
├── Data/
├── Events/
├── Exceptions/
├── Managers/
├── Models/
├── Providers/
├── Queries/
└── Traits/
class Asset extends ModelBase { protected ?string $module = 'eam'; protected $fillable = ['code', 'name', 'status']; }
Available Domains
| Package | Domain | Description |
|---|---|---|
obelaw/ium-eam |
eam |
Employee Asset Management — polymorphic asset lifecycle, transfers, categories, reports |
obelaw/ium-pim |
pim |
Product Information Management — products, variants, pricing, categories, stock strategy |
obelaw/ium-url |
url |
URL Shortening & Analytics — shortening, click tracking, aggregated stats |
obelaw/ium-wms |
wms |
Warehouse Management — inventory, locations, movements |
Usage
Mutation
use Obelaw\Ium\Eam\Data\AssetDTO; $asset = ium()->eam()->assets()->create(AssetDTO::from([ 'code' => 'LPT-001', 'name' => 'Dell Laptop', 'category_id' => 1, 'serial_number' => 'SN123456', 'purchase_date' => '2024-01-15', 'purchase_value' => 1200.00, ]));
assets()->create() builds an AssetDTO from the array, validates it, and dispatches CreateAssetAction. Persists through Eloquent, returns the Asset instance.
Query Pipeline
$assets = ium()->eam() ->assets() ->list(); $available = ium()->eam() ->assets() ->available();
Output Control
Once ->query() opens a pipeline, three patterns control what the terminal method returns:
Fluent Chain Pipeline
Intermediate methods return $this, deferring execution until a terminal method (get, first, paginate, count) is called:
$results = ium()->xyz() ->records() ->query() ->whereType('premium') ->activeOnly() ->sortBy('created_at', 'desc') ->get();
Property / Field Selection
Constrain returned columns with select(), reducing memory at enterprise scale:
$summaries = ium()->xyz() ->records() ->query() ->whereType('standard') ->select(['id', 'name', 'type']) ->paginate(25);
Output Formatter / Transformer
Reshape domain objects through a transformer before the terminal method returns:
$results = ium()->xyz() ->records() ->query() ->whereType('premium') ->select(['id', 'name', 'metadata']) ->transform(XyzSummary::class) ->get();
Cross-Domain Integration
Domains can delegate to each other through the shared ium() gateway:
// PIM delegates stock queries to WMS when configured $stock = ium()->pim()->stockManager()->getAvailable($productId);
Testing
php artisan migrate --path=vendor/obelaw/ium-xyz/database/migrations
composer test
it('creates and queries through the fluent pipeline', function () { $asset = ium()->eam()->assets()->create(AssetDTO::from([ 'code' => 'LPT-001', 'name' => 'Test', 'category_id' => 1, ])); $assets = ium()->eam()->assets()->available(); expect($assets)->toHaveCount(1); });
Requirements
- PHP ^8.2
- Laravel ^12.0 | ^13.0