Search by

overthink / db-snapshot

MarkoDevelop

Pull filtered MySQL snapshots from a remote server over SSH and restore them into your local database.

Package info

github.com/MarkoDevelop/db-snapshot

pkg:composer/overthink/db-snapshot

Statistics

Installs: 154

Dependents: 0

Suggesters: 0

Stars: 4

Open Issues: 4

v1.9.0 2026-11-06 11:31 UTC

README

Latest Version on Packagist GitHub Tests Action Status Total Downloads

Pull a filtered copy of a remote database (MySQL, MariaDB and PostgreSQL built in, others via drivers) over SSH and restore it into your local one. Pick the tables you need, keep only the last few months of the big history tables, and store that choice in a profile your team commits.

composer require --dev overthink/db-snapshot
php artisan snapshot

php artisan snapshot walks you through everything: it asks for the SSH and database settings, checks the connection (and lets you edit the settings until it works) and saves them to .env, analyzes the remote database, lets you build a profile, then pulls and restores (asking the same questions as snapshot:refresh), or only pulls. Run it again any time; it skips the steps that are already done.

To redo the analysis and the profile, run php artisan snapshot --fresh: it re-analyzes the remote database and opens the profile editor even when both already exist. The connection settings are kept.

The individual steps are commands too:

php artisan snapshot             # guided: setup → analyze → configure → pull → restore
php artisan snapshot:analyze     # sizes, rows, date columns and relations of every remote table → database/snapshot-analysis.json
php artisan snapshot:configure   # choose what to copy (searchable TUI), saved as a profile
php artisan snapshot:pull        # stream per-table gzipped dumps over SSH, in parallel
php artisan snapshot:restore     # drop + recreate the local DB from the latest snapshot
php artisan snapshot:refresh     # later on: refresh a whole profile, or just some tables
php artisan snapshot:refresh-table orders users   # pull + replace only these tables
php artisan snapshot:addon       # extra tables kept apart from the snapshot, to reapply after restores
php artisan snapshot:list        # pick a snapshot or add-on to restore, load or delete, or prune old ones (a plain table with --no-interaction)
php artisan snapshot:profiles    # saved profiles with their rules and last pull

Why

The usual mysqldump > dump.sql && gzip && scp routine writes the whole dump to the server's disk, copies every row of every table, and often takes a global read lock (--master-data). This package:

  • streams mysqldump | gzip straight over SSH into a local file, so nothing is written on the server;
  • dumps per table in parallel (--single-transaction --quick, no global lock, wrapped in nice/ionice);
  • filters per table: full, schema only, the last N months by a date column, a custom WHERE, or skip;
  • pulls once, restores many times: snapshots live in a directory that several checkouts or worktrees can share, and restores import tables in parallel.

On the server the package only ever runs read-only queries against the schema catalog (plus MIN/MAX on indexed date columns) and the database's dump tool.

Requirements

  • PHP ^8.3, Laravel ^11.0 || ^12.0 || ^13.0
  • Locally (where artisan runs): ssh, bash, gzip, and the database client (mysql, or psql and sed for pgsql)
  • On the server: bash, gzip, and the dump tools (mysqldump, or pg_dump and psql for pgsql)

Installation

composer require --dev overthink/db-snapshot
php artisan vendor:publish --tag="db-snapshot-config"   # optional

Running artisan inside Docker? Read Running in Docker first: the SSH key and snapshot directory need mounts.

php artisan snapshot asks for the connection and writes it to .env. To set it up by hand instead:

SNAPSHOT_SSH_HOST=203.0.113.10
SNAPSHOT_SSH_USER=deploy
SNAPSHOT_SSH_PORT=22
SNAPSHOT_SSH_KEY=/root/.ssh/snapshot_key

SNAPSHOT_DRIVER=mysql
SNAPSHOT_REMOTE_DB_HOST=127.0.0.1
SNAPSHOT_REMOTE_DB_PORT=3306          # default per driver
SNAPSHOT_REMOTE_DB_USERNAME=root      # default per driver
SNAPSHOT_REMOTE_DB_PASSWORD=
SNAPSHOT_REMOTE_DB_DATABASE=production

# Optional
SNAPSHOT_PATH=/var/snapshots        # default: storage/db-snapshots
SNAPSHOT_PARALLEL=4
SNAPSHOT_CONNECTION=                # restore target, default: the app's default connection
SNAPSHOT_DATABASE_NAME={source}_{date}   # name offered for a new database per snapshot
SNAPSHOT_ANONYMIZE=false                 # replace personal data while pulling (see below)
SNAPSHOT_ANONYMIZE_SALT=                 # same fakes in every snapshot when set

The remote password goes to the SSH session's stdin and is exported there (as MYSQL_PWD for the mysql driver), so it never shows up in a command line on either machine. Leave it empty to use the server user's ~/.my.cnf.

Profiles

snapshot:configure [profile] writes database/snapshot-profiles/<profile>.json.

Every command takes --profile= (snapshot:configure also accepts the name as an argument). Without one, the command asks Which profile?. Each profile is listed with its details:

Which profile?
› default · 12 rules · anonymizes 4 columns · pulled 2 hours ago
  nightly · 3 rules · never pulled
  New profile…
  Copy a profile…
  • snapshot:configure asks as soon as one profile exists. New profile… starts empty, and Copy a profile… starts from a copy, leaving the original as it is (or pass --from=nightly). Saving asks Save profile [name]?.
  • snapshot offers the same list, and the read-only commands (pull, refresh, refresh-table) show it without the New and Copy entries. These ask only when there are several profiles; with one, it's used.
  • With more than 7 profiles the list becomes searchable.
  • With no profiles, or with --no-interaction, default is used.

snapshot:profiles lists every profile with the same details. Profiles are plain JSON files, so remove or rename them like any file.

A profile looks like this:

{
    "default_mode": "full",
    "tables": {
        "activities": { "mode": "schema" },
        "event_logs": { "mode": "recent", "column": "created_at", "months": 3 },
        "failed_jobs": { "mode": "schema" },
        "imports": { "mode": "recent", "column": "created_at", "since": "2025-01-01" },
        "order_items": { "mode": "follow", "parent": "orders", "column": "order_id" },
        "orders": { "mode": "recent", "column": "created_at", "months": 6 },
        "telescope_entries": { "mode": "skip" },
        "users": { "mode": "where", "where": "deleted_at IS NULL" }
    }
}
Mode Result
full all rows
schema table structure only
recent rows where column >= now - months (whole days) or column >= since
where rows matching a custom WHERE clause
follow rows whose column points at a row the parent table copies (see Following other tables)
skip the table is left out

Tables the profile doesn't list use default_mode (full, schema or skip; set it with snapshot:configure --default-mode=). With full, a new table on the server is never silently dropped. months is relative, so a profile stays current without editing.

In snapshot:configure, one search picks the tables that need a rule. Before you type, it lists large tables (over large_table_mb, 200 MB by default) and tables that already have a rule, with their size, row count and indexed date columns, and existing rules are preselected. Typing searches every table. Where the terminal can't show a preselection (on Windows), you're first asked which current rules to keep. Tables that already have a rule keep it unless you check them under Change any of the current rules?, so editing one rule doesn't walk you through all of them. For recent rules, each period option shows an estimated size, and a summary table shows the expected snapshot size before you save.

To build a new profile from an existing one, pick Copy a profile… (or pass --from=) at the start; the original stays as it is.

If a recent or follow rule filters on a column without an index, the server has to scan the whole table to find the rows. snapshot:configure lists those columns and offers to generate a migration (database/migrations/…_add_snapshot_indexes.php) that adds the indexes. Review it and deploy it like any other migration. InnoDB builds indexes online, but on very large tables it still takes a while.

Following other tables

Keeping six months of orders is only half the job: order_items, order_serials and shipments should keep the rows of those orders, not all of theirs. Give them a follow rule:

"orders": { "mode": "recent", "column": "created_at", "months": 6 },
"order_items": { "mode": "follow", "parent": "orders", "column": "order_id" },
"order_serials": { "mode": "follow", "parent": "order_items", "column": "order_item_id" }

The rows are picked on the server with order_id IN (SELECT id FROM orders WHERE …), nested along the chain, so order_serials gets the serials of the items of the last six months of orders. Add "references": "number" when the column points at something other than the parent's id.

You rarely write these by hand:

  1. snapshot:analyze records how tables point at each other: real foreign keys, plus <name>_id columns whose name matches a table with an id of the same kind of type (so order_id → orders). An analysis made before this feature has no relations; snapshot:configure says so, and re-running snapshot:analyze fixes it. Polymorphic pairs (commentable_id next to commentable_type) are left out.
  2. snapshot:configure, once your rules are set, lists the tables pointing at filtered ones like orders, all checked: Enter makes them follow. A table pointing at several filtered tables is offered the one filtered by date, and nothing that would go in a circle is offered. Then it does the same for their own children. Guessed relations say so in the list, so uncheck the ones that are wrong. A table with relations also has Follow in its mode list. Follow is set on the child: order_items follows orders, never the other way round. When you pick a rule for a table others point at, the hint names them.
  3. snapshot:refresh-table orders, pulling from the server, asks whether to refresh the followers too (with --no-interaction it only names them). Then, after any rule changes for this run, it lists the other tables that point at orders, level by level (order_items, then order_serials). The ones you keep follow orders for this run, so all orders bring all their items and a filtered orders brings only its items. The profile isn't changed. Tables without a rule in the profile start checked; tables with one (a skip, say) start unchecked and show it. A table pointing at several picked ones follows the date-filtered one. Tables orders points at (currencies, partners) aren't offered; refresh them on their own when you need them. Refreshing only order_items uses the orders rule from the profile, so it still gets just the items of the copied orders.

Good to know:

  • A follower of a full parent is copied in full, and a follower of a schema or skip parent gets no rows. snapshot:pull warns about the latter, since it's usually a forgotten parent rule.
  • The tables and columns of a follow rule are checked before anything is dumped, including that the two columns can be compared without a cast (a text column following a numeric id would scan the whole table, or fail on PostgreSQL). A parent's where may use ORDER BY … LIMIT.
  • Rows whose column is NULL aren't copied by a follower.
  • On MySQL, the subquery needs the dump to run without table locks. The default dump_options (--single-transaction --skip-lock-tables) do that.
  • Tables are dumped one by one, so an order created during a pull can be missing while its items are there. With foreign key checks off (MySQL) or NOT VALID foreign keys (PostgreSQL), that's harmless.

Other filters leave rows in other tables that point at rows not copied. Restores run with foreign key checks off, so this is fine for development, but keep it in mind when you pick rules.

Analysis

snapshot:analyze writes database/snapshot-analysis.json: every remote table with its size, row count and date columns, plus the date range of indexed date columns on large tables, and the relations between tables used by follow rules. Commit it, so teammates can run snapshot:configure without touching the server first.

snapshot:configure and snapshot:pull check how old the analysis is. If it's older than analysis_max_age_days (30 by default), they ask whether to Analyze now or continue with the current one. Without interaction they only warn. Pass --analyze to either command to refresh it without being asked.

Anonymizing personal data

Off by default. Turn it on per project with:

SNAPSHOT_ANONYMIZE=true

While it's off, snapshot:configure has no anonymize step, snapshot:analyze writes no suggestions, and snapshot:pull copies every table as it is. If the profile has anonymize rules, pull says which tables it copied unchanged. The rules stay in the profile for when it's switched on.

With it on, columns can be replaced while the dump runs on the server, so real names, emails or phone numbers never leave it, and snapshot files are safe to keep around. For an orders table you keep the order number, totals and dates, and drop who placed the order:

"orders": {
    "mode": "recent", "column": "created_at", "months": 12,
    "anonymize": {
        "customer_email": "email",
        "customer_name": { "template": "Customer {hash}" },
        "customer_phone": "null",
        "shipping_address": { "template": "Street {id}" },
        "notes": "empty"
    }
},
"users": { "mode": "full", "anonymize": { "email": "email", "name": { "template": "User {id}" }, "password": { "fixed": "$2y$12$…" } } }
Strategy Becomes (NULL stays NULL)
email user_<hash>@example.test
hash <hash>: 12 hex characters
template your text with {hash} and/or {id} (the row's single-column primary key)
fixed the same value for every row, e.g. one known password hash so everyone can log in
null / empty NULL / ''
  • {hash} is a salted SHA-256 of the original value, so the same email becomes the same fake in every row and table: joins, duplicates and unique indexes keep working.
  • Set SNAPSHOT_ANONYMIZE_SALT to get the same fakes in every snapshot. Without it, a random salt is used per pull.
  • Results are cut to the column's length.
  • email, hash, template and empty need text columns. Other columns can use null (if nullable) or fixed.
  • snapshot:pull checks every rule against the real columns before dumping anything and lists all problems at once.

snapshot:analyze looks for columns that seem personal by name (emails, person names, phones, street addresses, IPs, IBANs, tokens…) and writes them, with a suggested replacement, to personal_data in snapshot-analysis.json. They're only suggestions. The profile decides what's anonymized:

  • snapshot:configure lists the suggestions. New ones are pre-checked, and you can search for any other column.
  • Suggestions you uncheck go to the profile's not_personal list ("not_personal": ["products.name"]), so they aren't treated as new again. They still show up, unchecked, if you change your mind.
  • snapshot:analyze and snapshot:pull warn about suggested columns the profile neither anonymizes nor lists in not_personal, e.g. after a migration added orders.customer_phone.
  • Hand edits to the profile always win. Your own strategy for a column is kept, rules for columns that no longer exist are dropped by snapshot:configure with a warning, and a rule that can't work fails snapshot:pull before anything is dumped.

Treat the suggestions as a starting point. Personal data also hides in free-text notes, JSON columns, logs and audit tables, so review what you copy.

How it works: for anonymized tables, MySQL dumps the definition with mysqldump and generates the rows with a SELECT on the server (one INSERT per row, so it's slower than plain mysqldump). PostgreSQL uses COPY (SELECT …). Both drivers implement the optional AnonymizesColumns interface.

What to commit

Path Commit?
database/snapshot-profiles/*.json Yes: the team shares which tables are copied and how much of them
database/snapshot-analysis.json Yes: teammates can run snapshot:configure without touching the server
database/migrations/…_add_snapshot_date_indexes.php Yes, after review, like any migration
the snapshot directory (SNAPSHOT_PATH, default storage/db-snapshots) Never: it holds production data

snapshot:pull writes a .gitignore (*, !.gitignore) into the snapshot directory, so the dumps stay out of commits even at the default location inside the project. An existing .gitignore there is left alone.

Pulling and restoring

php artisan snapshot:pull --profile=default --parallel=6
php artisan snapshot:restore                      # pick a snapshot (newest preselected), asks before dropping
php artisan snapshot:restore latest --profile=default --force
php artisan snapshot:restore 2026-10-08_120000_default --database=dev_other

Refreshing

snapshot:refresh asks what to refresh:

  • a profile: pulls a new snapshot with it and restores the whole database (--profile=nightly skips the question). Every question comes first: which profile (and creating it if it doesn't exist), whether to refresh an old analysis, which database to restore into, the drop confirmation, whether to point the app at a new database, and which add-ons to apply. After that it pulls and restores without stopping. --force asks nothing (an old analysis is only warned about, and a missing profile is an error) and restores into the app's database, and without interaction --force is required;
  • some tables: hands over to snapshot:refresh-table.

snapshot:refresh-table [tables...] pulls only the given tables (or the ones you pick in a search) and replaces just those tables in your local database. Nothing else is dropped. Each table uses its rule from the profile (--profile=). Tables picked in the search can get another rule for this run: check them under Change any rule for this run? (the profile itself isn't changed). Tables added because they follow a picked one keep following it. The pulled files are deleted afterwards unless you pass --keep or save them as an add-on. Partial pulls are never used by snapshot:restore as a snapshot. When add-ons exist and no tables are given, it first asks whether the tables come from the server or from an add-on.

A snapshot is a directory of tables/<table>.sql.gz files plus _views.sql.gz, _routines.sql.gz and manifest.json. It's written as <name>.partial and renamed only when every dump succeeds. A failed pull leaves nothing behind.

Without a snapshot name, snapshot:restore lists the snapshots (filtered by --profile=), newest first and preselected, so Enter restores the latest. Pass a name or latest, or run it with --no-interaction, to skip the question.

Snapshots hold production data and add up. snapshot:list shows how much space they take, and its Delete old snapshots… entry keeps only the newest N per profile (add-ons are left alone). Without interaction, snapshot:restore, snapshot:refresh and snapshot:refresh-table need --force, since there's nobody to confirm the drop.

One database per snapshot

To keep several snapshots side by side (per source, per day, …), restore into a database named from a template:

php artisan snapshot:restore latest --database='{source}_{date}'    # e.g. production_2026_10_08
Placeholder Value
{source} the remote database the snapshot came from
{profile} the snapshot's profile
{date} / {time} when it was pulled (Y_m_d / His)
{database} the connection's own database (DB_DATABASE)

Run interactively without --database or --force, snapshot:restore asks where to restore: the app's database, a new one named from SNAPSHOT_DATABASE_NAME (default {source}_{date}), or another name. After restoring into another database it offers to point the app at it by setting DB_DATABASE in .env. --switch-app does that without asking.

snapshot:restore refuses to run in the production environment.

Add-ons

Keep the base snapshot small, and keep the bigger copies you need now and then as add-ons. An add-on is a few tables, each pulled with the rule you choose (for example all of orders instead of the profile's last month), that you can load into any checkout or apply after any restore.

php artisan snapshot:addon
Which add-on?
› 2 hours ago · all-orders · orders, order_items · 1.2 GB
  New add-on…

What should happen with all-orders?
› Pull tables into it (replaces those, keeps the rest)
  Load it into the database
  Delete it

The usual steps:

  1. Save some tables to an add-on. They're pulled, replace your local copies, and are kept:
    php artisan snapshot:addon all-orders orders order_items   # add-on "all-orders" with these two tables
  2. Load it in another checkout or worktree, without pulling again:
    php artisan snapshot:refresh-table --from=all-orders
  3. Apply it after a full restore:
    php artisan snapshot:restore latest --addon=all-orders

Pass --from or --addon without a name to pick from a list (that also asks with --force). --addon can be given several times. You don't have to start from snapshot:addon: snapshot:refresh-table's last question, Replace … ?, also offers to save the tables to an add-on, or takes --save=all-orders (--save alone asks which add-on). snapshot:restore and snapshot:refresh ask which add-ons to apply (none by default, and not with --force); both take --addon. snapshot:list shows add-ons after the snapshots. snapshot:refresh with table names refuses --addon; load an add-on's tables with snapshot:refresh-table --from= instead.

Good to know:

  • Pulling into an existing add-on replaces only the tables you pulled. Its other tables stay, so an add-on can grow table by table.
  • Restore warns when an add-on has tables pulled more than addon_max_gap_days (7) days before or after the snapshot: their rows may point at rows the snapshot doesn't have.
  • Add-ons are applied after the snapshot's tables and before its views, ordered by when each was last saved. When two add-ons hold the same table, the one saved last wins.
  • With pgsql, replacing a table drops foreign keys in other tables that point at it (CASCADE). The add-on brings back the table's own indexes and constraints, but not those.

Drivers

Everything database-specific lives in a driver: reading the schema catalog, building the dump commands that run on the server, and importing dumps locally. Select one with SNAPSHOT_DRIVER.

mysql (built in) covers MySQL and MariaDB. It uses information_schema and mysqldump on the server and the mysql client locally. Driver options live under drivers.mysql in the config:

  • dump_options: extra mysqldump flags (defaults: --single-transaction --quick --skip-lock-tables --no-tablespaces --hex-blob).
  • skip_gtid_purged: adds --set-gtid-purged=OFF, so dumps from GTID-enabled servers can be imported table by table. With auto (the default) it checks mysqldump --version on the server once per pull and leaves the flag out for MariaDB's mysqldump, which doesn't know it. Set true or false to force it. The restore also strips any SET @@GLOBAL.GTID_PURGED it finds, so older snapshots import fine.

pgsql (built in) covers PostgreSQL 13 and newer. It reads pg_catalog and uses pg_dump and psql on the server and psql locally (default port 5432, user postgres). Options under drivers.pgsql:

  • schema: the schema to snapshot (SNAPSHOT_PGSQL_SCHEMA, default public). Other schemas are created locally.
  • dump_options: extra pg_dump flags. --no-owner --no-privileges are always used, so the server's roles don't have to exist locally.
  • maintenance_database: the local database psql connects to while dropping and recreating the target (default postgres).

How it differs from mysql, because pg_dump has no --where and Postgres has no "foreign key checks off" switch:

  • Filtered tables (recent, where, follow) are dumped as their definition plus COPY of a SELECT … WHERE …. Afterwards each sequence the table owns is moved past the highest restored id.
  • Indexes, constraints and triggers of all tables go into one post-data file that is restored after every table is loaded. Foreign keys are created NOT VALID: rows that point at filtered-out rows stay, and new rows are still checked.
  • Extensions and functions of the schema are restored before the tables, since columns and defaults may use them.
  • The restore drops statements that newer pg_dump versions write but older psql clients reject (SET transaction_timeout, \restrict).
  • DROP DATABASE … WITH (FORCE) disconnects anyone still connected to the local target database.

Limitations: one schema per snapshot; partitioned tables, materialized view data and sequences not owned by a table are not handled specially. snapshot:refresh-table drops the table with CASCADE, so foreign keys in other tables that point at it, and views on it, have to be restored again with a full snapshot:restore.

Snapshots record the driver that pulled them, and snapshot:restore always uses that driver. It refuses to restore into a connection of another kind (for example a MySQL snapshot into a pgsql connection).

Writing a driver

Implement Overthink\DbSnapshot\Contracts\Driver. Extending Overthink\DbSnapshot\Drivers\SshDriver gives you the SSH plumbing: the password sent over stdin, running a query and parsing tab-separated rows, and gzipping a remote dump into a local file. Then register it in a service provider:

use Overthink\DbSnapshot\Facades\DbSnapshot;

public function boot(): void
{
    DbSnapshot::extend('sqlsrv', fn ($app) => new SqlServerDriver(
        config('db-snapshot.ssh'),
        config('db-snapshot.remote'),
    ));
}
SNAPSHOT_DRIVER=sqlsrv

MysqlDriver is the reference implementation. Two contracts are optional: AnonymizesColumns for anonymizing, and FollowsRelations (foreign keys, indexed columns and the IN (SELECT …) condition) for follow rules.

Running in Docker

When artisan runs in a container (Sail, a custom workspace service, …), the SSH session and the mysql client run there too, so the container needs the tools, the key and a place for snapshots. The examples use the mysql driver's tools.

Tools. The image needs ssh, bash, gzip and a mysql client. Check with:

docker compose exec workspace sh -c 'which ssh bash gzip mysql'

For the mysql driver, the MariaDB mysql client restores into MySQL fine. For pgsql, check which psql sed instead; the local psql should be at least as new as the server.

SSH key. Mount only the key you need, read-only, into the home directory of the user the container runs as (docker compose exec workspace id shows it), and point SNAPSHOT_SSH_KEY at the path inside the container:

services:
    workspace:
        volumes:
            - ../:/var/www
            - ${HOME}/.ssh/deploy_key.pem:/home/app/.ssh/snapshot_key:ro
            - ../../snapshots:/var/snapshots
SNAPSHOT_SSH_KEY=/home/app/.ssh/snapshot_key
SNAPSHOT_PATH=/var/snapshots
  • Don't mount your whole ~/.ssh. It exposes every key to the container, and macOS-only options in ~/.ssh/config (such as UseKeychain) make Linux ssh fail.
  • The key must not be readable by others (chmod 600 on the host). OpenSSH refuses keys with looser permissions.
  • On the first connection StrictHostKeyChecking=accept-new adds the server to the container's ~/.ssh/known_hosts. If that directory isn't persisted, this happens again after the container is recreated, which is harmless. To pin the host key instead, also mount a known_hosts file there.

Snapshot directory. Point SNAPSHOT_PATH at a mounted directory, as above, so snapshots survive container rebuilds. Several checkouts of the same project (for example git worktrees) can mount the same directory: pull once, then restore in each checkout.

Restore target. snapshot:restore and snapshot:refresh-table restore into the app's own database connection (DB_HOST, DB_DATABASE, …), so inside the container that's usually the mysql service, e.g. DB_HOST=mysql. Use SNAPSHOT_CONNECTION to pick another connection, or --database= for one run.

Run commands from the compose directory. docker compose looks for a compose file in the current directory and then in its parents, so run docker compose exec workspace php artisan snapshot from the directory that holds your docker-compose.yml.

Developing the package locally. With a composer path repository ("url": "/var/db-snapshot", "options": {"symlink": true}), vendor/overthink/db-snapshot is a symlink to that path. Mount the package's source at that path in every container that boots the app (the CLI container and the web/PHP-FPM container), or the web app fails with Failed to open stream: …/DbSnapshotServiceProvider.php.

Testing

composer test
composer analyse

The integration tests run against real servers and are skipped unless DB_SNAPSHOT_PGSQL_HOST / DB_SNAPSHOT_MYSQL_HOST are set (_PORT, _USERNAME and _PASSWORD are optional). The database's command line tools must be on PATH:

DB_SNAPSHOT_PGSQL_HOST=127.0.0.1 DB_SNAPSHOT_PGSQL_PASSWORD=secret vendor/bin/pest tests/Integration/PgsqlDriverTest.php
DB_SNAPSHOT_MYSQL_HOST=127.0.0.1 DB_SNAPSHOT_MYSQL_PASSWORD=secret vendor/bin/pest tests/Integration/MysqlDriverTest.php

Changelog

Please see CHANGELOG for more information on what has changed recently.

License

The MIT License (MIT). Please see License File for more information.