PHPackages                             webgraphe/phlux - 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. webgraphe/phlux

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

webgraphe/phlux
===============

Yet another PHP DataTransferObject library

v0.4.0(5mo ago)03MITPHPPHP ^8.4

Since Jul 30Pushed 3mo ago1 watchersCompare

[ Source](https://github.com/webgraphe/phlux)[ Packagist](https://packagist.org/packages/webgraphe/phlux)[ RSS](/packages/webgraphe-phlux/feed)WikiDiscussions main Synced today

READMEChangelog (4)Dependencies (3)Versions (7)Used By (0)

Yet Another PHP Data Transfer Object Library?
=============================================

[](#yet-another-php-data-transfer-object-library)

Not so fast!

A true Data Transfer Object (DTO) in PHP is a class focused solely on storing and structuring data for transfer between different parts of an application with key features such as:

- strong typing
- immutability
- absence of business logic

As it is, the DTO below checks the aforementioned requirements.

```
readonly class UserProfile
{
    public function __construct(
        public int $id,
        public string $firstName,
        public string $lastName,
        public string $email,
    ) {}
}
```

However, things quickly get complicated when:

- a constructor with promoted properties becomes crowded
- transformation is required but the state of the original object must remain intact
- serialization/deserialization can be tedious
- correctness of complex structures such as collections of discriminable DTOs are challenging

This ^^ is Phlux, a PHP library with no other dependencies than PHP itself.

Declaration
-----------

[](#declaration)

A Phlux DTO is strictly declared using PHP language constructs; there is ***no magic***.

To make a Phlux DTO: extend the `readonly` class `Data`.

A Phlux DTO can declare `public` properties, a constructor promoting `public` properties, or both!

### Supported property types range from:

[](#supported-property-types-range-from)

- `int`, `float`, `string` and `bool`
- `DateTimeInterface`, or anything extending `DateTimeImmutable`
- Backed-enumerations
- Anything extending `Data`
- Composites (`array`, `object`) of all the above

Any property can be nullable, declared with the `?` prefix or as a union with `null`.

### Composites

[](#composites)

An `array` properties is always hydrated as `0`-based list.

An `object` property is always hydrated as a key-value map instance of `stdClass`.

Without attributes, composites can store arbitrary data.

A composite property may narrow the type(s) of items it contains with the `#[ItemType]` attribute, passing a `class-string` or the name of any aforementioned supported types above.

An `#[ItemPrototype]` attribute may be declared with the name of another property of the same class to be used as the prototype of the collection's item (non-`public` properties can used as prototype for that effect).

### Polymorphism

[](#polymorphism)

Adding the `#[Discriminator]` attribute on a `DataTransferObject` class (MUST be `abstract`) allows for inheritance and polymorphism of DTOs and their properties to hydrate. It must be given the name of a `final`, non-nullable `string` property on the attributed class containing the discriminator value which can be matched against a given mapping or composed with the namespace of the discriminated DTO.

### Limitations

[](#limitations)

There is no support (yet) for Union or Intersection properties, except unions with `null`.

Presentation and transportation are handled for all `public` properties (non-`public` properties may be defined but are not serialized).

Uninitialized properties are not serialized.

Instantiation
-------------

[](#instantiation)

DTOs may be hydrated in different ways:

The static method `instantiate()` acts as a constructor by accepting parameters named after its public properties. For discriminated DTOs, it resolves the discriminator value automatically.

The static method `from()` unmarshalls payloads, such as decoded JSON, `stdClass`, `ArrayObject` or SPL data structures (that can be transformed into raw PHP composites). It discriminates which class to instantiate from the payload.

Methods `lazyInstantiate()` and `lazyFrom()` creates lazy instances that initializes only when observed, which may reduce the number of precious CPU cycles when dealing with big nested DTOs and complex business logic partially navigating them.

Caution

Lazy DTO instantiations will defer exceptions that would otherwise have been thrown at creation time with their non-lazy corresponding methods only when they are observed for the first time; it is advised to unit test your work without lazy instances to validate your Data Transfer Object definitions.

### Default values

[](#default-values)

When the data for a property is missing from a payload, unless a `#[Present]` attribute is found on the property (indicating to skip initialization), a default value is assigned:

- `0` for `int`
- `0.0` for `float`
- `false` for `bool`
- `''` for `string`
- `[]` for `array`
- `new stdClass()` for `object`
- Current time for anything implementing `DateTimeInterface` and anything extending `DateTimeImmutable`
- The first `BackedEnum::cases()` item for `enum`
- A new instance with `null` payload for anything implementing `DataTransferObject`
- `null` when nullable

Examples
--------

[](#examples)

Polymorphism:

```
enum Color: string
{
    case RED = 'red';
    case GREEN = 'green';
    case BLUE = 'blue';
}

#[Webgraphe\Phlux\Attributes\Discriminator('type', self::MAPPING)]
abstract readonly class Shape extends Webgraphe\Phlux\Data
{
    public const MAPPING = [
        'square' => Square::class,
        'circle' => Circle::class
    ];

    public string $type;
    public Color $color;
}

final readonly class Square extends Shape
{
    public float $length;
}

final readonly class Circle extends Shape
{
    public float $radius;
}

$square = Square::instantiate(length: 4);
$square->length; // 4
// uninitialized enum defaults to first case
$square->color; // Color::RED
$square->type; // 'square'
json_encode($square); // '{"type":"square","color":"red","length":4}'

$circle = Shape::from(['type' => 'circle', 'color':'green', 'radius' => 5.0]);
// class inferred from discriminator
$circle::class; // 'Circle'
$circle->color; // Color::GREEN
$circle->radius; // 5.0
```

Constructor with parameters promoted as properties, and collection types:

```
final readonly class UserGroup extends Webgraphe\Phlux\Data
{
    public int $id;
    public int $name;
    #[Webgraphe\Phlux\Attributes\ItemPrototype('members')]
    public array $levelMembers;

    #[Webgraphe\Phlux\Attributes\ItemType(UserProfile::class)]
    private array $members;
}

final readonly class UserProfile extends Webgraphe\Phlux\Data
{
    public string $firstName;
    public string $lastName;
    #[Webgraphe\Phlux\Attributes\Present]
    public string $email;

    public function __construct(
        public int $id,
    )
}

$user = new UserProfile(123);
$user->id; // 123
isset($user->firstName); // false
isset($user->lastName); // false
isset($user->email); // false

$user = UserProfile::lazyInstantiate(id: 123, firstName: 'John');
// Object is lazy
Data::isLazy($user); // true
// lastName will always be initialized
isset($user->lastName); // true
// email is only initialized if a value is given (present)
isset($user->email); // false
// Object is no longer lazy now that we accessed the lastName and email properties
Data::isLazy($user); // false
// Serialization omits uninitialized properties
json_encode($user); // '{"id":123,"firstName":"John",'lastName":""}

$userGroup = UserGroup::instantiate(members:[0 => [$user]]);
count($userGroup->levelMembers); // 1
count($userGroup->levelMembers[$level = 0]); // 1
$userGroup->levelMembers[$level][0] === $user; // true
```

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance77

Regular maintenance activity

Popularity3

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity48

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

Total

4

Last Release

159d ago

### Community

Maintainers

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

---

Top Contributors

[![webgraphe](https://avatars.githubusercontent.com/u/17071579?v=4)](https://github.com/webgraphe "webgraphe (41 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/webgraphe-phlux/health.svg)

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

###  Alternatives

[georgique/world-geojson

GeoJson for all the countries, areas (regions) and some states.

4976.0k](/packages/georgique-world-geojson)

PHPackages © 2026

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