psx/model
PSX\Model is a lightweight PHP model and entity library from the PSX framework. It offers annotated model classes and DTOs, plus schema generation, hydration, and serialization helpers, letting you define typed, self-documenting data structures without a full ORM.
psx/model is a library of generated POPOs (Plain Old PHP Objects) — one class tree per open specification. You are not installing a framework feature; you are installing a vocabulary of ready-made DTOs for schema.org/ActivityStreams (ActivityPub), OpenAPI/Swagger, OAuth2, JSON-RPC, and TypeAPI (Api), plus a small Common set (Error, Message, Collection, Metadata) and the POPO contract that ties them together.
Minimal steps:
composer require psx/model
There is nothing to configure — no service provider, no config file, no facade, no migrations. It is a plain PSR-4 library under the PSX\Model\ namespace. In a Laravel app you just use the classes.
Where to look first, in this order:
src/POPO/Proxy.php — the interface every model implements. Read this before anything else; once you understand get() / set() / getAll() / setAll() / jsonSerialize(), every other class in the package is obvious.src/POPO/Popo.php — the trait providing the generic accessors and JSON serialization.src/ — each subfolder is one specification. Pick the folder matching the payload you actually need to model.src/OAuth2/Token.php or src/Api/Operation.php, to see the naming convention for setters.First use case — emit a typed response instead of a hand-rolled array:
use PSX\Model\OAuth2\Token;
$token = new Token();
$token->setAccessToken($jwt);
$token->setTokenType('bearer');
$token->setExpiresIn(3600);
$token->setScope('openid profile');
return response()->json($token); // models are JsonSerializable
The second use case, and the one that actually pays off: describing your own API. Instantiating Api / Swagger models lets you build a spec document programmatically and hand it to psx/schema or TypeAPI to generate OpenAPI, JSON Schema, or client SDKs — instead of maintaining YAML by hand.
Be clear-eyed about what you are getting: 9 stars, 0 dependents outside the apioo ecosystem (Fusio, TypeAPI). It is a first-party building block of one vendor's stack, published for reuse. Treat it as vendor code you will read, not a community library with a support surface.
Hydrate from arrays, don't hand-write twenty setters. The generic proxy accessors are the day-to-day workhorse:
use PSX\Model\Api\Operation;
$operation = new Operation();
$operation->setAll($row); // DB row / decoded JSON / request payload
$payload = $operation->getAll(); // plain array for caching, diffing, or tests
getAll() recursively returns the underlying property map, which makes it the natural bridge to array_diff_assoc, test fixtures, and json_encode.
Use it as the response contract in your Laravel controllers. The strongest integration point is not Laravel-specific at all: return a model from a controller and let JsonSerializable do the work. This buys you IDE completion, a single place where the shape of a payload lives, and a class name you can assert on in tests:
public function show(int $id): JsonResponse
{
$error = new \PSX\Model\Common\Error();
$error->setSuccess(false);
$error->setTitle('Not Found');
$error->setMessage("Record {$id} does not exist");
return response()->json($error, 404);
}
Pair with psx/schema for anything beyond transport. The models are deliberately dumb; validation, JSON Schema generation, and OpenAPI/TypeAPI emission live in psx/schema, which reads POPO classes via reflection. In practice: define/extend the model, register the class, and you get a schema plus a validator for free. If you are building an API, model → schema → validate → respond is the intended pipeline. Using the models without the schema layer gets you about 30% of the value.
Compose small models rather than flattening. Spec-derived classes reference each other (an operation has responses, a response has schemas). Follow that nesting; it keeps getAll() output structurally identical to the spec, which is what makes round-tripping to JSON and back lossless.
For ActivityPub / federation work, the ActivityPub tree is the most valuable part of the package — it saves you from transcribing the ActivityStreams vocabulary by hand. Build actor and activity objects as models, serialize, and serve with the correct content type. This is the one area where the package gives you something genuinely tedious to replicate.
Never hand-edit generated classes. These are produced from the upstream specification repos. If a field is missing from Swagger\Operation, the fix is upstream (or a local subclass), not a patch to vendor/. Local extension pattern:
class MyOperation extends \PSX\Model\Api\Operation
{
private ?string $tenantId = null;
// ... getter/setter, and include it in getAll() semantics
}
Keep the Proxy contract intact — getAll() / setAll() must round-trip your extra property, or schema generation and serialization will silently drop it.
Testing. Assert on getAll() arrays or on decoded JSON, not on object identity. Fixture-in / fixture-out is the pattern these classes were designed for.
setExpiresIn("abc") will not complain. All type safety is at the schema layer — if you skip psx/schema, you own the validation.@-prefixed or hyphenated key. Always dump getAll() and json_encode() for a new class before wiring it into an API response — a mismatch here produces a silently wrong payload, not an exception.null and serialize as null rather than being omitted. OpenAPI and ActivityStreams consumers often care about the difference. Filter explicitly, or use a spec-aware serializer, rather than assuming json_encode matches the spec's required semantics.additionalProperties: true / free-form maps. If the spec allows arbitrary keys, those are dropped on hydrate. Keep a raw array alongside the model for those nodes.composer.lock is your friend here; ^ ranges can hand you renamed setters.Proxy explicitly rather than via the Popo trait. A quick src/POPO/Popo.php read plus one method_exists($model, 'setAll') check saves a confusing BadMethodCallException.PSX\Model\POPO\Proxy. Implement it on your own DTOs and psx/schema tooling, hydrators, and serializers will treat your classes identically to the shipped ones. That is the package's real API — far more durable than any individual model class.src/ is the documentation. Budget an hour to read POPO/ and skim the spec folder you need; you will come out ahead of any tutorial, including this one.How can I help you explore Laravel packages today?