Download the PHP package getgrav/grav-db-kit without Composer

On this page you can find all versions of the php package getgrav/grav-db-kit. 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 grav-db-kit

grav-db-kit

Shared database, migration, job queue and event infrastructure for Grav plugins by Trilby Media: a PDO wrapper with SQLite, MySQL and PostgreSQL dialects, an upgrade-only migrator, a database-backed job queue with an inline drain, an event sink backbone, a rate limiter and a key/value store.

It is a Composer package, not a Grav plugin, and it has no Grav dependency. Each plugin bundles its own copy, namespace-prefixed with Strauss, so plugins update it in their own releases and never need another plugin installed.

The code is extracted from KahunaCart's newer database layer and Forum Pro's services. KahunaCart (1.3), Forum Pro (1.0.10) and Helpdesk Pro all bundle it now; Switching an existing plugin records what changed when the first two moved over.

Requirements

Until the package is on Packagist, plugins use a Composer path repository pointing at ../grav-db-kit.

What is in it

Namespace Classes
TrilbyMedia\GravDbKit\Database Connection, ConnectionFactory, Dialect\{Dialect, AbstractDialect, SqliteDialect, MysqlDialect, PgsqlDialect}, Migration, Migrator, KitTables, KitOptions, Lease, LeaseUnavailable, SchemaGuard, SchemaStateStore, KvSchemaState, CallbackSchemaState
TrilbyMedia\GravDbKit\Schema InfraTables
TrilbyMedia\GravDbKit\Support Clock, SystemClock, KvStore, RateLimiter, RateLimitResult, UnsubscribeSigner
TrilbyMedia\GravDbKit\Events EventSink, CompositeSink, NullSink
TrilbyMedia\GravDbKit\Jobs the job queue (ported separately from KahunaCart's classes/Jobs)
TrilbyMedia\GravDbKit\Testing MigratedDatabase, EngineProvider, SpySink, FrozenClock

The Testing classes ship in the package (they have no PHPUnit dependency) so plugin test suites can use them.

Connections

ConnectionFactory::sqlite() turns on WAL, foreign_keys, busy_timeout=5000 and synchronous=NORMAL, creates the directory when it is missing and puts a deny-all .htaccess and an empty index.html in it, because stock Grav web server rules do not block a .sqlite download from under user/. mysql() takes host/port or unix_socket, plus dbname, username and password. pgsql() also takes sslmode. fromPdo($pdo, 'sqlite'|'mysql'|'pgsql') wraps a PDO someone else opened (a grav-plugin-database named connection, a test fixture). mysqlIsStrict($db) tells a status page whether the server silently truncates data.

Connection is a thin PDO wrapper:

Method Does
run($sql, $params) Prepare and execute; returns the PDOStatement
fetchAll, fetchRow, fetchValue Read helpers (fetchRow/fetchValue return null for no row)
execute($sql, $params) Affected row count
insert($table, $row) Insert and return the generated id (Postgres uses RETURNING id, so the table needs an id column; use run() or upsert() for tables keyed otherwise)
insertMany($table, $columns, $rows) Bulk insert with supplied keys, chunked under each engine's bind-parameter limit
syncIdentitySequence($table) Move Postgres' identity sequence past a bulk load (a no-op elsewhere)
upsert($table, $row, $conflictColumns, $updateColumns) Insert or update on a unique key; empty $updateColumns means insert-if-absent
update($table, $values, $where, $params), delete($table, $where, $params) Affected row count
isUniqueViolation(PDOException) Whether the engine is saying "the row is already there", as opposed to a deadlock or any other error
transaction(fn (Connection $c) => …) Atomic; nested calls become savepoints; the outermost call retries up to three times on SQLite busy, MySQL deadlock/lock-wait and Postgres serialization/deadlock errors, so the callback must be safe to run again
inTransaction(), dialect(), pdo(), options() Accessors

Runtime SQL written against a Connection has to stay in the portable subset the three engines share: identifiers unquoted (validated names) or through Dialect::quoteIdentifier(), every value bound as a parameter, LIMIT/OFFSET on SELECT only, no RETURNING/ON CONFLICT/ON DUPLICATE outside the dialect, times as UTC epoch BIGINT, booleans as INTEGER 0/1.

The dialect gives migrations their DDL fragments (primaryKey(), textLong(), tableOptions()) and idempotency probes (tableExists, columnExists, createIndexIfMissing, dropIndexIfExists, renameTableIfExists), and gives Connection its engine-specific writes and error classification (isRetryableError, isUniqueViolation).

Table names and options

Nothing in the kit writes a table name inline. KitTables names every table the kit itself touches:

Property Default Used by
migrations kit_migrations Migrator tracking table
migrationsUnique uq_ + migrations the UNIQUE (migration, step) constraint the migrator's bootstrap creates
locks kit_locks Lease (and so the migrator's run-lock)
kv kit_kv KvStore, KvSchemaState
jobs kit_jobs the job queue
rateLimits kit_rate_limits RateLimiter

Every name is validated as a plain SQL identifier when the object is built. toArray() lists them.

KitOptions holds the rest:

Property Default Meaning
savepointPrefix sp_ Nested transactions are savepoints {prefix}1, {prefix}2… (Forum Pro used fp_sp_, KahunaCart cp_sp_)
lockOwner null (host and pid) What a lease row says about who holds it; owner() resolves it
lockTtl 600 Default lease length in seconds, and the migration lock's

Pass the same KitOptions to ConnectionFactory and every class that takes one. Classes that take a Connection and no options use the connection's.

Migrations

A migration is a file named NNNN_snake_name.php that returns an instance of Migration:

Steps run in array order and are recorded one at a time (MySQL commits DDL implicitly, so a crash can land between steps), which is why every step has to be idempotent.

SchemaGuard

SchemaGuard::ensure(Migrator $migrator, SchemaStateStore $state, string $policy): array is the per-request "is the schema current?" check. It returns ['policy' => …, 'pending' => int, 'applied' => int].

When the store remembers the current fingerprint, it answers at once with nothing pending. Otherwise it asks the migrator, applies pending steps if the policy allows, and records the fingerprint once nothing is pending. Adding, removing or editing a migration file, or an add-on bringing its directory in, changes the fingerprint, so the full check runs once more.

Policy Applies pending steps
auto on every engine
sqlite only on SQLite; server databases are migrated from the CLI
sqlite-only same as sqlite (Forum Pro's spelling)
manual never; pending steps are only counted

An unknown policy throws InvalidArgumentException rather than quietly behaving like manual. SchemaGuard::normalizePolicy() and SchemaGuard::allows($policy, $engine) are public for plugins that want to check a setting early.

Two stores:

Call ensure() outside any open transaction.

InfraTables

InfraTables::kv|jobs|rateLimits(Dialect $d, KitTables $t) return migration step closures that create the tables the kit's classes use, under the names in KitTables:

The migrations and locks tables are not here, because the migrator creates them before it runs anything.

Lease

A named, expiring lock held as one row in the locks table. It replaces the four copies of the same code in KahunaCart and Forum Pro.

An expired row is taken over in place, so a process that died holding a lease blocks others for at most its TTL. Every acquisition gets its own owner string (the configured owner plus a random tail), and release() only deletes the row this object took, so two leases in one request never release each other's. No step depends on catching a failed insert, which keeps a lease usable inside an open PostgreSQL transaction and lets real errors (a deadlock) reach the caller instead of reading as "busy".

KvStore, RateLimiter, UnsubscribeSigner, Clock

KvStore($db, $tables, $clock) holds installation-scoped values: get, set, delete and remember($key, $create). remember writes insert-if-absent and reads back, so two first requests agree on one value (a secret that changed under a link already sent would break the link). Keys are 1 to 64 bytes; a longer one is refused rather than cut.

RateLimiter($db, $tables, $pruneOdds = 50, $clock) is KahunaCart's fixed-window limiter:

hit(bucket, key, limit, window, ?now) returns a RateLimitResult (allowed, remaining, retryAfter). Windows are aligned to the epoch; a denied call still counts; a limit of zero or less turns the bucket off and costs no queries. One row per bucket and key is rolled forward from window to window, and old rows in the calling bucket are swept on roughly one call in $pruneOdds (0 turns that off). A bucket has one window length, because the sweep works out its cutoff from the calling window. prune($before, ?$bucket) drops windows that started before a time, in one bucket or in all of them. Keys longer than 190 bytes are stored as their SHA-256. anonymize($value) hashes an IP or email for use as a key.

UnsubscribeSigner($kv, $secretKey = 'unsubscribe_secret') signs one-click unsubscribe links: sign(string $subject, string $scope) and verify($subject, $scope, $token). The token is an HMAC-SHA256 over {subject}|{scope} with a secret created once in the KV store. A scope may not contain |.

Clock is one method, now(): int. SystemClock is time(). Every class that decides by time takes an optional Clock, and tests pass Testing\FrozenClock.

Events

EventSink::emit(string $event, array $payload): void receives domain events after the write's transaction commits. Names are dotted (ticket.created), payloads flat, and a sink must never throw.

CompositeSink fans one emission out to several sinks. A sink that throws is caught and the others still run. withErrorReporter(fn (Throwable $e, string $event, EventSink $sink) => $log->error(…)) returns a copy that reports those failures (a reporter that throws is ignored too). add($sink) appends one. NullSink discards everything.

Testing helpers

Running the kit's own tests

A local MariaDB whose root account uses unix_socket auth is reachable through its socket as your own OS user. CI runs all three engines on PHP 8.3 and 8.4.

tests/Integration/AdoptionTest.php needs real Forum Pro and KahunaCart databases. It looks for them under ~/workspace/grav-forum and ~/workspace/grav-kahunacart (override with GRAVDBKIT_ADOPT_FORUM_DB, GRAVDBKIT_ADOPT_FORUM_PLUGIN, GRAVDBKIT_ADOPT_KAHUNACART_DB, GRAVDBKIT_ADOPT_KAHUNACART_PLUGIN) and skips, saying where it looked, when they are missing. It only ever copies the originals.

Bundling with Strauss

Grav loads every plugin's autoloader into one process, so each plugin bundles its own copy of the kit under its own namespace prefix (TrilbyMedia\GravDbKit\… becomes, for example, Grav\Plugin\HelpdeskPro\Vendor\TrilbyMedia\GravDbKit\…). The kit is written so that works: no global state shared between copies, no class_exists() checks against its own unprefixed names, and no class names built from strings. Refer to kit classes with use statements and ::class, which Strauss rewrites, including in migration files.

The exact Strauss config, commands, .gitignore rules and verification steps are in docs/packaging.md; composer packaging-check runs the harness that proves two prefixed copies coexist.

Changes from the in-tree copies

Public class and method names follow KahunaCart's, with these differences:

Switching an existing plugin

A plugin moves to the kit without moving any data: it passes the table names and savepoint prefix it already uses.

  1. Require getgrav/grav-db-kit and set up the Strauss prefix.
  2. Build the connection through the kit's ConnectionFactory with the plugin's KitOptions, and pass the plugin's KitTables to the Migrator, Lease, KvStore, RateLimiter and job queue.
  3. In every migration file, point the three use lines at the kit (…\Database\Connection, …\Database\Dialect\Dialect, …\Database\Migration). Nothing else in a migration file changes, and no step re-runs: the tracking rows are keyed by migration and step name, which stay the same.
  4. Point every other use …\Database\Connection (repositories, services, tests) at the kit, and delete the in-tree classes/Database (except anything plugin-specific, such as KahunaCart's SettingsTable).
  5. Replace the in-tree lease code and ensureMigrated()/autoMigrate() with Lease and SchemaGuard::ensure().
  6. If the plugin uses the kit's job queue, append a step with InfraTables::jobs($d, $tables), which brings an older jobs table forward in place. If it uses the kit's RateLimiter, replace its rate-limit table as described below.

AdoptionTest checks steps 2 and 3 against copies of real databases. For each plugin, the kit's migrator reports exactly the pending steps the plugin's own migrator reports, applies exactly those, and reports zero pending on a database the plugin itself brought current, without re-running anything.

Forum Pro

KahunaCart

License

MIT, Copyright (c) 2026 Trilby Media, LLC. See LICENSE.


All versions of grav-db-kit with dependencies

PHP Build Version
Package Version
Requires php Version >=8.3
ext-pdo Version *
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 getgrav/grav-db-kit contains the following files

Loading the files please wait ...