PHPackages                             akosnoavek/models-data-extractor - 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. akosnoavek/models-data-extractor

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

akosnoavek/models-data-extractor
================================

Extract information from models or other data structures based on a given schema to the formats you prefer

v2.0(4w ago)040↓60%MITPHPPHP ^8.4

Since Jul 10Pushed 4w agoCompare

[ Source](https://github.com/AkosNoavekCode/Laravel-models-data-extractor)[ Packagist](https://packagist.org/packages/akosnoavek/models-data-extractor)[ RSS](/packages/akosnoavek-models-data-extractor/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (9)Versions (5)Used By (0)

Laravel Models Data Extractor
=============================

[](#laravel-models-data-extractor)

Extract information from Eloquent models, plain PHP arrays, or generic objects, based on a declarative **schema**, and render the result as an `IteratorElement` tree, a plain array, JSON, HTML, CSV, or XLSX.

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

[](#installation)

```
composer require akosnoavek/models-data-extractor
```

The package ships a Laravel service provider (`AkosNoavek\DataExtractor\Support\ServiceProvider`) and a `DataExtractor` facade, both auto-discoverable via the `laravel.providers` / `laravel.aliases` entries in `composer.json`.

Configuration
-------------

[](#configuration)

Publish the config file to customize it:

```
php artisan vendor:publish --tag=config
```

This creates `config/data_extractor.php`:

```
return [
    // Custom base classes that should be treated as a single Eloquent model
    // when resolved through a section's "root" (see below).
    'model_classes' => [],

    // Default format used to render fields declared with "date" => true.
    'date_format' => 'd/m/Y',
];
```

- **`model_classes`** — a `root` section decides whether it points at a *single* related model or a *collection* by inspecting the parent class of the resolved value: `Illuminate\Database\Eloquent\Model` is always recognized. If your application extends a shared intermediate base class (e.g. `App\Models\BaseModel extends Model`), list that class's FQCN here so a `root` resolving to one of its instances is treated as a single model instead of being (mis-)treated as a collection.

    This matters because the failure mode is silent: an Eloquent model instance is not iterable and exposes no public properties, so if its base class isn't recognized, the `root` section's `foreach` produces zero clones and the **entire section silently disappears from the output** — no error is raised.

    ```
    // config/data_extractor.php
    return [
        'model_classes' => [\App\Models\BaseModel::class],
    ];
    ```
- **`date_format`** — the `date()`/`Carbon::format()` pattern applied to any field declared with `"date" => true` (default `d/m/Y`).

Core concepts
-------------

[](#core-concepts)

Every extraction involves two independent things:

1. **The target** — the data you are extracting *from*. It can be an Eloquent `Model`, a plain PHP `array`, or a generic `object` (e.g. the result of `json_decode`).
2. **The schema** — a declarative description of *what* to extract and how to label it: which fields to pull out, how to group them into sections, and how to format them.

```
use AkosNoavek\DataExtractor\Facades\DataExtractor;

$array = DataExtractor::make($target)->toArray(data: $schema);
```

`DataExtractor::make($target)` binds the target; the terminal method (`toArray`, `toJson`, `toHtml`, `toCsv`, `toXlsx`, `extract`) binds the schema and runs the extraction.

The schema data structure
-------------------------

[](#the-schema-data-structure)

A schema is a nested associative array (or its JSON equivalent) built from two kinds of nodes: **fields** and **sections**.

### Field

[](#field)

A leaf node that reads a single value out of the target.

```
[
    "path"  => "field_one",      // required — dot-notation path into the target
    "label" => "Field One",      // required — human-readable label used in output
    "date"  => false,            // optional — format the value as a date (d/m/Y)
    "view"  => null,             // optional — Blade view name for custom HTML rendering
    "evaluate_when_empty" => true, // optional — keep the field even if its value is empty
    "extra_attributes" => null,  // optional — arbitrary array passed through to the output/view
]
```

- `path` supports dot notation for nested properties/relations, e.g. `"path" => "address.city"`.
- `date` parses the resolved value with Carbon and formats it as `d/m/Y` (falls back to an empty string if it can't be parsed).
- `evaluate_when_empty: false` removes the field from the output entirely when its resolved value is empty — useful for optional data.

### Section

[](#section)

A node that groups other fields/sections together under a `label`.

```
[
    "type"   => "section",       // required — marks this node as a section
    "label"  => "Section label", // required
    "fields" => [                // required, non-empty — child fields/sections, keyed by
                                  // any array key (only used while building the schema)
        "field_one" => ["path" => "field_one", "label" => "Field One"],
        "field_two" => ["path" => "field_two", "label" => "Field Two"],
    ],
]
```

Sections can be nested arbitrarily deep — a section's `fields` array can itself contain other sections.

Internally, every node (field or section) is parsed into an `AkosNoavek\DataExtractor\Iterators\IteratorElement`. The keys of the `fields` array are only used as PHP array keys while you author the schema; they are not part of the output. If a child node doesn't declare its own `label`, its schema key is used as the label.

### Root sections (relations / collections)

[](#root-sections-relations--collections)

A section can pull its target from a relation or nested collection on the *parent* target, instead of the outer target, via `root`:

```
[
    "type"  => "section",
    "label" => "Manager",
    "root"  => "manager",        // path to a single related model
    "fields" => [
        "manager_name" => ["path" => "name", "label" => "Manager name"],
    ],
]
```

- If `root` resolves to a **single related model** (or a plain array/object), the section's fields are simply resolved against that related record.
- If `root` resolves to a **collection** (e.g. a `hasMany` relation, or an array of arrays), the section is **cloned once per item**, in order, and each clone's fields are resolved against that item. This means a schema with a single `root` section template can expand into N sections/rows in the output.

A resolved Eloquent model is only recognized as a "single related model" if its base class is `Illuminate\Database\Eloquent\Model` or is registered via [`data_extractor.model_classes`](#configuration) — otherwise it is (mis-)treated as an empty collection and the whole section silently disappears from the output. If your models extend a shared base class, make sure to register it.

```
$parent->setRelation('children', new Collection([$child_one, $child_two]));

$schema = [
    "type" => "section",
    "label" => "outer",
    "fields" => [
        "children_section" => [
            "type" => "section",
            "label" => "Child section",
            "root" => "children",
            "fields" => [
                "child_name" => ["path" => "name", "label" => "Child name"],
            ],
        ],
    ],
];
```

- `toArray()` / `toJson()` / `toHtml()` / `extract()` reflect this naturally: you get one section/row per related record.
- `toCsv()` / `toXlsx()` produce **one row per resolved section instance**: a `root` pointing at a collection of N records generates N rows, each sharing the same set of columns. Fields declared directly on an *outer* section (not inside a `root` section) are constant and get repeated onto every row generated by its nested `root` sections.
- Because of this, **every nested section in a CSV/Excel schema must declare a `root`** — a plain nested section (no `root`) only ever produces a single, non-repeating set of values, which is redundant in a flat export: declare those fields directly on the parent section instead. `toCsv()`/`toXlsx()` throw an exception if they encounter a nested section without `root`.
- Multiple direct fields on the *same* section that resolve to the same label (e.g. two schema entries pointing at different paths but sharing a label) are still merged into a single column, joined with `"; "`.

### Multi-value fields (`.*.` path syntax)

[](#multi-value-fields--path-syntax)

A single field can also flatten a to-many relation into one value, without declaring a nested section, using `.*.` in its `path`:

```
["path" => "children.*.name", "label" => "Children names"]
```

This resolves `name` on every item of the `children` relation/collection and joins them into one string, using the separator configured for the output format (`toHtml`/`toCsv`/`toXlsx`/`toArray` use `""`).

Providing the schema: inline array vs. JSON file
------------------------------------------------

[](#providing-the-schema-inline-array-vs-json-file)

Every terminal method (`extract`, `toArray`, `toJson`, `toHtml`, `toCsv`, `toXlsx`) accepts the schema in one of two equivalent ways:

**1. As an inline PHP array**, via the `data` argument:

```
$result = DataExtractor::make($target)->toArray(data: [
    "type" => "section",
    "label" => "Section label",
    "fields" => [
        "field_one" => ["path" => "field_one", "label" => "Field One"],
    ],
]);
```

Under the hood, the array is `json_encode`d to a temporary file and read back as JSON — so an inline array and a JSON file are handled identically.

**2. As a path to a JSON schema file**, via the `filename` argument:

```
// schema.json
// {
//     "type": "section",
//     "label": "Section label",
//     "fields": {
//         "field_one": { "path": "field_one", "label": "Field One" }
//     }
// }

$result = DataExtractor::make($target)->toArray(filename: base_path('schemas/schema.json'));
```

You must pass at least one of `data` or `filename` — an exception is thrown if neither is provided. If both are given, `data` takes precedence.

A JSON file can also contain **multiple named schemas** (top-level keys), and you extract a specific one via the `section` argument:

```
// schema.json: { "employees": { "type": "section", ... }, "managers": { "type": "section", ... } }

DataExtractor::make($target)->toArray(filename: base_path('schemas/schema.json'), section: 'employees');
```

Working with different target types
-----------------------------------

[](#working-with-different-target-types)

The same schema works unchanged against any of the three supported target shapes — only how `path` is resolved differs internally (array key lookup vs. object/model property access):

```
use Illuminate\Database\Eloquent\Model;

// Eloquent model
$model = SomeModel::find(1);
DataExtractor::make($model)->toArray(data: $schema);

// Plain array
$data = ["field_one" => "value one"];
DataExtractor::make($data)->toArray(data: $schema);

// Generic object (e.g. decoded JSON)
$object = json_decode(json_encode(["field_one" => "value one"]));
DataExtractor::make($object)->toArray(data: $schema);
```

Output formats
--------------

[](#output-formats)

```
$schema = [
    "type" => "section",
    "label" => "Section label",
    "fields" => [
        "field_one" => ["path" => "field_one", "label" => "Field One"],
        "field_two" => ["path" => "field_two", "label" => "Field Two"],
    ],
];

$el    = DataExtractor::make($target)->extract(data: $schema); // AkosNoavek\DataExtractor\Iterators\IteratorElement tree
$array = DataExtractor::make($target)->toArray(data: $schema); // nested PHP array
$json  = DataExtractor::make($target)->toJson(data: $schema);  // JSON string
$html  = DataExtractor::make($target)->toHtml(data: $schema);  // rendered HTML (Blade)
$path  = DataExtractor::make($target)->toCsv(data: $schema);   // path to a generated .csv file
$path  = DataExtractor::make($target)->toXlsx(data: $schema);  // path to a generated .xlsx file
```

- `extract()` returns the raw `IteratorElement` tree (each element exposes `type`, `label`, `data`, `path`, `fields`, `root`, etc.) — useful when you want to post-process the result yourself.
- `toArray()` / `toJson()` return a nested structure of `{label, value, ...}` leaves grouped under `{label, data: [...]}` sections, mirroring the schema's shape (including root-generated clones).
- `toHtml()` renders an unordered list per section by default; a field/section can opt into a custom Blade `view` for full control over its markup.
- `toCsv()` / `toXlsx()` flatten the whole tree into a header row and one or more data rows — one column per unique label, one row per resolved `root` instance. See [Root sections](#root-sections-relations--collections) above: every nested section must declare a `root`, and duplicate labels within the same section are merged.

Running the tests
-----------------

[](#running-the-tests)

```
composer test
# or
vendor/bin/pest
```

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance94

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity54

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

Total

2

Last Release

29d ago

Major Versions

1.0.1 → v2.02026-07-30

PHP version history (2 changes)1.0.1PHP ^8.2

v2.0PHP ^8.4

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/41312448?v=4)[Akos](/maintainers/Akos)[@akos](https://github.com/akos)

---

Top Contributors

[![AkosKereseth](https://avatars.githubusercontent.com/u/168841737?v=4)](https://github.com/AkosKereseth "AkosKereseth (34 commits)")[![AkosNoavekCodeKill](https://avatars.githubusercontent.com/u/168841737?v=4)](https://github.com/AkosNoavekCodeKill "AkosNoavekCodeKill (34 commits)")[![AkosNoavekCode](https://avatars.githubusercontent.com/u/197122899?v=4)](https://github.com/AkosNoavekCode "AkosNoavekCode (1 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/akosnoavek-models-data-extractor/health.svg)

```
[![Health](https://phpackages.com/badges/akosnoavek-models-data-extractor/health.svg)](https://phpackages.com/packages/akosnoavek-models-data-extractor)
```

###  Alternatives

[unopim/unopim

UnoPim Laravel PIM

10.8k2.5k](/packages/unopim-unopim)[kimai/kimai

Kimai - Time Tracking

4.8k9.4k1](/packages/kimai-kimai)[yajra/laravel-datatables-export

Laravel DataTables Queued Export Plugin.

362.3M5](/packages/yajra-laravel-datatables-export)[tomshaw/electricgrid

A feature-rich Livewire package designed for projects that require dynamic, interactive data tables.

119.8k](/packages/tomshaw-electricgrid)[team-nifty-gmbh/tall-datatables

Server-side rendered datatables for Laravel and Livewire

1422.0k5](/packages/team-nifty-gmbh-tall-datatables)[crumbls/layup

A visual page builder plugin for Filament 5 — Divi-style grid layouts with extensible widgets.

604.0k2](/packages/crumbls-layup)

PHPackages © 2026

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