{"id":15029382,"url":"https://github.com/igniphp/storage","last_synced_at":"2025-08-11T14:47:38.666Z","repository":{"id":56989380,"uuid":"123737479","full_name":"igniphp/storage","owner":"igniphp","description":"Minimalistic entity framework with multi database support.","archived":false,"fork":false,"pushed_at":"2018-09-27T06:37:18.000Z","size":834,"stargazers_count":12,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-11T19:12:55.474Z","etag":null,"topics":["database","entity","mongodb","mysql","odm","orm","pdo","php7","storage"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igniphp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-03-03T22:39:56.000Z","updated_at":"2024-12-25T03:32:15.000Z","dependencies_parsed_at":"2022-08-21T12:50:47.390Z","dependency_job_id":null,"html_url":"https://github.com/igniphp/storage","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/igniphp/storage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igniphp%2Fstorage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igniphp%2Fstorage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igniphp%2Fstorage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igniphp%2Fstorage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igniphp","download_url":"https://codeload.github.com/igniphp/storage/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igniphp%2Fstorage/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269906270,"owners_count":24494327,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","entity","mongodb","mysql","odm","orm","pdo","php7","storage"],"created_at":"2024-09-24T20:10:28.472Z","updated_at":"2025-08-11T14:47:38.631Z","avatar_url":"https://github.com/igniphp.png","language":"PHP","readme":"# ![Igni logo](https://github.com/igniphp/common/blob/master/logo/full.svg)\n[![Build Status](https://travis-ci.org/igniphp/storage.svg?branch=master)](https://travis-ci.org/igniphp/storage)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/igniphp/storage/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/igniphp/storage/?branch=master)\n[![Code Coverage](https://scrutinizer-ci.com/g/igniphp/storage/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/igniphp/storage/?branch=master)\n\n## Igni Storage\n\nIgni storage is minimalistic mapping/hydration framework with support for PDO and MongoDB databases with cross database access.\n# Introduction\n\n```php\n\u003c?php declare(strict_types=1);\n\nuse Igni\\Storage\\Driver\\Pdo\\Connection;\nuse Igni\\Storage\\Storage;\nuse Igni\\Storage\\Driver\\ConnectionManager;\n\n// Define connection:\nConnectionManager::registerDefault(new Connection('sqlite:/' . __DIR__ . '/db.db'));\n\n// Initialize storage:\n$storage = new Storage();\n$artist = $storage-\u003eget(Artist::class, 1);\n\n// Update artist's name\n$artist-\u003ename = 'John Lennon';\n\n// Save changes in memory\n$storage-\u003epersist($artist);\n\n// Commit changes to database\n$storage-\u003ecommit();\n```\n\n## Table of contents\n- [Introduction](#introduction)\n  * [Features](#features)\n  * [Requirements](#requirements)\n  * [Installation](#installation)\n  * [Basic Concepts](#basic-concepts)\n- [Connecting](#connecting)\n  + [Mysql/PgSQL](#mysql-pgsql)\n  + [Sqlite](#sqlite)\n  + [MongoDB](#mongodb)\n  + [Connection Manager](#connection-manager)\n- [Mapping](#mapping)\n  * [Repositories](#repositories-1)\n      - [Defining Repository](#defining-repository)\n      - [Registering Repository](#registering-repository)\n  * [Entity](#entity)\n      - [Defining Entities](#defining-entities)\n      - [Types](#types)\n        - [Date](#date)\n        - [Decimal](#decimal)\n        - [Embed](#embed)\n        - [Enum](#enum)\n        - [Float](#float)\n        - [Id](#id)\n        - [Integer](#integer)\n        - [Text](#text)\n        - [Reference](#reference)\n  * [Working with custom hydrators](#working-with-custom-hydrators)\n  * [Working with custom types](#working-with-custom-types)\n  * [Working with Collections](#working-with-collections)\n  * [Working with Lazy Collections](#working-with-lazy-collections)\n\n\n## Features\n###### Works with native queries\nJust pass your query to the driver, you are no longer limited to custom query builders api, or complex setup and hacks\nto force library to work with your input.\n\n###### Small learning curve\nThere is one page documentation which you can grasp in an hour and many examples that are working straight away\nwithout complex configuration.\n\n###### Support for multiple types of databases \nMongo, pgsql, mysql, sqlite - you can use all of them together. If this is not sufficient you can write custom driver to support database of your choice. \n\n###### Embed entities\nAllows you to store complex data in your database \n\n###### Cross database references\nIt does not matter if you use mongo with sqlite or mysql or any other database, you can keep references to entities stored\nin different types of databases with ease. \n\n###### Support for declarative programming\nCollection and LazyCollection classes provides interface that supports declarative programming.\n\n\n## Requirements\n\n - \u003e= PHP 7.1\n - PDO for mysql, sqlite and/or pgsql support\n - MongoDB extension for mongo support\n \n \n## Installation\n\n```\ncomposer install igniphp/storage\n```\n\n## Basic Concepts\n\nIgni strongly bases on repository and unit of work patterns. This two patterns are intended to create an abstraction \nlayer between the data access layer and the business logic layer of your application. \n\nThe facilitation that is created by UoW makes track of changes and automated unit testing to be achieved in much simpler manner.\n\n#### Unit of Work\nShortly saying UoW maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems ([source](https://martinfowler.com/eaaCatalog/unitOfWork.html))._\n\n[Entity Storage](src/Storage.php) is responsible for providing UoW implementation.\n\n#### Repositories\nRepository is a central place where data is stored and maintained. Igni provides basic implementation per each of the supported drivers:\n\n - [Pdo Repository](src/Driver/Pdo/Repository.php) \n - [Mongo Repository](src/Driver/MongoDB/Repository.php)\n\n# Connecting\n\n## Mysql/PgSQL\n\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\Pdo\\Connection;\nuse Igni\\Storage\\Driver\\Pdo\\ConnectionOptions;\n\nnew Connection('mysql:host=localhost', new ConnectionOptions(\n    $database = 'test',\n    $username = 'root',\n    $password = 'password'\n));\n```\n\n## Sqlite\n\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\Pdo\\Connection;\n\n$connection = new Connection('sqlite:/path/to/database');\n```\n\n## MongoDB\n\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\MongoDB\\Connection;\nuse Igni\\Storage\\Driver\\MongoDb\\ConnectionOptions;\n\n$connection = new Connection('localhost', new ConnectionOptions(\n    $database = 'test',\n    $username = 'test',\n    $password = 'password'\n));\n```\n\n## Connection manager\nConnection manager is static class used for registering and obtaining active connections by repositories.\nIf you would like your repositories to automatically retrieve a connection you have to register one in the\nconnection manager.\n\n### Registering connection\n```php\n\u003c?php \nuse Igni\\Storage\\Driver\\ConnectionManager;\n\nConnectionManager::register($name = 'my_connection', $connection);\n```\n\n### Checking if connection exists\n```php\n\u003c?php \nuse Igni\\Storage\\Driver\\ConnectionManager;\n\nConnectionManager::has($name = 'my_connection');// return true\n```\n\n### Retrieving connection\n```php\n\u003c?php \nuse Igni\\Storage\\Driver\\ConnectionManager;\n\nConnectionManager::get($name = 'my_connection');// return true\n```\n\n### Releasing connections\n```php\n\u003c?php \nuse Igni\\Storage\\Driver\\ConnectionManager;\n\n// Releases and closes all registered connections\nConnectionManager::release();\n```\n\n# Mapping\n\nMapping tells how data that is stored in database should be reflected in your code.\n\nLibrary provides following tools to map data:\n- Repositories (manages entities and provides access to your entities)\n- Cursors (used to process and execute queries in the database)\n- Collections (abstraction layer around cursors)\n- Hydrators (used to map data from and to database)\n- Entities (unit of data, can be single person, place or thing)\n\n## Repositories\nRepository is a central place where data is stored and maintained. Repository must implement [Repository interface](src/Repository.php) or\nextend one of the provided repository classes, depending which database you are using:\n\n - [Pdo Repository](src/Driver/Pdo/Repository.php) \n - [Mongo Repository](src/Driver/MongoDB/Repository.php)\n\nRepositories have to be defined and registered in order to be recognized by unit of work. \n\n#### Defining Repository\n```php\n\u003c?php declare(strict_types=1);\n\nuse Igni\\Storage\\Driver\\Pdo;\nuse Igni\\Storage\\Driver\\MongoDB;\n\n// Use pdo repository\nclass TrackRepository extends Pdo\\Repository\n{\n    public static function getEntityClass(): string \n    {\n        return Track::class;\n    }\n}\n\n// Use mongodb repository\nclass PlaylistRepository extends MongoDB\\Repository\n{\n    public static function getEntityClass(): string \n    {\n        return Playlist::class;\n    }\n}\n```\n\n#### Registering Repository\n```php\n\u003c?php declare(strict_types=1);\nuse Igni\\Storage\\Storage;\n\n// Initialize storage:\n$storage = new Storage();\n\n// Add repository\n$storage-\u003eaddRepository(new TrackRepository($storage-\u003egetEntityManager()));\n```\n\n## Entity\nAn entity is an object that exists. It can perform various actions and has its own identity. \nAn entity can be a single thing, person, place, or object. Entity defines attributes, which keeps information about\nwhat entity needs in order to live. \n\n#### Defining Entities\nEntity must implement `\\Igni\\Storage\\Storable` interface in order to be stored, updated or deleted. \nThe interface requires you to define `getId` method.\n\nThe simplest entity may look like this:\n\n```php\n\u003c?php\nuse Igni\\Storage\\Storable;\nuse Igni\\Storage\\Id;\nuse Igni\\Storage\\Id\\Uuid;\n\nclass SongEntity implements Storable\n{\n    private $id;\n    \n    public function __construct()\n    {\n        $this-\u003eid = new Uuid();\n    }\n    \n    public function getId(): Id\n    {\n        return $this-\u003eid;\n    }\n}\n```\n\nThis entity cannot be stored yet. What is missing here is:\n- the information where entity should be persisted\n- which property keeps entity's identity\n\nThis and other meta information can be injected to the entity with annotations. Annotation is a note by way of explanation/comment\nadded to a code. In php world it is kept in doc block comment prefixed by `@`.\n\nFollowing example stores song in table/collection named `songs` with identity set on `id` property.\n\n```php\n\u003c?php\nuse Igni\\Storage\\Storable;\nuse Igni\\Storage\\Id;\nuse Igni\\Storage\\Id\\Uuid;\nuse Igni\\Storage\\Mapping\\Annotation as Storage;\n\n/**\n * @Storage\\Entity(source=\"albums\", connection=\"default\")\n */\nclass SongEntity implements Storable\n{\n    /**\n     * @var Uuid\n     * @Storage\\Types\\Id()\n     */\n    private $id;\n    \n    public function __construct()\n    {\n        $this-\u003eid = new Uuid();\n    }\n    \n    public function getId(): Id\n    {\n        return $this-\u003eid;\n    }\n}\n```\nThe above entity can be stored, retrieved and deleted but it contains no viable data like: title, artist, album, etc.\nAltering more data in the entity can be achieved by creating more properties and annotating them with desired type\nannotation. \n\n##### Entity Annotation\nUsed to register an entity within storage framework\n\n##### _Accepted attributes:_\n\n`source` _(required)_ generally speaking this is name of place where entity is kept in your database (collection, table, etc.)\n\n`hydrator` class name of custom hydrator that should be used during retrieve and persist process\n\n`connection` specify the connection name that should be used by entity's repository \n\n#### Types\nTypes are used to tell library how properties should be treated when data is retrieved and/or stored. \nIgni contains 9 built-in types that you can use straight away and can be found in `Igni\\Storage\\Mapping\\Strategy` namespace.\nEach of the built-in type also have corresponding annotation that can be found in `Igni\\Storage\\Mapping\\Annotations\\Types` namespace.\n\n#### Date\nUsed to map datetime and date data types.\n\n##### _Accepted attributes:_\n\n`name` keeps equivalent key name stored in database\n\n`format` string representation of a [valid format](http://php.net/manual/pl/function.date.php) that is being used to store the value\n\n`timezone` string representation of any [valid timezone](http://php.net/manual/pl/timezones.php) that is being used to store the value\n\n`immutable` tells whether the value should be instantiated as `\\DateTimeImmutable` or `\\DateTime`\n\n`readonly` property marked as readonly is ignored during persistence operations\n\n```php\n\u003c?php declare(strict_types=1);\n\nclass Example implements Igni\\Storage\\Storable\n{\n    /**\n     * @Igni\\Storage\\Mapping\\Annotation\\Property\\Date(format=\"Ymd\", immutable=true, timezone=\"UTC\")\n     */\n    private $value;\n    \n    public function getId(): Igni\\Storage\\Id \n    {\n        //...\n    }\n}\n```\n\n#### Decimal\nDecimals are safe way to deal with fragile numerical data like money. [bcmath](http://php.net/manual/pl/book.bc.php) \nextension is required in order to use decimal values.\n\n##### _Accepted attributes:_\n\n`name` keeps equivalent key name stored in database\n\n`scale` is the number of digits to the right of the decimal point in a number\n\n`precision` is the number of digits in a number\n\n`readonly` property marked as readonly is ignored during persistence operations\n\n```php\n\u003c?php declare(strict_types=1);\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"examples\") */\nclass Example implements Igni\\Storage\\Storable\n{\n    /**\n     * For example we can store the number 12.45 that has a precision of 4 and a scale of 2.\n     * @Igni\\Storage\\Mapping\\Annotation\\Property\\DecimalNumber(scale=2, precision=4)\n     */\n    private $value;\n    \n    public function getId(): Igni\\Storage\\Id \n    {\n        //...\n    }\n}\n```\n\n#### Embed\nEmbed is an object that is not entity itself but it is composed into the entity. Embeds can be stored in the database as\njson or serialized php array.\n\n##### _Accepted attributes:_\n\n`class` _(required)_ contains information about the type of embed object\n\n`name` keeps equivalent key name stored in database\n\n`storeAs` keeps information how data should be stored in the column/property. Can be one of the following values: \n- _plain_\n- _json_\n- _serialized_\n\n`readonly` property marked as readonly is ignored during persistence operations\n\n```php\n\u003c?php declare(strict_types=1);\n\n/** @Igni\\Storage\\Mapping\\Annotation\\EmbeddedEntity() */\nclass Address\n{\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Text() */\n    private $street;\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Text() */\n    private $postalCode;\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Text() */\n    private $city;\n}\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"users\") */\nclass User implements Igni\\Storage\\Storable\n{\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Embed(Address::class, storeAs=\"json\") */\n    private $address;\n    \n    public function getId(): Igni\\Storage\\Id \n    {\n        //...\n    }\n}\n```\n\n    Note: Storing embeds as json in SQL databases can be really usefull, databases like MySQL or PgSQL have good support\n    for JSON datatypes.\n\n#### Enum\nEnums should be always used when variable can be one out of small set of possible values. It can be used to save storage\nspace, add additional checks in your code, etc. \n\n\n##### _Accepted attributes:_\n\n`values` _(required)_ can be either class that implements `Igni\\Storage\\Enum` interface or array of values\n\n`name` keeps equivalent key name stored in database\n  \n`readonly` property marked as readonly is ignored during persistence operations\n  \n```php\n\u003c?php declare(strict_types=1);\n\nclass AudioType implements \\Igni\\Storage\\Enum\n{\n    const MPEG = 0;\n    const AAC = 1;\n    const MPEG_4 = 2;\n    \n    private $value;\n    \n    public function __construct($value)\n    {\n        $this-\u003evalue = (int) $value;    \n        if (!in_array($this-\u003evalue, [0, 1, 2])) {\n            throw new \\InvalidArgumentException('Invalid audio type');\n        }\n    }\n    \n    public function getValue(): int\n    {\n        return $this-\u003evalue;\n    }\n}\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"tracks\") */\nclass Track implements Igni\\Storage\\Storable\n{\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Enum(AudioType::class) */\n    private $audioTypeEnumClass; // This will be instance of AudioType class    \n    \n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Enum({\"MPEG\", \"AAC\", \"MPEG-4\"}) */\n    private $audioTypeList; // This can be one of the following strings: \"MPEG\", \"AAC\", \"MPEG-4\", but persisted as integer.\n    \n    public function getId(): Igni\\Storage\\Id \n    {\n        //...\n    }\n}\n```\n\n#### Float\nMaps float numbers.\n\n##### _Accepted attributes:_\n\n`name` keeps equivalent key name stored in database\n\n`readonly` property marked as readonly is ignored during persistence operations\n  \n```php\n\u003c?php declare(strict_types=1);\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"tracks\") */\nclass Track implements Igni\\Storage\\Storable\n{\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Float(name=\"audio_length\") */\n    private $length;  \n\n    public function getId(): Igni\\Storage\\Id \n    {\n        //...\n    }\n}\n```\n\n#### Id\nId is value object that is used to update, remove and retrieve documents by default repository classes.\nOnce id is set on an object it should not be changed during the runtime. \n\nIf no `class` attribute is specified id by default becomes instance of `Igni\\Storage\\Id\\GenericId`. \nYou can map id to your custom class that implements `Igni\\Storage\\Id` interface.\n\nIgni provides two default implementations for id value object:\n\n- `Igni\\Storage\\Id\\GenericId` \n- `Igni\\Storage\\Id\\Uuid`\n\n`Igni\\Storage\\Id\\GenericId` can be any value, it accepts everything by default and it is not recommended to use it unless\nyou have no other option.\n\n`Igni\\Storage\\Id\\Uuid` any value passed to the constructor of this class must be valid uuid number. Uuid is kept as 21-22\nlong varchar value to save the storage space.\n\n##### _Accepted attributes:_\n\n`name` keeps equivalent key name stored in database\n\n`class` once this is set id becomes instance of the given class\n  \n```php\n\u003c?php declare(strict_types=1);\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"tracks\") */\nclass Track implements Igni\\Storage\\Storable\n{\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Id(class=Igni\\Storage|Id\\Uuid::class) */\n    private $id;  \n\n    public function getId(): Igni\\Storage\\Id \n    {\n        return $this-\u003eid;\n    }\n}\n```\n\n##### Autogenerated ids\nThe following example shows how to auto-generate ids for your entity.\n\n```php\n\u003c?php declare(strict_types=1);\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"tracks\") */\nclass Track implements Igni\\Storage\\Storable\n{\n    use Igni\\Storage\\Id\\AutoGenerateId;\n}\n```\n\n#### Integer\nMaps integer numbers.\n\n##### _Accepted attributes:_\n\n`name` keeps equivalent key name stored in database\n\n`readonly` property marked as readonly is ignored during persistence operations\n  \n```php\n\u003c?php declare(strict_types=1);\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"tracks\") */\nclass Track implements Igni\\Storage\\Storable\n{\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\IntegerNumber() */\n    private $length;  \n\n    public function getId(): Igni\\Storage\\Id \n    {\n        //...\n    }\n}\n```\n\n#### Text\n\n##### _Accepted attributes:_\n\n`name` keeps equivalent key name stored in database\n\n`readonly` property marked as readonly is ignored during persistence operations\n  \n```php\n\u003c?php declare(strict_types=1);\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"tracks\") */\nclass Track implements Igni\\Storage\\Storable\n{\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Text() */\n    private $lyrics;  \n\n    public function getId(): Igni\\Storage\\Id \n    {\n        //...\n    }\n}\n```\n\n#### Reference\nReferences are properties that stores ids to other entities in your data layer. Storage framework resolves them\nautomatically on hydration phase.\n\n##### _Accepted attributes:_\n\n`target` _(required)_ FQCN of the entity that is stored as reference in the property \n\n`name` keeps equivalent key name stored in database\n\n`readonly` property marked as readonly is ignored during persistence operations\n \n```php\n\u003c?php declare(strict_types=1);\n\n/** @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"tracks\") */\nclass Track implements Igni\\Storage\\Storable\n{\n    /** @var Igni\\Storage\\Mapping\\Annotation\\Property\\Reference(target=Album::class) */\n    private $album;  \n\n    public function getId(): Igni\\Storage\\Id \n    {\n        //...\n    }\n    \n    public function getAlbum(): Album\n    {\n        return $this-\u003ealbum;\n    }\n}\n```\n\nIf entity has to store collection of references it is recommended to create custom hydrator.\n\n\n## Working with custom hydrators \n\nAuto-resolving complex schema is memory and cpu consuming and in most cases not sufficient enough. \nAt the time like this it is good to have set of tools that will support you in building application layer\nwhere you have total control what is happening on your database layer.\nStorage framework was build to provide this kind set of tools, one of them is possibility to define and use \ncustom hydrators to help you out with reflecting database schema in your application code.\n\nCustom hydrator is a decorator for hydrator generated by [`Igni\\Storage\\Hydration\\HydratorFactory`](src/Hydration/HydratorFactory.php) \nand must implement [`\\Igni\\Storage\\Hydration\\ObjectHydrator`](src/Hydration/ObjectHydrator.php) interface.\n\nThe following code is the simplest implementation of custom hydrator:\n\n```php\n\u003c?php\nclass CustomTrackHydrator implements Igni\\Storage\\Hydration\\ObjectHydrator\n{\n    private $baseHydrator;\n    \n    public function __construct(Igni\\Storage\\Hydration\\GenericHydrator $baseHydrator) \n    {\n        $this-\u003ebaseHydrator = $baseHydrator;    \n    }\n    \n    public function hydrate(array $data) \n    {\n        $entity = $this-\u003ebaseHydrator-\u003ehydrate($data);\n        // Modify entity to your needs\n        return $entity;\n    }\n    public function extract($entity): array \n    {\n        $extracted = $this-\u003ebaseHydrator-\u003eextract($entity);\n        // Modify the data before storing it in database.\n        return $extracted;\n    }\n}\n```\n\nStorage framework can recognize custom-defined hydrator once it is set in the [`@Entity`](src/Mapping/Annotation/Entity.php) annotation.\n\nWith custom hydrators you can define your own many-to-many and one-to-many relation handling and more.  \n\n```php\n\u003c?php\n\n/**\n * @Igni\\Storage\\Mapping\\Annotation\\Entity(source=\"tracks\", hydrator=CustomTrackHydrator::class)\n */\nclass TrackEntity implements Igni\\Storage\\Storable\n{\n    public function getId(): Igni\\Storage\\Id \n    {\n        // ...\n    }\n}\n```\n\nFor full example please visit [examples directory](examples).\n\n## Working with custom types\n\nStorage provides useful set of daily-basis types like: int, decimal float or reference. If you find you lack some\ntype that will fulfill your needs there is easy way to define your own custom data-type.\n\nThere are two steps required to create custom data-type:\n- Create class responsible for data mapping (from and to database)\n- Registering defined type\n\nMapping strategy class must implement [`Igni\\Storage\\Mapping\\MappingStrategy`](src/Mapping/MappingStrategy.php) interface:\n\n```php\n\u003c?php\nfinal class MyType implements Igni\\Storage\\Mapping\\MappingStrategy\n{\n    public static function hydrate(\u0026$value) \n    {\n        // Here format data that will be used in the code-land\n    }\n    \n    public static function extract(\u0026$value) \n    {\n        // Here format data that will be persisted to database\n    }\n}\n\n```\n\nFor full example please visit [examples directory](examples).\n\n## Working with Collections\n\nCollection is a container that groups multiple elements into a single unit. Storage framework contains collection implementation\nthat allows to easier represent or manipulate data that are provided by cursor or any other Iterable instance.\n \nStorage framework's collection supports declarative programming, it has support for map/reduce operations.\n \n### Instantiating new collection\nCollection can be instantiated with any iterable including cursor.\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n// From iterable\n$connection = ConnectionManager::getDefault();\n$collection = new Collection($connection-\u003eexecute('SELECT *FROM artists'));\n\n// From list of items\n$numbers = Collection::fromList(1, 2, 3);\n```\n\n### Adding new item to a collection\n```php\n\u003c?php\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$collection = new Collection();\n$withNewItem = $collection-\u003eadd(1);\n$withMultipleItems = $collection-\u003eaddMany(1, 2, 3);\n```\n\n### Removing item from collection\n```php\n\u003c?php\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$collection = new Collection([1, 2, 3, 4]);\n\n$collectionWithoutItem = $collection-\u003eremove(2);\n\n$collectionWithoutManyItems = $collection-\u003eremoveMany(1, 4);\n```\n\n### Iterating through collection\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$connection = ConnectionManager::getDefault();\n$collection = new Collection($connection-\u003eexecute('SELECT *FROM artists'));\n\n// Imperative approach.\n$mappedData = new Collection();\nforeach ($collection as $item) {\n    $item['age'] = 20;\n    $mappedData = $mappedData-\u003eadd($item);\n}\n\n// Declarative approach\n$mappedData = $collection-\u003emap(function ($item) {\n    $item['age'] = 20;\n    return $item;\n});\n```\n\n### Sorting/Reversing items in collection\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$connection = ConnectionManager::getDefault();\n$collection = new Collection($connection-\u003eexecute('SELECT *FROM artists'));\n\n// Sort by age\n$sorted = $collection-\u003esort(function(array $current, array $next) {\n    return $current['age'] \u003c=\u003e $next['age'];\n});\n\n// Reverse\n$reversed = $sorted-\u003ereverse(); \n```\n\n### Checking if collection contains an element\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$connection = ConnectionManager::getDefault();\n$collection = new Collection($connection-\u003eexecute('SELECT name, age FROM artists'));\n\n\nif ($collection-\u003econtains(['name' =\u003e 'Bob', 'age' =\u003e 20])) {\n    // There is Bob in the collection\n}\n```\n\n### Searching items in collection\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$connection = ConnectionManager::getDefault();\n$collection = new Collection($connection-\u003eexecute('SELECT *FROM artists'));\n\n// Age greater than 50\n$elders = $collection-\u003ewhere(function(array $artist) {\n    return $artist['age'] \u003e 50;\n});\n```\n\n### Checking if any item in a collection fulfils given requirement \n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$connection = ConnectionManager::getDefault();\n$collection = new Collection($connection-\u003eexecute('SELECT *FROM artists'));\n\nif ($collection-\u003eany(function($artist) { return $artist['age'] \u003e 70; })) {\n    // There is at least one artist who is over 70 yo\n}\n```\n\n### Checking if every item in a collection fulfils given requirement \n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$connection = ConnectionManager::getDefault();\n$collection = new Collection($connection-\u003eexecute('SELECT *FROM artists'));\n\nif ($collection-\u003eevery(function($artist) { return $artist['age'] \u003e 2; })) {\n    // All artists are above 2 yo\n}\n```\n\n\n### Reducing collection to a single value\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\Collection;\n\n$connection = ConnectionManager::getDefault();\n$collection = new Collection($connection-\u003eexecute('SELECT *FROM artists'));\n\n$totalAge = $collection-\u003ereduce(\n    function(int $total, array $artist) {\n        return $total + $artist['age'];\n    }, \n    $initialValue = 0\n);\n```\n\n## Working with Lazy collections\n\nLazy collection are immutable lazy bastards, they do nothing all the day but iterate through cursor\nin the way where item is not fetched from database until it is really needed (such lazy, wow!).\n(If you reached this point of documentation take my congratulations :D)\n\nLazy collection was specially made to work with cursors so it accepts only cursors:\n```php\n\u003c?php\nuse Igni\\Storage\\Driver\\ConnectionManager;\nuse Igni\\Storage\\Mapping\\Collection\\LazyCollection;\n\n$connection = ConnectionManager::getDefault();\n\n$lazyBastard = new LazyCollection($connection-\u003eexecute('SELECT *FROM artists'));\n\n// Iterating\nforeach ($lazyBastard as $item) {\n    // Do something here\n}\n\n// You have changed your mind and get fed with laziness- no probs:\n$nonLazy = $lazyBastard-\u003etoCollection();\n```\n\n##### That's all folks!\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figniphp%2Fstorage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figniphp%2Fstorage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figniphp%2Fstorage/lists"}