Download the PHP package nandan108/attrecord-migrations without Composer

On this page you can find all versions of the php package nandan108/attrecord-migrations. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package attrecord-migrations

attrecord-migrations

CI Coverage Packagist Version PHP Version

Declarative schema convergence for attrecord: the Record class is the schema, so evolution is convergence — introspect the live database, diff it against the attribute-derived TableSchema, and apply a classified, guarded ALTER plan. No migration files, no second source of truth, no down scripts.

Design contract: attrecord's docs/arch-migrations.md — read it for the full rationale (why convergence beats a migration chain for attribute-driven schemas and file-replacement deployments, prior art, and the non-goals fence).

Backends: MySQL/MariaDB, PostgreSQL, SQLite — same tri-dialect matrix as attrecord itself.


Installation

Requires PHP 8.1+ and nandan108/attrecord ^0.17.1 || ^0.18. No other runtime dependencies.

Footprint: src/ is ~143 KB across 33 files (~89 KB of that is code — the other 38% is docblocks), so the published package is ~64 KB zipped. That is on top of attrecord itself, and only for consumers who want schema convergence: the split exists so writing rows never drags in the machinery that rewrites schemas.

It is a separate package by design: attrecord's DDL producer is fresh-install only, and schema evolution carries risks (destructive ALTERs, live introspection, a safety model) that have no business being reachable from a library you pull in to write rows. Nothing here runs unless you call it.


Quick start

A converged database re-plans empty — that invariant (create → introspect → identical canonical tuples on both sides) is pinned by the test suite on all three backends.

Change classes — the safety model

Every planned change carries a class; apply(allow:) is a ceiling over the ladder Safe → Destructive → Assisted, each admitting everything at or below it:

Class Applied Examples
Safe (default) yes ADD COLUMN (nullable/defaulted), ADD INDEX, widenings (VARCHAR(64)→(191), SMALLINT→INT), default changes, declared renames. ADD UNIQUE/ADD FK/ADD CHECK are Safe but flagged mayRejectExistingRows — they can loudly reject (atomic failure, never silent loss). Constraint drops are here too — foreign keys and CHECKs alike: the drop costs no data, and an undeclared constraint contradicts the model rather than adding to it (see below).
Destructive opt-in only DROP COLUMN (declared absent or not), narrowing conversions, NULL→NOT NULL tightening, undeclared-index drops.
Assisted opt-in, its own ceiling A changed generation expression. The statement is known and carried; what it needs is a person who read it and said yes. Not reached by opting into Destructive — a widened destructive policy must not sweep in changes chosen deliberately.
Manual never PK changes, auto-increment drift, anything the pipeline is unsure about, SQLite rebuild-only changes. No SQL — a reason to read, not a statement to run, which is why no ceiling admits it.

The pipeline's bias is fail-safe: an unparseable live type or ambiguous facet degrades to Manual with a reason. It never guesses an ALTER (the Doctrine schema-tool:update lesson — normalization + classification are the load-bearing parts, not the diff).

Why a constraint drop is Safe

Dropping a constraint removes no row and no column value, and re-adding it a moment later always succeeds, because the data that satisfied it still does. "Drops" in the Destructive row means drops of data-bearing things — columns, tables.

The sharper reason is what an undeclared constraint is: a rule forbidding writes the Records permit, so leaving it in place is drift that silently overrules the schema. It is not hypothetical — MySQL's CHANGE COLUMN carries a column's constraints along with it, so a declared rename can leave an ON DELETE CASCADE pointing at a table the column no longer belongs to, quietly deleting rows on the next parent delete.

The line is contradiction, not losslessness, which is why an undeclared index stays Destructive: it forbids nothing, so it adds to the model rather than overruling it. Being wrong about one is also expensive and invisible — it may be an operator's tuning index, and dropping that degrades a query plan under load rather than raising anything. Foreign keys and CHECKs both forbid, and both drop at the default ceiling.

Ownership is a separate axis, with two granularities: #[Unmanaged] names one object as somebody else's, PartiallyDeclared says it of a whole table. Prefer the first where you can name what is not yours — the interface buys its silence by going quiet about all drift on that table.

A foreign key or index whose shape changed is one replace_foreign_key / replace_index carrying both statements, not a separate drop and add, so no ceiling can authorise half of a replacement.

Saying what the Records do not

A schema describes what exists. Three things a differ has to know are in no schema at all, and the attrecord attributes that declare them (0.19.0) are inert until this package reads them.

A column rename is declared, never inferred.

produces a data-preserving RENAME/CHANGE COLUMN instead of a destructive drop+add. Inference from drop+add similarity is a known trap (Skeema refuses it; Django prompts a human).

An index rename is inferred, because a wrong guess there costs a rebuild rather than rows. An orphaned add and an orphaned drop of identical shape are the same index, emitted as one rename_index; ambiguity falls back to plain create + drop. Declare it when the shape changed too, which is the case no heuristic can see:

A retired object is declared absent, which is what makes dropping it safe rather than a guess:

An index or unique key declared absent drops at the Safe ceiling. A column does not — saying you meant it does not bring the values back — but the plan now says declared absent since 2.0.0 rather than exists live but is not declared, so a Destructive plan distinguishes the deliberate removal from the surprise.

Deleting an #[Absent] line later is safe: the object falls back to the undeclared path, still reported, just no longer automatic. The exception is a PartiallyDeclared table, where nothing undeclared is reported at all.

An object somebody else owns is declared unmanaged:

Change-attached steps

When a data transform rides a schema change, the schema state is the marker — a TEXT column is the unwrapped state, JSON the wrapped one — so attach the transform to the change rather than versioning it:

The selector is "kind table.subject" in the same vocabulary a plan prints, so it can be read off plan()'s own output. Steps run at their position in the apply order, are recorded in the run ledger, and a failing step stops the run and is reported against the change it was attached to.

A selector matching no change is a no-op, on purpose. On the second boot of a converged install the change was applied long ago and the plan is empty, while your code still attaches its step — erroring there would fail every subsequent boot. The mistake that can be caught, a kind that does not exist, is refused when you write it; unmatched steps are recorded, so "my backfill never ran" is answerable afterwards.

The pair is not atomic on MySQL or MariaDB, and no API can make it so: DDL auto-commits there, so a step and its change cannot share a transaction. PostgreSQL and SQLite have transactional DDL and let a caller wrap the whole apply. Write steps to be re-runnable where the transform allows it.

Run-once data steps

Schema converges because its state is introspectable. Data shape is not — so transforms the differ cannot see (content changes within an unchanged column type) use the run-once registry:

At most once per database, recorded in the step ledger (attrecord_schema_steps) — the one place the ledger is authoritative. There is no down(): on a file-replacement deployment model no code exists to run it at the right moment; roll forward or restore a backup.

Circular foreign keys

Two tables that reference each other have no creation order that works while every FK is emitted inline — whichever goes first points at a table that does not exist yet. Rather than refuse the model, one edge of each loop is deferred:

Nothing to configure: the cycle is found in the declared graph, and which edge gets deferred falls out of the input order, so the same model set always resolves the same way. The deferred ADD is ordered after every create in the plan, because its target may be created later in the same run.

Tables whose shape is computed

Some tables have a shape that is only known at runtime: a registry that grows a column per registered dimension, an extension table a plugin writes into. A Record class cannot describe those, so plan() also accepts a ready-made TableSchema — derive one with attrecord's TableSchema::extendedWith() and pass it alongside your class-strings:

Those columns are then created, converged and diffed like any other — including being added to an existing table when the runtime set grows. fingerprint() covers them too, so the fast path notices when the runtime set changes. Describing them beats the alternative, a hand-written ALTER TABLE run at boot: a second source of DDL that no tooling can see or verify.

When you can't describe them: PartiallyDeclared

If the extra columns genuinely cannot be enumerated, the Record can opt out of drift detection for whatever it does not declare:

The differ is then narrowed to what the Record declares: missing declared columns and indexes are still added, declared ones still converge, but nothing undeclared is ever proposed for dropping — columns, indexes and constraints alike. The trade-off is one-directional, which is why it is opt-in per Record: on such a table, a genuinely stray column from an old version is never surfaced either. Prefer describing the columns when you can; this is the fallback.

If you can name the objects that are not yours, #[Unmanaged] is the better tool — it excludes exactly those and leaves the rest of the table under the differ's eye. Reach for the interface only when the shape is genuinely computed and cannot be named ahead of time.

(The two do not combine: a TableSchema you built is taken at face value, since the point of building it is that the columns are now described.)

Fingerprint fast path

sha256 over the dialect's own buildCreateTable() output. Store the last-converged value (e.g. a WordPress option) and skip even plan()'s introspection while the running code's fingerprint matches.

The ledger

Every apply() run is recorded in attrecord_schema_runs (statements + outcomes + error) — for forensics only. The differ never reads it: truth about the live schema comes from the live schema, so a restored backup or hand-edited database simply re-plans correctly.

Both ledger tables can live under your own naming — subclass the Record with its own #[Table] and hand the subclass to the migrator:

Limitations (all fail LOUD as Manual or Assisted, never silently wrong)


Running tests

Integration tests reuse attrecord's containers but their own database (attrecord_migrations_test, auto-created by the test support layer) — never attrecord's attrecord_test. Environment variables (defaults shown):

An unreachable engine makes its suite skip, which locally is a convenience and in CI would be a lie — so CI runs with --fail-on-skipped. A skipped backend is not a passing backend.

Two suites carry most of the weight, both against real engines:

Code style & static analysis

Style is enforced with PHP CS Fixer (the @Symfony ruleset plus project overrides in .php-cs-fixer.php), and types with Psalm at level 1:

All three run in CI against PHP 8.1–8.4 with MySQL 8.0/8.4, MariaDB 10.11/11.4, PostgreSQL 14–17 and SQLite — the same published attrecord release a consumer would get.


Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md for the dev setup and the checks to run. Behavioural design changes belong in the design contract (attrecord's docs/arch-migrations.md), whose §7 is the non-goals fence.


License

MIT © Samuel de Rougemont


All versions of attrecord-migrations with dependencies

PHP Build Version
Package Version
Requires php Version ^8.1
nandan108/attrecord Version ^0.19 || ^0.20
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package nandan108/attrecord-migrations contains the following files

Loading the files please wait ...