PHPackages                             amtgard/aaro-extensions - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. amtgard/aaro-extensions

ActiveLibrary[Database &amp; ORM](/categories/database)

amtgard/aaro-extensions
=======================

Extended production features for the AARO ORM

v0.9.2(yesterday)05↑2900%MITPHPPHP ^8.3

Since Jun 8Pushed yesterdayCompare

[ Source](https://github.com/amtgard/aaro-extensions)[ Packagist](https://packagist.org/packages/amtgard/aaro-extensions)[ Docs](https://github.com/amtgard/aaro-extensions)[ RSS](/packages/amtgard-aaro-extensions/feed)WikiDiscussions main Synced yesterday

READMEChangelog (3)Dependencies (8)Versions (8)Used By (0)

aaro-extensions
===============

[](#aaro-extensions)

Extended production features for the [AARO ORM](https://github.com/amtgard/active-record-orm) (`amtgard/active-record-orm`).

Requirements
------------

[](#requirements)

- PHP 8.3+
- Composer
- For integration tests: Docker (MariaDB via Compose)

Installation
------------

[](#installation)

```
composer require amtgard/aaro-extensions
```

Local development against the sibling ORM checkout:

```
composer install
```

The package `composer.json` includes a path repository to `../active-record-orm`.

---

Usage
-----

[](#usage)

Wrap a core table (or repository) so inserts, updates, and deletes are recorded automatically in a companion audit table. Application code continues to use the core table normally.

### Table level

[](#table-level)

```
use Amtgard\AaroExtensions\Audit\AuditConfiguration;
use Amtgard\AaroExtensions\Audit\AuditTableFactory;

$config = AuditConfiguration::builder()
    ->editedBySupplier(fn () => $currentUserId)
    ->build();

$table = AuditTableFactory::build($database, $policy, 'users', $config);

$table->clear();
$table->name = 'Alice';
$table->save();
```

### Repository / EntityManager level

[](#repository--entitymanager-level)

```
use Amtgard\AaroExtensions\Audit\AuditTableFactory;

$em = EntityManager::builder()
    ->database($database)
    ->dataAccessPolicy($policy)
    ->mapperSupplier(fn ($db, $policy, $name) => AuditTableFactory::mapperSupplier(
        $db,
        $policy,
        $name,
        $auditConfiguration,
    ))
    ->build();
```

Or use **`AuditRepositoryEntityTrait`** on a `RepositoryEntity` and implement `auditConfiguration()`.

### Configuration

[](#configuration)

```
AuditConfiguration::builder()
    ->auditTableName('users_history')   // default: {table}_audit
    ->editedBySupplier(fn () => 42)     // or EditedBySupplier instance
    ->auditInserts(false)               // default: true
    ->build();
```

OptionDefaultDescriptionAudit table name`{core_table}_audit`Companion audit table`editedBySupplier``null`Resolves `edited_by_id` on each audit row`auditInserts``true`Write an audit row when a core row is inserted---

Keeping audit tables in sync
----------------------------

[](#keeping-audit-tables-in-sync)

Every auditable core table needs a matching audit table. By convention the audit table is named `{core_table}_audit` (configurable via `AuditConfiguration`).

When the **core schema changes**, the audit table must be updated as well:

Core changeAudit table actionNew tableCreate `{table}_audit` with metadata columns plus nullable mirrors of every core column except `id`Column addedAdd the same column to the audit table (`null => true`)Column type changedUpdate the mirrored column type on the audit tableColumn droppedRename the audit column to `dropped_{n}_{column_name}` to preserve historical valuesThe audit table always includes these metadata columns (Phinx adds an auto-increment `id` PK automatically):

- `audit_id` — core row `id` (NOT NULL)
- `edit_at`, `edit_fields`, `edited_by_id`, `operation`

See [Audit reference](#audit-reference) below for full schema and write semantics.

### Migration workflow

[](#migration-workflow)

1. **Create** — after defining a core table, create its audit table in Phinx with mirrored columns (all nullable except metadata).
2. **Patch** — whenever a core migration adds, changes, or drops columns, apply the corresponding change to the audit table in the same release (use `dropped_{n}_*` for removed columns).

Migration generator and patcher commands:

```
# Generate Phinx migration(s) for audit table(s) from the live database schema
composer audit:phinx -- --env=.env --out-dir=db/migrations [--table=users]

# Emit a patch migration after core schema changes
composer audit:patch -- --env=.env --out-dir=db/migrations [--table=users]

# Merge project-specific table exclusions with the bundled defaults
composer audit:phinx -- --env=.env --out-dir=db/migrations --exclude-file=db/audit-exclusions.yaml
```

Or invoke the binary directly: `vendor/bin/aaro-audit-migrate phinx ...`

When `--table` is omitted, the CLI scans every table in the database and generates migrations for each one that is not excluded. Explicit `--table` always targets that table, even if it appears in an exclusions file.

### Table exclusions

[](#table-exclusions)

Infrastructure and framework tables (Phinx migration log, existing audit tables, etc.) should not receive audit migrations. The package ships a default list at `resources/audit-table-exclusions.yaml`:

```
tables:
  - phinxlog

suffixes:
  - _audit
```

KeyMatches`tables`Exact table names`suffixes`Table names ending with the given suffixAdd project-specific exclusions in a local YAML file and pass it with `--exclude-file`. Entries are **merged** with the bundled defaults, not replaced:

```
# db/audit-exclusions.yaml
tables:
  - queue_jobs
  - sessions
suffixes:
  - _history
```

```
composer audit:patch -- --env=.env --out-dir=db/migrations --exclude-file=db/audit-exclusions.yaml
```

To exclude a table that ships in the defaults (unlikely), target it explicitly with `--table` instead of relying on the bulk scan.

### Example audit migration (Phinx)

[](#example-audit-migration-phinx)

```
$this->table('users_audit')
    ->addColumn('audit_id', 'integer', ['null' => false])
    ->addColumn('edit_at', 'datetime', ['null' => false])
    ->addColumn('edit_fields', 'json', ['null' => true])
    ->addColumn('edited_by_id', 'integer', ['null' => true])
    ->addColumn('operation', 'enum', ['values' => ['insert', 'update', 'delete'], 'null' => false])
    ->addColumn('name', 'string', ['null' => true, 'limit' => 255])
    ->addColumn('email', 'string', ['null' => true, 'limit' => 255])
    ->create();
```

---

Testing
-------

[](#testing)

### Unit tests

[](#unit-tests)

```
composer test:unit
```

### Integration tests (Docker)

[](#integration-tests-docker)

Integration tests use MariaDB on port **24307** via this package's `docker-compose.dev.yml`.

```
composer docker:up
composer migrate:test
composer test:integ
composer docker:down
```

Copy `test-resources/.env.example` to `test-resources/.env` if needed.

```
composer test    # unit + integration
```

---

Audit reference
---------------

[](#audit-reference)

Technical details for the row-level audit feature.

### Overview

[](#overview)

Transparent auditing for ORM `Table` and `Repository` usage. The wrapper records inserts, updates, and deletes in a companion audit table without audit-specific code in business logic.

Each audit row is a **rolling snapshot** of what happened (new/current values), ordered by time so you can answer “who changed this record to what?” and replay a record’s history from insert through delete.

### Table pairing

[](#table-pairing)

Core tableAudit table (default)`users``users_audit`### Schema

[](#schema)

Phinx adds an auto-increment **`id`** as the audit row primary key. You declare the rest.

**Metadata columns**

ColumnTypeNullableDescription`audit_id`same as core PKNOCore row `id` this event belongs to`edit_at`datetimeNOWhen the event was recorded`edit_fields`JSONYESField names changed on **update**; `[]` on insert/delete`edited_by_id`int (typical)YESActor id from supplier`operation`enumNO`insert`, `update`, or `delete`**Mirrored core columns**

- Every core column **except** `id` is replicated on the audit table.
- All mirrored columns are **explicitly nullable**.
- Values are snapshots after the operation (see write semantics).

When a core column is dropped, patch migrations preserve history as `dropped_{n}_{column_name}` on the audit table.

### Write semantics

[](#write-semantics)

EventWhen`operation``edit_fields`Mirrored columnsInsertAfter core insert`insert``[]`All inserted valuesUpdateAfter core update (only if something changed)`update`Changed field namesNew values for changed fields onlyDeleteBefore core delete`delete``[]`Full final rowExample for core row `id = 42`:

```
id | audit_id | operation | edit_at          | edit_fields      | string_value
---+----------+-----------+------------------+------------------+-------------
 1 |       42 | insert    | 2026-06-07 10:00 | []               | Alice
 2 |       42 | update    | 2026-06-07 11:00 | ["string_value"] | Alicia
 3 |       42 | delete    | 2026-06-07 12:00 | []               | Alicia

```

### Package layout

[](#package-layout)

```
src/Audit/
  AuditTable.php              Wrapper around core Table
  AuditTableFactory.php       Builds AuditTable + EntityMapper supplier
  AuditConfiguration.php      Table name, supplier, insert toggle
  AuditSnapshotCapture.php    Computes snapshot payload per operation
  AuditSnapshot.php           Value object for one audit event
  AuditOperation.php          insert | update | delete
  AuditColumns.php            Metadata column name constants
  AuditRepository.php         Repository base class
  AuditRepositoryEntityTrait.php
  EditedBySupplier.php        Optional supplier interface
  Migration/
    AuditMigrationService.php       phinx / patch orchestration
    AuditSchemaGenerator.php        core schema → audit schema
    AuditSchemaDiffer.php             diff core vs audit for patches
    AuditPhinxWriter.php              Phinx migration file output
    AuditTableExclusions.php          table/suffix exclusion rules
    AuditTableExclusionsLoader.php    YAML loader for exclusions
    Cli/AuditMigrateCommand.php       aaro-audit-migrate CLI
resources/
  audit-table-exclusions.yaml   default tables skipped by bulk scan
bin/
  aaro-audit-migrate            CLI entry point

```

---

Roadmap
-------

[](#roadmap)

- **Phase 1** — Runtime audit wrapper + unit tests
- **Phase 2** — Docker Compose + integration tests
- **Phase 3** — Phinx migration generator and audit-table patcher (`audit:phinx`, `audit:patch`)

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance100

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

1d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/1b6d7e891827c823a1d8bbef786eb928fe33647f76f0ae469c3bea4757b51888?d=identicon)[amtgard](/maintainers/amtgard)

---

Top Contributors

[![esdraelon](https://avatars.githubusercontent.com/u/7896847?v=4)](https://github.com/esdraelon "esdraelon (5 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/amtgard-aaro-extensions/health.svg)

```
[![Health](https://phpackages.com/badges/amtgard-aaro-extensions/health.svg)](https://phpackages.com/packages/amtgard-aaro-extensions)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k11](/packages/tempest-framework)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1155.2k](/packages/rcsofttech-audit-trail-bundle)[kimai/kimai

Kimai - Time Tracking

4.7k8.7k1](/packages/kimai-kimai)[perplorm/perpl

Perpl is an improved and still maintained fork of Propel2, an open-source Object-Relational Mapping (ORM) for PHP.

239.4k](/packages/perplorm-perpl)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1715.6k12](/packages/2lenet-crudit-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
