kdyby/strict-objects
kdyby/strict-objects provides a PHP trait that makes objects strict: reading or writing undeclared properties throws an exception instead of silently creating dynamic ones. It catches typos and enforces explicit property declarations in Nette projects.
Architecture fit
kdyby/strict-objects is a single-responsibility behaviour trait. It supplies magic member-access hooks (__get, __set, __isset, __unset, __call, __callStatic) that raise a MemberAccessException when a class is asked for a member it does not declare, replacing PHP's default silent null or unconditional method dispatch. There is no container binding, no service provider, no configuration, and no framework coupling — the architectural question is therefore not "does it fit Laravel" but "does it fit the object model of the classes we put it on."
Good candidates: hand-written domain value objects (Money, Email, DateRange), non-Eloquent DTOs, command/query handlers, small result objects, and any class whose public surface is fully declared and constructor-injected.
Poor candidates, and these dominate a typical Laravel codebase: anything inheriting magic-member behaviour from a parent or another trait — Eloquent models (whose __get/__set/__isset back attributes, casts and relations), Illuminate\Support\Fluent, Macroable consumers (whose __call drives macro dispatch), Doctrine entities with proxy hydration, container property-injection targets, and Blade-facing view models that lean on null-coalescing. In these cases the trait does not fail loudly; it silently displaces the framework's hook and breaks the class in ways that surface far from the change.
Integration feasibility
Installation is trivial (composer require, then one use statement per class). Integration effort is measured in developer-minutes per class, not tickets. The real cost is the per-class validation pass: identifying every class where introducing the trait changes behaviour under existing magic-method dispatch. That audit, not the install, is the work.
Technical risk
NOASSERTION. Until a human reads the license file, this cannot clear a standard OSS intake review. Kdyby's historical packages were permissive, but "probably fine" is not an approval.composer.json may cap php below your runtime or pin dev dependencies that conflict in a Laravel 10/11 graph. The trait body itself is version-agnostic plain PHP, so this is a packaging problem — but it can block composer update, and --ignore-platform-reqs is not an acceptable workaround.__get/__set marks a class as "magic" to PHPStan and Psalm, which then stop reporting genuinely undefined properties on it. The package trades a build-time error for a runtime error on precisely the classes where you most want static guarantees. In a codebase already running PHPStan at level 6+, this is likely net-negative.isset($obj->prop) and expects a boolean; serializers and hydrators introspect via get_object_vars and reflection; json_encode bypasses __get entirely. A "strict" object can therefore serialize successfully without the member that reading it throws on.Key questions
--ignore-platform-reqs? (blocking)__get/__set/__call from a parent or another trait? Trait methods win over inherited ones silently — verify per class.isset/empty semantics — throw, or return false? This single answer determines whether probing code breaks.final with declared, typed properties? Declaration is cheaper and safer than a runtime guard.Stack fit
Laravel-side, treat this as a per-class opt-in utility, never a base-class concern. It must not be applied to Illuminate\Database\Eloquent\Model subclasses or to Macroable consumers, where its hooks collide with framework dispatch. Safe first targets are domain value objects, non-Eloquent DTOs, and constructor-only domain services.
Migration path
Compatibility
require constraints against the real lockfile.Fluent, and any class whose parent defines the same magic methods.__get" behaviour in PHPStan/Psalm that suppresses diagnostics; baseline or annotate deliberately.Sequencing
composer update --dry-run against the real lockfile.MemberAccessException during serialization, queueing or view rendering?Maintenance
Ownership sits with the adopting team regardless of who publishes the package. With no release since 2020, expect to patch constraints — and eventually the code — yourself. Budget it as "vendored code with a public upstream," not as "a dependency." Upgrade risk is near zero because the API surface is tiny, but so is upstream responsiveness: a future PHP change to magic-method semantics lands on you.
Support
No SLA, no security disclosure policy, one maintainer, 31 stars, 0 dependents. The escalation path is a GitHub issue that may never be read. The internal runbook must be "we can patch this in an hour because it is forty lines we have read."
Scaling
Runtime overhead is negligible: the hooks fire only on access that would otherwise be an error, and backtrace construction in the exception is paid on bugs, not on the hot path. The scaling concern is cognitive — every new engineer must know which classes are "strict" and why isset behaves unexpectedly there. Enforce that with a namespace convention or a lint rule, not tribal knowledge.
Failure modes
null used to be. A nullable read in a Blade partial, a queued job hydrating a payload, or a third-party package probing isset($obj->foo). Turning silent-null bugs loud is the point — but loud in production is worse than loud in CI. Confirm error handling reports class and member context independent of APP_DEBUG.json_encode, toArray and reflection hydrators bypass __get, so a strict object can serialize successfully without the member that reading it throws on. Test the round-trip explicitly.__get can produce no error, no warning and no behaviour change. Per-class verification is mandatory.Ramp-up
Minutes to use; roughly the length of a magic-method audit to use safely — a day for a small app, a week for a large legacy one. Given a score of 0.485 and an opportunity score of 34.31, the defensible recommendation is to document the pattern internally, prefer declared typed properties plus static analysis, and treat this repository as a reference implementation to copy from rather than a dependency to adopt.
How can I help you explore Laravel packages today?