Download the PHP package jakubboucek/hydrator without Composer

On this page you can find all versions of the php package jakubboucek/hydrator. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package hydrator

Hydrator

Fast bidirectional hydrator between typed PHP entities and database rows (or other data formats), built for modern PHP.

Entities are plain data objects: typed public properties, property hooks, no magic getters/setters and no mandatory attributes. The only contract is the empty Entity marker interface — it keeps the entity a plain object while letting the hydrator (and your IDE) refuse foreign objects early instead of failing later with confusing field-mismatch errors. The hydrator maps entities to and from associative data — a database row, a raw PDO result or any other representation described by a format.

[!WARNING] The library is in development stage (0.x versions): the API may change between minor versions until it stabilizes.

Why another hydrator

General mapping libraries struggle with entities written in modern PHP style. This library is designed around them by design:

Built for gradual modernization

The hydrator is designed to work as an intermediate step when modernizing a legacy application: it requires no change of the database-access paradigm and no changes to the database structure. Retrofitting Doctrine or another full ORM into a large existing codebase tends to be a demanding, all-or-nothing endeavor; typed entities backed by this library can instead be adopted piecemeal — one table or one query at a time — while the rest of the application keeps its existing database layer, making it a practical vehicle for refactoring database access gradually.

The only hard requirement is PHP 8.4. For an older application that is usually a far smaller obstacle than a data-access rewrite: a PHP upgrade is well supported by automated tooling (such as Rector), whereas replacing the database layer of a grown codebase rarely is.

Installation

Requires PHP 8.4+. No runtime dependencies.

Usage

The entity is a plain object:

Property names map to field names by convention (camelCase ↔ snake_case by default, defined by the format).

Data sets and streaming

fromDataSet() returns an EntitySet — a lazy, single-pass stream of hydrated entities. Nothing is buffered and the data source is not touched until the first consumption; rows hydrate one by one as you iterate, so a result of any size streams in constant memory.

Stream keys are transparent: the keys of the source iteration pass through unchanged (a nette/database Selection keys its rows by the table primary key, a plain list yields sequential keys). To re-key the stream, pass keyBy: with an entity property name — the key is read from the hydrated entity, so it carries the property's type (a stringifying driver still yields int keys) and the format's naming convention never leaks into calling code. The property must be a hydrated, non-nullable int or string property — anything else fails loudly before the source is touched:

The set is strictly single-pass: once consumed — iterated or collected, even partially — a second consumption attempt throws a StreamException. The library never keeps data or entities around; holding onto results is the application's decision. For data sets that are small by design that decision is first-class: the collect* terminals materialize the stream into an array — collectList() discards keys, collectMap() preserves them (duplicate keys overwrite, as with iterator_to_array()). The vocabulary is intentional: lazy streaming is the default state, materialization is a conscious, named, greppable termination of the stream.

There is deliberately no eager variant on the hydrator or factory and no re-iterable set — the lazy path stays the easiest one, and an API that buffered or re-queried behind the scenes would only move the memory decision out of sight.

Strictness

Every writable property requires its field in data: a missing field, a null for a non-nullable property or a value of an unexpected type throws an exception with the entity class, property and field name in the message. Extra fields in data with no matching property are silently ignored, and fields of non-writable backed properties (readonly, private(set)) are never required. Virtual properties have no stored state, so the hydrator ignores them entirely — a data key matching a virtual property's would-be field is foreign data like any other extra key. All library exceptions implement the JakubBoucek\Hydrator\Exception\HydratorException marker interface.

Legacy zero dates ('0000-00-00', '0000-00-00 00:00:00') hydrate as null with an E_USER_WARNING — matching nette/database's behavior — so a non-nullable property over such data fails loudly instead of receiving a nonsense date.

Both strictness rules have explicit, per-call switches on fromData()/fromDataSet():

There is deliberately no factory-wide default for either switch — tolerance is a per-call decision, not a mode.

Asking what a partial entity carries

A partially hydrated entity is the patch — but application code cannot just read its properties, because reading an uninitialized typed property is a fatal Error. For most cases PHP itself has the answer: isset() and the ??/??= operators are safe on uninitialized typed properties, and for a non-nullable property they answer exactly:

Where the native idioms fall short, the hydrator answers — from the backing store, in property vocabulary:

In a repository where extraction happens anyway, the cheapest empty-patch guard needs no introspection at all — extract first and test the payload (this reads no field names or values, and an update() needs the guard anyway):

An unknown property name throws a MetadataException — a typo is a bug, not "not set". So does asking about a virtual property: it has no stored state to ask about. The promise is precisely the stored value is initialized, not reading is safe — a get hook may still fail on its own uninitialized dependencies. Write get hooks to tolerate uninitialized state (or accept the consequences): the hydrator never forces partial entities on you — the strict default requires every field — it only enables them.

[!IMPORTANT] toData() is not the way to ask this question. Its output is addressed to the storage driver — field names and format-encoded values. The point of the hydrator is that application code never speaks that vocabulary.

Formats

A format describes how values are represented in data: the field naming convention and the codecs for booleans, date-times, dates and intervals. Formats are stateless and identified by their class name:

Export values by format

What toData() produces for each property type:

Property type NetteDatabase Mysql Json
int, float, string as-is as-is as-is
bool bool 1 / 0 bool
BackedEnum backing value backing value backing value
DateTimeImmutable instance 1) 'Y-m-d H:i:s' 2) RFC 3339 2)
#[Type\Date] instance 1) 'Y-m-d' 2) 'Y-m-d' 2)
#[Type\Time] 'H:i:s' 3) 'H:i:s' 3) 'H:i:s' 3)
DateInterval instance 1) 'HH:MM:SS' 4) 'HH:MM:SS' 4)
Struct JSON string 5) JSON string 5) nested array
custom types by native type by native type by native type
mixed / untyped as-is as-is as-is

1) Instance pass-through — the database layer formats it itself.\ 2) Rendered in the application time zone.\ 3) Wall clock of the value, no zone conversion; fractional seconds appended when non-zero. A plain time string is used even with nette/database — Nette would write an instance as a full 'Y-m-d H:i:s'.\ 4) Full TIME domain kept: sign, hours over 24, fractional seconds.\ 5) The struct's own toJson() rendering; an empty struct is stored as NULL.

The #[Fraction] and #[DateFormat] attributes override these default renderings — see Attributes.

Hydration inputs by format

What fromData() accepts for each property type:

Property type NetteDatabase Mysql Json
int, float, string scalar (cast) scalar (cast) scalar (cast)
bool bool, 0/1, '0'/'1' bool, 0/1, '0'/'1' bool only
BackedEnum backing value 6) backing value 6) backing value 6)
DateTimeImmutable instance, string 7) instance, string 7) instance, string 7)
#[Type\Date] instance, string 7) instance, string 7) instance, string 7)
#[Type\Time] instance, 'HH:MM:SS', DateInterval 8) instance, 'HH:MM:SS' 8) instance, 'HH:MM:SS' 8)
DateInterval instance, 'HH:MM:SS' 9) instance, 'HH:MM:SS' 9) instance, 'HH:MM:SS' 9)
Struct JSON string, NULL 10) JSON string, NULL 10) array, null 10)
custom types by native type by native type by native type
mixed / untyped anything, as-is anything, as-is anything, as-is

6) int or string, cast to the enum backing type, mapped via ::from().\ 7) Any DateTimeInterface instance is converted into the application time zone; a naive string is interpreted in it, a string carrying its own offset is recalculated into it.\ 8) Day range enforced (00:00:00 <= x < 24:00:00); a DateInterval beyond the day scope (Nette delivers those for MySQL TIME) is rejected.\ 9) Full TIME domain: sign, hours over 24, fractional seconds.\ 10) Parsed by the struct itself (fromJson/fromArray); NULL hydrates into an empty struct instance — see Structs.

Custom format = subclass:

Thanks to instanceof scope matching a subclass automatically inherits attribute scopes targeting its parents.

Attributes

Attributes are opt-in escape hatches for edge cases — the default mapping is fully conventional.

#[Name] overrides the field name, optionally scoped to formats (a concrete class, ancestor, or a family interface like Format\DatabaseFormat). Attributes are evaluated top-down, first match wins — declare more specific scopes first; an unscoped attribute is a catch-all and must come last:

#[Type\Date] refines a DateTimeImmutable property to a date-only value (see above).

#[Type\Time] refines a DateTimeImmutable property to a time-of-day value: the date is pinned to 0001-01-01 — a date that predates DST rules, so every wall time on it exists exactly once — and string formats represent it as 'HH:MM:SS'.

A TIME column can therefore be mapped in two ways; pick by the domain of the column:

[!NOTE] With nette/database on MySQL, TIME columns arrive as DateInterval instances; the NetteDatabase format converts them for #[Type\Time] properties within the day range and rejects values beyond it — such columns belong to a DateInterval property.

#[Fraction] controls fractional seconds on export — the analogy of DATETIME(n)/TIME(n) column precision — for date-time, time and interval properties. Without it the format defaults apply (date-times render without a fraction, times and intervals append one when non-zero); with it the rendering is strict: exactly digits places (zero-padded, truncated), digits: 0 never renders one, omitZero: true drops a zero-valued part:

#[DateFormat] sets a custom output pattern (a native PHP date format string) for date-time, date and time properties. The same pattern drives both directions — export via format(), import via DateTimeImmutable::createFromFormat(), strictly, with no fallback to constructor parsing — so a pattern capturing the full value roundtrips losslessly, and a lossy pattern (e.g. without seconds) zeroes the uncaptured parts deterministically instead of crashing:

#[DateFormat] is deliberately not available for DateInterval: DateInterval::format() has no parsing counterpart in PHP, so the bidirectional promise could not be kept — exotic interval renderings belong to a custom format subclass.

Both attributes are scoped like #[Name] and mutually exclusive per (property, format) — one property may combine a strict fraction for databases with a pattern for JSON:

[!NOTE] With #[Fraction] or #[DateFormat] the NetteDatabase format exports a finished string instead of the instance — Nette's own 'Y-m-d H:i:s' formatting would drop the fraction, which is the very motivation: DATETIME(6) columns keep their microseconds.

Structs

A struct is an autonomous structure stored in a single JSON column: for the database the column stays an ordinary string, but the entity works with it as a typed object. The hydrator never looks inside — it hands the serialized value to the struct and asks for it back; parsing, rendering and emptiness are fully the struct's domain. The Struct interface carries two representations: fromJson/toJson (databases) and fromArray/toArray (plain data, used by the Json format).

Rules of the mechanism:

Bundled implementations: BaseStruct (declared fields; unknown keys are dropped and nulls filtered — documented lossy traits), DynamicObject (lossless free-form, an stdClass analogy), JsonObject (mutable whole-payload array, see below), RawJsonObject (verbatim read-only document, see below) and the list-shaped showcases TagList and NoteList (add…/remove…, iteration, toText()).

Whole-payload structs: JsonObject

When the application just needs the whole decoded document as one editable array — a config blob, a preferences column — JsonObject holds it in a single public $value: decode on load, encode on store, nothing else. No declared fields (that is BaseStruct), no keys-as-properties (DynamicObject), no byte-level fidelity (RawJsonObject/RawJsonValue — this class re-encodes by design).

Verbatim documents: RawJsonObject

When a column carries a foreign JSON document — a webhook payload, an API response — the document must be stored as-is while the application reads only the fields it cares about. BaseStruct drops unknown keys, and even the key-lossless DynamicObject re-encodes on the way out, which changes number representation (1e5 → 100000.0, integers beyond 253 lose precision), escaping, and duplicate keys. RawJsonObject keeps the JSON string itself as the single source of truth: toJson() returns it byte-exact, never re-encoded; the decoded document is only a lazy read-only cache built on first read.

Subclasses map the fields of interest through a protected read API and expose their own typed getters:

Custom types

A custom type maps a domain value object to a single column through an intermediate native type: the value first passes the format codec of that native type — with all its strictness — and only then reaches the custom conversion. Custom types are therefore format-blind: the same type renders as 'Y-m-d H:i:s' in Mysql, RFC 3339 in Json and an instance pass-through with nette/database, and how a bool or a date-time is represented never becomes the custom code's business.

Own types implement one typed sub-interface of the CustomValue marker — the interface choice declares the native type: StringValue, IntValue, FloatValue, BoolValue, DateTimeValue, IntervalValue.

Foreign classes — types that exist independently and cannot implement the interface (ramsey/uuid and friends) — get a registered TypeAdapter:

Rules of the mechanism:

A native JSON representation: JsonValue

Sometimes the intermediate native type is right for a database but wrong for a JSON payload — a value stored as a compact string in a column should appear as a nested object in an API document, not as that string. A custom value may therefore additionally implement JsonValue: with formats of the JSON family (the JsonFormat marker, implemented by Json) the value then travels through fromJsonValue()/toJsonValue() as a decoded JSON value — string, number, bool or a nested array — and the intermediate native type is bypassed. Everywhere else the CustomValue pair keeps working; custom code stays format-blind, the interface addresses a representation family, never a concrete format (the same way a Struct offers its array pair).

Whole-document values: RawJsonValue

Where RawJsonObject maps the fields of interest inside a foreign JSON object, RawJsonValue treats a whole document of any root type — object, array, string, number, bool or null — as one opaque value: it only decodes and encodes, nothing more. It is a custom value (StringValue + JsonValue), not a Struct, and the string is the single source of truth: toNative() returns it byte-exact, never re-encoded, so number representation, escaping, key order and duplicate keys survive a database roundtrip untouched.

Tests

Integration tests against a real MariaDB server (common column types over PDO, mysqli and nette/database in every convertBoolean/newDateTime configuration) run when DATABASE_DSN (plus optional DATABASE_USER/DATABASE_PASSWORD) points to a server, and skip otherwise.

License

MIT. See the LICENSE file.


All versions of hydrator with dependencies

PHP Build Version
Package Version
Requires php Version ~8.4
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package jakubboucek/hydrator contains the following files

Loading the files please wait ...