Repository navigation
Conversation
…erver - Remove the global ORMSubject singleton; observer dispatch now flows through ObserverBridge, a DatabaseEventObserverInterface implementation attached per DatabaseExecutor - Query builders return OrmSqlStatement (extends SqlStatement) stamped with the ORM event and affected table - All writes now notify: save(), delete(), deleteByQuery(), bulkExecute() (after commit), buildAndExecute() and built statements executed directly on the executor - save() keeps its previous semantics via deferred dispatch: observers receive the entity rehydrated with generated keys - New ObserverEvent::SoftDelete case: soft deletes now fire an event - New StatementHookInterface: before-execute hook with veto (throwing aborts the write) - Repository::addObserver() also accepts raw DatabaseEventObserverInterface; new Repository::removeObserver(); addDbDriverForWrite() re-attaches observers to the new write executor - ObserverData::getStatement() exposes the SQL statement to observers - Require byjg/anydataset-db ^7.0 and migrate tests/docs off the driver query methods removed in that release
FieldUuidAttribute exposed only primaryKey/fieldName/fieldAlias/syncWithDb and hard-coded the UUID select/update mappers, so a binary UUID foreign key could not register a relationship via ORM::addRelationship — unlike a plain FieldAttribute. Add the parentTable pass-through (updateFunction/selectFunction stay fixed to the UUID mappers). The join SQL built from the relationship is dialect-agnostic (QueryBasic::getJoin concatenates a standard INNER JOIN ... ON), so this works across MySQL, Postgres, Sqlite and SqlServer. insertFunction is intentionally not exposed (the UUID conversion for writes is handled by the update function on the value-prepare path).
getRelationship() retried incomplete relationships (parent primary key still '?') with an inverted condition: it skipped exactly the case where the parent mapper had since been registered, so a child mapper created before its parent kept a '?' primary key and produced a broken join (parent.? = child.fk). Flip the guard so the retry runs once the parent is available and the real primary key is filled in. This matters when relationships are declared via FieldAttribute(parentTable:) and mappers register in dependency-injection order rather than parent-first.
…ORM::getQueryInstance joinRelated/leftJoinRelated/rightJoinRelated derive a JOIN's ON condition from a registered parentTable relationship and connect a table to one already in the query. If they are not directly related, the intermediate tables on the shortest relationship path are joined automatically (tables already in the query are skipped); the query keeps its own base table, so it composes with a repository query. A shared addRelatedJoin() helper walks the path and delegates to join()/leftJoin()/rightJoin(). ActiveRecord::joinWith() now builds on joinRelated() (starting from the model's own table) instead of ORM::getQueryInstance. ORM::getQueryInstance() is removed — joinRelated/joinWith replace it, keeping the base table, supporting INNER/LEFT/RIGHT, and auto-joining intermediates without the malformed double-join it produced. The relationship registry (addRelationship/getRelationship/ getRelationshipData) is unchanged. Docs (auto-discovering-relationship, comparison-with-other-orms) and CHANGELOG-7.0 updated.
…ion) Passing a class-string to joinRelated/leftJoinRelated/rightJoinRelated/joinWith now resolves it to its table via new ORM::getTableFromClass(), registering the entity's mapper on demand. Registration is reflection-only (no DB connection), so relationship auto-discovery works on a request that never touched that entity's repository. Name each entity in a multi-hop path (like hasManyThrough's through-model) to make the intermediate mapper known. Backward compatible: a table-name string does not class_exists, so it is used verbatim.
Widen the PHP constraint to ">=8.3 <8.7"; the previous "<8.6" is exclusive and excluded PHP 8.6. Bump byjg/* dependencies to ^7.0. While 7.0 is unreleased these resolve to 7.0.x-dev from each component's 7.0 branch, via minimum-stability dev with prefer-stable. Pin PHPUnit to ^12.5 and move Psalm to tools/psalm/composer.json. Psalm enumerates supported PHP versions and no release lists 8.6, so as a require-dev it made "composer install" fail on the 8.6 build before any test ran. "composer psalm" bootstraps the tool and runs it. CI: add PHP 8.6 to the matrix; the Psalm job runs on 8.5. Housekeeping: rename phpunit.xml.dist to phpunit.xml, carry the 6.0 changelog onto this branch, and update CHANGELOG-7.0.md.
licensing.html returns 404; the page is /license/. The Dependencies mermaid drifted from composer.json, which is now what the site's graph is built from (collect-deps.py in add-doc).
This copy came from a template shared by eleven repositories: its Issues link was the placeholder github.com/your-repo/issues, and nothing in it was specific to this repository. GitHub now serves the account-wide CONTRIBUTING.md from byjg/.github, which links the guide on opensource.byjg.com.
- Install Psalm as psalm/phar instead of the tools/psalm project - MapperFunctionsTest stubs DatabaseExecutor instead of mocking it without expectations (two PHPUnit notices) - phpunit.xml: PHPUnit 12.5 schema and failOnPhpunitDeprecation - Workflow: actions/checkout@v7 - README and docs: Installation and Running Tests sections, sidebar positions, https links - Remove FUNDING.yml (comes from byjg/.github), IDE files and test.php - CHANGELOG-7.0.md: in-development banner
Repository::save() replaced the primary key of a new instance: the table's seed function generated a new one, and without a seed function the last insert id read back after the insert took its place, so an id of 100 came back as 1. The given key is now inserted as is; a key is only generated or read back when it is empty.
TableUuidPKAttribute generates a version 7 UUID (time-ordered) in PHP and FieldUuidAttribute writes UUIDs in the syntax of the repository's database: X'...' for BINARY(16) on MySQL and SQLite, 0x... for BINARY(16) on SQL Server, and the quoted text for a native uuid on PostgreSQL. It used to write X'...' everywhere, which PostgreSQL and SQL Server do not accept. New HexUuidLiteral::v7() and HexUuidLiteral::forDatabase(). The MySQL, SQLite and PostgreSQL table attributes are deprecated aliases of TableUuidPKAttribute; the MySQL one called uuid_to_bin(uuid()), which failed on any other database. PostgresUuidLiteral now writes the quoted text form instead of an unquoted \x.... The UUID repository tests run on MySQL, SQLite, PostgreSQL and SQL Server (pdo_dblib and pdo_sqlsrv), which are added to docker-compose and CI.
The SQL Server tests called markTestSkipped() inside setUp(), so on a machine without the extension (CI has no pdo_sqlsrv) tearDown() still ran and used the executor setUp() never set: 6 errors. Each per-database test now declares #[RequiresPhpExtension], which PHPUnit checks before setUp().
- An empty primary key is left out of the INSERT instead of written as NULL: MySQL and SQLite generate the key anyway, but PostgreSQL (serial) and SQL Server (identity) reject an explicit NULL. - created_at and updated_at (NowUtcMapper) and a soft delete (deleted_at) write CURRENT_TIMESTAMP. They wrote the dialect's sqlDate(), a text made to display a date, which PostgreSQL rejects for a timestamp column and SQL Server could not convert. Tested on MySQL, SQLite, PostgreSQL and SQL Server (dblib and sqlsrv); CURRENT_TIMESTAMP was also checked on Oracle.
A property typed with a backed enum (public ?Status $status) is saved as the enum's value, loaded back as the enum, and filters by its value in queryInstance($model). byjg/serializer 7.0 does the conversion (a1ca2ea); RepositoryBackedEnumTest runs the three paths on SQLite, and docs/getting-started-model.md shows the model.
- Paging returned the wrong rows past the first page. - where() accepted an array that always failed with a TypeError.
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
Version 7.0 rewires the entire observer system on top of the low-level
DatabaseExecutorobserver frombyjg/anydataset-db7.0, and migrates off the driver query methods removed in that release.ORMSubjectremoved: dispatch now flows throughObserverBridge, aDatabaseEventObserverInterfaceimplementation attached one-per-DatabaseExecutor(tracked in aWeakMap— no global state, nothing to clear in tests)save(),delete(),deleteByQuery(),bulkExecute()(after commit, nothing on rollback),buildAndExecute(), and built statements executed directly on the executorsave(): deferred dispatch keeps notifying after key rehydration, so Insert observers still receive the entity with its generated PKOrmSqlStatement: every builderbuild()returns it, stamped with the ORM event and affected table; exposed to observers viaObserverData::getStatement()ObserverEvent::SoftDelete: soft deletes now fire an event (previously silent)StatementHookInterface: before-execute hook with full SQL/params access; throwing vetoes the writeRepository::addObserver()also accepts a rawDatabaseEventObserverInterface; newremoveObserver()ObserverProcessorInterfaceimplementations require no code change. Breaking changes and the migration table are documented inCHANGELOG-7.0.md.Breaking changes
ORMSubjectremoved (includingclearObservers())DatabaseExecutorinstances no longer cross-notifyRepositoryprotected helpersinsert()/insertWithAutoInc()/insertWithKeyGen()/update()now receive the pre-builtOrmSqlStatementbyjg/anydataset-db: ^7.0(driver query methods removed upstream)Test plan
tests/ObserverBridgeTest.php: soft delete event, direct executor writes, bulk commit/rollback, per-executor scoping, veto hook,getStatement(), raw passthrough,addDbDriverForWrite()re-attach,onError()swallowinganydataset-db7.0.x-dev; Psalm clean