psx/record
psx/record is a lightweight PHP record abstraction that maps database rows to objects and provides CRUD, querying, and relation handling. It integrates with PSX components and SQL backends for simple, model-driven persistence.
psx/record is a small, framework-agnostic value-object library: a Record is a typed bag of key/value pairs with helpers for reading, writing, patching (merging new data through filters), filtering, and converting to array/JSON. It is not a Laravel package — there is no service provider, facade, or config file — so you treat it as a plain Composer dependency you reach for when you want a DTO-ish container that plays well with psx/data, psx/schema, and the wider Apioo stack.
Minimal steps:
composer require psx/record
First look at src/ in the repository — the public surface is small and centred on Psx\Record\RecordInterface and Psx\Record\Record. Read those two files before anything else; the README is thin and the code is the documentation.
First use case — wrapping a request payload so you stop passing arrays around:
use Psx\Record\Record;
$record = Record::fromArray($request->validated());
$record->get('name'); // string|null
$record->has('email'); // bool
$record->getProperties(); // array of keys
$record->toArray(); // plain array back out
Because it is just PHP objects, it drops into a Laravel app with zero wiring: construct records in controllers, form requests, jobs, or console commands and pass them to services instead of associative arrays.
DTO layer over Eloquent. Keep models at the persistence boundary and hand Record instances to your domain code. This gives you a single, explicitly-shaped object that is trivial to serialise in a job or an API response:
$record = Record::fromArray($user->only(['id', 'name', 'email']));
Patch/merge instead of array_merge. The patch()-style API on Record lets you overlay new data (typically with a filter attached so incoming strings are coerced) while keeping the existing record intact. This is the pattern to use for partial updates:
$record->patch($request->only(['name', 'email']));
Filtering/casting as an anti-corruption layer. Combine psx/record with psx/filter so that values from HTTP or a message queue are coerced once, at the edge, rather than repeatedly in your service classes. Wrap the result in your own small factory:
final class UserPayload
{
public static function fromRequest(array $data): Record
{
return Record::fromArray($data); // apply casts here
}
}
Use as a Laravel API Resource substitute for non-HTTP contexts. Records serialise cleanly; if you find yourself writing (array) $dto or json_decode(json_encode(...)) in tests, a Record with toArray() is a lighter alternative.
Integration tips.
RecordFactory in a service provider so the Psx\Record\Record class never leaks into controllers.toArray() in the constructor and rebuild with Record::fromArray() in handle() — this keeps job payloads plain arrays and avoids serialising library internals.Http::get()->json()) where a stdClass is awkward and a full model is overkill.psx/filter or your own rules; do not expect Laravel's Validator to understand records.composer.json and keep an eye on the Apioo monorepo for breaking changes — these packages move together.psx/schema/psx/data.Record instances compare by reference unless the package exposes a value comparison (check the class for equals). Do not use records as array keys or rely on == in tests.Cache::put / queue serialisation will work, but always round-trip through toArray() for anything that crosses a process boundary. Keep records out of Session unless you control the class version.dd($record) shows the internal property store; if a key seems missing, check getProperties() rather than toArray() — null values may be stored but filtered differently on output. Also check the constructor/factory signature: passing a flat array to the constructor is not always the same as Record::fromArray().Record rather than modifying it; the interface is the contract to code against (RecordInterface). If you swap the backing store later, your services still type-hint the interface, which is the main long-term benefit of adopting the package at all.readonly class with constructor promotion or spatie/laravel-data will give you more (validation, casting, resource output) with far better support. Choose psx/record when you are already in the PSX/PSX-API ecosystem or need something deliberately minimal.How can I help you explore Laravel packages today?