Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ name: CI

on:
pull_request:
branches:
- main
push:
branches:
- main
Expand Down Expand Up @@ -69,6 +67,7 @@ jobs:
--prefer-dist \
--no-progress
composer check-platform-reqs
composer dump-autoload --optimize --strict-psr
composer audit \
--no-interaction \
--abandoned=fail
Expand Down Expand Up @@ -102,6 +101,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
git diff --check
git diff --exit-code

unit-regression:
Expand Down Expand Up @@ -153,13 +153,14 @@ jobs:
shell: bash
run: |
set -euo pipefail
vendor/bin/phpunit --testsuite unit
vendor/bin/phpunit --testsuite regression
composer test:unit
composer test:regression

- name: Repository integrity
shell: bash
run: |
set -euo pipefail
git diff --check
git diff --exit-code

lowest-dependencies:
Expand Down Expand Up @@ -256,11 +257,10 @@ jobs:
shell: bash
run: |
set -euo pipefail
vendor/bin/phpunit --testsuite unit
vendor/bin/phpunit --testsuite regression
vendor/bin/phpunit --testsuite integration
vendor/bin/phpunit --testsuite integration
vendor/bin/phpunit
composer test:unit
composer test:regression
composer test:integration
composer test:integration

- name: Verify MySQL residue
if: ${{ always() && !cancelled() && steps.setup-php.outcome == 'success' }}
Expand All @@ -287,14 +287,15 @@ jobs:
'maa_persistence_test_global_ordering',
'maa_persistence_test_scoped_ordering',
'maa_persistence_test_pagination_items',
'maa_persistence_test_transaction_savepoint_items',
];
$triggers = [
'maa_persistence_test_fail_global_target_update',
'maa_persistence_test_fail_scoped_target_update',
];

$tableStatement = $pdo->prepare(
'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (?, ?, ?) ORDER BY TABLE_NAME'
'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (?, ?, ?, ?) ORDER BY TABLE_NAME'
);
$tableStatement->execute([$schema, ...$tables]);
$remainingTables = $tableStatement->fetchAll(PDO::FETCH_COLUMN);
Expand Down Expand Up @@ -324,6 +325,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
git diff --check
git diff --exit-code

integration:
Expand Down Expand Up @@ -413,13 +415,18 @@ jobs:
exit 1
fi

- name: Run Integration and full suites
- name: Run Consumer Verification Harness
shell: bash
run: |
set -euo pipefail
composer test:consumer

- name: Run Integration and repeatability suites
shell: bash
run: |
set -euo pipefail
vendor/bin/phpunit --testsuite integration
vendor/bin/phpunit --testsuite integration
vendor/bin/phpunit
composer test:integration
composer test:integration

- name: Verify MySQL residue
if: ${{ always() && !cancelled() && steps.setup-php.outcome == 'success' }}
Expand All @@ -446,14 +453,15 @@ jobs:
'maa_persistence_test_global_ordering',
'maa_persistence_test_scoped_ordering',
'maa_persistence_test_pagination_items',
'maa_persistence_test_transaction_savepoint_items',
];
$triggers = [
'maa_persistence_test_fail_global_target_update',
'maa_persistence_test_fail_scoped_target_update',
];

$tableStatement = $pdo->prepare(
'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (?, ?, ?) ORDER BY TABLE_NAME'
'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (?, ?, ?, ?) ORDER BY TABLE_NAME'
);
$tableStatement->execute([$schema, ...$tables]);
$remainingTables = $tableStatement->fetchAll(PDO::FETCH_COLUMN);
Expand Down Expand Up @@ -483,6 +491,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
git diff --check
git diff --exit-code

ci-gate:
Expand Down
19 changes: 19 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# تعليمات مستودع maatify/persistence

## التفعيل المعياري

قبل التخطيط أو التنفيذ أو المراجعة، يجب قراءة [ملف معيار الاعتماد المثبت](docs/php-engineering-standards/standards/STANDARDS_ADOPTION_STANDARD_AR.md) كاملًا، ثم قراءة المعايير المنطبقة المسجلة في [STANDARDS_MANIFEST.md](docs/php-engineering-standards/STANDARDS_MANIFEST.md) بحسب نطاق المهمة.

يسجل [STANDARDS_MANIFEST.md](docs/php-engineering-standards/STANDARDS_MANIFEST.md) مجموعة الاعتماد المحلية ونتيجة الحل، ولا يجوز استخدام مرجع upstream عائم أو نسخ معايير إضافية خارج المجموعة المسجلة.

## قواعد خاصة بالمشروع

- هذه الحزمة مكتبة Composer مستقلة، framework-agnostic وhost-agnostic، وتستخدم PDO المباشر مع SQL متوافق مع ملف MySQL/MariaDB. التحقق التشغيلي في هذا المستودع يتم على MySQL الحقيقي فقط، وMariaDB ليست مدخلة تحقق مستقلة في مصفوفة CI الحالية.
- يجب الحفاظ على عقد v1.4.0 العام، بما في ذلك ملكية المعاملات، ودعم المعاملة الخارجية، ودلالات savepoint المعتمدة؛ لا يُجرى تغيير breaking public API.
- اختبارات قاعدة البيانات تتطلب MySQL حقيقيًا؛ لا يجوز استخدام SQLite أو mocks لإثبات سلوك persistence الحقيقي.
- لا يُتتبّع composer.lock، ولا يُضاف حقل Composer باسم version، ولا تُنشأ Tag أو GitHub Release ضمن أعمال هذا المستودع ما لم يصدر تصريح صريح بذلك.
- يجب أن تبقى تغييرات Work Unit داخل نطاقها، وأن تمر عبر بوابات التحقق الفعلية الموثقة في [CONTRIBUTING.md](CONTRIBUTING.md) قبل النشر أو فتح Pull Request.

## تعليمات إضافية

لا توجد ملفات AGENTS.md إضافية خاصة بمسارات فرعية في هذا المستودع.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.4.0] - 2026-09-18

### Added
* Transaction savepoint orchestration capability (`SavepointTransactionRunnerInterface`, `PdoSavepointTransactionRunner`) for safe, operation-local rollback boundaries within caller-owned PDO transactions.
* `TransactionExecutionException` for package-detected non-throwing failures of transaction control statements.

## [1.3.0] - 2026-09-10

### Added
Expand Down Expand Up @@ -65,7 +71,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Rolls back owned transactions after operation failures and rethrows the original throwable.
* Enforced real MySQL testing; SQLite substitution is explicitly disabled.

[Unreleased]: https://github.com/Maatify/persistence/compare/v1.3.0...HEAD
[Unreleased]: https://github.com/Maatify/persistence/compare/v1.4.0...HEAD
[1.4.0]: https://github.com/Maatify/persistence/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/Maatify/persistence/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/Maatify/persistence/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/Maatify/persistence/compare/v1.0.0...v1.1.0
Expand Down
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ This Code of Conduct applies within all community spaces for the `maatify/persis

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at:

[support@maatify.com](mailto:support@maatify.com)
[support@maatify.dev](mailto:support@maatify.dev)

All complaints will be reviewed and investigated promptly and fairly.

Expand Down
25 changes: 22 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Contributions should respect the current directory structure:

* `src/`: Contains the production source code.
* `tests/`: Contains the test suites (`unit`, `regression`, and `integration`).
* `docs/`: Contains internal documentation and standards.
* `docs/`: Contains internal documentation, architecture decisions, and the pinned engineering standards.

## Local Verification

Expand All @@ -39,12 +39,24 @@ Before submitting a Pull Request, please ensure all local verification steps pas
```bash
composer install
composer validate --strict
composer dump-autoload --optimize --strict-psr
composer check-platform-reqs
composer audit --no-interaction --abandoned=fail
composer analyse
composer test:unit
composer test:regression
composer test:integration
composer test:consumer
vendor/bin/php-cs-fixer fix --dry-run --diff
git diff --check
```

The commands above are the local parity sequence for the CI quality and test
gates. `composer test:integration` and `composer test:consumer` require the
real MySQL service configured below. The consumer harness must be run from the
package root; it creates and removes its own clean consumer root and test
table twice.

### Integration Testing

Integration tests require a real MySQL database. SQLite is explicitly **not** supported as a substitute for these tests.
Expand All @@ -69,6 +81,13 @@ Or to run the full test suite:
composer test
```

Workflow syntax is verified locally with actionlint `v1.7.12`, using the
checksum pinned in `.github/workflows/ci.yml`:

```bash
actionlint -color
```

## Architectural Contribution Rules

When contributing code, you must adhere to the following architectural rules:
Expand All @@ -80,12 +99,12 @@ When contributing code, you must adhere to the following architectural rules:
* **No Generic Repository Abstraction**: Keep the logic specific to the current goals (e.g., PDO ordering utilities).
* **SQL Identifiers**: Table and column names must remain trusted configurations, not raw user input.
* **Prepared Statements**: All runtime values must be bound using prepared statements.
* **Transaction Ownership**: `moveWithinScope()` owns a transaction only when no transaction is active and participates in an active caller-owned transaction without committing or rolling it back. Composed operations must use the same PDO connection.
* **Transaction Ownership**: `moveWithinScope()` owns a transaction only when no transaction is active and participates in an active caller-owned transaction without committing or rolling it back. Composed operations and savepoint orchestrations must use the same PDO connection.
* **Scope Isolation**: Do not break scope isolation; rows outside the affected range must not be moved.
* **No Global Normalization**: Do not perform global normalization of gaps as a side effect of a scoped operation.
* **Rollback Behavior**: Rollbacks must preserve the original error/exception.
* **Exception Handling**: Do not catch every `\PDOException` or external `\Throwable` randomly to wrap it in a package exception. `PersistenceException` is strictly for package-defined exceptions.
* **Composer Lock**: This reusable library does not track `composer.lock`, in accordance with the [Composer Package Standard](docs/standards/COMPOSER_PACKAGE_STANDARD.md). Remove any locally generated `composer.lock` before submitting changes.
* **Composer Lock**: This reusable library does not track `composer.lock`, in accordance with the [Composer Package Standard](docs/php-engineering-standards/standards/packages/COMPOSER_PACKAGE_STANDARD.md). Remove any locally generated `composer.lock` before submitting changes.

## Pull Request Rules

Expand Down
49 changes: 48 additions & 1 deletion PERSISTENCE_PACKAGE_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
* **Boundaries**: Framework-agnostic and host-agnostic. No HTTP API, no generic application repository, no ORM, no container bindings.
* **Note**: PDO Pagination was introduced in v1.1.0.

## Persistence and Schema Ownership

The package owns the reusable PDO ordering, transaction, savepoint, and
pagination behavior, but it does not own a persistent business entity or a
production table. Consumers provide their own trusted table and column
identifiers, SQL, scopes, and mapping. The package does not create migrations,
foreign keys, or joins to Host tables. The persistence profile is
MySQL/MariaDB-compatible SQL through direct PDO. Repository integration and
Consumer verification run against real MySQL, with MySQL 8.4.10 as the current
CI baseline; MariaDB is not independently verified by the current CI matrix.
The package-level schema notes are in [schema/README.md](schema/README.md).

## Public API Inventory

### `Maatify\Persistence\Pdo\Ordering\ScopedOrderingConfig`
Expand Down Expand Up @@ -123,6 +135,26 @@
* **Composition Requirement**: Atomic composition requires every participant to
use the same PDO connection.

### `Maatify\Persistence\Pdo\Transaction\SavepointTransactionRunnerInterface`
* **Status**: `interface`
* **Extends**: `TransactionRunnerInterface`
* **Public Methods**:
* Inherits `run(callable $callback): mixed`
* **Contract**: Provides stronger operation-local savepoint semantics when an outer transaction exists, without altering the inherited signature.

### `Maatify\Persistence\Pdo\Transaction\PdoSavepointTransactionRunner`
* **Status**: `final readonly class`
* **Implements**: `SavepointTransactionRunnerInterface`
* **Constructor**:
```php
public function __construct(\PDO $pdo)
```
* **Transaction Boundary**:
* **No active transaction**: Preserves normal owned-transaction behavior.
* **Active outer transaction**: Uses an operation-local savepoint. The caller-owned outer transaction is never committed or fully rolled back by the runner.
* **Composition Requirement**: Same PDO connection is required. Nested and repeated usage is completely supported.
* **Deprecation status**: Neither this runner nor `PdoTransactionRunner` is deprecated. Neither replaces the other.

### `Maatify\Persistence\Pdo\Pagination\PageRequest`
* **Status**: `final readonly class`
* **Constructor**:
Expand Down Expand Up @@ -249,6 +281,12 @@
* **Implements**: `Maatify\Persistence\Exception\PersistenceException`
* **Trigger**: Package-owned execution and result-contract failures, including non-throwing `prepare()`, `bindValue()`, or `execute()` failures, invalid count shape or count value, fetch-state failures, invalid mapper result, and invalid `PageResult` invariants. Thrown `PDOException` and mapper `Throwable` instances propagate without wrapping.

### `Maatify\Persistence\Exception\TransactionExecutionException`
* **Status**: `final class`
* **Extends**: `Maatify\Exceptions\Exception\System\SystemMaatifyException`
* **Implements**: `Maatify\Persistence\Exception\PersistenceException`
* **Trigger**: Package-owned non-throwing execution failures for transaction control statements (e.g., when PDO `exec()` returns `false` instead of throwing an exception during savepoint orchestration).

## SQL and Trust Boundaries

* **Identifiers**: SQL identifiers (table, columns) are validated configuration, not user input. They cannot be PDO-bound. Supported formats are standard identifier naming rules and `schema.table`.
Expand All @@ -270,6 +308,10 @@
* `moveWithinScope()` locks the applicable active ordering scope with
`SELECT ... FOR UPDATE` inside the transaction it owns or joins.

## Transaction Savepoint Orchestration Boundaries

For the detailed behavioral contract and runner selection guidance across both composed transactions and savepoint orchestration, see the [PDO Transaction Architecture](docs/architecture/PDO_TRANSACTION_ARCHITECTURE.md).

## Pagination Boundaries
* **Normalization**: Strict page and per-page normalization.
* **Query Ownership**: Package handles total, filtered-count, and data query execution.
Expand Down Expand Up @@ -310,6 +352,7 @@ Renaming the marker MAY be reconsidered only as part of a separately approved, m
| `InvalidPaginationConfigurationException` | `SystemMaatifyException` | `ErrorCodeEnum::MAATIFY_ERROR` | default | Invalid per-page bounds, whitelist errors. |
| `InvalidPaginationQueryException` | `SystemMaatifyException` | `ErrorCodeEnum::MAATIFY_ERROR` | default | Missing/empty SQL, semicolons, reserved parameters. |
| `PaginationExecutionException` | `SystemMaatifyException` | `ErrorCodeEnum::MAATIFY_ERROR` | default | Package-owned execution and result-contract failures. |
| `TransactionExecutionException` | `SystemMaatifyException` | `ErrorCodeEnum::MAATIFY_ERROR` | default | Package-owned non-throwing execution failures for transaction control statements (e.g. savepoint execution). |
### `Maatify\Persistence\Exception\PersistenceException`
* **Status**: `interface`
* **Extends**: `\Throwable`
Expand Down Expand Up @@ -370,7 +413,11 @@ Renaming the marker MAY be reconsidered only as part of a separately approved, m
* `PERSISTENCE_TEST_MYSQL_USER`
* `PERSISTENCE_TEST_MYSQL_PASSWORD`
* **Test Database Isolation**: Assumes isolated test tables and requires local package privileges (trigger/table cleanup). Tests include trigger failure injection.
* **Current CI MySQL Baseline**: 8.4.10.
* **Current CI MySQL Baseline**: MySQL 8.4.10. MariaDB is not independently verified by the current CI matrix.
* **Consumer Verification Harness**: `composer test:consumer` installs the
package into a separate non-symlinked Composer root and verifies a public
ordering, savepoint, and pagination workflow against real MySQL twice from
clean consumer/database state.

## Verification Model

Expand Down
Loading