maatify / php-persistence
Direct-PDO transaction, pagination, and ordering utilities for MySQL and MariaDB.
Requires
- php: ^8.4
- ext-pdo: *
- ext-pdo_mysql: *
- maatify/exceptions: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- phpstan/phpstan: ^2.3
- phpunit/phpunit: ^12.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-11-13 14:43:42 UTC
README
PHP Persistence
Direct-PDO Transaction, Pagination, and Ordering utilities for MySQL and MariaDB.
Overview
maatify/php-persistence is a small, direct-PDO toolkit with three independent capabilities over a caller-supplied PDO connection: running callbacks inside a transaction or a caller's savepoint (with optional bounded deadlock replay), parameterized whitelist-sorted bounded pagination, and locking next-position/move operations for integer display-order columns. It is developed as a separate package from the earlier, published maatify/persistence (coexistence rules), not a drop-in replacement.
Release Candidate 1.0.0-rc.1 is published on Packagist and available as a GitHub pre-release. The package has no Stable release.
Key Features
- Transaction —
PdoTransactionRunner/PdoSavepointTransactionRunner: participate in a caller's active transaction (using a savepoint when one is needed) or own a new transaction; fail-closed on aBEGIN/COMMITthat returnsfalseinstead of throwing; optional bounded (0–3) deadlock replay for a package-owned transaction, off by default. - Pagination —
PdoPaginator::paginate(): normalizes untrustedpage/perPage/sort input, runs two independent count queries plus a bounded, sorted data query, and returns a validatedPageResultDTO(array/JSON-serializable). - Ordering —
ScopedOrderingManager: global, scoped, or nullable-scoped integer ordering with a plain next-position read, a locking next-position read for inserts, and a locked, clamped, savepoint-protectedmoveWithinScope(). - Host-agnostic: no connection factory, no schema ownership, no ORM, no HTTP/host logic. The host owns the
PDOconnection, the tables, and their meaning.
Requirements
Declared in composer.json, which is the governing reference:
- PHP
^8.4 ext-pdo,ext-pdo_mysqlmaatify/exceptions ^1.0
These are declared runtime requirements, not a statement that any specific database version is supported or verified — see Database support below.
Installation
Install the exact release candidate from Packagist:
composer require maatify/php-persistence:1.0.0-rc.1
See the changelog and security policy for release and support details.
Usage
Every capability takes a PDO connection the host already created and owns. A minimal Transaction example:
<?php use Maatify\PhpPersistence\Transaction\Repository\PdoTransactionRunner; $runner = new PdoTransactionRunner($pdo); // $maxDeadlockRetries defaults to 0 (no replay) $orderId = $runner->run(function () use ($pdo): int { $pdo->prepare('INSERT INTO orders (customer_id) VALUES (:customer_id)') ->execute(['customer_id' => 42]); return (int) $pdo->lastInsertId(); });
See docs/guides/USAGE_GUIDE.md for a capability map covering Transaction, Pagination, and Ordering, each with its own Input → Public Call → Result → Boundary walkthrough, and examples/ for complete, runnable scripts.
Public Runtime API
The public surface is three independent capability namespaces under Maatify\PhpPersistence\:
Transaction\Repository\{TransactionRunnerInterface, SavepointTransactionRunnerInterface, PdoTransactionRunner, PdoSavepointTransactionRunner}andTransaction\Exception\{InvalidTransactionConfigurationException, TransactionExecutionException}.Pagination\{Criteria\PageRequest, Config\PaginationConfig, ValueObject\SortWhitelist, Criteria\PdoPaginationQueryDescriptor, Repository\PdoPaginator, DTO\PageResultDTO, Enum\SortDirectionEnum}andPagination\Exception\{InvalidPaginationConfigurationException, InvalidPaginationQueryException, PaginationExecutionException}.Ordering\{Config\ScopedOrderingConfig, Repository\ScopedOrderingManager}andOrdering\Exception\{InvalidOrderingConfigurationException, InvalidOrderingOperationException}.- One package-wide exception marker:
Exception\PhpPersistenceExceptionInterface.
Two internal helper classes (Transaction\Repository\{OwnedTransactionExecutor, SavepointScope}) are reachable but are explicitly not part of the public contract or SemVer.
The complete, verified inventory — every constructor, named parameter, default, return type, thrown exception, and behavioral guarantee — is the canonical PHP_PERSISTENCE_PACKAGE_REFERENCE.md. This section is an overview only and is not repeated there in full.
Critical Runtime Behavior
- Caller-owned vs. package-owned transactions: every runner and
moveWithinScope()participates in an already-active transaction without ever beginning, committing, or finally rolling it back; it owns a transaction only when none was already active. The two transaction runners can opt into replay on that path. Ordering does not enable replay. See Package Reference §4 and §7. - Pagination runs the counts and optional data query separately. The caller controls snapshot consistency through its transaction, isolation level, and SQL read semantics. A
READ COMMITTEDtransaction can still observe concurrent commits between statements. See Package Reference §5.3. - MariaDB
innodb_snapshot_isolation=ONcan make a locking Ordering read fail with driver error1020after the server has already rolled back the caller's whole transaction. This is a documented hazard, not a handled case: no retry, no savepoint recovery, and no setting change is attempted. See Package Reference §7.3. - Deadlock replay is opt-in, bounded to 3 retries after the initial attempt, and applies only to a package-owned transaction under the exact SQLSTATE
40001/driver-code1213signature.
Exception and Error Propagation
Every exception the package defines implements Exception\PhpPersistenceExceptionInterface, so a consumer can catch all package-defined failures with one type. A PDO or callback failure that propagates retains the original exception. Cleanup can suppress secondary failures and an eligible owned deadlock may be recovered by replay. See Package Reference §6 for the full exception table and the inherited maatify/exceptions constructor shape.
Security and Trust Boundaries
Table and column identifiers passed to ScopedOrderingConfig and sort-key identifier paths passed to SortWhitelist are validated and quoted, but they are not bound as PDO parameters (SQL does not allow binding identifiers) — they must be trusted application constants, never raw user input. Ordering values and pagination filter parameters are bound. Pagination sort keys select validated identifiers from a whitelist and sort directions resolve to enum values. The caller supplies trusted pagination SQL.
Examples
Runnable, Public-API-only examples live in examples/: one each for Transaction, Pagination, and Ordering. Every example requires a real MySQL/MariaDB connection and is validated in CI using this repository's existing Integration infrastructure (no example invents a second database stack).
Documentation
- PHP_PERSISTENCE_PACKAGE_REFERENCE.md — canonical Public Runtime API, behavior, and exception contract.
- docs/guides/USAGE_GUIDE.md — capability map and walkthroughs.
- examples/ — runnable examples.
- CHANGELOG.md · SECURITY.md · CONTRIBUTING.md
- llms.txt — AI consumer navigation map.
- docs/architecture/ARCHITECTURE.md — design and topology (dated design record).
Database support
Neither MySQL nor MariaDB has a declared "supported range" today; both are design targets. The Transaction, Pagination, and Ordering capabilities' own tests (not just infrastructure checks) pass against specific named versions — MySQL 8.0.16, 8.4.13, 9.7.4, and MariaDB 10.11.19, 11.4.13, 11.8.9, 12.3.3 — which is "version tested" evidence, narrower than a declared "range verified" claim. See docs/architecture/DATABASE_SUPPORT_AND_TEST_POLICY.md §1 for what each term means and PHP_PERSISTENCE_PACKAGE_REFERENCE.md §8 for the current, verified claim.
Quality Status
CI requires PHPStan at level max with zero errors, PER Coding Style 3.1 checks, Unit and Regression suites on PHP 8.4 and 8.5 with latest and lowest compatible dependencies, and real-database Integration and example smoke checks. Integration exercises genuine deadlocks, a deterministic concurrent-writer snapshot test, and stale-snapshot concurrency. See GitHub Actions for results by commit and CI and Integration for the matrix.
Development and Testing
composer analyse # PHPStan level max composer format:check # PER-CS 3.1, non-mutating composer test:unit # no Docker needed composer test:regression composer test:integration # needs Docker; starts a disposable MySQL/MariaDB service composer examples:smoke # needs Docker; smoke-executes examples/ against a real engine
See docs/architecture/QUALITY_TOOLING.md, docs/architecture/CI_AND_INTEGRATION.md, and CONTRIBUTING.md for the full local-verification contract.
License
This package is proprietary. For use under the terms on this branch, request a free written license from support@maatify.dev. Include your organization name, if applicable, and an email address for security advisories and critical update notices. Maatify uses these details to administer licenses and send those notices. See LICENSE for the terms. Published versions carry their own license file.
👤 Author
Engineered by Mohamed Abdulalim (@megyptm)
Backend Lead & Technical Architect
https://www.maatify.dev
Built with ❤️ by Maatify.dev — Unified Ecosystem for Modern PHP Libraries