feat: implement phase 2 ORM improvements#24
Merged
davebarnwell merged 3 commits intomainfrom Mar 8, 2026
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Implements “Phase 2” ORM ergonomics improvements by adding instance-aware validation hooks, opt-in strict field assignment, and a small set of focused query helper APIs, while tightening typing across the library and tests.
Changes:
- Add strict field mode (per-model static flag + runtime toggle) and instance-aware validation hooks with legacy
validate()compatibility. - Add focused read helpers:
exists(),existsWhere(),fetchAllWhereOrderedBy(),fetchOneWhereOrderedBy(), andpluck(). - Enable
declare(strict_types=1)across core + tests and expand regression coverage + docs to match the new preferred APIs.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Model/Model.php | Core implementation: strict fields, instance validation hooks, focused query helpers, and tightened signatures/types. |
| src/Model/Exception/ModelException.php | Enable strict types for exception base class. |
| src/Model/Exception/UnknownFieldException.php | Enable strict types for exception. |
| src/Model/Exception/MissingDataException.php | Enable strict types for exception. |
| src/Model/Exception/InvalidDynamicMethodException.php | Enable strict types for exception. |
| src/Model/Exception/ConnectionException.php | Enable strict types for exception. |
| src/Model/Exception/ConfigurationException.php | Enable strict types for exception. |
| tests/Model/CategoryTest.php | Adds coverage for query helpers, strict fields, instance validation hooks, and legacy validation compatibility; enables strict types. |
| tests/Model/SqliteModelTest.php | Enables strict types. |
| test-src/Model/ValidatingCategory.php | Test helper model implementing instance validation hooks. |
| test-src/Model/LegacyValidatingCategory.php | Test helper model exercising legacy static validate() support. |
| test-src/Model/StrictCategory.php | Test helper model enabling strict field mode via static flag. |
| test-src/Model/Category.php | Enables strict types in test helper model. |
| test-src/Model/SqliteCategory.php | Enables strict types in test helper model. |
| test-src/Model/SqliteCodeCategory.php | Enables strict types in test helper model. |
| test-src/Model/SqliteStringCodeCategory.php | Enables strict types in test helper model. |
| test-src/Model/IsolatedConnectionCategoryA.php | Enables strict types in test helper model. |
| test-src/Model/IsolatedConnectionCategoryB.php | Enables strict types in test helper model. |
| README.md | Documents new focused query helpers, instance-aware validation, strict field mode, and preferred camelCase dynamic methods. |
| EXAMPLE.md | Updates examples to lead with camelCase dynamic methods and document new helpers/validation/strict fields. |
| .php-cs-fixer.dist.php | Enables strict types in tooling config file. |
Comments suppressed due to low confidence (1)
src/Model/Model.php:105
__constructnow requires anarrayand the file isstrict_types=1, which turns previously-tolerated inputs (e.g.nullorstdClass) into aTypeError. If backward compatibility is intended, consider acceptingarray|object|null(oriterable) and normalising to an array before callinghydrate()(and drop the redundantis_array()check once the signature matches the supported inputs).
public function __construct(array $data = [])
{
static::getFieldnames(); // only called once first time an object is created
$this->clearDirtyFields();
if (is_array($data)) {
$this->hydrate($data);
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR implements the Phase 2 roadmap work for the ORM by improving developer ergonomics and tightening the typed API without expanding the package into a heavyweight query builder or relationship system.
The user-visible effect is that model validation can now inspect instance state directly, strict field mode can fail fast on unknown assignments, and several common read patterns no longer require handwritten SQL fragments. The library also now declares strict types across the PHP files that participate in the package and test suite, which makes the public API clearer to IDEs and static analysis.
The root cause behind the current friction was that the base model still leaned on legacy patterns that were functional but awkward in practice. Validation lived on a static hook even though writes operate on object state, typo-prone field assignments could silently disappear at persistence time, and common "exists", ordered fetch, and single-column read operations required users to assemble SQL fragments manually. The codebase also still carried loosely typed entry points even after the package moved to PHP 8.3+.
The fix keeps backward compatibility where it matters while making the preferred API more explicit.
Modelnow supports instance-awarevalidateForSave(),validateForInsert(), andvalidateForUpdate()hooks, while still honoring the legacy staticvalidate()method for older models. Strict field mode can be enabled per model or at runtime. The ORM also adds focused helper methods such asexists(),existsWhere(),fetchAllWhereOrderedBy(),fetchOneWhereOrderedBy(), andpluck()to cover high-value query cases without introducing a chainable query builder. On top of that, the PHP files now declare strict types and several signatures inModelhave been tightened.The PR includes new regression coverage for strict field behavior, instance validation hooks, legacy validation compatibility, and the new query helpers. The documentation in
README.mdandEXAMPLE.mdhas been updated to lead with camelCase dynamic methods, instance-aware validation, strict field mode, and the new helper APIs.Validation
I validated the changes with the existing quality gates:
vendor/bin/phpunit -c phpunit.xml.distvendor/bin/phpstan analyse -c phpstan.neon