Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Strict Objects Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

StrictObjects is a single trait that turns PHP's silent "undefined member" behaviour into loud exceptions. It exists because PHP (and frameworks like Nette) will happily swallow $obj->undefinedProperty (returns null / triggers a notice) and $obj->undefinedMethod() (fatal, or a magic __call swallowing it), which hides typos and refactoring mistakes.

Minimum steps:

  1. composer require kdyby/strict-objects
  2. Read src/StrictObject.php — it's ~50 lines; you'll understand the whole package in one sitting. Then glance at MemberAccessException / MemberCallException in src/.
  3. Use it:
use Kdyby\StrictObjects\StrictObject;

final class Money
{
    use StrictObject;

    public function __construct(public int $amount, public string $currency) {}
}
  1. The first real use case: take a plain value object / DTO / entity and try $money->amoutn (typo) or $money->format() (missing method). Before the trait, you get null and a bug in production; after the trait, you get a MemberAccessException / MemberCallException at the exact call site.

This is the whole point: fail fast on typos, no magic, no configuration, no service provider, no DI extension.

Implementation Patterns

The trait cannot be used alongside real magic methods. That is the core constraint and the core pattern. StrictObject defines __get, __set, __isset, __unset, __call (and __callStatic). If your class already relies on __get/__call for lazy loading, proxies, or Nette's Component model, do not put the trait there — you'll break the magic and throw on legitimate calls.

Where it belongs:

  • Value objects and DTOs (Money, UserId, EmailAddress, API payloads) — these should never have dynamic members, so any miss is a bug.
  • Abstract base classes for your domain layer: abstract class AggregateRoot { use StrictObject; }. Every subclass inherits the strictness for free.
  • Entities where you use Doctrine-style explicit accessors rather than magic getters.
  • Services / config objects that you wrote yourself and fully control — a typo like $this->repositroy instead of $this->repository becomes a MemberAccessException instead of a null cascade.

Wrapping a legacy class that has some magic but you want strictness on the rest: use composition, or accept that this trait is mutually exclusive with your existing __get. You can also define __get yourself and delegate only what you need, then call StrictObject behaviour explicitly — but at that point just throw your own MemberAccessException; the trait's value is the "I have no magic, and I mean it" declaration.

Static side: if your trait version includes __callStatic, calling an undefined static method (MyClass::fromMoney() when it's on an instance) becomes a MemberCallException rather than a fatal error. Useful in test suites where you want a clean assertion target.

Integration tips:

  • Combine with PHPStan/Psalm at max level — the trait means runtime and static tooling agree on "no magic members".
  • In Nette/DI, no registration needed: the trait is picked up by autoloading; no services entry. It's zero-config.
  • Use it in final classes only, ideally. An inherited __get from a non-strict parent class will shadow the trait's if the trait is not on that parent — make sure the trait is on the class whose __get would otherwise be hit. If a parent class defines __get and the child uses the trait, the child's trait method wins (traits beat inherited methods), which is usually what you want, but confirm with a quick unit test.

Testing pattern: assert the exception type, not the message. MemberAccessException and MemberCallException both extend \LogicException, so expectException(\LogicException::class) is a stable contract; pinning exact message strings is brittle across releases.

Gotchas and Tips

  • Trait method collision. If you already use another trait that defines __get, __set, or __call (common in framework-provided traits), PHP throws a fatal trait-collision error at class load. Resolve with use StrictObject, OtherTrait { StrictObject::__get insteadof OtherTrait; } or, better, don't combine them.
  • isset() and empty() now throw. This is the most surprising behaviour in practice. isset($entity->relation) on a strict object throws MemberAccessException rather than returning false. Any code that probes members defensively — Laravel/Nette templates, generic serializers, array_map(fn($k) => $obj->$k, [...]) — will now explode. Check templates and reflection-ish generic code before applying the trait broadly.
  • property_exists() and get_object_vars() still work (they bypass magic methods), so hydration/un-hydration code generally survives. get_object_vars() returns only declared, initialised properties, which is exactly the intent.
  • PHP 8 named arguments hit __call differently: $obj->foo(name: 1) calls __call('foo', ['name' => 1]) — the exception still fires correctly, but if you override __call for anything, remember the args array can be associative.
  • Last release was 2020-12-17. Read composer.json before installing on PHP 8.2+/8.3/8.4 — check the php constraint. The code is trivial enough that a lockfile mismatch is the only realistic compat problem; if the constraint is too tight, fork or vendor the 50 lines.
  • License is marked NOASSERTION on the registry. The repo declares its license (check composer.json / LICENSE); confirm it against your org's policy before adding it to a commercial codebase — the automated score gives you no guarantees here.
  • Debugging tip: the exceptions include the class and member name in the message. When a stack trace lands in your log, search the message — it tells you the exact class and property that was mistyped, which is far faster than tracing a silent null.
  • It is not a substitute for typed properties. __get only fires for inaccessible/undefined properties; a declared-but-typed property accessed before initialisation still throws PHP's own Error, not MemberAccessException. Don't write a catch around StrictObject exceptions expecting to catch uninitialised-typed-property errors — those are separate.
  • Performance is a non-issue — you're adding a method that throws. The cost is only paid on the error path, so it's safe to sprinkle across hot-path value objects.
  • Extension point: the exceptions are the extension surface. If you want StrictObject misses to map to your app's own DomainException or to report to Sentry, catch \Kdyby\StrictObjects\MemberAccessException at a boundary layer rather than editing the trait.
  • Don't blanket-apply it to every class via a base class unless you control the whole hierarchy — see the isset() gotcha. Start with value objects and DTOs, expand as you gain confidence.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core