Skip to content

Commit safety

A URL must not reach the engines before the transaction that changed the row commits, or a rolled-back write is announced. Yii2 makes this harder than Laravel or Doctrine:

  • yii\db\Connection fires EVENT_COMMIT_TRANSACTION / EVENT_ROLLBACK_TRANSACTION only for the outermost transaction. Nested beginTransaction() calls are savepoints and fire nothing.
  • The classes involved (Transaction, Schema::createSavepoint()) are not designed for interception without replacing the connection's command classes in your configuration.

What the package does

  1. In the ActiveRecord event the URLs are resolved while the old state is live (that is why the observer is synchronous, see the core's adapters guide).
  2. $db->getTransaction() is null: the change is autocommitted, the URLs go to the collector right away.
  3. A transaction is open: the URLs are staged (core Transaction\VerifyingStaging) together with a verifier, a closure that re-reads the row by primary key with a plain Query (bypassing find() scopes) and answers whether the change landed:
  4. insert / update / rename: the row exists and carries the written values. Only the values whose text form every driver agrees on are compared (integers, strings, booleans, backed enums, null); a DECIMAL the driver pads to the scale of the column, a timestamp it hands back with a zone suffix and a JSON document it re-spells are skipped like a column the row does not carry, so they never discard the announcement.
  5. delete: no row. An update or a delete is staged under the subject (Post#7), so a second change of the same record in the same transaction joins the first instead of replacing it: the verifiers run once, at the end, and the first change's expected values are no longer in the row. An insert is staged on its own, because a savepoint rollback frees its primary key and the next insert takes it back.
  6. EVENT_COMMIT_TRANSACTION: the verifiers run, the URLs of the changes that landed go to the collector, the rest is dropped with a debug line (discarding N staged URL(s) of Post#7, change not committed). A change that did not land drops every URL it produced, including via pages and the old URL of a renamed page: announcing "deleted" for a page that still exists is the one outcome to avoid.
  7. EVENT_ROLLBACK_TRANSACTION: everything staged on that connection is dropped without a query.

Cost: one SELECT ... WHERE <pk> = ? per changed record, only for changes made inside an explicit transaction. Conformance A02, A05, A05b, A05c pass without touching the connection configuration.

What it cannot tell

  • An update inside a rolled-back savepoint whose written values happen to equal the row's current values (UPDATE ... SET title = title) passes verification and is sent as a harmless refresh of an existing page.
  • A verifier that throws (connection gone) counts as landed and is logged at warning: a stale URL costs one crawl, a lost one costs the update.
  • Records without a primary key cannot be verified; their URLs are sent (one warning per change inside a transaction, nothing at all outside one — there the URLs never go through a verifier).

Long-running commands

Staged URLs are delivered on the commit event, so an import that commits every 1000 rows sends as it goes. Without transactions the collector fills until the command ends (collector.max_urls caps it); call Yii::$app->indexnow->flush() between batches if you prefer smaller requests.