PHPackages                             earc/data - 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. [Framework](/categories/framework)
4. /
5. earc/data

ActiveLibrary[Framework](/categories/framework)

earc/data
=========

eArc - the explicit architecture framework - data component

0.0.1(5y ago)0745MITPHPPHP ^8.0

Since Apr 6Pushed 5y agoCompare

[ Source](https://github.com/Koudela/eArc-data)[ Packagist](https://packagist.org/packages/earc/data)[ RSS](/packages/earc-data/feed)WikiDiscussions master Synced 3d ago

READMEChangelog (2)Dependencies (3)Versions (3)Used By (5)

earc/data
=========

[](#earcdata)

Strongly decoupled data component of the eArc libraries.

Use this library if you want maximal control of the persistence process. Update/request your databases, caches, search indices, etc. during entity persistence/load processing in an optimal order determined by your project. You can even exchange them, stack them up or remove parts with minimal effort and without touching your business logic.

It works best with key-value based persistence like you would use with a [redis server](https://redis.io/) backed up by a search index server like [elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html), and a memory based entity caching. Checkout the eArc components [earc/data-redis](https://github.com/Koudela/eArc-data-redis)and earc/data-elasticsearch [earc/data-elasticsearch](https://github.com/Koudela/eArc-data-elasticsearch)for easy integration.

The usage of a traditional sql databases is possible too, but comes with a bit more effort on your side. Check the [writing your own bridge](#writing-your-own-bridge)section for a deeper insight.

table of contents
-----------------

[](#table-of-contents)

- [pro/cons](#procons)
- [installation](#installation)
- [basic usage](#basic-usage)
    - [classes](#classes)
        - [entities](#entities)
        - [collections](#collections)
        - [embedded entities](#embedded-entities)
        - [embedded collections](#embedded-collections)
    - [functions](#functions)
        - [data persist](#data-persist)
        - [data load](#data-load)
        - [data delete](#data-delete)
        - [data remove](#data-remove)
        - [data find](#data-find)
        - [data schedule](#data-schedule)
        - [data detach](#data-detach)
    - [livecycle-events](#livecycle-events)
        - [via entity](#via-entity)
        - [via service](#via-service)
    - [autogenerate keys](#autogenerate-keys)
    - [bridges](#bridges)
- [advanced usage](#advanced-usage)
    - [immutable entities](#immutable-entities)
    - [mutable references](#mutable-references)
    - [writing your own bridge](#writing-your-own-bridge)
    - [on data persist](#on-data-persist)
    - [on data load](#on-data-load)
    - [on data remove](#on-data-remove)
    - [on data find](#on-data-find)
    - [on primary key generation](#on-primary-key-generation)
- [releases](#releases)
    - [release 0.0.1](#release-001)
    - [release 0.0](#release-00)

pro/cons
--------

[](#procons)

### pro

[](#pro)

- **strongly decoupled** - write your business logic first and decide then about the persistence layer (databases, caches, search indices with respect to entities)
- **no configuration overhead** - there are no attributes that need to be kept in sync with the database relations. Just use the abstract classes (or the interfaces if you need more control).
- **use of global functions** - once initialized there is no need to inject a repository or a manager in any class.
- **use it everywhere** - saving and loading data works even in vanilla functions and closures.
- **architectural optimized code** - processing works in linear time to the size and count of your entities. No headache about an entity manager slowing down after persisting a couple of entities.
- **support for all standard dependency enrichment techniques** - like pre/post load/persist/remove events, wakeup/sleep events, cascading removals/updates.
- **support for immutables**
- **write transactions** for the complete persistence infrastructure
- **extendable** - integrates with nearly all kinds of persistence handling.

### cons

[](#cons)

- **dependency** on a data library - although it's a much softer coupling than for example doctrine provides.
- **small overhead** - entity, collection and event processing need some programming logic

installation
------------

[](#installation)

Install the earc data library via composer.

```
$ composer require earc/data

```

basic usage
-----------

[](#basic-usage)

The `data_*` functions need to be imported. Use this in your`index.php`, bootstrap or configuration script.

```
use eArc\Data\Initializer;

Initializer::init();
```

Then register your persistence infrastructure to the `onLoad`, `onPersit`, `onRemove`, `onFind` and `onAutoPrimaryKey` events.

```
use eArc\Data\ParameterInterface;

di_tag(ParameterInterface::TAG_ON_LOAD, MyCacheBridge::class);
di_tag(ParameterInterface::TAG_ON_LOAD, MyDataBaseBridge::class);

di_tag(ParameterInterface::TAG_ON_PERSIST, MyCacheBridge::class);
di_tag(ParameterInterface::TAG_ON_PERSIST, MyDataBaseBridge::class);
di_tag(ParameterInterface::TAG_ON_PERSIST, MySearchIndexBridge::class);

di_tag(ParameterInterface::TAG_ON_REMOVE, MyCacheBridge::class);
di_tag(ParameterInterface::TAG_ON_REMOVE, MyDataBaseBridge::class);
di_tag(ParameterInterface::TAG_ON_REMOVE, MySearchIndexBridge::class);

di_tag(ParameterInterface::TAG_ON_FIND, MySearchIndexBridge::class);
di_tag(ParameterInterface::TAG_ON_FIND, MyDataBaseBridge::class);

di_tag(ParameterInterface::TAG_ON_AUTO_PRIMARY_KEY, MyDataBaseBridge::class);
```

After that entities can be loaded and saved via the `data_*` functions.

```
$entity = new MyEntity('some arguments');

data_persist($entity); // saves the entity
$pk = $entity->getPrimaryKey(); // yields the primary key - may be set prior persistence
data_load(MyEntity::class, $pk); // returns the entity of MyEntity::class with the primary key $pk
data_delete($entity); // removes the entity
data_remove(MyEntity::class, $pk); // removes the entity without the need of being loaded before
data_find(MyEntity::class, ['name' => ['Anton', 'Max', 'Simon'], 'age' => 42]); // returns the primary keys for all MyEntity instances where the name property is equal to 'Anton', 'Max' or 'Simon' and the age property is 42

// expert functions - use only if you really know what you are doing
data_schedule($entity); // schedules the saving process until `data_persist` is called with any argument
data_detach(get_class($entity), [$pk]); // removes the entity from the earc/data entity cache
```

You can call the `data_*` functions from everywhere (i.e. constructors, methods, vanilla functions or plain code).

### classes

[](#classes)

There are five types of entity related objects *pure* **entities**, **collections***of pure entities*, **embedded entities**, **embedded collections** and **immutable entities**. The related interfaces are used in the persisting/loading process.

#### entities

[](#entities)

Entities are objects that are associated to a primary key, to identify them (in combination with their class name) for loading. They implement the `EntityInterface`and the `PrimaryKeyInterface`. To create an entity class just extend the `AbstractEntity`class and wire the `primaryKey` property.

For reverencing entities within other entities you have to use the primary key as property (like you would do it in a sql database).

```
use eArc\Data\Entity\AbstractEntity;

class MyReverencedEntity extends AbstractEntity {/*...*/}

class Entity extends AbstractEntity
{
    protected string $myReverencedEntityPK;

    public function setMyReverencedEntity(MyReverencedEntity $myReverencedEntity)
    {
        $this->myReverencedEntityPK = $myReverencedEntity->getPrimaryKey();
    }

    public function getMyReverencedEntity(): MyReverencedEntity
    {
        return data_load(MyReverencedEntity::class, $this->myReverencedEntityPK);
    }
}
```

This gives the advantage, that reverences between entities does not need to be managed by earc/data, and your reverenced entity is lazy loaded. Both saves a lot of overhead and gives you more control without doing much more programming.

#### collections

[](#collections)

Reverencing many entities follows the same principles. To make it easy use the `Collection` class. The presence of the `CollectionInterface` is mandatory for the persistence/loading process.

```
use eArc\Data\Collection\Collection;
use eArc\Data\Entity\AbstractEntity;

class MyReverencedEntity extends AbstractEntity {/*...*/}

class EntityWithCollection extends AbstractEntity
{
    protected Collection $myReverencedEntityCollection;

    public function __construct()
    {
        $this->myReverencedEntityCollection = new Collection($this, MyReverencedEntity::class);
    }

    public function getMyReverencedEntityCollection(): Collection
    {
        return $this->myReverencedEntityCollection;
    }
}

$entityWithCollection = new EntityWithCollection();
$myReverencedEntity = new MyReverencedEntity();
$collection = $entityWithCollection->getMyReverencedEntityCollection();
$collection->add($myReverencedEntity->getPrimaryKey());
$collection->remove($myReverencedEntity->getPrimaryKey());
foreach ($collection as $primaryKey) {
    echo $primaryKey;
}
foreach ($collection->asArray() as $entity) {
    echo $entity->getPrimaryKey();
}
```

Collections consist always of one type of entities.

Hint: If you extend the type of the entities used by your collection, you can add instances of this to the collection, but earc/data neither guarantees that the additional data is saved nor that it is not.

#### embedded entities

[](#embedded-entities)

If two entities are strongly coupled (one entity is needed nearly every time while working with the other and vice versa) for example within xml like document structures or products and their attributes, you should consider using embedded entities. They may have a better performance on loading and saving as the underlying datastructure is hit only once.

Embedded entities are saved and loaded with the root entity. The root entity is always a pure entity and has a primary key. The embedded entities do not have a primary key. They always hold a reverence to their parent (the embedding) entity. Embedded entities may be embedded within other embedded entities. They can form an entity tree.

They implement the `EmbeddedEntityInterface`. To create an embedded entity class just extend the `AbstractEmbeddedEntity` class and wire the `ownerEntity` property.

```
use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Entity\AbstractEmbeddedEntity;

class MyEmbeddedEntity extends AbstractEmbeddedEntity
{
    //...
    public function setParent(MyRootEntity $parent)
    {
        $this->ownerEntity = $parent;
    }
}

class MyRootEntity extends AbstractEntity
{
    protected MyEmbeddedEntity $myEmbeddedEntity;

    public function setMyReverencedEntity(MyEmbeddedEntity $myEmbeddedEntity)
    {
        $this->myEmbeddedEntity = $myEmbeddedEntity;
        $myEmbeddedEntity->setParent($this);
    }

    public function getMyEmbeddedEntity(): MyEmbeddedEntity
    {
        return $this->myEmbeddedEntity;
    }
}
```

#### embedded collections

[](#embedded-collections)

Embedded collections are collections of embedded entities embedded within other entities. Use the `EmbeddedCollection` class for them. The presence of the `EmbeddedCollectionInterface` is mandatory for the persistence/loading process.

```
use eArc\Data\Collection\EmbeddedCollection;
use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Entity\AbstractEmbeddedEntity;

class MyEmbeddedEntity extends AbstractEmbeddedEntity {/*...*/}

class MyRootEntity extends AbstractEntity
{
    protected EmbeddedCollection $myEmbeddedEntityCollection;

    public function __construct()
    {
        $this->myEmbeddedEntityCollection = new EmbeddedCollection($this, MyEmbeddedEntity::class);
    }

    public function getMyEmbeddedEntityCollection(): EmbeddedCollection
    {
        return $this->myEmbeddedEntityCollection;
    }
}

$rootEntity = new MyRootEntity();
$myEmbeddedEntity = new MyEmbeddedEntity();
$collection = $rootEntity->getMyEmbeddedEntityCollection();
$collection->add($myEmbeddedEntity);
$collection->remove($myEmbeddedEntity);
foreach ($collection as $entity) {
    echo $entity::class;
}
foreach ($collection->asArray() as $entity) {
    echo $entity::class;
}
```

Embedded collections consist always of one type of entities.

Hint: If you extend the type of the entities used by your collection, you can add instances of this to the collection and this time earc/data guarantees that the additional data is saved.

Embedded collections expose a `findBy()` method to search the collection in a key value based fashion.

```
$rootEntity = new MyRootEntity();
$collection = $rootEntity->getMyEmbeddedEntityCollection();
$entities = $collection->findBy(['name' => 'Claudia', 'age' => [31,32,33]]);
foreach ($entities as $entity) {
    echo $entity::class === MyEmbeddedEntity::class
        && $entity->getName() === 'Claudia'
        && in_array($entity->getAge(), [31, 32, 33]) ? 'true' : 'something went wrong';
}
```

Hint: If you reverence an object by a property of an entity that is no embedded entity and not an embedded collection earc/data neither guarantees that the data of the property is saved nor that it is not. It should be avoided, but it is not permitted. If you use your own bridge and serializer it may make sense.

### functions

[](#functions)

earc/data uses functions instead of services to give maximal freedom to your code.

#### data persist

[](#data-persist)

`data_persist` takes one entity as argument. The data of the entity will be saved. If the `getPrimaryKey()` method returns null an exception is thrown. There is only one exception to the rule: if the entity implements the `OnAutoPrimaryKeyInterface`and at least one callable registered to the `OnAutoPrimaryKeyInterface` event returns a string result. Then this result is used as primary key.

On calling `data_persist` all entities scheduled via `data_schedule` will be saved first. `data_persist` can be called without argument, to trigger the saving of the scheduled entities without the need to explicit save any entity.

To save multiple entities there is a `data_persist_batch` function.

#### data load

[](#data-load)

`data_load` takes the class name and primary key as arguments. It returns the entity.

`data_load` returns the same instance on successive calls when the same arguments are provided.

You can change this behaviour by calling `data_detach` in between. Please note the resulting behaviour might be unexpected for inattentive developers. Use with great care.

`data_load` takes a third facultative flag. If it is set to `DataStoreInterface::LOAD_FLAG_USE_FIRST_LEVEL_CACHE_ONLY`, only entities already loaded are returned. If it is set to `DataStoreInterface::LOAD_FLAG_SKIP_FIRST_LEVEL_CACHE`, earc/data does not look up the already loaded entities and does not add the loaded entities to the already loaded ones - the retrieved entities are in a detached state and will be another instance than the same entities loaded before or after that.

There is a `data_load_batch` function to load multiple entities at once.

#### data delete

[](#data-delete)

`data_delete` takes an entity as argument. It removes the entity data.

Entities implementing the `ImmutableEntityInterface` can only be deleted if the force flag has been set.

This function has a multiple counterpart `data_delete_batch`.

#### data remove

[](#data-remove)

`data_remove` takes the entity class name and primary key as arguments. It works as `data_delete` but without the need of the entity being loaded prior removal.

To delete multiple entities of the same class use the `data_remove_batch` function.

#### data find

[](#data-find)

`data_find` takes the entity class nam as argument. If it is called without further arguments, it returns all primary keys of the corresponding persisted entities.

The second optional argument takes key value pairs with keys equal to the property names of the entity. The value of each key has to be a data value or array of data values. Key value pairs are joint via logic `AND`. Value arrays are interpreted as `IN`. Not all key value pairs or value arrays may be supported. It depends on the used infrastructure, the setting (for example the usable sql indices) and the implementation of the bridge. If one or more key value pairs are not supported a `QueryException` is thrown.

Instead of calling `data_load_batch($fQCN, data_load($fQCN, $keyValuePairs)` you can use the shorthand `data_find_entities($fQCN, $keyValuePairs)`.

Hint: Beside this function there may be more ways to search for entities. These are not part of the earc/data abstraction.

#### data schedule

[](#data-schedule)

Server responses should be as fast as possible. To support this goal earc/data has the `data_schedule` function. The persisting of the supplied entity will be scheduled until `data_persist` is called. If you call `data_persist` in a shutdown function you can build and send your server response without being delayed by writing data to a database or index.

#### data detach

[](#data-detach)

earc/data holds a reverence to each loaded entity to ensure every entity exists only once. To circumvent this behaviour `data_detach` can be called. It deletes the reverence.

`data_detach` without arguments will delete all reverences. `data_detach` with a class name as argument will delete all reverences to entities of this class. `data_detach` called with class name and primary key deletes the reverence to only one entity.

There are three reasons for calling `data_detach`:

1. Changing the primary key without deleting the entity first.
2. Generating different instances from the same data.
3. Garbage collection.

Hint: If you change the primary key of a persisted entity without calling `data_detach`first you may encounter unexpected behaviour.

### livecycle events

[](#livecycle-events)

There are six livecycle events. They are represented by six interfaces (`PreLoadInterface`, `PostLoadInterface`, `PrePersistInterface`, `PostPersistInterface`, `PreRemoveInterface`, `PostRemoveInterface`). Each event exists as entity event and as service event. The service event is triggered prior the entity event. The interface method of the registered services are called every time the event is triggered, the interface methods of the entity only if the entity is affected by the event.

Any interface method will be called with the entity (if the method is not static) or class name and primary key (if the method is static). Returns are not evaluated, thus all interface methods have void return types.

#### via entity

[](#via-entity)

To use the livecycle events via an entity, the entity has to implement the corresponding interface.

Use cases are wakeup/sleep procedures or cascading removals/persistence.

A cascade persist can be implemented as follows:

```
use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Entity\Interfaces\EntityInterface;
use \eArc\Data\Entity\Interfaces\Events\PrePersistInterface;

class MyReverencedEntity extends AbstractEntity {/*...*/}

class SomeEntity extends AbstractEntity implements PrePersistInterface
{
    protected string $myReverencedEntityPK;

    public function setMyReverencedEntity(MyReverencedEntity $myReverencedEntity)
    {
        $this->myReverencedEntityPK = $myReverencedEntity->getPrimaryKey();
    }

    public function getMyReverencedEntity(): MyReverencedEntity
    {
        return data_load(MyReverencedEntity::class, $this->myReverencedEntityPK);
    }

    public function prePersist(EntityInterface $entity): void
    {
        if ($entity = data_load(MyReverencedEntity::class, $this->myReverencedEntityPK, true)) {
            data_persist($entity);
        }
    }
}
```

An implementation via a referenced service method and attributes might be more structured but also a bit slower. earc/data gives you the freedom to shape it for your needs or use third party plugins if available.

#### via service

[](#via-service)

Warning: Use service events only if you really need to process events globally, or your application is slowed down unnecessarily.

```
use eArc\Data\Entity\Interfaces\Events\PreRemoveInterface;

di_tag(PreRemoveInterface::class, MyPreRemoveService::class); // myImmutablePK = $entity->getPrimaryKey();
    }

    public function getMyImmutable(): MyImmutable|null
    {
         return is_null($this->myImmutablePK) ? null : data_load(MyImmutable::class, $this->myImmutablePK);
    }

    public function setMyImmutable(MyImmutable $immutable)
    {
        // If the current class does not have a primary key yet, we have to persist it first.
        // A post persist event does not reduce the effort and may lead into a infinity loop.
        // You may use the solution for reverences between two immutables to circumvent this.
        if (is_null($this->primaryKey)) {
            throw new DataException('Primary key is missing. Try to persist this entity first.');
        }

        // The reverse key is set on persist.
        $immutable->setMutableReverenceKey($this->primaryKey);
    }

    //...
}

class MyImmutable extends AbstractEntity implements MutableReverenceKeyInterface, ImmutableEntityInterface
{
    protected string|null $mutableReferencePrimaryKey;

    //...

    public function getMutableReverenceKey(): string
    {
        return $this->mutableReferencePrimaryKey;
    }

    public function setMutableReverenceKey(string $mutableReferencePrimaryKey): void
    {
        $this->mutableReferencePrimaryKey = $mutableReferencePrimaryKey;
    }

    public function getMutableReverenceClass(): string
    {
        return MyClassReverencingAImmutable::class;
    }

    //...
}

class ReferenceUsingProxy extends AbstractEntity
{
    protected string $myImmutableProxyFQCN;
    protected string $myImmutableProxyPK;

    //...

    public function setMyImmutable(MyImmutable $immutable)
    {
        $this->myImmutableProxyFQCN = $immutable->getMutableReverenceClass();
        $this->myImmutableProxyPK = $immutable->getMutableReverenceKey();
    }

    public function getMyImmutable(): MyImmutable
    {
        $proxy = data_load($this->myImmutableProxyFQCN, $this->myImmutableProxyPK)->getMyImmutable();

        return $proxy->getMyImmutable();
    }
    //...
}
```

To implement a mutable reference between two immutable entity chains you can use the `GenericMutableEntityReference` as link between them.

```
use eArc\Data\Entity\Interfaces\PrimaryKey\MutableReverenceKeyInterface;
use eArc\Data\Entity\Interfaces\ImmutableEntityInterface;
use eArc\Data\Entity\AbstractEntity;
use eArc\Data\Entity\GenericMutableEntityReference;

class MyImmutableClassReverencingAImmutable extends AbstractEntity
{
    protected string|null $myImmutableLinkPK;

    //...

    public function getMyImmutable(): MyImmutable|null
    {
         return is_null($this->myImmutableLinkPK) ? null :
            data_load(GenericMutableEntityReference::class, $this->myImmutableLinkPK)->getMutableReverenceTarget();
    }

    public function setMyImmutable(MyImmutable $immutable)
    {
        $this->myImmutableLinkPK = $immutable->getMutableReverenceKey();
    }

    //...
}

class MyImmutable extends AbstractEntity implements MutableReverenceKeyInterface, ImmutableEntityInterface
{
    protected string|null $mutableReferencePrimaryKey;

    public function __construct(GenericMutableEntityReference|null $mutableReference)
    {
        if (is_null($mutableReference)) {
            $mutableReference = new GenericMutableEntityReference();
        }

        if (is_null($mutableReference->getPrimaryKey())) {
            data_persist($mutableReference);
        }

        $this->mutableReferencePrimaryKey = $mutableReference->getPrimaryKey();
    }

    //...

    public function getMutableReverenceKey(): string
    {
        return $this->mutableReferencePrimaryKey;
    }

    public function getMutableReverenceClass(): string
    {
        return GenericMutableEntityReference::class;
    }

    //...
}
```

### writing your own bridge

[](#writing-your-own-bridge)

If there is no bridge available for your persistence infrastructure, or you need maximal control of the persisting process, you can write your own bridge.

For writing a bridge all you have to do is to implement the interfaces for the data persist/load/remove/find and primary key generation events.

#### on data persist

[](#on-data-persist)

The `onPersist()` method of the `OnPersistInterface` will be called with the entities to persist as argument. Persistence must not fail silently.

#### on data load

[](#on-data-load)

The `onLoad()` method of the `OnLoadInterface` will be called with fully qualified class name and the primary keys of the entities as arguments. It has to return the entity objects on success. If the entity could not be found the callable must not throw an error, but simply not return it. Other services may return the entity object thereafter.

The interface passes as third argument an array of callables. If a service fails to load an entity, it can add a callback to the array. The callables will be called when all entities are loaded, with the entities as argument. Thus, a cache can save missing entities, without the need to listen at every post load event.

#### on data remove

[](#on-data-remove)

The `onRemove()` method of the `OnReturnInterface` will be called with fully qualified class name and the primary keys of the entities as arguments. Removing an entity must not fail silently unless it has not existed yet.

#### on data find

[](#on-data-find)

The `onFind()` method of the `OnFindInterface` will be called with fully qualified class name and key-value-pairs of the entity as arguments. It has to return an array of primary keys. It may be empty if no entity fits to the key-value-pairs. They have to return null, if they cannot process the key-value-pairs because of the infrastructure or missing data, but they have to throw an earc/data `QueryExceptionInterface` if just a configuration or index is missing.

The empty key-value-pairs array witch has to return all existing primary keys of the entity class has to be supported always. The other key-value-pairs may not be supported or depend on configuration or indices.

Key value pairs have to be joint via logic `AND`. Value arrays have to be interpreted as `IN`.

For example `callable(User::class, ['firstname' => 'Max', 'age' => [18, 19, 20, 21, 22, 23])`has to return all users with first name Max and age between 18 and 23.

Bridges may extend this syntax to support a wider range of search requests.

#### on primary key generation

[](#on-primary-key-generation)

The `onAutoPrimaryKey()` method of the `OnAutoPrimaryKeyInterface` will be called with the entity in need for a new primary key as argument. It has to return a string, or null if it is not willing to process the request.

The key generation is called upon before saving the entity. This may be a contradiction to the infrastructure, for example if the entity is persisted to a sql database which generates the key. In these cases the method has to return an empty string. Of course this must be recognised by the on data persist services.

releases
--------

[](#releases)

### release 0.0.1

[](#release-001)

- transactions (beta - documentation in code only, no tests, interfaces may change)
- `CollectionBaseInterface` implements `EmbeddedEntityInterface`
- added `setOwnerEntity()` method to `EmbeddedEntityInterface`
- removed `getOwner()` method from `CollectionBaseInterface`

### release 0.0

[](#release-00)

- the first official release
- PHP ^8.0 support
- IDE support for PHPStorm:
    - return type support for `data_load`, `data_load_batch` and `data_find_entities`

###  Health Score

23

—

LowBetter than 27% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity9

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

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

Total

2

Last Release

1861d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b7308f2797252cace014cfec12c1b5978c1bf5608be78d7a188ff690192959f3?d=identicon)[Thomas Koudela](/maintainers/Thomas%20Koudela)

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/earc-data/health.svg)

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

###  Alternatives

[laravel/telescope

An elegant debug assistant for the Laravel framework.

5.2k67.8M192](/packages/laravel-telescope)[spiral/roadrunner

RoadRunner: High-performance PHP application server and process manager written in Go and powered with plugins

8.4k12.2M84](/packages/spiral-roadrunner)[nolimits4web/swiper

Most modern mobile touch slider and framework with hardware accelerated transitions

41.8k177.2k1](/packages/nolimits4web-swiper)[laravel/prompts

Add beautiful and user-friendly forms to your command-line applications.

708181.8M596](/packages/laravel-prompts)[cakephp/chronos

A simple API extension for DateTime.

1.4k47.7M121](/packages/cakephp-chronos)[laravel/pail

Easily delve into your Laravel application's log files directly from the command line.

91245.3M590](/packages/laravel-pail)

PHPackages © 2026

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