An open API service indexing awesome lists of open source software.

https://github.com/igniphp/storage

Minimalistic entity framework with multi database support.
https://github.com/igniphp/storage

database entity mongodb mysql odm orm pdo php7 storage

Last synced: about 2 months ago
JSON representation

Minimalistic entity framework with multi database support.

Awesome Lists containing this project

README

        

# ![Igni logo](https://github.com/igniphp/common/blob/master/logo/full.svg)
[![Build Status](https://travis-ci.org/igniphp/storage.svg?branch=master)](https://travis-ci.org/igniphp/storage)
[![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)
[![Code Coverage](https://scrutinizer-ci.com/g/igniphp/storage/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/igniphp/storage/?branch=master)

## Igni Storage

Igni storage is minimalistic mapping/hydration framework with support for PDO and MongoDB databases with cross database access.
# Introduction

```php
get(Artist::class, 1);

// Update artist's name
$artist->name = 'John Lennon';

// Save changes in memory
$storage->persist($artist);

// Commit changes to database
$storage->commit();
```

## Table of contents
- [Introduction](#introduction)
* [Features](#features)
* [Requirements](#requirements)
* [Installation](#installation)
* [Basic Concepts](#basic-concepts)
- [Connecting](#connecting)
+ [Mysql/PgSQL](#mysql-pgsql)
+ [Sqlite](#sqlite)
+ [MongoDB](#mongodb)
+ [Connection Manager](#connection-manager)
- [Mapping](#mapping)
* [Repositories](#repositories-1)
- [Defining Repository](#defining-repository)
- [Registering Repository](#registering-repository)
* [Entity](#entity)
- [Defining Entities](#defining-entities)
- [Types](#types)
- [Date](#date)
- [Decimal](#decimal)
- [Embed](#embed)
- [Enum](#enum)
- [Float](#float)
- [Id](#id)
- [Integer](#integer)
- [Text](#text)
- [Reference](#reference)
* [Working with custom hydrators](#working-with-custom-hydrators)
* [Working with custom types](#working-with-custom-types)
* [Working with Collections](#working-with-collections)
* [Working with Lazy Collections](#working-with-lazy-collections)

## Features
###### Works with native queries
Just pass your query to the driver, you are no longer limited to custom query builders api, or complex setup and hacks
to force library to work with your input.

###### Small learning curve
There is one page documentation which you can grasp in an hour and many examples that are working straight away
without complex configuration.

###### Support for multiple types of databases
Mongo, 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.

###### Embed entities
Allows you to store complex data in your database

###### Cross database references
It does not matter if you use mongo with sqlite or mysql or any other database, you can keep references to entities stored
in different types of databases with ease.

###### Support for declarative programming
Collection and LazyCollection classes provides interface that supports declarative programming.

## Requirements

- >= PHP 7.1
- PDO for mysql, sqlite and/or pgsql support
- MongoDB extension for mongo support


## Installation

```
composer install igniphp/storage
```

## Basic Concepts

Igni strongly bases on repository and unit of work patterns. This two patterns are intended to create an abstraction
layer between the data access layer and the business logic layer of your application.

The facilitation that is created by UoW makes track of changes and automated unit testing to be achieved in much simpler manner.

#### Unit of Work
Shortly 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))._

[Entity Storage](src/Storage.php) is responsible for providing UoW implementation.

#### Repositories
Repository is a central place where data is stored and maintained. Igni provides basic implementation per each of the supported drivers:

- [Pdo Repository](src/Driver/Pdo/Repository.php)
- [Mongo Repository](src/Driver/MongoDB/Repository.php)

# Connecting

## Mysql/PgSQL

```php
addRepository(new TrackRepository($storage->getEntityManager()));
```

## Entity
An entity is an object that exists. It can perform various actions and has its own identity.
An entity can be a single thing, person, place, or object. Entity defines attributes, which keeps information about
what entity needs in order to live.

#### Defining Entities
Entity must implement `\Igni\Storage\Storable` interface in order to be stored, updated or deleted.
The interface requires you to define `getId` method.

The simplest entity may look like this:

```php
id = new Uuid();
}

public function getId(): Id
{
return $this->id;
}
}
```

This entity cannot be stored yet. What is missing here is:
- the information where entity should be persisted
- which property keeps entity's identity

This and other meta information can be injected to the entity with annotations. Annotation is a note by way of explanation/comment
added to a code. In php world it is kept in doc block comment prefixed by `@`.

Following example stores song in table/collection named `songs` with identity set on `id` property.

```php
id = new Uuid();
}

public function getId(): Id
{
return $this->id;
}
}
```
The above entity can be stored, retrieved and deleted but it contains no viable data like: title, artist, album, etc.
Altering more data in the entity can be achieved by creating more properties and annotating them with desired type
annotation.

##### Entity Annotation
Used to register an entity within storage framework

##### _Accepted attributes:_

`source` _(required)_ generally speaking this is name of place where entity is kept in your database (collection, table, etc.)

`hydrator` class name of custom hydrator that should be used during retrieve and persist process

`connection` specify the connection name that should be used by entity's repository

#### Types
Types are used to tell library how properties should be treated when data is retrieved and/or stored.
Igni contains 9 built-in types that you can use straight away and can be found in `Igni\Storage\Mapping\Strategy` namespace.
Each of the built-in type also have corresponding annotation that can be found in `Igni\Storage\Mapping\Annotations\Types` namespace.

#### Date
Used to map datetime and date data types.

##### _Accepted attributes:_

`name` keeps equivalent key name stored in database

`format` string representation of a [valid format](http://php.net/manual/pl/function.date.php) that is being used to store the value

`timezone` string representation of any [valid timezone](http://php.net/manual/pl/timezones.php) that is being used to store the value

`immutable` tells whether the value should be instantiated as `\DateTimeImmutable` or `\DateTime`

`readonly` property marked as readonly is ignored during persistence operations

```php
value = (int) $value;
if (!in_array($this->value, [0, 1, 2])) {
throw new \InvalidArgumentException('Invalid audio type');
}
}

public function getValue(): int
{
return $this->value;
}
}

/** @Igni\Storage\Mapping\Annotation\Entity(source="tracks") */
class Track implements Igni\Storage\Storable
{
/** @var Igni\Storage\Mapping\Annotation\Property\Enum(AudioType::class) */
private $audioTypeEnumClass; // This will be instance of AudioType class

/** @var Igni\Storage\Mapping\Annotation\Property\Enum({"MPEG", "AAC", "MPEG-4"}) */
private $audioTypeList; // This can be one of the following strings: "MPEG", "AAC", "MPEG-4", but persisted as integer.

public function getId(): Igni\Storage\Id
{
//...
}
}
```

#### Float
Maps float numbers.

##### _Accepted attributes:_

`name` keeps equivalent key name stored in database

`readonly` property marked as readonly is ignored during persistence operations

```php
id;
}
}
```

##### Autogenerated ids
The following example shows how to auto-generate ids for your entity.

```php
album;
}
}
```

If entity has to store collection of references it is recommended to create custom hydrator.

## Working with custom hydrators

Auto-resolving complex schema is memory and cpu consuming and in most cases not sufficient enough.
At the time like this it is good to have set of tools that will support you in building application layer
where you have total control what is happening on your database layer.
Storage framework was build to provide this kind set of tools, one of them is possibility to define and use
custom hydrators to help you out with reflecting database schema in your application code.

Custom hydrator is a decorator for hydrator generated by [`Igni\Storage\Hydration\HydratorFactory`](src/Hydration/HydratorFactory.php)
and must implement [`\Igni\Storage\Hydration\ObjectHydrator`](src/Hydration/ObjectHydrator.php) interface.

The following code is the simplest implementation of custom hydrator:

```php
baseHydrator = $baseHydrator;
}

public function hydrate(array $data)
{
$entity = $this->baseHydrator->hydrate($data);
// Modify entity to your needs
return $entity;
}
public function extract($entity): array
{
$extracted = $this->baseHydrator->extract($entity);
// Modify the data before storing it in database.
return $extracted;
}
}
```

Storage framework can recognize custom-defined hydrator once it is set in the [`@Entity`](src/Mapping/Annotation/Entity.php) annotation.

With custom hydrators you can define your own many-to-many and one-to-many relation handling and more.

```php
execute('SELECT *FROM artists'));

// From list of items
$numbers = Collection::fromList(1, 2, 3);
```

### Adding new item to a collection
```php
add(1);
$withMultipleItems = $collection->addMany(1, 2, 3);
```

### Removing item from collection
```php
remove(2);

$collectionWithoutManyItems = $collection->removeMany(1, 4);
```

### Iterating through collection
```php
execute('SELECT *FROM artists'));

// Imperative approach.
$mappedData = new Collection();
foreach ($collection as $item) {
$item['age'] = 20;
$mappedData = $mappedData->add($item);
}

// Declarative approach
$mappedData = $collection->map(function ($item) {
$item['age'] = 20;
return $item;
});
```

### Sorting/Reversing items in collection
```php
execute('SELECT *FROM artists'));

// Sort by age
$sorted = $collection->sort(function(array $current, array $next) {
return $current['age'] <=> $next['age'];
});

// Reverse
$reversed = $sorted->reverse();
```

### Checking if collection contains an element
```php
execute('SELECT name, age FROM artists'));

if ($collection->contains(['name' => 'Bob', 'age' => 20])) {
// There is Bob in the collection
}
```

### Searching items in collection
```php
execute('SELECT *FROM artists'));

// Age greater than 50
$elders = $collection->where(function(array $artist) {
return $artist['age'] > 50;
});
```

### Checking if any item in a collection fulfils given requirement
```php
execute('SELECT *FROM artists'));

if ($collection->any(function($artist) { return $artist['age'] > 70; })) {
// There is at least one artist who is over 70 yo
}
```

### Checking if every item in a collection fulfils given requirement
```php
execute('SELECT *FROM artists'));

if ($collection->every(function($artist) { return $artist['age'] > 2; })) {
// All artists are above 2 yo
}
```

### Reducing collection to a single value
```php
execute('SELECT *FROM artists'));

$totalAge = $collection->reduce(
function(int $total, array $artist) {
return $total + $artist['age'];
},
$initialValue = 0
);
```

## Working with Lazy collections

Lazy collection are immutable lazy bastards, they do nothing all the day but iterate through cursor
in the way where item is not fetched from database until it is really needed (such lazy, wow!).
(If you reached this point of documentation take my congratulations :D)

Lazy collection was specially made to work with cursors so it accepts only cursors:
```php
execute('SELECT *FROM artists'));

// Iterating
foreach ($lazyBastard as $item) {
// Do something here
}

// You have changed your mind and get fed with laziness- no probs:
$nonLazy = $lazyBastard->toCollection();
```

##### That's all folks!