PHPackages                             stratadox/table-loader - 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. stratadox/table-loader

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

stratadox/table-loader
======================

v0.3(7y ago)198MITPHPPHP &gt;=7.2

Since Apr 22Pushed 5y ago1 watchersCompare

[ Source](https://github.com/Stratadox/TableLoader)[ Packagist](https://packagist.org/packages/stratadox/table-loader)[ RSS](/packages/stratadox-table-loader/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (8)Versions (4)Used By (0)

Table Loader
============

[](#table-loader)

[![Build Status](https://camo.githubusercontent.com/65ccd4eee5ca34ad18bbaa30673b48b1cd699a9484eb6cb00938294c424bcd63/68747470733a2f2f7472617669732d63692e6f72672f537472617461646f782f5461626c654c6f616465722e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/Stratadox/TableLoader)[![Coverage Status](https://camo.githubusercontent.com/8f1f91d140322449e9eb3f95b460eb10c72a3ccb845d6a2213594c920d82c2ad/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f537472617461646f782f5461626c654c6f616465722f62616467652e7376673f6272616e63683d6d6173746572)](https://coveralls.io/github/Stratadox/TableLoader?branch=master)[![Scrutinizer Code Quality](https://camo.githubusercontent.com/69f4c78e693895e652c011bdefd230d3e2674459e978c49ef9d8ea3e3c547ced/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f537472617461646f782f5461626c654c6f616465722f6261646765732f7175616c6974792d73636f72652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/Stratadox/TableLoader/?branch=master)[![Infection Minimum](https://camo.githubusercontent.com/416cfd74a77aee9719fd9a0c0b5f9dd6970485b055c1e9a4cd01ce05e08a1754/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d73692d3130302d627269676874677265656e2e737667)](https://travis-ci.org/Stratadox/TableLoader)[![PhpStan Level](https://camo.githubusercontent.com/7d521d242d198a9468ca6170cc4416765da9273ff094476e7125934666afc7da/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068707374616e2d372f372d627269676874677265656e2e737667)](https://travis-ci.org/Stratadox/TableLoader)[![Maintainability](https://camo.githubusercontent.com/53d6e2104828273c3d431900fcd8d040c2abe8458bd165411721e129cf1e225f/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f32313861346631353363663236393837666635352f6d61696e7461696e6162696c697479)](https://codeclimate.com/github/Stratadox/TableLoader/maintainability)[![Latest Stable Version](https://camo.githubusercontent.com/2ce9269380f235064dc555e13c984f1c12c9b3c5c3f75eceb2421fec42881aa6/68747470733a2f2f706f7365722e707567782e6f72672f737472617461646f782f7461626c652d6c6f616465722f762f737461626c65)](https://packagist.org/packages/stratadox/table-loader)[![License](https://camo.githubusercontent.com/471442ae991f73fa90da1a5884e5cf1eaa6b3ccc968c1e8523808048fe1651c1/68747470733a2f2f706f7365722e707567782e6f72672f737472617461646f782f7461626c652d6c6f616465722f6c6963656e7365)](https://packagist.org/packages/stratadox/table-loader)

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

[](#installation)

Install using `composer require stratadox/table-loader`

What is this?
-------------

[](#what-is-this)

The `TableLoader` package is designed to transform the result of a select query into objects. It solves the challenge of deserialising joined table rows into objects without duplicating the entities.

To *load* a table means to produce objects from the associative arrays that result from a SQL query.

An object that [`LoadsTables`](https://github.com/Stratadox/TableLoader/blob/master/contracts/LoadsTables.php)can make interrelated objects from a list of associative arrays.

Table loading works closely together with the [Hydration](https://github.com/Stratadox/Hydrate)modules to easily integrate with mapped hydration and lazy- and extra lazy loading.

What does it do?
----------------

[](#what-does-it-do)

The purpose of the `TableLoader` package is to transform SQL-like table results into a set of objects.

### Connecting eagerly loaded relationships

[](#connecting-eagerly-loaded-relationships)

When eagerly loading a relationship from a SQL database, one generally performs some kind of `JOIN` query.

The `TableLoader` package provides several options for converting the joined result into interconnected objects.

Each entity can be given any number of `has-one` and/or `has-many` relationships. Bidirectional associations can be produced by assigning such relationships to both sides.

Any number of tables can be joined at a time. Self-referencing joins are equally supported.

### Mapping concrete subclasses

[](#mapping-concrete-subclasses)

When dealing with polymorphism in a SQL schema, objects are generally mapped in either of three ways:

- [Single Table Inheritance](https://martinfowler.com/eaaCatalog/singleTableInheritance.html)
- [Class Table Inheritance](https://martinfowler.com/eaaCatalog/classTableInheritance.html)
- [Concrete Table Inheritance](https://martinfowler.com/eaaCatalog/concreteTableInheritance.html)

The `TableLoader` supports any of these methods, so long as a `decision key` is provided. (Also known as `discriminator column`)

### Managing identities

[](#managing-identities)

It can happen that some of the objects have already been loaded by a previous query.

For example, let's assume we're first loading only employee `X`. Later on we're fetching company `Y` with all employees - including employee `X`.

While we *do* want company `Y` to include employee `X` on the books, we *do not* want two copies of employee `X` in memory.

To solve this challenge, loaded entities are added to an [Identity Map](https://github.com/Stratadox/IdentityMap). The table loader consults the identity map when extracting an entity from the table row data. A new entity is only produced if it was not already present in the map.

If employee `X` had a `lazy has-one` mapping to their company, the company relation was a [`Proxy`](https://github.com/Stratadox/Proxy)when the employee was first loaded. By loading the company `Y`, and its `eager has-many` employees mapping, the real company `Y` is automatically loaded into the relationship that previously held a proxy.

Usage Samples
-------------

[](#usage-samples)

### Simple result without (eager) relations:

[](#simple-result-without-eager-relations)

```
$data = table([
    //----------+-----------------+,
    [ 'id'      , 'name'          ],
    //----------+-----------------+,
    [  1        , 'foo'           ],
    [  2        , 'bar'           ],
    //----------+-----------------+,
]);

$make = SimpleTable::converter(
    'thing',
    SimpleHydrator::forThe(Thing::class),
    Identified::by('id')
);

$things = $make->from($data)['thing'];

assert($things['1']->equals(new Thing(1, 'foo')));
assert($things['2']->equals(new Thing(2, 'bar')));
```

Assuming for table:

```
function table(array $table): array
{
    $keys = array_shift($table);
    $result = [];
    foreach ($table as $row) {
        $result[] = array_combine($keys, $row);
    }
    return $result;
}
```

### Unidirectional has-many mapping:

[](#unidirectional-has-many-mapping)

```
$data = table([
    //----------+------------------+-------------+----------------+,
    [ 'club_id' , 'club_name'      , 'member_id' , 'member_name'  ],
    //----------+------------------+-------------+----------------+,
    [  1        , 'Kick-ass Club'  ,  1          , 'Chuck Norris' ],
    [  1        , 'Kick-ass Club'  ,  2          , 'Jackie Chan'  ],
    [  2        , 'The Foo Bar'    ,  1          , 'Chuck Norris' ],
    [  2        , 'The Foo Bar'    ,  3          , 'John Doe'     ],
    [  3        , 'Space Club'     ,  4          , 'Captain Kirk' ],
    [  3        , 'Space Club'     ,  5          , 'Darth Vader'  ],
    //----------+------------------+-------------+----------------+,
]);

$make = Joined::table(
    Load::each('club')
        ->as(Club::class, ['name' => Is::string()])
        ->havingMany('memberList', 'member', MemberList::class),
    Load::each('member')
        ->as(Member::class, ['name' => Is::string()])
)();

$actualClubs = $make->from($data)['club'];

$chuckNorris = Member::named('Chuck Norris');
$expectedClubs = [
    '1' => Club::establishedBy($chuckNorris, 'Kick-ass Club'),
    '2' => Club::establishedBy($chuckNorris, 'The Foo Bar'),
    '3' => Club::establishedBy(Member::named('Captain Kirk'), 'Space Club'),
];
Member::named('Jackie Chan')->join($expectedClubs['1']);
Member::named('John Doe')->join($expectedClubs['2']);
Member::named('Darth Vader')->join($expectedClubs['3']);

assert($expectedClubs == $actualClubs);
```

### Bidirectional has-many mapping:

[](#bidirectional-has-many-mapping)

```
$data = table([
    //---------------------+---------------------+-----------------------+,
    [ 'student_first_name' , 'student_last_name' , 'book_name'           ],
    //---------------------+---------------------+-----------------------+,
    [ 'Alice'              , 'of Wonderland'     , 'Catching rabbits'    ],
    [ 'Alice'              , 'of Wonderland'     , 'Hacking 101'         ],
    [ 'Bob'                , 'the Builder'       , 'Toolset maintenance' ],
    //---------------------+---------------------+-----------------------+,
]);

$make = Joined::table(
    Load::each('student')
        ->by('first_name', 'last_name')
        ->as(Student::class, [
            'name' => Has::one(Name::class)
                ->with('firstName', In::key('first_name'))
                ->with('lastName', In::key('last_name'))
        ])
        ->havingMany('books', 'book'),
    Load::each('book')
        ->by('name')
        ->as(Book::class)
        ->havingOne('owner', 'student')
)();

$objects = $make->from($data);
$student = $objects['student'];
$book = $objects['book'];

assert($student['Alice:of Wonderland']->hasThe($book['Catching rabbits']));
assert($book['Catching rabbits']->isOwnedBy($student['Alice:of Wonderland']));

assert($student['Bob:the Builder']->hasThe($book['Toolset maintenance']));
assert($book['Toolset maintenance']->isOwnedBy($student['Bob:the Builder']));

assert($student['Alice:of Wonderland']->name() instanceof Name);
assert('Alice of Wonderland' === (string) $student['Alice:of Wonderland']->name());
```

### Many-to-many relationship:

[](#many-to-many-relationship)

```
$data = table([
    //--------------+-----------------------+,
    [ 'student_name', 'course_name'         ],
    //--------------+-----------------------+,
    [ 'Alice'       , 'Catching rabbits'    ],
    [ 'Alice'       , 'Hacking 101'         ],
    [ 'Bob'         , 'Toolset maintenance' ],
    [ 'Bob'         , 'Hacking 101'         ],
    //--------------+-----------------------+,
]);

$make = Joined::table(
    Load::each('student')
        ->by('name')
        ->as(Student::class)
        ->havingMany('courses', 'course', Courses::class),
    Load::each('course')
        ->by('name')
        ->as(Course::class)
        ->havingMany('subscribedStudents', 'student', Students::class)
)();

$objects = $make->from($data);
$student = $objects['student'];
$course = $objects['course'];

assert($student['Alice']->follows($course['Catching rabbits']));
assert($student['Alice']->follows($course['Hacking 101']));
assert($student['Alice']->doesNotFollow($course['Toolset maintenance']));

assert($student['Bob']->doesNotFollow($course['Catching rabbits']));
assert($student['Bob']->follows($course['Hacking 101']));
assert($student['Bob']->follows($course['Toolset maintenance']));

assert(count($course['Catching rabbits']->subscribedStudents()) === 1);
assert(count($course['Hacking 101']->subscribedStudents()) === 2);
assert(count($course['Toolset maintenance']->subscribedStudents()) === 1);
```

### Multiple joined tables:

[](#multiple-joined-tables)

```
$data = table([
    //----------+--------------+---------------+---------------+,
    ['firm_name', 'lawyer_name', 'client_name' , 'client_value'],
    //----------+--------------+---------------+---------------+,
    ['The Firm' , 'Alice'      , 'John Doe'    , 10000         ],
    ['The Firm' , 'Bob'        , 'Jackie Chan' , 56557853526   ],
    ['The Firm' , 'Alice'      , 'Chuck Norris', 9999999999999 ],
    ['The Firm' , 'Bob'        , 'Alfred'      , 845478        ],
    ['Law & Co' , 'Charlie'    , 'Slender Man' , 95647467      ],
    ['The Firm' , 'Alice'      , 'Foo Bar'     , 365667        ],
    ['Law & Co' , 'Charlie'    , 'John Cena'   , 4697669670    ],
    //----------+--------------+---------------+---------------+,
]);

$make = Joined::table(
    Load::each('firm')->by('name')->as(Firm::class)->havingMany('lawyers', 'lawyer'),
    Load::each('lawyer')->by('name')->as(Lawyer::class)->havingMany('clients', 'client'),
    Load::each('client')->by('name')->as(Client::class)
)();

$firms = $make->from($data)['firm'];

$theFirm = $firms['The Firm'];
$lawAndCo = $firms['Law & Co'];

[$alice, $bob] = $theFirm->lawyers();
[$charlie] = $lawAndCo->lawyers();

assert(3 == count($alice->clients()));
assert(2 == count($bob->clients()));
assert(2 == count($charlie->clients()));
```

To do
-----

[](#to-do)

- Make simple table builder.
- Segregate builder interfaces.
- Allow direct hydrator injection in joined table builder?
- More unhappy path testing and better exception handling.
- Use deserializer instead of an old hydrator version.

###  Health Score

24

—

LowBetter than 32% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity11

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity50

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

Total

3

Last Release

2728d ago

PHP version history (2 changes)v0.1PHP &gt;=7.1

v0.2PHP &gt;=7.2

### Community

Maintainers

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

---

Top Contributors

[![Stratadox](https://avatars.githubusercontent.com/u/5333260?v=4)](https://github.com/Stratadox "Stratadox (98 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/stratadox-table-loader/health.svg)

```
[![Health](https://phpackages.com/badges/stratadox-table-loader/health.svg)](https://phpackages.com/packages/stratadox-table-loader)
```

###  Alternatives

[symfony/polyfill-php72

Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions

4.8k674.7M31](/packages/symfony-polyfill-php72)[symfony/polyfill-intl-icu

Symfony polyfill for intl's ICU-related data and classes

2.6k251.4M96](/packages/symfony-polyfill-intl-icu)[nette/php-generator

🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.5 features.

2.2k64.2M574](/packages/nette-php-generator)[consolidation/site-process

A thin wrapper around the Symfony Process Component that allows applications to use the Site Alias library to specify the target for a remote call.

5345.3M8](/packages/consolidation-site-process)[sycho/flarum-profile-cover

Adds the ability to add a cover image to a profile.

1836.6k](/packages/sycho-flarum-profile-cover)

PHPackages © 2026

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