PHPackages                             pdombrovsky/dyna-exp - 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. pdombrovsky/dyna-exp

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

pdombrovsky/dyna-exp
====================

Fluent builders and typed helpers for DynamoDB expressions

1.x-dev(1mo ago)06.8k↑15.9%MITPHPPHP &gt;=8.2.0

Since Feb 27Pushed 2w ago1 watchersCompare

[ Source](https://github.com/pdombrovsky/dyna-exp)[ Packagist](https://packagist.org/packages/pdombrovsky/dyna-exp)[ Docs](https://github.com/pdombrovsky/dyna-exp)[ RSS](/packages/pdombrovsky-dyna-exp/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (2)Versions (11)Used By (0)

DynaExp
=======

[](#dynaexp)

Build DynamoDB expressions (ConditionExpression, FilterExpression, KeyConditionExpression, UpdateExpression, ProjectionExpression) with a small set of typed helpers. DynaExp keeps the verbose string juggling out of your application code while staying close to native DynamoDB semantics.

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

[](#requirements)

- PHP &gt;= 8.2

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

[](#installation)

> ⚠️ The library is currently in **alpha**. Interfaces may change between releases until we hit the stable 1.x line.

```
composer require pdombrovsky/dyna-exp:^1.0@alpha
```

Overview
--------

[](#overview)

- **Nodes** – small immutable objects such as `Path`, `Condition`, `Operation`, `Projection`, or `Update`. They hold typed expression data and evaluate themselves through the internal evaluator contract.
- **Factories** – ergonomic wrappers (`Attribute`, `Key`, `AttributeSize`, `DefaultValue`, …) that expose DynamoDB-oriented helpers. One `Attribute` instance can create conditions, updates, projections, search expressions, and aliases without re-parsing strings.
- **Builders** – fluent APIs for assembling nodes (`ConditionBuilder`, `KeyConditionBuilder`, `ProjectionBuilder`, `UpdateBuilder`, `ExpressionBuilder`).
- **Evaluator** – turns nodes into DynamoDB strings, allocates deterministic `ExpressionAttributeNames`/`ExpressionAttributeValues`, keeps alias usage consistent even when nodes are reused, and can optionally normalize nodes through a preprocessor before rendering.
- **ExpressionResult** – a read-only result object with a simple `toArray()` export. You stay in control of marshalling to DynamoDB types, so the library works with any SDK or transport layer.

Quick Start
-----------

[](#quick-start)

```
use DynaExp\Factories\Attribute;
use DynaExp\Factories\Key;
use DynaExp\Builders\ConditionBuilder;
use DynaExp\Builders\ExpressionBuilder;

$price = Attribute::create('price');
$stock = Attribute::create('inventory', 'total');

$condition = ConditionBuilder::allOf(
    $price->lessThanEqual(100),
    $stock->greaterThan(0)
)->build();

$expr = (new ExpressionBuilder())
    ->setFilter($condition)
    ->setKeyCondition(
        Key::create('pk')->equal('PRODUCT#123')
    )
    ->build()
    ->toArray();

// [
//   'FilterExpression' => '#0  :1',
//   'KeyConditionExpression' => '#2 = :2',
//   'ExpressionAttributeNames' => ['#0' => 'price', '#1' => 'total', '#2' => 'pk'],
//   'ExpressionAttributeValues' => [':0' => 100, ':1' => 0, ':2' => 'PRODUCT#123'],
// ]
```

The builders never mutate state after build time, so the same nodes can be reused in different expressions.

### Evaluation Pipeline

[](#evaluation-pipeline)

- Each node exposes `evaluate(EvaluatorInterface $evaluator)`.
- `Evaluator` implements that internal evaluator contract, recursively evaluates nested nodes, and centralizes name/value alias allocation.
- `ExpressionBuilder` can receive an optional `ExpressionPreprocessorInterface` and creates a fresh `Evaluator` for every `build()` call.
- If you need domain-specific rewrites before rendering, see `Advanced Customization` below.

### Validation Scope

[](#validation-scope)

DynaExp validates local expression-builder invariants: path syntax, path segments, empty builders, and collection node shape. It does not validate your table schema, request shape, key role semantics, or every DynamoDB grammar rule. For example, the package does not know which attributes are partition or sort keys, whether an `ADD` action targets a valid DynamoDB type, or whether a final set of expressions is valid for a specific DynamoDB operation.

Factories
=========

[](#factories)

Attribute
---------

[](#attribute)

`Attribute` is the main fluent helper for attribute access in filters, projections, conditions, and updates. Under the hood it wraps an immutable `DynaExp\Nodes\Path`, validates every segment, and keeps DynamoDB-specific metadata such as deterministic string representations, `searchExpression()` output, and stable aliased evaluation output.

A single `Attribute` object can be reused across the whole expression: the helper methods mixed in from `ConditionTrait` and `OperationTrait` let you create equality/range/containment checks, attribute existence/type predicates, arithmetic updates, list append/prepend operations, `if_not_exists`, `size()`, and more – all off the same root path.

Examples:

```
use DynaExp\Factories\Attribute;
use DynaExp\Evaluation\Evaluator;
use DynaExp\Nodes\Path;

// Programmatic attribute helper
$p = Attribute::create('map', 'nested', 0, 'attr');   // map.nested[0].attr

// From string with quotes to keep dots inside a segment
$p2 = Attribute::fromString('map."a.b"[3].c');        // map.a.b[3].c

// Standalone path node, useful when you need the raw expression operand
$nodePath = Path::fromString('map."a.b"[3].c');

// JMESPath-like search expression (quoted segments). Optional index reset.
$p2->searchExpression();        // "map"."a.b"[3]."c"
$p2->searchExpression(true);    // "map"."a.b"[0]."c"

// JMESPath for marshaled DynamoDB AttributeValue item data.
// The expression is relative to the item object; add Item. or Items[0]. outside if needed.
$p2->marshaledSearchExpression();                          // "map".M."a.b".L[3].M."c"
$p2->marshaledSearchExpression(true);                      // "map".M."a.b".L[0].M."c"

// Evaluation output with aliases
$evaluator = new Evaluator();
$exprPath = $evaluator->evaluate($p2->project());   // "#0.#1[3].#2"
$namesMap = $evaluator->getAttributeNameAliases();  // ['#0' => 'map', '#1' => 'a.b', '#2' => 'c']

// Parent/child helpers
$p3 = Attribute::create('root', 'child');  // root.child
$p3Parent = $p3->parent();             // Attribute for 'root'
$p3Child  = $p3->child('leaf');        // root.child.leaf

// Check ancestry
$nested = $p3->child('leaf', 'branch');
$p3->isParentOf($nested); // true
$p3->project()->relativePathOf($nested->project()); // Path for 'leaf.branch'
count($nested->project()); // 4

$counter = Attribute::create('stats', 'counter');

// Reuse the same path for conditions and update actions
$isNonNegative = $counter->greaterThanEqual(0);                   // Semantics: stats.counter >= :0
$increment = $counter->set($counter->ifNotExists(0)->plus(1));    // Semantics: SET stats.counter = if_not_exists(stats.counter, :1) + :2
```

Notes:

- Capabilities:
    - `project()` exposes the underlying `DynaExp\Nodes\Path` for projection builders or manual evaluation.
    - `searchExpression($resetIndexes = false)` formats a deterministic, JMESPath-compatible string for native/unmarshaled item data. Attribute segments are emitted as quoted identifiers with JSON string escaping.
    - `marshaledSearchExpression($resetIndexes = false)` formats a JMESPath-compatible string for marshaled DynamoDB AttributeValue item data and points to the target AttributeValue wrapper.
    - `parent()`, `child(...)`, `isParentOf(...)`, `lastSegment()`, `Path::relativePathOf(...)`, and `Countable` support safe path-tree navigation.
    - Condition helpers include equality/range checks, `between()`, `in()`, `beginsWith()`, `contains()`, `attributeExists()`, and `attributeType()`.
    - Negative condition helpers include `notEqual()`, `notBetween()`, `notIn()`, `notBeginsWith()`, `notContains()`, `attributeNotExists()`, and `attributeTypeNot()`.
- Parser rules (`Attribute::fromString()` and `DynaExp\Nodes\Path::fromString()`):
    - Dots split attribute segments: `map.nested.attr`
    - Brackets denote list indexes: `list[0][10]`
    - Double quotes wrap a segment to allow dots: `attr1."some.nested.attribute".attr2`
    - Inside quotes, JSON string escapes are supported: use `\"` for a literal quote, `\\` for a literal backslash, and escapes such as `\n`, `\t`, `\/`, or `\uXXXX` when needed
    - Quotes are not allowed inside brackets
- Limitations:
    - Negative indexes, leading-zero indexes except `[0]`, indexes larger than `PHP_INT_MAX`, and empty segments are rejected at construction time (string parser or programmatic API).
    - Invalid JSON escape sequences in quoted segments are rejected.
    - Attribute/path helpers do not marshal attribute values; combine evaluated expressions with your own DynamoDB encoder.
- Values vs expression operands:
    - Plain values are stored in `ExpressionAttributeValues`.
    - Objects passed as values are treated as opaque user payloads. For example, `Attribute::create('a')->equal(Attribute::create('b'))` stores the right-side `Attribute` object as `:0`.
    - To use another path as an expression operand, pass the underlying node explicitly: `Attribute::create('a')->equal(Attribute::create('b')->project())` renders as `#0 = #1`.

Key
---

[](#key)

Description:

- Factory to build key condition expression fragments. Use with KeyConditionBuilder to combine.
- DynaExp does not know your table schema and does not validate which attribute is the partition or sort key.

Examples:

```
use DynaExp\Factories\Key;
use DynaExp\Builders\KeyConditionBuilder;

$hash  = Key::create('pk')->equal('H');
$range = Key::create('sk')->between(100, 200);

$kc = (new KeyConditionBuilder($hash))
    ->and($range)
    ->build();
```

Notes:

- Other helpers on Key: `beginsWith()`, `greaterThan()`, `lessThanEqual()`, etc.

AttributeSize (via Attribute)
-----------------------------

[](#attributesize-via-attribute)

Description:

- Wrapper to use the DynamoDB `size()` function on a path, returning a helper with condition methods.

Examples:

```
use DynaExp\Builders\ConditionBuilder;
use DynaExp\Factories\Attribute;

$sizeCond = Attribute::create('a')->size()->greaterThan(0);  // size(a) > :0

// You can nest size inside other expressions
$existsAndSize = ConditionBuilder::allOf(
    Attribute::create('a')->attributeExists(),
    Attribute::create('a')->size()->lessThanEqual(25)
)->build();
```

Notes:

- Usually created through `Attribute::create(...)->size()`; constructing directly is rarely needed.

DefaultValue (via Attribute)
----------------------------

[](#defaultvalue-via-attribute)

Description:

- Wrapper for `if_not_exists(path, value)` to use inside SET operations or nested operations.

Examples:

```
use DynaExp\Factories\Attribute;

$p = Attribute::create('counter');
$setIfNot = $p->set($p->ifNotExists(0));  // SET counter = if_not_exists(counter, :0)

// Preparing a default payload and storing a backup
$map = Attribute::create('items', 0);
$score = $map->child('score');
$backup = $map->child('backup');
$update = (new DynaExp\Builders\UpdateBuilder())
    ->add(
        $score->set(
            $score->ifNotExists(0)->plus($backup->ifNotExists(1))
        ),
        $backup->set($score->ifNotExists(0)),
    )
    ->build();
```

Notes:

- Typically used as a nested value in `set()` or arithmetic operations.

Builders
========

[](#builders)

ConditionBuilder
----------------

[](#conditionbuilder)

Description:

- Fluent AND/OR composition of conditions. Supports passing other builders (auto-parenthesized evaluation inside) and static constructors.

Examples:

```
use DynaExp\Factories\Attribute;
use DynaExp\Builders\ConditionBuilder;
use DynaExp\Builders\ExpressionBuilder;

$a = Attribute::create('a');
$b = Attribute::create('b');

$nested = (new ConditionBuilder($a->attributeExists()))
    ->and(
        ConditionBuilder::anyOf(
            $b->notBetween(5, 10), // renders as: NOT b BETWEEN ...
            $b->in('x', 'y', 'z')  // renders as: b IN (...)
        ),
    )
    ->and($a->contains('x'))
    ->build();

// Evaluate to see final strings/aliases
$ctx = (new ExpressionBuilder())
    ->setFilter($nested)
    ->build()
    ->toArray();

// $ctx === [
//     'FilterExpression' => 'attribute_exists (#0) AND (NOT #1 BETWEEN :0 AND :1 OR #1 IN (:2, :3, :4)) AND contains (#0, :5)',
//     'ExpressionAttributeNames' => ['#0' => 'a', '#1' => 'b'],
//     'ExpressionAttributeValues' => [':0' => 5, ':1' => 10, ':2' => 'x', ':3' => 'y', ':4' => 'z', ':5' => 'x'],
// ];
```

Notes:

- If no initial condition is set, `.and()`/`.or()` require at least two arguments.
- Passing another `ConditionBuilder` into `.and()`/`.or()` wraps that nested builder in parentheses.

KeyConditionBuilder
-------------------

[](#keyconditionbuilder)

Description:

- Combines individual key conditions with `AND`.
- The first condition is passed to the constructor; additional conditions can be appended with repeated `.and()` calls.

Examples:

```
use DynaExp\Factories\Key;
use DynaExp\Builders\KeyConditionBuilder;

$keyCondition = (new KeyConditionBuilder(
    Key::create('tournamentId')->equal('WINTER2026')
))
    ->and(Key::create('region')->equal('EU'))
    ->and(Key::create('round')->equal(3))
    ->and(Key::create('bracket')->beginsWith('UP'))
    ->build();

// Semantics:
// tournamentId = :0
// AND region = :1
// AND round = :2
// AND begins_with(bracket, :3)
```

This shape can be used for DynamoDB multi-attribute GSI key conditions, as well as the traditional partition-key plus sort-key form.

Notes:

- Pass each individual key condition separately. A condition that already represents an `AND` expression is rejected by the builder.
- `.and()` may be called multiple times.
- DynamoDB currently allows up to four partition key attributes and four sort key attributes in a multi-attribute GSI. `KeyConditionBuilder` intentionally does not enforce this limit because it is schema-agnostic and does not know which table or index the expression targets.
- The builder validates only its local composition rules. It does not know the table or index schema and therefore does not validate partition key or sort key membership, component order, required key parts, or whether a specific operator is allowed for a particular key component.

ProjectionBuilder
-----------------

[](#projectionbuilder)

Description:

- Aggregates projected attributes into a `Projection` node. Inputs must implement `ProjectableInterface`, so both `Attribute` and `Key` can be projected.

Examples:

```
use DynaExp\Builders\ProjectionBuilder;
use DynaExp\Factories\Key;
use DynaExp\Factories\Attribute;

$projection = (new ProjectionBuilder(
    Attribute::create('a'),
    Attribute::create('b'),
    Key::create('pk')
))->build();
```

Notes:

- Projection evaluates to a comma-separated list with aliased names.
- Building an empty projection throws `DynaExp\Exceptions\RuntimeException`.

UpdateBuilder
-------------

[](#updatebuilder)

Description:

- Collects actions (SET/REMOVE/ADD/DELETE) and groups them by action type before rendering.

Examples:

```
use DynaExp\Builders\UpdateBuilder;
use DynaExp\Factories\Attribute;
use DynaExp\Builders\ExpressionBuilder;

$counter = Attribute::create('counter');
$deprecatedFlag = Attribute::create('flags', 'deprecated');

$update = (new UpdateBuilder())
    ->add(
        $counter->set(1),            // SET counter = :0
        $deprecatedFlag->remove()    // REMOVE flags.deprecated
    )
    ->build();

$ctx = (new ExpressionBuilder())
    ->setUpdate($update)
    ->build()
    ->toArray();

// Example output:
// $ctx['UpdateExpression'] === 'SET #0 = :0 REMOVE #1.#2'
// $ctx['ExpressionAttributeNames'] === ['#0' => 'counter', '#1' => 'flags', '#2' => 'deprecated']
// $ctx['ExpressionAttributeValues'] === [':0' => 1]
```

### Nested operations for SET

[](#nested-operations-for-set)

```
use DynaExp\Builders\UpdateBuilder;
use DynaExp\Factories\Attribute;
use DynaExp\Builders\ExpressionBuilder;

$listAttr = Attribute::create('listAttr');
$counter  = Attribute::create('counter');

$appendItems = $listAttr->set(
    $listAttr->ifNotExists([])->listAppend([1, 2, 3])
);

$incrementCounter = $counter->set(
    $counter->ifNotExists(0)->plus(1)
);

$update = (new UpdateBuilder())
    ->add($appendItems, $incrementCounter)
    ->build();

$ctx = (new ExpressionBuilder())
    ->setUpdate($update)
    ->build()
    ->toArray();

// Example output:
// $ctx['UpdateExpression'] === 'SET #0 = list_append(if_not_exists(#0, :0), :1), #1 = if_not_exists(#1, :2) + :3'
// $ctx['ExpressionAttributeNames'] === ['#0' => 'listAttr', '#1' => 'counter']
// $ctx['ExpressionAttributeValues'] === [':0' => [], ':1' => [1, 2, 3], ':2' => 0, ':3' => 1]
```

Notes:

- DynamoDB evaluates update expression sections internally in the order **REMOVE → SET → ADD → DELETE**. The service accepts any section order in your request payload and normalizes it when processing.
- `listPrepend()` is a convenience helper: DynamoDB supports only `list_append(left, right)`. Implementing a prepend behaviour strictly would require introducing dedicated wrapper objects for values so callers could control argument order, which would make the public API more awkward to use; instead the helper simply swaps the arguments to preserve the mental model (`list_prepend(target, payload)` → `list_append(payload, target)`).
- Building an empty update throws `DynaExp\Exceptions\RuntimeException`.
- Update action sections are grouped by action type and rendered once per type.

### Complex nested update

[](#complex-nested-update)

```
$itemRoot = Attribute::create('items', 0);
$score = $itemRoot->child('score');
$backup = $itemRoot->child('scoreBackup');
$history = $itemRoot->child('history');
$historyPayload = $itemRoot->child('historyPayload');
$stats = Attribute::create('stats', 'totalScore');
$tags = $itemRoot->child('tags');

$update = (new UpdateBuilder())
    ->add(
        $score->set(
            $score->ifNotExists(0)->plus($backup->ifNotExists(1))
        ),
        $history->set(
            $history->ifNotExists([])->listAppend(
                $historyPayload->ifNotExists([])
            )
        ),
        $stats->add(10),
        $tags->delete(['legacy'])
    )
    ->build();

$ctx = (new ExpressionBuilder())
    ->setUpdate($update)
    ->build()
    ->toArray();

// SET #0[0].#1 = if_not_exists(#0[0].#1, :0) + if_not_exists(#0[0].#2, :1),
//     #0[0].#3 = list_append(if_not_exists(#0[0].#3, :2), if_not_exists(#0[0].#4, :3))
// ADD #5.#6 :4
// DELETE #0[0].#7 :5
```

ExpressionBuilder
-----------------

[](#expressionbuilder)

Description:

- Gathers optional parts (filter/condition/key condition/update/projection), evaluates them through an Evaluator, and returns `ExpressionResult`.

Examples:

```
use DynaExp\Builders\ConditionBuilder;
use DynaExp\Builders\ExpressionBuilder;
use DynaExp\Builders\ProjectionBuilder;
use DynaExp\Builders\UpdateBuilder;
use DynaExp\Enums\ExpressionTypeEnum;
use DynaExp\Factories\Key;
use DynaExp\Factories\Attribute;

$name = Attribute::create('name');
$price = Attribute::create('price');
$status = Attribute::create('status');

$filter = ConditionBuilder::allOf(
    $price->lessThan(100),
    $status->equal('ACTIVE')
)->build();

$projection = (new ProjectionBuilder($name, $price))->build();

$keyCondition = Key::create('pk')->equal('PRODUCT#123');

$update = (new UpdateBuilder())
    ->add($status->set('ACTIVE'))
    ->build();

$expr = (new ExpressionBuilder())
    ->setFilter($filter)
    ->setProjection($projection)
    ->setKeyCondition($keyCondition)
    ->setUpdate($update)
    ->build();

$array = $expr->toArray();
// Keys reflect DynamoDB API: ProjectionExpression, FilterExpression, UpdateExpression,
// KeyConditionExpression (if any), plus ExpressionAttributeNames/ExpressionAttributeValues when needed.

$expr->has(ExpressionTypeEnum::filter); // true
```

Notes:

- Empty parts are omitted from the output map.
- `ExpressionResult::has(ExpressionTypeEnum $type)` checks whether a result component exists.
- If you need custom normalization before rendering, pass an `ExpressionPreprocessorInterface` into `ExpressionBuilder`.
- If you need to marshal `ExpressionAttributeValues` for a specific client, do that after `toArray()` on the returned `ExpressionResult`.

Advanced Customization
======================

[](#advanced-customization)

ExpressionPreprocessorInterface
-------------------------------

[](#expressionpreprocessorinterface)

`ExpressionPreprocessorInterface` is an advanced hook for local node rewrites before rendering.

Use it only when the expression tree is valid as-is, but you still need a small amount of application-specific preprocessing before aliases are allocated and strings are rendered.

Typical cases:

- normalize one node shape into another for infrastructure constraints
- inject small compatibility rewrites without changing the public builders

Important rules:

- transform only the current node
- return the original node when no rewrite is needed
- keep the transformation idempotent
- do not traverse child nodes manually; `Evaluator` already does that during recursive rendering

Prefer the regular builders whenever the expression can already be modeled directly. A preprocessor is an edge-case extension point, not the default way to build expressions.

### Example: rewrite one `IN (...)` condition into smaller chunks

[](#example-rewrite-one-in--condition-into-smaller-chunks)

DynamoDB allows at most 100 operands in a single `IN` condition. You can technically rewrite a larger input into multiple `IN (...)` groups joined with `OR`, but this does not remove DynamoDB's overall size limits: each individual expression string is limited to 4 KB, and the total size of expression substitution variables is limited to 2 MB. A preprocessor can help with the `IN type !== ConditionTypeEnum::inCond) {
            return $node;
        }

        $path = $node->firstOperand();
        $values = $node->tailOperands();

        if (count($values) chunkSize) {
            return $node;
        }

        $chunks = array_chunk($values, $this->chunkSize);
        $conditions = array_map(
            fn (array $chunk): Condition => Condition::in($path, ...$chunk),
            $chunks
        );

        return array_reduce(
            array_slice($conditions, 1),
            fn (Condition $carry, Condition $next): Condition => Condition::or($carry, $next),
            $conditions[0]
        );
    }
}
```

This example is intentionally more advanced: it shows that a preprocessor can perform a structural rewrite, not just tweak a scalar value.

Use a preprocessor when:

- the rewrite must happen before alias allocation
- the rule is local to one node at a time
- the existing builders already express the public DSL well enough

Do not use a preprocessor when:

- you can express the logic with the existing builders directly
- you only need to post-process the final `ExpressionResult`
- you want to manually traverse the whole tree

The builder creates a fresh evaluator on every `build()` call, so alias state does not leak between independent compilations.

Supporting Types
================

[](#supporting-types)

Deterministic String Conversion (debug/tests)
---------------------------------------------

[](#deterministic-string-conversion-debugtests)

Nodes that implement `Stringable` or contain arrays/objects use a deterministic conversion in `NodesToStringTrait`:

- Stringable -&gt; `__toString()`
- JsonSerializable -&gt; json-encode `jsonSerialize()`
- Objects with `toArray()` -&gt; json-encode that array
- Arrays -&gt; json-encode recursively (stable output)
- Scalars -&gt; `true|false|null`, numbers, strings
- Other objects -&gt; `object(FQCN)` marker

Note: these conversions are intended for debugging, tests and logs only. Do not use them to build wire payloads for DynamoDB; use the evaluator output and, if needed, marshal values with your SDK/adapter.

> ⚠️ Recursive references and cyclic structures are not supported. Debug string conversion may fail or produce unusable output for cyclic graphs. Keep arrays and objects finite and acyclic when relying on `__toString()`.

### Attribute value aliases

[](#attribute-value-aliases)

- Each call to the values aliaser produces a fresh placeholder (`:0`, `:1`, …) even if the same PHP value is passed multiple times. This avoids ambiguity for mutable or complex payloads and keeps the generated expression consistent with the generated value map.

Errors from Attribute::fromString / Path::fromString
----------------------------------------------------

[](#errors-from-attributefromstring--pathfromstring)

Parser provides specific messages with processed prefix for:

- Empty attribute name (including trailing dot)
- Empty index
- Quoted attribute inside brackets
- Unmatched quote
- Invalid JSON escape sequences inside quoted attributes
- Nested brackets / unmatched bracket
- Invalid index (non-digit)
- Leading-zero indexes except `[0]`
- Indexes larger than `PHP_INT_MAX`
- Negative index via programmatic APIs
- Unsupported segment types via programmatic APIs
- Invalid UTF-8 string segments via programmatic APIs

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance95

Actively maintained with recent releases

Popularity24

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

Recently: every ~56 days

Total

9

Last Release

18d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/12052b3aceba1e6c08fcd6ca6c267b42abf88f040b9f3a5cee3e1bba0f40287d?d=identicon)[pdombrovsky](/maintainers/pdombrovsky)

---

Top Contributors

[![pdombrovsky](https://avatars.githubusercontent.com/u/81902477?v=4)](https://github.com/pdombrovsky "pdombrovsky (67 commits)")

---

Tags

awsdynamodbbuilderexpressionupdatecondition-expression

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/pdombrovsky-dyna-exp/health.svg)

```
[![Health](https://phpackages.com/badges/pdombrovsky-dyna-exp/health.svg)](https://phpackages.com/packages/pdombrovsky-dyna-exp)
```

###  Alternatives

[baopham/dynamodb

Eloquent syntax for DynamoDB

4986.1M6](/packages/baopham-dynamodb)[anourvalar/eloquent-serialize

Laravel Query Builder (Eloquent) serialization

11225.5M36](/packages/anourvalar-eloquent-serialize)[kettle/dynamodb-orm

A lightweight object-dynamodb mapper for PHP

49216.2k](/packages/kettle-dynamodb-orm)[riverline/dynamodb

Amazon WebService DynamoDB PHP object wrapper

3439.2k](/packages/riverline-dynamodb)[oryxcloud/laravel-dynamodb-session-driver

DynamoDB Session Driver for Laravel 5

1461.2k](/packages/oryxcloud-laravel-dynamodb-session-driver)[bentools/where

PHP7.1 Fluent, immutable SQL query builder. Connectionless, framework-agnostic, no dependency.

125.2k2](/packages/bentools-where)

PHPackages © 2026

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