Skip to content
This repository has been archived by the owner on Oct 28, 2022. It is now read-only.
/ persist-core Public archive

Service based DB abstraction layer for PHP

License

Notifications You must be signed in to change notification settings

jasny/persist-core

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jasny-banner

Persist - Database abstraction layer

Build Status Scrutinizer Code Quality Code Coverage Packagist Stable Version Packagist License

Installation

This library should not be installed directly. The Persist library contains design pattern definitions and implementations. It serves as an abstract base for concrete libraries implemented for specific PHP extensions.

Implementations

Usage

Fetch a list

use Persist\Option\Functions as opt;
use Persist\Mongo\Gateway\Gateway;

$collection = (new MongoDB\Client())->test->users;
$gateway = new Gateway($collection);

$list = $gateway
    ->fetch(
        [
            'invite.group' => 'the A-Team',
            'activation_date (min)' => new DateTime(),
            'role (any)' => ['user', 'admin', 'super']
        ],
        opt\fields('name', 'role', 'age'),
        opt\limit(10),
    )
    ->map(function(array $user): string {
        return sprintf("%s (%s) - %s", $user['name'], $user['age'], $user['role']);
    });

Read and write

use Persist\Mongo\Gateway\Gateway;

$collection = (new MongoDB\Client())->test->users;
$gateway = new Gateway($collection);

$user = $gateway->fetch(['id' => '12345'])->first();
$user->count = "bar";

$gateway->save($user);

Update multiple

use Persist\Update\Functions as update;
use Persist\Mongo\Gateway;

$collection = (new MongoDB\Client())->test->users;
$gateway = new Gateway($collection);

$gateway->update(
    ['type' => 'admin'],
    [
        update\inc('reward', 100),
        update\set('rewarded', new DateTime()),
    ],
);

Iterators and generators

Persist uses PHP iterators and generators. It's important to understand what they are and how they work. If you're not familiar with this concept, first read "What are iterators?".

Filters

The reader and writer methods accept a $filter argument. The filter is an associated array with field key and corresponding value.

A filter SHOULD always result in the same or a subset of the records you'd get when calling the method without a filter.

$zoo = $gateway->fetch(['reference' => 'zoo'])->first();
$count = $gateway->count(['bar' => 10]);
$gateway->update(['id' => 123], update\set(['type' => 'admin']));

Filter keys may include an operator. The following operator are supported by default

Key Value Description
"field" scalar Field is the value
"field (not)" scalar Field is not the value
"field (min)" scalar Field is equal to or greater than the value
"field (max)" scalar Field is equal to or less than the value
"field (any)" array Field is one of the values in the array
"field (none)" array Field is none of the values in the array

To filter between two values, use both (min) and (max).

If the field is an array, you may use the following operators

Key Value Description
"field (has)" scalar The value is part of the field
"field (has-not)" scalar The value is not part of the field
"field (has-any)" array Any of the values are part of the field
"field (has-all)" array All of the values are part of the field
"field (has-none)" array None of the values are part of the field

For data stores that support structured data (as MongoDB) the field may use the dot notation to reference a deeper properties.

The filter is a simple associative array, rather than an array of objects, making it easier to pass (part of) the HTTP query parameters as filter.

Options

In additions to a filter, database specific options can be passed. Such options include limiting the number of results, loading related data, sorting, etc. These options are passed to the query builder.

$list = $gateway->fetch(
    ['active' => true],
    opt\fields('name', 'role', 'age'),
    opt\limit(10),
    opt\sort('~activation_date', 'name'),
);

This library defines the concept of options, and a number of common options.

  • fields(string ...$fields)
  • omit(string ...$fields)
  • sort(string ...$fields)
  • limit(int limit, int offset = 0)
  • page(int pageNr, int pageSize) (pagination is 1-indexed)

For sorting, add a ~ in front of the field to sort in descending order.

Persist implementations may define additional options.

Gateway

A storage reader can be used to fetch from a persistent data storage (DB table, collection, etc). The storage is not embedded to the service, but instead passed to it when calling a method.

Each implementation has its own Gateway service that converts the generic filter into a DB specific query and wraps the query result in an iterator pipeline.

The fetch and count methods accept a filter and options ($opts). The available options differ per implementation. Typically you can set the fields that should be part of the result, a limit and offset and possibly which metadata you want to grab.

The save, update and delete methods accept options ($opts).

fetch

Result fetch(array $filter, OptionInterface ...$opts)

Query and fetch data.

count

int count(array $filter, OptionInterface ...$opts)

Query and count result.

save

array|object save(array|object $item, OptionInterface ...$opts)

Save the item. If an item has a unique id update it, otherwise add it.

The method will return the item, possibly modified with generated values, like auto-increment id.

saveAll

Result saveAll(iterable $items, OptionInterface ...$opts)

Save the items. If an item has a unique id update it, otherwise add it.

The method returns a result with the items, possibly modified with generated values, like auto-increment ids.

update

Result update($storage, array $filter, UpdateInstruction|UpdateInstruction[] $update, OptionInterface ...$opts)

Query and update records.

use Persist\Update as update;
use Persist\Mongo\Gateway as Gateway;

$userCollection = (MongoDB\Client())->tests->users;
$gateway = Gateway::basic()->forCollection($userCollection);

$gateway->update(['id' => 10], [update\set('last_login', new DateTime()), update\inc('logins')]);

The $changes argument must be one or more UpdateOperation objects. Rather than creating such an object by hand, the following helper functions exist in the Persist\Update\Functions namespace:

  • set(iterable $values)
  • set(string $field, mixed $value)
  • patch(string $field, array|object $value)
  • inc(string $field, int|float $value = 1)
  • dec(string $field, int|float $value = 1)
  • mul(string $field, int|float $value)
  • div(string $field, int|float $value)

If the field is an array, the following operations are also available

  • push(string $field, mixed $value, ...) - Add elements to the array
  • pull(string $field, mixed $value, ...) - Remove elements from the array

To prevent accidentally swapping the changes and filter, passing a normal associative array is not allowed. Instead use update\set($values), where values are all values that need to be set.

If you want to update every record of the storage (table, collection, etc) you have to supply an empty array as filter.

The method returns a result without any items, but it may contain metadata.

delete

Result delete($storage, array $filter, array $opts = [])

Query and delete records.

The method returns a result without any items, but it may contain metadata.

Result

The Gateway::fetch() method returns a Result object which extends iterator pipeline. As such, it provides methods, like map/reduce, to further process the result.

$employees = $gateway->fetch(['type' => 'admin'])
    ->map(fn(array $user) => $user + ['fullname' => $user['firstname'] . ' ' . $user['lastname']])
    ->group(fn(array $user) => $user['organization_id']);

Metadata

Services may add metadata to the result, this may include the total number of results, if the result set is limited, or the indexes that were used for the query.

The metadata is available through the getMeta() method.

$meta = $result->getMeta(); // ['total' => 42, 'cursor_id' => "94810124093"] 
$totalCount = $result->getMeta('total'); // 42

Rather than getting all meta, you can get a specific item by specifying the key.

Custom result

The read service has a withResultBuilder() which takes a ResultBuilder object. You can add steps to this which are performed for every result.

use Persist\Result\Result;
use Persist\Mongo\Gateway\Gateway;

$resultBuilder = Result::build()
    ->filter(function($value) {
        return $value !== null;
    });

$gateway = (new Gateway())->withResultBuilder($resultBuilder);

If needed, you can extend the Result class to add custom methods

use Persist\Result\Result;

class MyResult extends Result
{
    public function product(iterable $iterable)
    {
        $product = 1;
        
        foreach ($iterable as $value) {
            $product *= $value;
        }
        
        return $product;
    }
}

Use build() on your custom class to get a result builder that creates your custom result object.

use Persist\Mongo\Gateway\Gateway;

$gateway = (new Gateway())->withResultBuilder(MyResult::build());

Field map

The field map can be used both as a step in a 'prepare' stage of a query or as step of the result builder. If convert database field names into field names used in the PHP app and visa versa.

Construct the map using an associative array in the form [from => to]. The flip() method flips the from and to.

use Persist\Map\DeepMap;

$fieldMap = new DeepMap(['ref' => 'reference', 'foo_bar_setting' => 'foo_bar']);

$gateway = new Gateway();
$queryBuilder = $gateway->getQueryBuilder()->onPrepare($fieldMap);
$resultBuilder = $gateway->getResultBuilder()->then($fieldMap->flip());

$gateway = $gateway
    ->withQueryBuilder($queryBuilder)
    ->withResultBuilder($resultBuilder);

About

Service based DB abstraction layer for PHP

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages