PHPackages                             sweikenb/dirty - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. sweikenb/dirty

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

sweikenb/dirty
==============

Library for checking if an object or array has changes (is dirty) since the last check.

v0.0.2(1y ago)08MITPHPPHP ^8.2CI failing

Since Jul 14Pushed 1y ago1 watchersCompare

[ Source](https://github.com/sweikenb/dirty)[ Packagist](https://packagist.org/packages/sweikenb/dirty)[ RSS](/packages/sweikenb-dirty/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (5)Versions (3)Used By (0)

sweikenb/dirty
==============

[](#sweikenbdirty)

Library for checking if an object or array has changes (is dirty) since the last check.

License: MIT

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

[](#installation)

```
composer require sweikenb/dirty
```

If you plan to use this library in a **Symfony** project, consider checking out the corresponding [DirtyBundle](https://github.com/sweikenb/dirty-bundle) instead.

Usage
-----

[](#usage)

**How does it work?**

In order to check if the test-subject has untracked changes, the given object or array will be normalized, flattened and stored using a configurable storage adapter.

The next time the check is executed the current data will be compared to the data of the previous check. Which of the subjects fields will be tracked or ignored can be [configured](#configuration) (see below).

**Usage:**

As the result of the check, you will receive a detailed list of fields that changed and the corresponding previous and current value:

```
$categoryId = 'category:123';
$categoryData = [
    'title' => 'My Category',
    'tags' => ['Foo', 'Bar', 'Baz'],
    'createdAt' => '2024-07-10 15:31:00'
];

$service = new \Sweikenb\Library\Dirty\Service\DirtyCheckService();

$result = $service->execute($categoryId, $categoryData);

if ($result->isDirty) {
    foreach ($result->diffs as $fieldPath => $diff) {
        echo sprintf("Field '%s' is dirty! '%s' -> '%s'\n", $fieldPath, $diff->previously, $diff->currently);
    }
}
```

### Configuration

[](#configuration)

In some cases you might have data-structures that contain volatile values (e.g. dynamic timestamps) that will always trigger a false-positiv for the dirty-check:

#### Ignore fields

[](#ignore-fields)

If you want to **ignore** certain fields, you can specify which fields should be ignored during the check. If the configured fields contain complex data *(object or array)* the affected field and all of it subsequent data will be **ignored** *(the field acts like a **wildcard**)*:

```
$userId = 'user:123';
$userData = [
    'username' => 'some-user'
    'security' => [
        'password' => '...',
        'passwordSalt' => '...',
        'pgp-key' => '...'
    ]
    'meta' => [
        'source' => 'sso'
        'createdAt' => '2024-07-10 15:41:10'
    ]
];

$config = new \Sweikenb\Library\Dirty\Model\ConfigModel(ignoreFieldPath: [
    'security',         // will ignore the whole "security" subset
    'meta.createdAt'    // will only ignore the "createdAt" field under "meta"
]);

$service = new \Sweikenb\Library\Dirty\Service\DirtyCheckService();

$result = $service->execute($userId, $userData, $config);

if ($result->isDirty) {
    foreach ($result->diffs as $fieldPath => $diff) {
        echo sprintf("Field '%s' is dirty! '%s' -> '%s'\n", $fieldPath, $diff->previously, $diff->currently);
    }
}
```

#### Check only certain fields

[](#check-only-certain-fields)

You can also explicitly allow fields that should be checked, any other fields will be ignored. If the configured fields contain complex data *(object or array)* the affected field and all of it subsequent data will be **checked** *(the field acts like a **wildcard**)*:

```
$userId = 'user:123';
$userData = [
    'username' => 'some-user'
    'security' => [
        'password' => '...',
        'passwordSalt' => '...',
        'pgp-key' => '...',
    ]
    'meta' => [
        'source' => 'sso'
        'createdAt' => '2024-07-10 15:41:10'
    ]
];

$config = new \Sweikenb\Library\Dirty\Model\ConfigModel([
    'username',     // check the "username" field
    'meta',         // check the "meta" field with all containing sub-fields
]);

$service = new \Sweikenb\Library\Dirty\Service\DirtyCheckService();

$result = $service->execute($userId, $userData, $config);

if ($result->isDirty) {
    foreach ($result->diffs as $fieldPath => $diff) {
        echo sprintf("Field '%s' is dirty! '%s' -> '%s'\n", $fieldPath, $diff->previously, $diff->currently);
    }
}
```

#### Combine check and ignore fields

[](#combine-check-and-ignore-fields)

Please note that the "ignore"-configuration will be applied after the "allow"-configuration, that means you can combine them to enable certain structures and then explicitly remove a single field or subset from it:

```
$userId = 'user:123';
$userData = [
    'username' => 'some-user'
    'security' => [
        'password' => '...',
        'passwordSalt' => '...',
        'pgp-key' => '...',
    ]
    'meta' => [
        'source' => 'sso'
        'createdAt' => '2024-07-10 15:41:10'
    ]
];

$config = new \Sweikenb\Library\Dirty\Model\ConfigModel(
    [
        'username',        // check the "username" field
        'meta',            // check the "meta" field with all containing sub-fields
    ],
    [
        'meta.createdAt',  // ignore the "createdAt" sub-field even tough "meta" was explicitly configured to be checked
    ]
);

$service = new \Sweikenb\Library\Dirty\Service\DirtyCheckService();

$result = $service->execute($userId, $userData, $config);

if ($result->isDirty) {
    foreach ($result->diffs as $fieldPath => $diff) {
        echo sprintf("Field '%s' is dirty! '%s' -> '%s'\n", $fieldPath, $diff->previously, $diff->currently);
    }
}
```

Storage Adapters
----------------

[](#storage-adapters)

Storage adapters and their primary use-cases:

- **Filesystem Adapter** *(default)*
    - local **development** or stage environments
    - single-server setups
    - low amounts of data to check
    - this adapter is **NOT RECOMMENDED** to be used with a network storage mount and highly benefits from a fast underlying storage *(e.g. SSD)*
    - files will not be cleaned up automatically, you need to write your own script for that!
- **REDIS Adapter** *(or compatible such as "ValKey")*
    - **Symfony** applications via [DirtyBundle](https://github.com/sweikenb/dirty-bundle)
    - **production** or stage environments
    - multi-server setups
    - any data-set size

You can add custom storage adapters if needed by implementing the `Sweikenb\Library\Dirty\Api\StorageAdapterInterface`.

Configuration and customization
-------------------------------

[](#configuration-and-customization)

- *TODO*

###  Health Score

28

—

LowBetter than 54% of packages

Maintenance50

Moderate activity, may be stable

Popularity4

Limited adoption so far

Community7

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 ~298 days

Total

2

Last Release

366d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e3d14640f5364ded9d425197636c706000f3012f350ef488806ebb72d9b9101e?d=identicon)[sweikenb](/maintainers/sweikenb)

---

Top Contributors

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

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/sweikenb-dirty/health.svg)

```
[![Health](https://phpackages.com/badges/sweikenb-dirty/health.svg)](https://phpackages.com/packages/sweikenb-dirty)
```

###  Alternatives

[hirethunk/verbs

An event sourcing package that feels nice.

513162.9k6](/packages/hirethunk-verbs)[cognesy/instructor-php

The complete AI toolkit for PHP: unified LLM API, structured outputs, agents, and coding agent control

310107.9k1](/packages/cognesy-instructor-php)[symfony/ai-platform

PHP library for interacting with AI platform provider.

51927.7k134](/packages/symfony-ai-platform)[solspace/craft-freeform

The most flexible and user-friendly form building plugin!

52664.9k12](/packages/solspace-craft-freeform)[symfony/ai-agent

PHP library for building agentic applications.

30536.7k44](/packages/symfony-ai-agent)[morrislaptop/laravel-popo-caster

Automatically cast JSON columns to rich PHP objects in Laravel using Symfony's Serializer

27643.8k](/packages/morrislaptop-laravel-popo-caster)

PHPackages © 2026

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