smoren / schemator
Schematic data converter
Requires
- php: >=7.4
Requires (Dev)
- codeception/codeception: ^4.2.1
- codeception/module-asserts: ^2.0
- php-coveralls/php-coveralls: ^2.0
- phpstan/phpstan: ^1.8
- squizlabs/php_codesniffer: 3.*
README
Schematic data mapper is a tool for converting nested data structures (any composition of associative arrays, non-associative arrays and objects) according to the given conversion schema.
How to install to your project
composer require smoren/schemator
Usage
Simple usage
use Smoren\Schemator\Factories\SchematorFactory; $input = [ 'id' => 100, 'name' => 'Oxford', 'country' => [ 'id' => 10, 'name' => 'UK', 'neighbours' => ['Ireland', 'Sweden', 'France'], 'capitals' => [ 'lnd' => 'London', 'edb' => 'Edinburgh', ], ], 'streets' => [ [ 'id' => 1000, 'name' => 'Woodstock Rd', 'houses' => [1, 5, 9], ], [ 'id' => 1002, 'name' => 'Banbury Rd', 'houses' => [22, 35, 49], ], [ 'id' => 1003, 'name' => 'Beamont St', 'houses' => [11, 12, 15], ], ], 'lnd_path' => 'country.capitals.lnd', ]; $schema = [ 'city_id' => 'id', 'city_name' => 'name', 'city_street_names' => 'streets.*.name', 'country_id' => 'country.id', 'country_name' => 'country.name', 'country_neighbours' => 'country.neighbours', 'country_neighbour' => 'country.neighbours', 'country_first_capital' => 'country.capitals.lnd', 'country_second_capital' => 'country.capitals.edb', 'country_data.country_id' => 'country.id', 'country_data.country_name' => 'country.name', ]; $schemator = SchematorFactory::create(); $output = $schemator->convert($input, $schema); print_r($output); /* Array ( [city_id] => 100 [city_name] => Oxford [city_street_names] => Array ( [0] => Woodstock Rd [1] => Banbury Rd [2] => Beamont St ) [country_id] => 10 [country_name] => UK [country_neighbours] => Array ( [0] => Ireland [1] => Sweden [2] => France ) [country_neighbour] => Array ( [0] => Ireland [1] => Sweden [2] => France ) [country_first_capital] => London [country_second_capital] => Edinburgh [country_data] => Array ( [country_id] => 10 [country_name] => UK ) ) */
Setting errors level
use Smoren\Schemator\Factories\SchematorFactory; use Smoren\Schemator\Structs\ErrorsLevelMask; use Smoren\Schemator\Exceptions\SchematorException; $input = [ 'some_key' => null, ]; $schema = [ 'my_value' => ['some_key', ['date', 'Y-m-d']], ]; $schemator = SchematorFactory::createBuilder() ->withErrorsLevelMask( ErrorsLevelMask::nothing() ->add([SchematorException::FILTER_ERROR, SchematorException::CANNOT_GET_VALUE]) ) ->get(); try { $schemator->convert($input, $schema); } catch(SchematorException $e) { echo $e->getMessage(); // filter error: 'date' }
Using base filters
use Smoren\Schemator\Factories\SchematorFactory; use Smoren\Schemator\Filters\BaseFiltersStorage; $input = [ 'id' => 100, 'name' => 'Oxford', 'country' => [ 'id' => 10, 'name' => 'UK', 'neighbours' => ['Ireland', 'Sweden', 'France'], 'capitals' => [ 'lnd' => 'London', 'edb' => 'Edinburgh', ], ], 'streets' => [ [ 'id' => 1000, 'name' => 'Woodstock Rd', 'houses' => [1, 5, 9], ], [ 'id' => 1002, 'name' => 'Banbury Rd', 'houses' => [22, 35, 49], ], [ 'id' => 1003, 'name' => 'Beamont St', 'houses' => [11, 12, 15], ], ], 'lnd_path' => 'country.capitals.lnd', ]; $schema = [ 'city_street_names.all' => ['streets.*.name', ['implode', ', ']], 'city_street_names.sorted' => ['streets.*.name', ['sort'], ['implode', ', ']], 'city_street_names.filtered' => ['streets.*.name', ['filter', fn (string $candidate) => strpos($candidate, 'Ban') !== false]], 'lnd' => ['lnd_path', ['path']], 'city_street_houses' => ['streets.*.houses', ['flatten']], ]; $schemator = SchematorFactory::create(); $output = $schemator->convert($input, $schema); print_r($output); /* Array ( [city_street_names] => Array ( [all] => Woodstock Rd, Banbury Rd, Beamont St [sorted] => Banbury Rd, Beamont St, Woodstock Rd [filtered] => Array ( [0] => Banbury Rd ) ) [lnd] => London [city_street_houses] => Array ( [0] => 1 [1] => 5 [2] => 9 [3] => 22 [4] => 35 [5] => 49 [6] => 11 [7] => 12 [8] => 15 ) ) */
Using smart filter and replace
use Smoren\Schemator\Factories\SchematorFactory; use Smoren\Schemator\Filters\BaseFiltersStorage; $schemator = SchematorFactory::create(); $input = [ 'numbers' => [-1, 10, 5, 22, -10, 0, 35, 7, 8, 9, 0], ]; $output = $schemator->convert($input, [ 'positive' => [ 'numbers', ['filter', [['>', 0]]], ['sort'], ], 'negative' => [ 'numbers', ['filter', [['<', 0]]], ['sort'], ], 'complicated' => [ 'numbers', ['filter', [['>=', 8], ['<', 0]]], ['filter', [['<', 22]]], ['sort'], ], ]); print_r($output); /* Array ( [positive] => Array ( [0] => 5 [1] => 7 [2] => 8 [3] => 9 [4] => 10 [5] => 22 [6] => 35 ) [negative] => Array ( [0] => -10 [1] => -1 ) [complicated] => Array ( [0] => -10 [1] => -1 [2] => 8 [3] => 9 [4] => 10 ) ) */ $output = $schemator->convert($input, [ 'number_types' => ['numbers', [ 'replace', [ ['=0', '=', 0], ['>9', '>', 9], ['<0', '<', 0], ['1-8', 'between', 1, 8], ] ]] ]); print_r($output); /* Array ( [number_types] => Array ( [0] => <0 [1] => >9 [2] => 1-8 [3] => >9 [4] => <0 [5] => =0 [6] => >9 [7] => 1-8 [8] => 1-8 [9] => 9 [10] => =0 ) ) */
Using custom filters
use Smoren\Schemator\Factories\SchematorFactory; use Smoren\Schemator\Interfaces\FilterContextInterface; $schemator = SchematorFactory::createBuilder() ->withFilters([ 'startsWith' => function (FilterContextInterface $context, string $start) { return array_filter($context->getSource(), function (string $candidate) use ($start) { return strpos($candidate, $start) === 0; }); }, ]) ->get(); $input = [ 'streets' => ['Woodstock Rd', 'Banbury Rd', 'Beamont St'], ]; $schema = [ 'street_names' => ['streets', ['startsWith', 'T'], ['implode', ', ']], ]; $output = $schemator->convert($input, $schema); print_r($output); /* Array ( [street_names] => Woodstock Rd, Beamont St ) */
Mass usage
use Smoren\Schemator\Factories\SchematorFactory; $massSchemator = SchematorFactory::createMass(); $cities = [ [ 'id' => 100, 'name' => 'London', 'country' => [ 'id' => 10, 'name' => 'UK', ], 'streets' => [ [ 'id' => 1001, 'name' => 'The Mall', ], [ 'id' => 1002, 'name' => 'Carnaby Street', ], ], ], [ 'id' => 101, 'name' => 'Oxford', 'country' => [ 'id' => 10, 'name' => 'UK', ], 'streets' => [ [ 'id' => 1003, 'name' => 'Turl Street', ], [ 'id' => 1004, 'name' => 'Holywell Street', ], ], ], ]; $schema = [ 'city_id' => 'id', 'city_name' => 'name', 'city_street_names' => 'streets.*.name', 'country_id' => 'country.id', 'country_name' => 'country.name', ]; $gen = $massSchemator->generate($cities, $schema); $result = []; foreach($gen as $item) { $result[] = $item; } print_r($result); /* Array ( [0] => Array ( [city_id] => 100 [city_name] => London [city_street_names] => Array ( [0] => The Mall [1] => Carnaby Street ) [country_id] => 10 [country_name] => UK ) [1] => Array ( [city_id] => 101 [city_name] => Oxford [city_street_names] => Array ( [0] => Turl Street [1] => Holywell Street ) [country_id] => 10 [country_name] => UK ) ) */
Filters
const
Sets the value from const param.
Schema:
["value" => [["const", "My const value"]]]
Result:
["value" => "My const value"]
sum
Returns the sum of given array.
Given:
["numbers" => [1, 2, 3, 4, 5]]
Schema:
["value" => ["numbers", ["sum"]]]
Result:
["value" => 15]
average
Returns the average of given array.
Given:
["numbers" => [1, 2, 3, 4, 5]]
Schema:
["value" => ["numbers", ["average"]]]
Result:
["value" => 3]
date
Returns formatted date from the Unix timestamp given value.
Params:
- Date format
- required
- data type — string
- example:
d.m.Y H:i:s
- more about formats
- Time zone offset from GMT
- optional
- data type — integer
- default — 0
Given:
["some_date" => 1651481881]
Schema:
["value" => ["some_date", ["date", "d.m.Y H:i:s", 3]]]
Result:
["value" => "02.05.2022 11:58:01"]
implode
Returns string of imploded items of given array with separator from args list.
params:
- Separator
- required
- data type — string
- example:
;
Given:
["numbers" => [1, 2, 3, 4, 5]]
Schema:
["value" => ["numbers", ["implode", "; "]]]
Result:
["value" => "1; 2; 3; 4; 5"]
explode
Returns array of exploded strings from given string with separator from args list
params:
- Separator
- required
- data type — string
- example:
;
Given:
["numbers" => "1; 2; 3; 4; 5"]
Schema:
["value" => ["numbers", ["explode", "; "]]]
Result:
["value" => ["1", "2", "3", "4", "5"]]
flatten
Returns flat array contains all the dead end leaves of tree array.
Given:
[ "numbers" => [ [ [1, 2, 3], [4, 5, 6] ], [7, 8, 9] ], ]
Schema:
["value" => ["numbers", ["flatten"]]]
Result:
["value" => [1, 2, 3, 4, 5, 6, 7, 8, 9]]
sort
Sorts and returns given array.
Given:
["numbers" => [3, 5, 4, 1, 2]]
Schema:
["value" => ["numbers", ["sort"]]]
Result:
["value" => [1, 2, 3, 4, 5]]
rsort
Sorts reversely and returns given array.
Given:
["numbers" => [3, 5, 4, 1, 2]]
Schema:
["value" => ["numbers", ["sort"]]]
Result:
["value" => [5, 4, 3, 2, 1]]
filter
Returns array containing elements from given array that match the predicates from the params list.
Rules:
- Every predicate has such format
["predicate name", ...parans]
. - Predicates in one filter apply according the "OR" logic.
- To apply "AND" logic use chain of filters.
- Available predicates:
["=", 10]
meansvalue = 10
[">", 10]
meansvalue > 10
[">=", 10]
meansvalue >= 10
["<", 10]
meansvalue < 10
["<=", 10]
meansvalue <= 10
["in", [1, 2]]
meansvalue = 1 OR value = 2
["not in", [1, 2]]
meansvalue != 1 AND value != 2
["between", 1, 5]
means1 <= value <= 5
["between strict", 1, 5]
means1 < value < 5
Given:
["numbers" => [-5, -3, -1, 1, 3, 5]]
Schema:
[ "value" => [ "numbers", [ "filter", [[">", 1], ["<", -1]] // value > 1 OR value < -1 ], ], ]
Result:
["value" => [-5, -3, 3, 5]]
replace
Returns array of elements with values replaced according to the rules in the params list.
Rules:
- Every rule has such format
["value to replace", "rule name", ...params]
. - Rules in one filter apply according the "OR" logic.
- To apply "AND" logic use chain of filters.
- Available rules:
["=", 10]
meansvalue = 10
[">", 10]
meansvalue > 10
[">=", 10]
meansvalue >= 10
["<", 10]
meansvalue < 10
["<=", 10]
meansvalue <= 10
["in", [1, 2]]
meansvalue = 1 или value = 2
["not in", [1, 2]]
meansvalue != 1 и value != 2
["between", 1, 5]
means1 <= value <= 5
["between strict", 1, 5]
means1 < value < 5
["else"]
— no rules matched for value (If ruleelse
did not use, by default such values are replaced withnull
)
Given:
["numbers" => [-5, -3, -1, 0, 1, 3, 5]]
Schema:
[ "value" => [ "numbers", [ "replace", [ ["positive", ">", 0], ["negative", "<", 0], ["zero", "else"] ], ], ], ]
Result:
["value" => ["negative", "negative", "negative", "zero", "positive", "positive", "positive"]]
Chain of filters
Given:
["numbers" => [-5, -3, -1, 1, 3, 5]]
Schema:
[ "value" => [ "numbers", [ "filter", [[">", 1], ["<", -1]] // (value > 1 OR value < -1) ], // AND [ "filter", [[">=", -3]] // value >= -3 ], ], ]
Result:
["value" => [-3, 3, 5]]
Unit testing
composer install
composer test-init
composer test
Standards
Schemator conforms to the following standards:
- PSR-1 — Basic coding standard
- PSR-4 — Autoloader
- PSR-12 — Extended coding style guide
License
Schemator is licensed under the MIT License.