openstatspec/php is the PHP reference adapter for the OpenStatSpec specification.
It imports an unencrypted SPSS .sav or .zsav dataset into a relational database as one source-faithful wide SQL table plus a metadata catalogue. It reconstructs that catalogue as a typed php-spss V3 Dataset and exports SAV or ZSAV.
This is an early reference implementation. Its round-trip contract is semantic, not byte-identical: supported cases, order, variables, values, dictionary metadata and technical metadata are preserved; compression layout, timestamps and other writer-specific bytes are not promised.
SQLite, PostgreSQL 17.x/18.x, MySQL 8.4.x/9.7.x, MariaDB
11.4.x/11.8.x/12.3.x and Dolt 2.2.x with the explicit
>=2.2.2,<2.3.0 floor/range are implemented PDO profiles.
Server-family claims are conservative compatibility policies; CI records exact
evidence at PostgreSQL 17.10/18.4, MySQL 8.4.11/9.7.2 and MariaDB
11.4.12/11.8.8/12.3.2, and Dolt 2.2.2/2.2.3. Each service job verifies its live normalized product
version before the run counts as evidence.
| Engine / profile | Runtime support policy | Exact CI-tested versions |
|---|---|---|
SQLite core / sqlite |
>=3.24.0,<4.0.0 |
Runtime-provided PDO SQLite; no pinned server image |
PostgreSQL / postgresql |
17.x and 18.x | 17.10 and 18.4 |
MySQL / mysql |
8.4.x and 9.7.x | 8.4.11 and 9.7.2 |
MariaDB / mariadb |
11.4.x, 11.8.x, and 12.3.x | 11.4.12, 11.8.8, and 12.3.2 |
Dolt / dolt |
2.2.x family with >=2.2.2,<2.3.0 |
2.2.2 and 2.2.3 |
The PHP SQLite core profile remains >=3.24.0,<4.0.0. The Python adapter's
optional Transformation Workflow has its own narrower >=3.35.0,<4.0.0
policy; it does not change PHP support. Microsoft SQL Server is not supported
by this adapter and remains roadmap-only in the specification's
MSSQL dialect roadmap.
Each implemented profile follows one strict-wide contract:
- One source dataset becomes one dedicated SQL data table.
- One SPSS case becomes one SQL row.
- One SPSS variable becomes one physical SQL column in source order.
__case_ordinalis the technical primary key that preserves case order and is never exported as an SPSS variable.- Separate catalogue tables preserve dictionary and operation metadata.
The catalogue retains source and physical variable names, storage kind and widths, labels, print/write formats, measurement/display metadata, typed value labels, user-missing rules, documents, technical file metadata, attributes, variable sets, multiple-response sets and roles. Numeric system-missing values are SQL NULL; user-missing values remain ordinary stored values and are described by metadata. Strings are non-null and an empty string remains a value.
Only unencrypted SAV and ZSAV are supported. Encrypted files, Portable (.por) files, EAV/cell tables, reshaping, automatic harmonisation, inferred respondent IDs and byte-identical reproduction are out of scope.
- PHP 8.4.1 or later
ext-pdo- Selected PDO driver:
pdo_sqlite,pdo_pgsql, orpdo_mysql openstatspec/spss-sav3.x, installed by Composer. The php-spss V3 engine needsext-bcmath,ext-mbstringandext-zlib.
Composer resolves dependencies against PHP 8.4.1, the package minimum.
use OpenStatSpec\Spss\GuardedImportSpssEngine;
use OpenStatSpec\Spss\SpssAdapter;
$pdo = new PDO('pgsql:host=localhost;dbname=statistics', $user, $password);
$adapter = new SpssAdapter($pdo);
$import = $adapter->import('/data/survey.zsav', 'survey_2026');
// SpssImportResult: operationId, datasetName, caseCount, diagnostics
$export = $adapter->export('survey_2026', '/data/survey-export.sav');
// SpssExportResult: operationId, datasetName, caseCount, diagnostics, allowLossUse GuardedImportSpssEngine when an engine must read from an ephemeral
descriptor while the adapter and database receive only a logical source path:
$engine = new GuardedImportSpssEngine($innerEngine, $procFdPath, 'sav');
$adapter = new SpssAdapter($pdo, $engine);
$import = $adapter->import(
$engine->logicalPath(),
'survey_2026',
verifiedSourceSha256: $verifiedSourceSha256,
);verifiedSourceSha256 must be exactly 64 lowercase hexadecimal characters.
The adapter persists it as dataset.source_hash, but validates only its shape:
the caller is responsible for proving that it hashes the exact bytes read by
the engine. Keep any physical guarded path, such as /proc/self/fd/...,
internal to the engine; SpssAdapter::import() rejects exact Linux
descriptor paths under /proc/*/fd/ and /dev/fd/ before any database
mutation. GuardedImportSpssEngine also recursively rejects descriptor
paths in inner-engine identity keys or values and replaces every inner read
exception with a neutral logical-source error. Sanitized errors do not chain
the original exception, so descriptor paths cannot enter operation or fidelity
journals through identity metadata or read failures. The adapter and catalogue
need only the logical .sav/.zsav path
and the verified hash. Omitting the argument preserves the
existing behavior: a readable source file is hashed by pathname, otherwise
dataset.source_hash is NULL.
Export is fail-closed. If an exporter reports a known fidelity diagnostic, it does not write a file until the caller explicitly accepts its code:
$export = $adapter->export(
'survey_2026',
'/data/survey-export.sav',
allowLoss: ['example_diagnostic_code'],
);Pass only loss codes consciously accepted for that conversion. operation_catalog records successful and failed imports/exports; fidelity_event_catalog records emitted diagnostics. A failed preflight is therefore auditable even when it created no dataset. Each operation also records the selected SPSS engine package and Composer version in engine_details.
src/Core- diagnostics and fail-closed fidelity policy.src/Sql- PDO profiles, strict-wide DDL, import/export and catalogues.src/Spss- SAV/ZSAV gating, typed V3 engine bridge and public adapter API.
See docs/architecture.md for the complete relational contract. See docs/transformations.md for the current package-local legacy transformation plan, frontend boundaries, in-place guarantees, supported syntax, and the required migration path to the official OpenStatSpec plan profiles. The capability declaration exposes no official plan or frontend contract claim yet.
After upgrading the package, run the catalogue migration once before importing or exporting:
$adapter = new SpssAdapter($pdo);
$adapter->migrateCatalog();The migration is idempotent. It upgrades the compatibility catalogue, creates the versioned normative OpenStatSpec catalogue, and backfills datasets imported by earlier adapter versions. Back up a production database before package upgrades and run this call from the application's normal deployment migration. The current catalogue migration version is recorded in openstatspec_schema_migration.
OpenStatSpec uses generic, unqualified catalogue names such as dataset,
variable, operation, and fidelity_event. Give the adapter a dedicated
database namespace and a PDO connection whose namespace cannot be changed by
unrelated application code while an adapter operation is running:
- PostgreSQL: create a dedicated schema and use a dedicated connection with a
fixed
search_pathcontaining that schema only. - MySQL/MariaDB/Dolt: select a dedicated database in the adapter DSN.
- SQLite: use a dedicated database file and connection.
The machine-readable capability declaration must expose the active namespace
under active_connection. Check that value against the intended deployment namespace
before importing. See the architecture guide
for the isolation contract and examples.
The current adapter is not streaming. The SPSS engine materializes a typed dataset, import normalization retains its rows, and export reconstructs a full dataset before writing. Peak memory therefore depends on the supplied file's case count, variable count, string sizes, and metadata.
Measure a representative, user-supplied SAV or ZSAV file in an isolated process:
php tools/memory-probe.php --source=/data/representative-large.savThe command creates a temporary dedicated SQLite database, performs one semantic
import/export round trip, removes its temporary artifacts, and prints one JSON
report to standard output. It reports input size, case count, PHP memory_limit,
baseline memory, and process peak memory. It deliberately declares
streaming: false and does not infer a universal safe file-size limit.
To retain the generated SQLite database and exported SPSS file for inspection, provide a new database path and keep the artifacts:
php tools/memory-probe.php \
--source=/data/representative-large.zsav \
--database=/tmp/openstatspec-memory-probe.sqlite \
--keep-artifactsRun the local gate before committing:
composer install
composer checkInstall the tracked pre-commit hook once per clone:
bash tools/install-git-hooks.shcomposer check validates Composer configuration, lints PHP, checks style, runs PHPStan and runs PHPUnit. Use composer fix for safe style fixes, then rerun composer check.
GitHub Actions runs the regular suite on PHP 8.4 and 8.5. It also runs real SAV and ZSAV integration round trips against exact PostgreSQL 17.10/18.4, MySQL 8.4.11/9.7.2, MariaDB 11.4.12/11.8.8/12.3.2, and Dolt 2.2.2/2.2.3. Those checks use their PDO drivers and php-spss V3 read/write paths, not only DDL snapshots. Family policies remain runtime claims and exact patches are CI evidence points; Dolt's 2.2.x family claim additionally has an explicit 2.2.2 minimum and 2.3.0 exclusive upper bound.
The normative model lives in the OpenStatSpec specification repository. Contributions are welcome for strict-scope adapters, database profiles, SAV/ZSAV fixtures, conformance tests and documentation.
New work must preserve the source-faithful wide-table contract, retain supported SPSS semantics in catalogue metadata and emit explicit diagnostics for unsupported conversion or capability limits. Include focused tests and run composer check before opening a pull request.
The package is framework-neutral and has no Yii2 or Laravel dependency. Applications supply their own PDO connection; a framework integration may wrap that connection but must not replace the OpenStatSpec mapping.
The selected engine is OpenStatSpec/php-spss, consumed as Composer dependency openstatspec/spss-sav 3.x and maintained alongside this adapter.