PHPackages                             safe-access-inline/safe-access-inline - 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. [Testing &amp; Quality](/categories/testing)
4. /
5. safe-access-inline/safe-access-inline

ActiveLibrary[Testing &amp; Quality](/categories/testing)

safe-access-inline/safe-access-inline
=====================================

Safe nested data access with dot notation — supports Array, Object, JSON, XML, YAML, TOML, INI, CSV, ENV.

0.3.1(3mo ago)00MITPHPPHP ^8.2

Since Mar 11Pushed 2w agoCompare

[ Source](https://github.com/felipesauer/safe-access-inline-php)[ Packagist](https://packagist.org/packages/safe-access-inline/safe-access-inline)[ Docs](https://github.com/felipesauer/safe-access-inline)[ GitHub Sponsors](https://github.com/sponsors/felipesauer)[ RSS](/packages/safe-access-inline-safe-access-inline/feed)WikiDiscussions main Synced 3mo ago

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

 [![safeaccess-inline logo](https://raw.githubusercontent.com/felipesauer/safeaccess-inline/main/.github/assets/logo.svg)](https://raw.githubusercontent.com/felipesauer/safeaccess-inline/main/.github/assets/logo.svg)

Safe Access Inline — PHP
========================

[](#safe-access-inline--php)

PHP library for safe nested data access with security validation on by default — JSON, YAML, XML, INI, ENV, NDJSON, arrays and objects. Includes a full PathQuery engine with filters, wildcards, slices, and projections. Zero production dependencies.

 [![Packagist](https://camo.githubusercontent.com/a82b29515e8391a75312c52eeca9fe8941725874fd233df9e69e97ed912b1f57/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736166656163636573732f696e6c696e653f6c6162656c3d7061636b6167697374)](https://packagist.org/packages/safeaccess/inline) [![License: MIT](https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667)](../../LICENSE) [![PHP 8.2+](https://camo.githubusercontent.com/ccaa43fc634d348cffccb1d8db7b55d9f17c5d46944bc99a15c3c982724b387d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/ccaa43fc634d348cffccb1d8db7b55d9f17c5d46944bc99a15c3c982724b387d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465) [![PHPStan max](https://camo.githubusercontent.com/f2568d8beea68c8a86467f6ba935b0c4d58f5e9658a938509f7cd62d2793b582/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6d61782d304136444144)](https://camo.githubusercontent.com/f2568d8beea68c8a86467f6ba935b0c4d58f5e9658a938509f7cd62d2793b582/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6d61782d304136444144) [![Tested with Pest](https://camo.githubusercontent.com/2656eb78211bc4cfda004686e4bdcea8c19c9bef4c374f55531694d9d21b28b3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f546573746564253230776974682d506573742d464635373333)](https://camo.githubusercontent.com/2656eb78211bc4cfda004686e4bdcea8c19c9bef4c374f55531694d9d21b28b3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f546573746564253230776974682d506573742d464635373333) [![Infection MSI](https://camo.githubusercontent.com/a7675cba29953f37883f03f52d76d8224e4bca59ddc7093938ec7799850c11df/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f75726c3d68747470733a2f2f676973742e67697468756275736572636f6e74656e742e636f6d2f66656c69706573617565722f38306336303262313731303766383866623137373934643464343463393466612f7261772f696e66656374696f6e2d6d73692e6a736f6e)](https://camo.githubusercontent.com/a7675cba29953f37883f03f52d76d8224e4bca59ddc7093938ec7799850c11df/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f75726c3d68747470733a2f2f676973742e67697468756275736572636f6e74656e742e636f6d2f66656c69706573617565722f38306336303262313731303766383866623137373934643464343463393466612f7261772f696e66656374696f6e2d6d73692e6a736f6e)

---

The problem
-----------

[](#the-problem)

Reading nested data from external sources requires more than null-safe access. You also need to defend against XXE in XML, anchor bombs in YAML, PHP magic method injection, stream wrapper abuse, superglobal access, and payload size attacks. Without a tool for this, that validation is boilerplate you write manually for every format and every endpoint.

**Without this library (XML from an external API):**

```
libxml_disable_entity_loader(true);
$xml = simplexml_load_string($input, 'SimpleXMLElement', LIBXML_NOENT);
if ($xml === false) {
    throw new RuntimeException('Invalid XML');
}
// validate keys against magic methods, superglobals, stream wrappers...
// enforce depth and key count limits...
$host = isset($xml->database->host) ? (string) $xml->database->host : null;
```

**With this library:**

```
$host = Inline::fromXml($input)->get('database.host');
// XXE blocked, forbidden keys validated, depth enforced — by default
```

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

[](#installation)

```
composer require safeaccess/inline
```

**Requirements:** PHP 8.2+, extensions: `json`, `simplexml`, `libxml`

**Optional:** `ext-yaml` for improved YAML parsing performance (a built-in minimal parser is used by default).

Quick start
-----------

[](#quick-start)

```
use SafeAccess\Inline\Inline;

$accessor = Inline::fromJson('{"user": {"name": "Alice", "age": 30}}');

$accessor->get('user.name');           // 'Alice'
$accessor->get('user.email', 'N/A');   // 'N/A' (default when missing)
$accessor->has('user.age');            // true
$accessor->getOrFail('user.name');     // 'Alice' (throws if missing)

// Immutable writes - original is never modified
$updated = $accessor->set('user.email', 'alice@example.com');
$updated->get('user.email');           // 'alice@example.com'
$accessor->has('user.email');          // false (original unchanged)
```

Security
--------

[](#security)

All public entry points validate input **by default**. Every key passes through `SecurityGuard` and `SecurityParser` before being accessible.

### What gets blocked

[](#what-gets-blocked)

CategoryExamplesReasonPHP magic methods`__construct`, `__destruct`, `__wakeup`, `__sleep`, `__toString`, ...Prevent PHP magic behavior via data keysPrototype pollution`__proto__`, `constructor`, `prototype`Prevent prototype pollution attacksPHP superglobals`GLOBALS`, `_GET`, `_POST`, `_COOKIE`, `_SERVER`, `_ENV`, ...Prevent superglobal variable accessStream wrapper URIs`php://input`, `phar://...`, `data://...`, `file://...`Prevent stream wrapper injection### Format-specific protections

[](#format-specific-protections)

FormatProtectionXMLRejects `fromJson($data);
```

### Disabling validation for trusted input

[](#disabling-validation-for-trusted-input)

```
$accessor = Inline::withStrictMode(false)->fromJson($trustedPayload);
```

> **Warning:** Disabling strict mode skips **all** validation. Only use with application-controlled input.

For vulnerability reports, see [SECURITY.md](../../SECURITY.md).

Dot notation syntax
-------------------

[](#dot-notation-syntax)

### Basic syntax

[](#basic-syntax)

SyntaxExampleDescription`key.key``user.name`Nested key access`key.0.key``users.0.name`Numeric key (array index)`key\.with\.dots``config\.db\.host`Escaped dots in key names`$` or `$.path``$.user.name`Optional root prefix (stripped)```
$data = Inline::fromJson('{"users": [{"name": "Alice"}, {"name": "Bob"}]}');
$data->get('users.0.name'); // 'Alice'
$data->get('users.1.name'); // 'Bob'
```

### Advanced PathQuery

[](#advanced-pathquery)

SyntaxExampleDescription`[0]``users[0]`Bracket index access`*` or `[*]``users.*`Wildcard — expand all children`..key``..name`Recursive descent — find key at any depth`..['a','b']``..['name','age']`Multi-key recursive descent`[0,1,2]``users[0,1,2]`Multi-index selection`['a','b']``['name','age']`Multi-key selection`[0:5]``items[0:5]`Slice — indices 0 through 4`[::2]``items[::2]`Slice with step`[::-1]``items[::-1]`Reverse slice`[?expr]``users[?age>18]`Filter predicate expression`.{fields}``.{name, age}`Projection — select fields`.{alias: src}``.{fullName: name}`Aliased projection### Filter expressions

[](#filter-expressions)

```
$data = Inline::fromJson('[
    {"name": "Alice", "age": 25, "role": "admin"},
    {"name": "Bob",   "age": 17, "role": "user"},
    {"name": "Carol", "age": 30, "role": "admin"}
]');

// Comparison: ==, !=, >, =, get('[?age>18]');                          // Alice and Carol

// Logical: && and ||
$data->get('[?age>18 && role==\'admin\']');       // Alice and Carol

// Built-in functions: starts_with, contains, values
$data->get('[?starts_with(@.name, \'A\')]');      // Alice
$data->get('[?contains(@.name, \'ob\')]');        // Bob

// Arithmetic: +, -, *, /
$orders = Inline::fromJson('[{"price": 10, "qty": 5}, {"price": 3, "qty": 2}]');
$orders->get('[?@.price * @.qty > 20]');          // first order only
```

Supported formats
-----------------

[](#supported-formats)

**JSON**```
$accessor = Inline::fromJson('{"users": [{"name": "Alice"}, {"name": "Bob"}]}');
$accessor->get('users.0.name'); // 'Alice'
```

**YAML**```
$yaml = get('database.host'); // 'localhost'

// Also accepts SimpleXMLElement
$accessor = Inline::fromXml(simplexml_load_string($xml));
```

**INI**```
$accessor = Inline::fromIni("[database]\nhost=localhost\nport=5432");
$accessor->get('database.host'); // 'localhost'
```

**ENV (dotenv)**```
$accessor = Inline::fromEnv("APP_NAME=MyApp\nAPP_DEBUG=true\nDB_HOST=localhost");
$accessor->get('DB_HOST'); // 'localhost'
```

**NDJSON**Each line is parsed as an independent JSON object and indexed from `0` by its position in the input. Blank lines and trailing newlines are skipped. Security validation is applied to each line individually.

```
$ndjson = '{"id":1,"name":"Alice"}' . "\n" . '{"id":2,"name":"Bob"}';
$accessor = Inline::fromNdjson($ndjson);
$accessor->get('0.name'); // 'Alice'
$accessor->get('1.name'); // 'Bob'
```

**Array / Object**```
$accessor = Inline::fromArray(['users' => [['name' => 'Alice'], ['name' => 'Bob']]]);
$accessor->get('users.0.name'); // 'Alice'

$accessor = Inline::fromObject((object) ['name' => 'Alice']);
$accessor->get('name'); // 'Alice'
```

**Any (custom format via integration)**```
use SafeAccess\Inline\Contracts\ParseIntegrationInterface;

// Requires implementing ParseIntegrationInterface
$accessor = Inline::withParserIntegration(new MyCsvIntegration())->fromAny($csvString);
$accessor->get('0.column_name');
```

**Dynamic (by TypeFormat enum)**```
use SafeAccess\Inline\Enums\TypeFormat;
$accessor = Inline::from(TypeFormat::Json, '{"key": "value"}');
$accessor->get('key'); // 'value'
```

Reading &amp; writing
---------------------

[](#reading--writing)

```
$accessor = Inline::fromJson('{"a": {"b": 1, "c": 2}}');

// Read
$accessor->get('a.b');                  // 1
$accessor->get('a.missing', 'default'); // 'default'
$accessor->getOrFail('a.b');            // 1 (throws PathNotFoundException if missing)
$accessor->has('a.b');                  // true
$accessor->all();                       // ['a' => ['b' => 1, 'c' => 2]]
$accessor->count();                     // 1 (root keys)
$accessor->count('a');                  // 2 (keys under 'a')
$accessor->keys();                      // ['a']
$accessor->keys('a');                   // ['b', 'c']
$accessor->getMany([
    'a.b' => null,
    'a.x' => 'fallback',
]);                                     // ['a.b' => 1, 'a.x' => 'fallback']
$accessor->getRaw();                    // original JSON string

// Write (immutable - every write returns a new instance)
$updated = $accessor->set('a.d', 3);
$updated = $updated->remove('a.c');
$updated = $updated->merge('a', ['e' => 4]);
$updated = $updated->mergeAll(['f' => 5]);
$updated->all();                        // ['a' => ['b' => 1, 'd' => 3, 'e' => 4], 'f' => 5]

// Readonly mode - block all writes
$readonly = $accessor->readonly();
$readonly->get('a.b');                  // 1 (reads work)
$readonly->set('a.b', 99);             // throws ReadonlyViolationException
```

Configure
---------

[](#configure)

### Builder pattern

[](#builder-pattern)

```
use SafeAccess\Inline\Inline;
use SafeAccess\Inline\Security\SecurityGuard;
use SafeAccess\Inline\Security\SecurityParser;

$accessor = Inline::withSecurityGuard(new SecurityGuard(extraForbiddenKeys: ['secret']))
    ->withSecurityParser(new SecurityParser(maxDepth: 5))
    ->withStrictMode(true)
    ->fromJson($untrustedInput);
```

### Builder methods

[](#builder-methods)

MethodDescription`withSecurityGuard($guard)`Custom forbidden-key rules and depth limits`withSecurityParser($parser)`Custom payload size and structural limits`withPathCache($cache)`Path segment cache for repeated lookups`withParserIntegration($integration)`Custom format parser for `fromAny()``withStrictMode(false)`Disable security validation (trusted input only)Error handling
--------------

[](#error-handling)

All exceptions extend `AccessorException`:

```
use SafeAccess\Inline\Exceptions\AccessorException;
use SafeAccess\Inline\Exceptions\InvalidFormatException;
use SafeAccess\Inline\Exceptions\SecurityException;
use SafeAccess\Inline\Exceptions\PathNotFoundException;
use SafeAccess\Inline\Exceptions\ReadonlyViolationException;

try {
    $accessor = Inline::fromJson($untrustedInput);
    $value = $accessor->getOrFail('config.key');
} catch (InvalidFormatException $e) {
    // Malformed JSON, XML, INI, or NDJSON
} catch (SecurityException $e) {
    // Forbidden key, payload too large, depth/key-count exceeded
} catch (PathNotFoundException $e) {
    // Path does not exist
} catch (ReadonlyViolationException $e) {
    // Write on readonly accessor
} catch (AccessorException $e) {
    // Catch-all for any library error
}
```

### Exception hierarchy

[](#exception-hierarchy)

ExceptionExtendsWhen`AccessorException``RuntimeException`Root — catch-all`SecurityException``AccessorException`Forbidden key, payload, structural limits`InvalidFormatException``AccessorException`Malformed JSON, XML, INI, NDJSON`YamlParseException``InvalidFormatException`Unsafe or malformed YAML`PathNotFoundException``AccessorException``getOrFail()` on missing path`ReadonlyViolationException``AccessorException`Write on readonly accessor`UnsupportedTypeException``AccessorException`Unknown accessor class in `make()``ParserException``AccessorException`Internal parser errorsAdvanced usage
--------------

[](#advanced-usage)

### Strict mode

[](#strict-mode)

```
// Disable all security validation for trusted input
$accessor = Inline::withStrictMode(false)->fromJson($trustedPayload);
```

> **Warning:** Disabling strict mode skips **all** validation. Only use with application-controlled input.

### Path cache

[](#path-cache)

```
// Implement PathCacheInterface for repeated lookups
$cache = new MyPathCache();
$accessor = Inline::withPathCache($cache)->fromJson($data);
$accessor->get('deeply.nested.path'); // parses path
$accessor->get('deeply.nested.path'); // cache hit
```

### Custom format integration

[](#custom-format-integration)

```
// Implement ParseIntegrationInterface for custom formats
class CsvIntegration implements ParseIntegrationInterface
{
    public function assertFormat(mixed $raw): bool
    {
        return is_string($raw) && str_contains($raw, ',');
    }

    public function parse(mixed $raw): array
    {
        // Parse CSV to associative array
        return $parsed;
    }
}

$accessor = Inline::withParserIntegration(new CsvIntegration())->fromAny($csvString);
```

API reference
-------------

[](#api-reference)

### `Inline` facade

[](#inline-facade)

#### Static factory methods

[](#static-factory-methods)

MethodInputReturns`fromArray($data)``array``ArrayAccessor``fromObject($data)``object``ObjectAccessor``fromJson($data)`JSON `string``JsonAccessor``fromXml($data)`XML `string` or `SimpleXMLElement``XmlAccessor``fromYaml($data)`YAML `string``YamlAccessor``fromIni($data)`INI `string``IniAccessor``fromEnv($data)`dotenv `string``EnvAccessor``fromNdjson($data)`NDJSON `string``NdjsonAccessor``fromAny($data, $integration?)``mixed``AnyAccessor``from($typeFormat, $data)``TypeFormat` enum`AccessorsInterface``make($class, $data)``class-string``AbstractAccessor`#### Accessor read methods

[](#accessor-read-methods)

MethodReturns`get($path, $default?)`Value at path, or default`getOrFail($path)`Value or throws `PathNotFoundException``getAt($segments, $default?)`Value at key segments`has($path)``bool``hasAt($segments)``bool``getMany($paths)``array``all()``array``count($path?)``int``keys($path?)``list``getRaw()``mixed`#### Accessor write methods (immutable)

[](#accessor-write-methods-immutable)

MethodDescription`set($path, $value)`Set at path`setAt($segments, $value)`Set at key segments`remove($path)`Remove at path`removeAt($segments)`Remove at key segments`merge($path, $value)`Deep-merge at path`mergeAll($value)`Deep-merge at root#### Modifier methods

[](#modifier-methods)

MethodDescription`readonly($flag?)`Block all writes`strict($flag?)`Toggle security validation#### TypeFormat enum

[](#typeformat-enum)

`Array` · `Object` · `Json` · `Xml` · `Yaml` · `Ini` · `Env` · `Ndjson` · `Any`

Contributing
------------

[](#contributing)

See [CONTRIBUTING.md](../../CONTRIBUTING.md) for development setup, commit conventions, and pull request guidelines.

License
-------

[](#license)

[MIT](../../LICENSE) © Felipe Sauer

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance90

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor2

2 contributors hold 50%+ of commits

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

Total

9

Last Release

96d ago

### Community

Maintainers

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

---

Top Contributors

[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (13 commits)")[![felipesauer](https://avatars.githubusercontent.com/u/120697114?v=4)](https://github.com/felipesauer "felipesauer (12 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (7 commits)")

---

Tags

composerdata-accessdot-notationfluent-apiimmutablejsonpestphpphp8phpstansafe-accesssecurityxmlyamlzero-dependencyjsonlaravelschemayamlpluginsdot notationnested-datasafe-access

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/safe-access-inline-safe-access-inline/health.svg)

```
[![Health](https://phpackages.com/badges/safe-access-inline-safe-access-inline/health.svg)](https://phpackages.com/packages/safe-access-inline-safe-access-inline)
```

###  Alternatives

[sixlive/laravel-json-schema-assertions

Laravel JSON Schema assertions

71211.0k](/packages/sixlive-laravel-json-schema-assertions)[estahn/phpunit-json-assertions

JSON assertions for PHPUnit (including JSON Schema)

361.0M7](/packages/estahn-phpunit-json-assertions)[romaricdrigon/metayaml

Using \[Yaml|Xml|json\] schemas files to validate \[Yaml|Xml|json\]

103311.2k8](/packages/romaricdrigon-metayaml)[guanguans/laravel-soar

SQL optimizer and rewriter for laravel. - laravel 的 SQL 优化器和重写器。

2228.3k](/packages/guanguans-laravel-soar)

PHPackages © 2026

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