Version 1.0.0
This commit is contained in:
22
.editorconfig
Normal file
22
.editorconfig
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.php]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
indent_size = 2
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
vendor/
|
||||||
|
|
||||||
|
.phpunit.cache/
|
||||||
19
LICENSE
Normal file
19
LICENSE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright 2025 Viktor S. <thinlineseverywhere@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the “Software”),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
230
README.md
Normal file
230
README.md
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
# Data Enrichment Kit
|
||||||
|
|
||||||
|
A collection of components for building microservice-based applications.
|
||||||
|
This library provides tools for enriching data during communication
|
||||||
|
between system components.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install the package
|
||||||
|
composer require diffhead/php-data-enrichment-kit
|
||||||
|
|
||||||
|
# Run library tests
|
||||||
|
composer test
|
||||||
|
```
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
To use this library, you first need to create repositories
|
||||||
|
that implement the `Repository` interface:
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
interface Repository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $field
|
||||||
|
* @param array $values
|
||||||
|
*
|
||||||
|
* @return iterable<int,\Diffhead\PHP\DataEnrichmentKit\Interface\Entity>
|
||||||
|
*/
|
||||||
|
public function getByFieldValues(string $field, array $values): iterable;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each entity must implement the `Entity` interface:
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
interface Entity extends \ArrayAccess, \JsonSerializable {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating Enrichment Requests and Attaching to HTTP Messages
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Builder;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Message;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Header;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Serializer;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Parser;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
|
||||||
|
$builder = Builder::withTarget('user', 'id');
|
||||||
|
$builder
|
||||||
|
->item('data.posts.*.creator_id', 'creator')
|
||||||
|
->item('data.posts.*.moderator_id', 'moderator');
|
||||||
|
|
||||||
|
$requests = new Requests([
|
||||||
|
$builder->build()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$message = new Message(new Serializer(), new Parser());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Psr\Http\Message\MessageInterface $psrMessage
|
||||||
|
*
|
||||||
|
* Message contains the following body data:
|
||||||
|
*
|
||||||
|
* {"data":{"posts":[{"id":1,"title":"String","creator_id":1,"moderator_id":3}]}}
|
||||||
|
*
|
||||||
|
* This call will attach enrichment requests to a message
|
||||||
|
*/
|
||||||
|
$message->setRequests($psrMessage, Header::XEnrichmentRequest, $requests);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Receiving Data and Performing Enrichment
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Enricher;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Message;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Header;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Enrichment;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Serializer;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Parser;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Repositories;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository implementation example
|
||||||
|
*/
|
||||||
|
$repository = new class() implements Repository
|
||||||
|
{
|
||||||
|
private array $items = [
|
||||||
|
['id' => 1, 'name' => 'Antony'],
|
||||||
|
['id' => 3, 'name' => 'Martin']
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getByFieldValues(string $field, array $values): iterable
|
||||||
|
{
|
||||||
|
return array_filter($this->items, fn(array $item) => in_array($item[$field], $values));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$repositories = new Repositories([
|
||||||
|
'user' => $repository
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize services
|
||||||
|
*/
|
||||||
|
$enricher = new Enricher(new Enrichment($repositories));
|
||||||
|
$message = new Message(new Serializer(), new Parser());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting enrichment requests from psr message
|
||||||
|
*/
|
||||||
|
$requests = $message->getRequests($psrMessage, Header::XEnrichmentRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting array payload from message
|
||||||
|
*/
|
||||||
|
$payload = $message->getPayload($psrMessage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enrich the payload using the registered repositories
|
||||||
|
* Expected enriched structure:
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* "data" => [
|
||||||
|
* "posts" => [
|
||||||
|
* [
|
||||||
|
* "id" => 1,
|
||||||
|
* "title" => "String",
|
||||||
|
* "creator_id" => 1,
|
||||||
|
* "moderator_id" => 3,
|
||||||
|
* "creator" => ["id" => 1, "name" => "Antony"],
|
||||||
|
* "moderator" => ["id" => 3, "name" => "Martin"]
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
$enriched = $enricher->enrich($payload, $requests);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set new payload to psr message
|
||||||
|
*/
|
||||||
|
$psrMessage = $message->setPayload($psrMessage, $enriched);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Anatomy
|
||||||
|
|
||||||
|
This library consists of the following **high-level service components**:
|
||||||
|
|
||||||
|
* **Enricher** – a self-explanatory service that enriches data.
|
||||||
|
* **Message** – a PSR-compatible component for interacting with
|
||||||
|
HTTP requests and responses using `MessageInterface`.
|
||||||
|
* **Builder** – helps build enrichment request objects.
|
||||||
|
|
||||||
|
**Lower-level components** for building custom enrichment algorithms:
|
||||||
|
|
||||||
|
* **Enrichment** – implements the `Enrichment` interface and contains
|
||||||
|
the enrichment logic.
|
||||||
|
* **Parser** – parses raw and returns `Request` objects.
|
||||||
|
* **Serializer** – serializes `Request` objects to strings.
|
||||||
|
|
||||||
|
**Value objects:**
|
||||||
|
|
||||||
|
* **Item** – a unit describing an enrichment reference and alias.
|
||||||
|
* **ItemsBag** – stores multiple `Item` instances.
|
||||||
|
* **Request** – represents a single enrichment request.
|
||||||
|
* **Target** – specifies the entity to enrich.
|
||||||
|
|
||||||
|
**Storage objects:**
|
||||||
|
|
||||||
|
* **Requests** – contains enrichment requests and implements `IteratorAggregate`.
|
||||||
|
* **Repositories** – stores a map of entity repositories.
|
||||||
|
|
||||||
|
# Customize
|
||||||
|
|
||||||
|
You can extend the library by implementing your own:
|
||||||
|
|
||||||
|
### Data enrichment logic
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
|
||||||
|
interface Enrichment
|
||||||
|
{
|
||||||
|
public function enrich(array $data, Requests $requests): array;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request parsing
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
|
||||||
|
interface Parser
|
||||||
|
{
|
||||||
|
public function parse(string $value): Request;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request serialization
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
|
||||||
|
interface Serializer
|
||||||
|
{
|
||||||
|
public function toString(Request $request): string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
* Designed primarily for HTTP, but low-level components allow you
|
||||||
|
to quickly implement high-level integrations for any data source.
|
||||||
|
|
||||||
|
* Supports PSR-compliant messages and can be integrated into
|
||||||
|
frameworks like Laravel with custom adapters.
|
||||||
36
composer.json
Normal file
36
composer.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "diffhead/php-data-enrichment-kit",
|
||||||
|
"description": "Data enrichment library. A suitable component for microservice architectures.",
|
||||||
|
"type": "library",
|
||||||
|
"license": "MIT",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"keywords": [
|
||||||
|
"php", "data enrichment", "data transformation", "psr",
|
||||||
|
"pipeline", "message", "processing", "serializer", "parser",
|
||||||
|
"microservice", "php 8"
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Diffhead\\PHP\\DataEnrichmentKit\\": "src/",
|
||||||
|
"Diffhead\\PHP\\DataEnrichmentKit\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"psr/http-message": "^1.0",
|
||||||
|
"nyholm/psr7": "^1.8.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^12.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "phpunit"
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Viktor S.",
|
||||||
|
"email": "thinlineseverywhere@gmail.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1882
composer.lock
generated
Normal file
1882
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
phpunit.xml
Normal file
27
phpunit.xml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
cacheDirectory=".phpunit.cache"
|
||||||
|
executionOrder="depends,defects"
|
||||||
|
requireCoverageMetadata="true"
|
||||||
|
beStrictAboutCoverageMetadata="true"
|
||||||
|
beStrictAboutOutputDuringTests="true"
|
||||||
|
displayDetailsOnPhpunitDeprecations="true"
|
||||||
|
failOnPhpunitDeprecation="true"
|
||||||
|
failOnRisky="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
testdox="true"
|
||||||
|
colors="true">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Unit">
|
||||||
|
<directory>tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
|
||||||
|
<include>
|
||||||
|
<directory>src</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
|
</phpunit>
|
||||||
54
src/Builder.php
Normal file
54
src/Builder.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Exception\TargetIsNull;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
|
||||||
|
class Builder
|
||||||
|
{
|
||||||
|
private array $items = [];
|
||||||
|
private ?Target $target = null;
|
||||||
|
|
||||||
|
public static function withTarget(string $entity, string $field): static
|
||||||
|
{
|
||||||
|
$builder = new static();
|
||||||
|
$builder->target($entity, $field);
|
||||||
|
|
||||||
|
return $builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function item(string $key, string $alias = ''): static
|
||||||
|
{
|
||||||
|
$this->items[] = new Item($key, $alias);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function target(string $entity, string $field): static
|
||||||
|
{
|
||||||
|
$this->target = new Target($entity, $field);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build(): Request
|
||||||
|
{
|
||||||
|
if (is_null($this->target)) {
|
||||||
|
throw new TargetIsNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
|
||||||
|
foreach ($this->items as $item) {
|
||||||
|
$items->push($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Request($items, $this->target);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/Enricher.php
Normal file
20
src/Enricher.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Enrichment;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
|
||||||
|
class Enricher
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Enrichment $enrichment,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function enrich(array $data, Requests $requests): array
|
||||||
|
{
|
||||||
|
return $this->enrichment->enrich($data, $requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/Exception/InvalidRequest.php
Normal file
17
src/Exception/InvalidRequest.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class InvalidRequest extends RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(string $value)
|
||||||
|
{
|
||||||
|
return parent::__construct(
|
||||||
|
sprintf('Invalid enrichment request: "%s".', $value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/Exception/PayloadIsNotJson.php
Normal file
9
src/Exception/PayloadIsNotJson.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class PayloadIsNotJson extends RuntimeException {}
|
||||||
17
src/Exception/RepositoryNotFound.php
Normal file
17
src/Exception/RepositoryNotFound.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class RepositoryNotFound extends RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(string $target)
|
||||||
|
{
|
||||||
|
return parent::__construct(
|
||||||
|
sprintf('Repository not found for target "%s".', $target)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/Exception/TargetIsNull.php
Normal file
9
src/Exception/TargetIsNull.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Exception;
|
||||||
|
|
||||||
|
use LogicException;
|
||||||
|
|
||||||
|
class TargetIsNull extends LogicException {}
|
||||||
10
src/Header.php
Normal file
10
src/Header.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit;
|
||||||
|
|
||||||
|
enum Header: string
|
||||||
|
{
|
||||||
|
case XEnrichmentRequest = 'X-Enrichment-Request';
|
||||||
|
}
|
||||||
12
src/Interface/Enrichment.php
Normal file
12
src/Interface/Enrichment.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
|
||||||
|
interface Enrichment
|
||||||
|
{
|
||||||
|
public function enrich(array $data, Requests $requests): array;
|
||||||
|
}
|
||||||
10
src/Interface/Entity.php
Normal file
10
src/Interface/Entity.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
interface Entity extends ArrayAccess, JsonSerializable {}
|
||||||
12
src/Interface/Parser.php
Normal file
12
src/Interface/Parser.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
|
||||||
|
interface Parser
|
||||||
|
{
|
||||||
|
public function parse(string $value): Request;
|
||||||
|
}
|
||||||
16
src/Interface/Repository.php
Normal file
16
src/Interface/Repository.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
interface Repository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $field
|
||||||
|
* @param array $values
|
||||||
|
*
|
||||||
|
* @return iterable<int,\Diffhead\PHP\DataEnrichmentKit\Interface\Entity>
|
||||||
|
*/
|
||||||
|
public function getByFieldValues(string $field, array $values): iterable;
|
||||||
|
}
|
||||||
12
src/Interface/Serializer.php
Normal file
12
src/Interface/Serializer.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
|
||||||
|
interface Serializer
|
||||||
|
{
|
||||||
|
public function toString(Request $request): string;
|
||||||
|
}
|
||||||
85
src/Message.php
Normal file
85
src/Message.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit;
|
||||||
|
|
||||||
|
use BackedEnum;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Exception\PayloadIsNotJson;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Parser;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Serializer;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
use Nyholm\Psr7\Stream;
|
||||||
|
use Psr\Http\Message\MessageInterface;
|
||||||
|
|
||||||
|
class Message
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Serializer $serializer,
|
||||||
|
private Parser $parser,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Psr\Http\Message\MessageInterface $message
|
||||||
|
* @param \BackedEnum $header
|
||||||
|
* @param \Diffhead\PHP\DataEnrichmentKit\Storage\Requests $requests
|
||||||
|
*
|
||||||
|
* @return \Psr\Http\Message\MessageInterface
|
||||||
|
*/
|
||||||
|
public function setRequests(
|
||||||
|
MessageInterface $message,
|
||||||
|
BackedEnum $header,
|
||||||
|
Requests $requests
|
||||||
|
): MessageInterface {
|
||||||
|
if ($requests->count() === 0) {
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = $header->value;
|
||||||
|
|
||||||
|
foreach ($requests as $request) {
|
||||||
|
$request = $this->serializer->toString($request);
|
||||||
|
$message = $message->withAddedHeader($header, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Psr\Http\Message\MessageInterface $message
|
||||||
|
* @param \BackedEnum $header
|
||||||
|
*
|
||||||
|
* @return \Diffhead\PHP\DataEnrichmentKit\Storage\Requests
|
||||||
|
*/
|
||||||
|
public function getRequests(MessageInterface $message, BackedEnum $header): Requests
|
||||||
|
{
|
||||||
|
$header = $header->value;
|
||||||
|
$requests = $message->getHeader($header);
|
||||||
|
|
||||||
|
$container = new Requests();
|
||||||
|
|
||||||
|
foreach ($requests as $request) {
|
||||||
|
$container->append($this->parser->parse($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPayload(MessageInterface $message): array
|
||||||
|
{
|
||||||
|
$payload = json_decode($message->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
if (! is_array($payload)) {
|
||||||
|
throw new PayloadIsNotJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPayload(MessageInterface $message, array $payload): MessageInterface
|
||||||
|
{
|
||||||
|
return $message->withBody(
|
||||||
|
Stream::create(json_encode($payload))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Object/Item.php
Normal file
23
src/Object/Item.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Object;
|
||||||
|
|
||||||
|
class Item
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $key,
|
||||||
|
private string $alias = ''
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function key(): string
|
||||||
|
{
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alias(): string
|
||||||
|
{
|
||||||
|
return $this->alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/Object/ItemsBag.php
Normal file
29
src/Object/ItemsBag.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Object;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
|
use IteratorAggregate;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
class ItemsBag implements IteratorAggregate
|
||||||
|
{
|
||||||
|
private array $items = [];
|
||||||
|
|
||||||
|
public function push(Item $item): static
|
||||||
|
{
|
||||||
|
$this->items[] = $item;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Traversable<int,\Diffhead\PHP\DataEnrichmentKit\Object\Item>
|
||||||
|
*/
|
||||||
|
public function getIterator(): Traversable
|
||||||
|
{
|
||||||
|
return new ArrayIterator($this->items);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Object/Request.php
Normal file
23
src/Object/Request.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Object;
|
||||||
|
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ItemsBag $items,
|
||||||
|
private Target $target
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function items(): ItemsBag
|
||||||
|
{
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function target(): Target
|
||||||
|
{
|
||||||
|
return $this->target;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Object/Target.php
Normal file
23
src/Object/Target.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Object;
|
||||||
|
|
||||||
|
class Target
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $entity,
|
||||||
|
private string $field
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function entity(): string
|
||||||
|
{
|
||||||
|
return $this->entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function field(): string
|
||||||
|
{
|
||||||
|
return $this->field;
|
||||||
|
}
|
||||||
|
}
|
||||||
153
src/Service/Enrichment.php
Normal file
153
src/Service/Enrichment.php
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Service;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Enrichment as EnrichmentInterface;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Repositories;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Utility\Arr;
|
||||||
|
|
||||||
|
class Enrichment implements EnrichmentInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Repositories $repositories,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $payload
|
||||||
|
* @param \Diffhead\PHP\DataEnrichmentKit\Storage\Requests $requests
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function enrich(array $data, Requests $requests): array
|
||||||
|
{
|
||||||
|
foreach ($requests as $request) {
|
||||||
|
$data = $this->enrichSingle($data, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function enrichSingle(array $data, Request $request): array
|
||||||
|
{
|
||||||
|
$targetFieldValues = [];
|
||||||
|
$target = $request->target();
|
||||||
|
|
||||||
|
$items = $request->items();
|
||||||
|
|
||||||
|
$targetFieldValues = $this->getFieldValuesByItems($data, $items);
|
||||||
|
|
||||||
|
$targets = $this->getTargetsByFieldValue(
|
||||||
|
$target->entity(),
|
||||||
|
$target->field(),
|
||||||
|
$targetFieldValues
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->enrichUsingTargets($data, $items, $targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFieldValuesByItems(array $data, ItemsBag $items): array
|
||||||
|
{
|
||||||
|
$targetFieldValues = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Diffhead\PHP\DataEnrichmentKit\Object\Item $item
|
||||||
|
*/
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$valueOrValues = Arr::get($item->key(), $data);
|
||||||
|
|
||||||
|
if (is_array($valueOrValues)) {
|
||||||
|
$targetFieldValues = array_merge($targetFieldValues, $valueOrValues);
|
||||||
|
} else {
|
||||||
|
$targetFieldValues[] = $valueOrValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique($targetFieldValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTargetsByFieldValue(string $entity, string $field, array $values): array
|
||||||
|
{
|
||||||
|
$targets = $this->repositories->get($entity)
|
||||||
|
->getByFieldValues($field, $values);
|
||||||
|
|
||||||
|
$targetsHaveArrayAccess = is_array($targets) || $targets instanceof ArrayAccess;
|
||||||
|
$targetsByFieldValue = [];
|
||||||
|
|
||||||
|
foreach ($targets as $index => $target) {
|
||||||
|
$fieldValue = $target[$field];
|
||||||
|
$targetsByFieldValue[$fieldValue] = $target;
|
||||||
|
|
||||||
|
if ($targetsHaveArrayAccess) {
|
||||||
|
unset($targets[$index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $targetsByFieldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function enrichUsingTargets(array $data, ItemsBag $items, array $targets): array
|
||||||
|
{
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$data = $this->enrichByItem($data, $item, $targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function enrichByItem(array $data, Item $item, array &$targets): array
|
||||||
|
{
|
||||||
|
$keyFirstItemIsWildcard = strpos($item->key(), '*.') === 0;
|
||||||
|
|
||||||
|
if (array_is_list($data) && $keyFirstItemIsWildcard) {
|
||||||
|
/**
|
||||||
|
* If passed *.user_id key as example then
|
||||||
|
* we need to remove first wildcard part
|
||||||
|
*/
|
||||||
|
$parts = explode('.', $item->key());
|
||||||
|
$nextKey = implode('.', array_slice($parts, 1));
|
||||||
|
|
||||||
|
$item = new Item($nextKey, $item->alias());
|
||||||
|
|
||||||
|
foreach ($data as $index => $entry) {
|
||||||
|
$data[$index] = $this->enrichByItem($entry, $item, $targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyIsWildcard = is_numeric(strpos($item->key(), '*.'));
|
||||||
|
|
||||||
|
if ($keyIsWildcard) {
|
||||||
|
$parts = explode('.', $item->key());
|
||||||
|
$first = $parts[0];
|
||||||
|
|
||||||
|
$temporary = Arr::get($first, $data, []);
|
||||||
|
|
||||||
|
$nextKey = implode('.', array_slice($parts, 1));
|
||||||
|
$nextItem = new Item($nextKey, $item->alias());
|
||||||
|
|
||||||
|
$data[$first] = $this->enrichByItem($temporary, $nextItem, $targets);
|
||||||
|
} else {
|
||||||
|
$value = Arr::get($item->key(), $data);
|
||||||
|
$parts = explode('.', $item->key());
|
||||||
|
|
||||||
|
$alias = $item->alias()
|
||||||
|
? $item->alias()
|
||||||
|
: $parts[count($parts) - 1];
|
||||||
|
|
||||||
|
$parts[count($parts) - 1] = $alias;
|
||||||
|
$nextKey = implode('.', $parts);
|
||||||
|
|
||||||
|
$data = Arr::set($nextKey, $data, $targets[$value] ?? null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/Service/Parser.php
Normal file
57
src/Service/Parser.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Exception\InvalidRequest;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Parser as ParserInterface;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
|
||||||
|
class Parser implements ParserInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return \Diffhead\PHP\DataEnrichmentKit\Object\Request
|
||||||
|
*
|
||||||
|
* @throws \Diffhead\PHP\DataEnrichmentKit\Exception\InvalidRequest
|
||||||
|
*/
|
||||||
|
public function parse(string $value): Request
|
||||||
|
{
|
||||||
|
$referenceWithTarget = explode('@', $value);
|
||||||
|
|
||||||
|
if (! isset($referenceWithTarget[1])) {
|
||||||
|
throw new InvalidRequest($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityWithField = explode(',', $referenceWithTarget[1]);
|
||||||
|
|
||||||
|
if (! isset($entityWithField[0]) || ! isset($entityWithField[1])) {
|
||||||
|
throw new InvalidRequest($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entity = $entityWithField[0];
|
||||||
|
$field = $entityWithField[1];
|
||||||
|
|
||||||
|
$target = new Target($entity, $field);
|
||||||
|
|
||||||
|
$references = $referenceWithTarget[0];
|
||||||
|
$references = explode(',', $references);
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
|
||||||
|
foreach ($references as $reference) {
|
||||||
|
$referenceWithAlias = explode('+', $reference);
|
||||||
|
|
||||||
|
$items->push(
|
||||||
|
new Item($referenceWithAlias[0], $referenceWithAlias[1] ?? '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Request($items, $target);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/Service/Serializer.php
Normal file
40
src/Service/Serializer.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Serializer as SerializerInterface;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
|
||||||
|
class Serializer implements SerializerInterface
|
||||||
|
{
|
||||||
|
public function toString(Request $request): string
|
||||||
|
{
|
||||||
|
$targetParts = [
|
||||||
|
$request->target()->entity(),
|
||||||
|
$request->target()->field(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$target = implode(',', $targetParts);
|
||||||
|
|
||||||
|
$itemsParts = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Diffhead\PHP\DataEnrichmentKit\Object\Item $item
|
||||||
|
*/
|
||||||
|
foreach ($request->items() as $item) {
|
||||||
|
$itemParts = [$item->key()];
|
||||||
|
|
||||||
|
if ($alias = $item->alias()) {
|
||||||
|
$itemParts[] = $alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
$itemsParts[] = implode('+', $itemParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = implode(',', $itemsParts);
|
||||||
|
|
||||||
|
return sprintf('%s@%s', $items, $target);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/Storage/Repositories.php
Normal file
45
src/Storage/Repositories.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Storage;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Exception\RepositoryNotFound;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Repository;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class Repositories
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<string,\Diffhead\PHP\DataEnrichmentKit\Interface\Repository> $map
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private array $map = []
|
||||||
|
) {
|
||||||
|
foreach ($this->map as $target => $repository) {
|
||||||
|
if (! $repository instanceof Repository) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Map should contains only Repository instances as values'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(string $target, Repository $repository): static
|
||||||
|
{
|
||||||
|
$this->map[$target] = $repository;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $target): Repository
|
||||||
|
{
|
||||||
|
if (! isset($this->map[$target])) {
|
||||||
|
throw new RepositoryNotFound($target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->map[$target];
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/Storage/Requests.php
Normal file
54
src/Storage/Requests.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Storage;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use IteratorAggregate;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
|
class Requests implements IteratorAggregate
|
||||||
|
{
|
||||||
|
private int $count = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int,\Diffhead\PHP\DataEnrichmentKit\Object\Request> $requests
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private array $requests = []
|
||||||
|
) {
|
||||||
|
foreach ($this->requests as $request) {
|
||||||
|
if (! $request instanceof Request) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Requests should be an array of Request instances'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function append(Request $request): void
|
||||||
|
{
|
||||||
|
$this->count++;
|
||||||
|
$this->requests[] = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return $this->count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Traversable<int,\Diffhead\PHP\DataEnrichmentKit\Object\Request>
|
||||||
|
*/
|
||||||
|
public function getIterator(): Traversable
|
||||||
|
{
|
||||||
|
return new ArrayIterator($this->requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
174
src/Utility/Arr.php
Normal file
174
src/Utility/Arr.php
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Utility;
|
||||||
|
|
||||||
|
class Arr
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get value inside array using key dot notation is supported
|
||||||
|
* also supported wildcard keys as "users.*.name" and alike.
|
||||||
|
*
|
||||||
|
* Returns empty array when passed wildcard key but no values found.
|
||||||
|
* This mode will never return the default value and values strictly
|
||||||
|
* equal to default are skipping.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param array $data
|
||||||
|
* @param mixed $default
|
||||||
|
*
|
||||||
|
* @return mixed|array<int,mixed>
|
||||||
|
*/
|
||||||
|
public static function get(string $key, array $data, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
$parts = explode('.', $key);
|
||||||
|
|
||||||
|
$temporary = $data;
|
||||||
|
|
||||||
|
foreach ($parts as $index => $part) {
|
||||||
|
if ($part === '*') {
|
||||||
|
$values = [];
|
||||||
|
|
||||||
|
foreach ($temporary as $item) {
|
||||||
|
if (! is_array($item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = implode('.', array_slice($parts, $index + 1));
|
||||||
|
$extracted = self::get((string) $key, $item, $default);
|
||||||
|
|
||||||
|
if (is_array($extracted)) {
|
||||||
|
$values = array_merge($values, $extracted);
|
||||||
|
} else if ($extracted === $default) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$values[] = $extracted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isNotArray = ! is_array($temporary);
|
||||||
|
$isNotExisting = $isNotArray || ! array_key_exists($part, $temporary);
|
||||||
|
|
||||||
|
if ($isNotExisting) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$temporary = $temporary[$part];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $temporary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test array having value inside using key dot notation is supported.
|
||||||
|
*
|
||||||
|
* If passed strict as true then will return true if any of the wildcard
|
||||||
|
* paths exist else will return true only if all wildcard paths exist.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param array $data
|
||||||
|
* @param bool $strict
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function has(string $key, array $data, bool $strict = false): bool
|
||||||
|
{
|
||||||
|
$parts = explode('.', $key);
|
||||||
|
$any = ! $strict;
|
||||||
|
|
||||||
|
$temporary = $data;
|
||||||
|
|
||||||
|
foreach ($parts as $index => $part) {
|
||||||
|
if ($part === '*') {
|
||||||
|
if (! is_array($temporary)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$test = null;
|
||||||
|
|
||||||
|
foreach ($temporary as $item) {
|
||||||
|
$key = implode('.', array_slice($parts, $index + 1));
|
||||||
|
$has = self::has($key, $item, $strict);
|
||||||
|
|
||||||
|
if ($strict) {
|
||||||
|
$test = $has && (is_null($test) ? true : $test);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($any && $has) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_null($test) ? false : $test;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isNotArray = ! is_array($temporary);
|
||||||
|
$isNotExisting = $isNotArray || ! array_key_exists($part, $temporary);
|
||||||
|
|
||||||
|
if ($isNotExisting) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$temporary = $temporary[$part];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set value inside array using key dot notation support
|
||||||
|
* Also supported wildcard keys as "users.*.name" and alike
|
||||||
|
*
|
||||||
|
* @param string $key Includes dot notation support
|
||||||
|
* @param array $data
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function set(string $key, array $data, mixed $value): array
|
||||||
|
{
|
||||||
|
$parts = explode('.', $key);
|
||||||
|
$last = array_pop($parts);
|
||||||
|
|
||||||
|
$temporary = &$data;
|
||||||
|
|
||||||
|
foreach ($parts as $index => $part) {
|
||||||
|
if ($part === '*') {
|
||||||
|
$isNotArray = ! is_array($temporary);
|
||||||
|
$isEmptyArray = is_array($temporary) && ! count($temporary);
|
||||||
|
|
||||||
|
if ($isNotArray || $isEmptyArray) {
|
||||||
|
$temporary[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($temporary as $i => $item) {
|
||||||
|
$actuallyParts = array_slice($parts, $index + 1);
|
||||||
|
$actuallyParts[] = $last;
|
||||||
|
|
||||||
|
$key = implode('.', $actuallyParts);
|
||||||
|
|
||||||
|
$temporary[$i] = self::set($key, $item, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isNotExisting = ! array_key_exists($part, $temporary);
|
||||||
|
$isNotArray = $isNotExisting || ! is_array($temporary[$part]);
|
||||||
|
|
||||||
|
if ($isNotExisting && $isNotArray) {
|
||||||
|
$temporary[$part] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$temporary = &$temporary[$part];
|
||||||
|
}
|
||||||
|
|
||||||
|
$temporary[$last] = $value;
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
tests/BuilderTest.php
Normal file
57
tests/BuilderTest.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Builder;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Exception\TargetIsNull;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Builder::class)]
|
||||||
|
#[CoversMethod(Builder::class, 'withTarget')]
|
||||||
|
#[CoversMethod(Builder::class, 'item')]
|
||||||
|
#[CoversMethod(Builder::class, 'target')]
|
||||||
|
#[CoversMethod(Builder::class, 'build')]
|
||||||
|
class BuilderTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testBuildingWithTarget(): void
|
||||||
|
{
|
||||||
|
$builder = Builder::withTarget('entity', 'field');
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Builder::class, $builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItemsInsideTheRequest(): void
|
||||||
|
{
|
||||||
|
$builder = new Builder();
|
||||||
|
$builder->item('key', 'alias');
|
||||||
|
|
||||||
|
$request = $builder->target('entity', 'field')->build();
|
||||||
|
|
||||||
|
$this->assertCount(1, $request->items()->getIterator());
|
||||||
|
$this->assertEquals('key', $request->items()->getIterator()[0]->key());
|
||||||
|
$this->assertEquals('alias', $request->items()->getIterator()[0]->alias());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTargetInsideTheRequest(): void
|
||||||
|
{
|
||||||
|
$builder = new Builder();
|
||||||
|
$builder->target('entity', 'field');
|
||||||
|
|
||||||
|
$request = $builder->build();
|
||||||
|
|
||||||
|
$this->assertEquals('entity', $request->target()->entity());
|
||||||
|
$this->assertEquals('field', $request->target()->field());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBuildThrowsExceptionWhenTargetIsNull(): void
|
||||||
|
{
|
||||||
|
$this->expectException(TargetIsNull::class);
|
||||||
|
|
||||||
|
$builder = new Builder();
|
||||||
|
$builder->build();
|
||||||
|
}
|
||||||
|
}
|
||||||
116
tests/EnricherTest.php
Normal file
116
tests/EnricherTest.php
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Enricher;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Repository;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Enrichment;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Repositories;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Enricher::class)]
|
||||||
|
class EnricherTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEnrich(): void
|
||||||
|
{
|
||||||
|
$posts = [
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id' => 2,
|
||||||
|
'content' => 'Post by user 2'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id' => 3,
|
||||||
|
'content' => 'Post by user 3'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$enrichment = new Enrichment($this->getRepositories());
|
||||||
|
$enricher = new Enricher($enrichment);
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
$items->push(new Item('*.user_id', 'user'));
|
||||||
|
|
||||||
|
$target = new Target('user', 'id');
|
||||||
|
|
||||||
|
$requests = new Requests([
|
||||||
|
new Request($items, $target)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$enriched = $enricher->enrich($posts, $requests);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1',
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'Blank.1',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 2,
|
||||||
|
'content' => 'Post by user 2',
|
||||||
|
'user' => [
|
||||||
|
'id' => 2,
|
||||||
|
'name' => 'Blank.2',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched[1]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 3,
|
||||||
|
'content' => 'Post by user 3',
|
||||||
|
'user' => [
|
||||||
|
'id' => 3,
|
||||||
|
'name' => 'Blank.3',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched[2]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRepositories(): Repositories
|
||||||
|
{
|
||||||
|
return new Repositories([
|
||||||
|
'user' => new EnricherTestUserRepository(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnricherTestUserRepository implements Repository
|
||||||
|
{
|
||||||
|
public function getByFieldValues(string $field, array $values): iterable
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$this->getBlankWithId(1),
|
||||||
|
$this->getBlankWithId(2),
|
||||||
|
$this->getBlankWithId(3),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBlankWithId(int $id): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $id,
|
||||||
|
'name' => sprintf('Blank.%d', $id)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
127
tests/MessageTest.php
Normal file
127
tests/MessageTest.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Exception\PayloadIsNotJson;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Header;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Message;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Parser;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Serializer;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||||
|
use Nyholm\Psr7\Stream;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Message::class)]
|
||||||
|
#[CoversMethod(Message::class, 'setRequests')]
|
||||||
|
#[CoversMethod(Message::class, 'getRequests')]
|
||||||
|
class MessageTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testSetRequests(): void
|
||||||
|
{
|
||||||
|
$serializer = new Serializer();
|
||||||
|
$parser = new Parser();
|
||||||
|
$message = new Message($serializer, $parser);
|
||||||
|
|
||||||
|
$psrFactory = new Psr17Factory();
|
||||||
|
$psrMessage = $psrFactory->createResponse();
|
||||||
|
|
||||||
|
$itemsBag = new ItemsBag();
|
||||||
|
$itemsBag->push(new Item('key1', 'alias1'));
|
||||||
|
|
||||||
|
$target = new Target('entity', 'field');
|
||||||
|
|
||||||
|
$requests = new Requests([
|
||||||
|
new Request($itemsBag, $target)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$header = Header::XEnrichmentRequest;
|
||||||
|
$result = $message->setRequests($psrMessage, $header, $requests);
|
||||||
|
|
||||||
|
$this->assertTrue($result->hasHeader($header->value));
|
||||||
|
$this->assertNotEmpty($result->getHeader($header->value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetRequests(): void
|
||||||
|
{
|
||||||
|
$serializer = new Serializer();
|
||||||
|
$parser = new Parser();
|
||||||
|
$message = new Message($serializer, $parser);
|
||||||
|
|
||||||
|
$header = Header::XEnrichmentRequest;
|
||||||
|
|
||||||
|
$psrFactory = new Psr17Factory();
|
||||||
|
$psrMessage = $psrFactory->createResponse()
|
||||||
|
->withHeader($header->value, 'key1+alias1@entity,field');
|
||||||
|
|
||||||
|
$requests = $message->getRequests($psrMessage, $header);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Requests::class, $requests);
|
||||||
|
$this->assertCount(1, $requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPayload(): void
|
||||||
|
{
|
||||||
|
$serializer = new Serializer();
|
||||||
|
$parser = new Parser();
|
||||||
|
$message = new Message($serializer, $parser);
|
||||||
|
|
||||||
|
$psrFactory = new Psr17Factory();
|
||||||
|
$psrMessage = $psrFactory->createResponse()
|
||||||
|
->withBody(Stream::create('{"key":"value"}'));
|
||||||
|
|
||||||
|
$payload = $message->getPayload($psrMessage);
|
||||||
|
|
||||||
|
$this->assertIsArray($payload);
|
||||||
|
$this->assertSame(['key' => 'value'], $payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetPayloadThrowsExceptionOnNonJson(): void
|
||||||
|
{
|
||||||
|
$this->expectException(\JsonException::class);
|
||||||
|
|
||||||
|
$serializer = new Serializer();
|
||||||
|
$parser = new Parser();
|
||||||
|
$message = new Message($serializer, $parser);
|
||||||
|
|
||||||
|
$psrFactory = new Psr17Factory();
|
||||||
|
$psrMessage = $psrFactory->createResponse()
|
||||||
|
->withBody(Stream::create('Invalid JSON'));
|
||||||
|
|
||||||
|
$this->expectException(PayloadIsNotJson::class);
|
||||||
|
|
||||||
|
$message->getPayload($psrMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetPayload(): void
|
||||||
|
{
|
||||||
|
$serializer = new Serializer();
|
||||||
|
$parser = new Parser();
|
||||||
|
$message = new Message($serializer, $parser);
|
||||||
|
|
||||||
|
$psrFactory = new Psr17Factory();
|
||||||
|
$psrMessage = $psrFactory->createResponse();
|
||||||
|
|
||||||
|
$payload = ['key' => 'value'];
|
||||||
|
$payloadString = json_encode($payload, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
$result = $message->setPayload($psrMessage, $payload);
|
||||||
|
|
||||||
|
$this->assertEquals($payloadString, (string) $result->getBody());
|
||||||
|
|
||||||
|
$payloadNew = ['newKey' => 'newValue'];
|
||||||
|
$payloadNewString = json_encode($payloadNew, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
$resultNew = $message->setPayload($result, $payloadNew);
|
||||||
|
|
||||||
|
$this->assertEquals($payloadNewString, (string) $resultNew->getBody());
|
||||||
|
}
|
||||||
|
}
|
||||||
24
tests/Object/ItemTest.php
Normal file
24
tests/Object/ItemTest.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Interface;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Item::class)]
|
||||||
|
#[CoversMethod(Item::class, 'key')]
|
||||||
|
#[CoversMethod(Item::class, 'alias')]
|
||||||
|
class ItemTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testProperlyInitialized(): void
|
||||||
|
{
|
||||||
|
$item = new Item('key', 'alias');
|
||||||
|
|
||||||
|
$this->assertEquals('key', $item->key());
|
||||||
|
$this->assertEquals('alias', $item->alias());
|
||||||
|
}
|
||||||
|
}
|
||||||
28
tests/Object/ItemsBagTest.php
Normal file
28
tests/Object/ItemsBagTest.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Object;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(ItemsBag::class)]
|
||||||
|
#[CoversMethod(ItemsBag::class, 'push')]
|
||||||
|
#[CoversMethod(ItemsBag::class, 'getIterator')]
|
||||||
|
class ItemsBagTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testProperlyInitialized(): void
|
||||||
|
{
|
||||||
|
$itemsBag = new ItemsBag();
|
||||||
|
$item = new Item('key', 'alias');
|
||||||
|
|
||||||
|
$itemsBag->push($item);
|
||||||
|
|
||||||
|
$this->assertCount(1, iterator_to_array($itemsBag->getIterator()));
|
||||||
|
$this->assertSame($item, iterator_to_array($itemsBag->getIterator())[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
tests/Object/RequestTest.php
Normal file
28
tests/Object/RequestTest.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Object;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Request::class)]
|
||||||
|
#[CoversMethod(Request::class, 'items')]
|
||||||
|
#[CoversMethod(Request::class, 'target')]
|
||||||
|
class RequestTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testProperlyInitialized(): void
|
||||||
|
{
|
||||||
|
$itemsBag = new ItemsBag();
|
||||||
|
$target = new Target('entity', 'field');
|
||||||
|
$request = new Request($itemsBag, $target);
|
||||||
|
|
||||||
|
$this->assertSame($itemsBag, $request->items());
|
||||||
|
$this->assertSame($target, $request->target());
|
||||||
|
}
|
||||||
|
}
|
||||||
24
tests/Object/TargetTest.php
Normal file
24
tests/Object/TargetTest.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Object;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Target::class)]
|
||||||
|
#[CoversMethod(Target::class, 'entity')]
|
||||||
|
#[CoversMethod(Target::class, 'field')]
|
||||||
|
class TargetTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testProperlyInitialized(): void
|
||||||
|
{
|
||||||
|
$target = new Target('entity', 'field');
|
||||||
|
|
||||||
|
$this->assertEquals('entity', $target->entity());
|
||||||
|
$this->assertEquals('field', $target->field());
|
||||||
|
}
|
||||||
|
}
|
||||||
403
tests/Service/EnrichmentTest.php
Normal file
403
tests/Service/EnrichmentTest.php
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Enrichment as EnrichmentInterface;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Repository;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Enrichment;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Repositories;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Enrichment::class)]
|
||||||
|
#[CoversMethod(Enrichment::class, 'enrich')]
|
||||||
|
class EnrichmentTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testImplementsEnrichmentInterface(): void
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf(
|
||||||
|
EnrichmentInterface::class,
|
||||||
|
new Enrichment($this->getRepositories())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnrichmentJsonObjectWithArraysInside(): void
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
"data" => [
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id' => 2,
|
||||||
|
'content' => 'Post by user 2'
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
$items->push(new Item('data.*.user_id', 'user'));
|
||||||
|
|
||||||
|
$target = new Target('user', 'id');
|
||||||
|
|
||||||
|
$requests = new Requests();
|
||||||
|
$requests->append(new Request($items, $target));
|
||||||
|
|
||||||
|
$enrichment = new Enrichment($this->getRepositories());
|
||||||
|
$enriched = $enrichment->enrich($payload, $requests);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1',
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'Blank.1',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched['data'][0]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 2,
|
||||||
|
'content' => 'Post by user 2',
|
||||||
|
'user' => [
|
||||||
|
'id' => 2,
|
||||||
|
'name' => 'Blank.2',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched['data'][1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnrichmentJsonObject(): void
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1'
|
||||||
|
];
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
$items->push(new Item('user_id', 'user'));
|
||||||
|
|
||||||
|
$target = new Target('user', 'id');
|
||||||
|
|
||||||
|
$requests = new Requests();
|
||||||
|
$requests->append(new Request($items, $target));
|
||||||
|
|
||||||
|
$enrichment = new Enrichment($this->getRepositories());
|
||||||
|
$enriched = $enrichment->enrich($payload, $requests);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1',
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'Blank.1',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnrichArrayItemsUsingProperlyRequest(): void
|
||||||
|
{
|
||||||
|
$posts = [
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id' => 2,
|
||||||
|
'content' => 'Post by user 2'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id' => 3,
|
||||||
|
'content' => 'Post by user 3'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
$items->push(new Item('*.user_id', 'user'));
|
||||||
|
|
||||||
|
$target = new Target('user', 'id');
|
||||||
|
|
||||||
|
$requests = new Requests();
|
||||||
|
$requests->append(new Request($items, $target));
|
||||||
|
|
||||||
|
$enrichment = new Enrichment($this->getRepositories());
|
||||||
|
$enriched = $enrichment->enrich($posts, $requests);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1',
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'Blank.1',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 2,
|
||||||
|
'content' => 'Post by user 2',
|
||||||
|
'user' => [
|
||||||
|
'id' => 2,
|
||||||
|
'name' => 'Blank.2',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched[1]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 3,
|
||||||
|
'content' => 'Post by user 3',
|
||||||
|
'user' => [
|
||||||
|
'id' => 3,
|
||||||
|
'name' => 'Blank.3',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched[2]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tesEnrichArrayItemsWithoutExistingTargets(): void
|
||||||
|
{
|
||||||
|
$posts = [
|
||||||
|
[
|
||||||
|
'user_id' => 10,
|
||||||
|
'content' => 'Post by user 10'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user_id' => 20,
|
||||||
|
'content' => 'Post by user 20'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
$items->push(new Item('*.user_id', 'user'));
|
||||||
|
|
||||||
|
$target = new Target('user', 'id');
|
||||||
|
|
||||||
|
$requests = new Requests();
|
||||||
|
$requests->append(new Request($items, $target));
|
||||||
|
|
||||||
|
$enrichment = new Enrichment($this->getRepositories());
|
||||||
|
$enriched = $enrichment->enrich($posts, $requests);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 10,
|
||||||
|
'content' => 'Post by user 10',
|
||||||
|
'user' => null,
|
||||||
|
],
|
||||||
|
$enriched[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 20,
|
||||||
|
'content' => 'Post by user 20',
|
||||||
|
'user' => null,
|
||||||
|
],
|
||||||
|
$enriched[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnrichArrayUsingNotWildcardKey(): void
|
||||||
|
{
|
||||||
|
$posts = [
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
$items->push(
|
||||||
|
new Item('data.invalid_path', 'user')
|
||||||
|
);
|
||||||
|
|
||||||
|
$target = new Target('user', 'id');
|
||||||
|
|
||||||
|
$requests = new Requests();
|
||||||
|
$requests->append(new Request($items, $target));
|
||||||
|
|
||||||
|
$enrichment = new Enrichment($this->getRepositories());
|
||||||
|
$enriched = $enrichment->enrich($posts, $requests);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1',
|
||||||
|
],
|
||||||
|
'data' => [
|
||||||
|
'user' => null
|
||||||
|
]
|
||||||
|
],
|
||||||
|
$enriched
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnrichNestedArrayItems(): void
|
||||||
|
{
|
||||||
|
$categoriesGroups = [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'category_id' => 1,
|
||||||
|
'name' => 'Category 1',
|
||||||
|
'creator_id' => 1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'category_id' => 2,
|
||||||
|
'name' => 'Category 2',
|
||||||
|
'creator_id' => 3
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
$items->push(new Item('*.*.creator_id', 'creator'));
|
||||||
|
|
||||||
|
$target = new Target('user', 'id');
|
||||||
|
|
||||||
|
$requests = new Requests();
|
||||||
|
$requests->append(new Request($items, $target));
|
||||||
|
|
||||||
|
$enrichment = new Enrichment($this->getRepositories());
|
||||||
|
$enriched = $enrichment->enrich($categoriesGroups, $requests);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'category_id' => 1,
|
||||||
|
'name' => 'Category 1',
|
||||||
|
'creator_id' => 1,
|
||||||
|
'creator' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'Blank.1',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'category_id' => 2,
|
||||||
|
'name' => 'Category 2',
|
||||||
|
'creator_id' => 3,
|
||||||
|
'creator' => [
|
||||||
|
'id' => 3,
|
||||||
|
'name' => 'Blank.3',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
$enriched
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProperlyWorkingWithRepositoriesReturnNonArrayAccessObject(): void
|
||||||
|
{
|
||||||
|
$posts = [
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1'
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$items = new ItemsBag();
|
||||||
|
$items->push(new Item('*.user_id', 'user'));
|
||||||
|
|
||||||
|
$target = new Target('user', 'id');
|
||||||
|
|
||||||
|
$requests = new Requests();
|
||||||
|
$requests->append(new Request($items, $target));
|
||||||
|
|
||||||
|
$enrichment = new Enrichment($this->getRepositories(true));
|
||||||
|
$enriched = $enrichment->enrich($posts, $requests);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'user_id' => 1,
|
||||||
|
'content' => 'Post by user 1',
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'Blank.1',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
$enriched[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRepositories(bool $repoUsingGenerator = false): Repositories
|
||||||
|
{
|
||||||
|
return new Repositories([
|
||||||
|
'user' => $repoUsingGenerator
|
||||||
|
? new EnrichmentTestUserRepositoryUsingGenerator()
|
||||||
|
: new EnrichmentTestUserRepository()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnrichmentTestUserRepository implements Repository
|
||||||
|
{
|
||||||
|
public function getByFieldValues(string $field, array $values): iterable
|
||||||
|
{
|
||||||
|
return array_filter(
|
||||||
|
[
|
||||||
|
$this->getBlankWithId(1),
|
||||||
|
$this->getBlankWithId(2),
|
||||||
|
$this->getBlankWithId(3),
|
||||||
|
],
|
||||||
|
fn ($item) => in_array($item[$field], $values, true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBlankWithId(int $id): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $id,
|
||||||
|
'name' => sprintf('Blank.%d', $id)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnrichmentTestUserRepositoryUsingGenerator implements Repository
|
||||||
|
{
|
||||||
|
public function getByFieldValues(string $field, array $values): iterable
|
||||||
|
{
|
||||||
|
$items = [
|
||||||
|
$this->getBlankWithId(1),
|
||||||
|
$this->getBlankWithId(2),
|
||||||
|
$this->getBlankWithId(3),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (in_array($item[$field], $values, true)) {
|
||||||
|
yield $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBlankWithId(int $id): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $id,
|
||||||
|
'name' => sprintf('Blank.%d', $id)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
66
tests/Service/ParserTest.php
Normal file
66
tests/Service/ParserTest.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Parser;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Exception\InvalidRequest;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Parser as ParserInterface;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Parser::class)]
|
||||||
|
#[CoversMethod(Parser::class, 'parse')]
|
||||||
|
class ParserTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testImplementsParserInterface(): void
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf(ParserInterface::class, new Parser());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseValidInput(): void
|
||||||
|
{
|
||||||
|
$parser = new Parser();
|
||||||
|
$input = 'key1+alias1,key2+alias2@entity,field';
|
||||||
|
|
||||||
|
$request = $parser->parse($input);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(Request::class, $request);
|
||||||
|
$this->assertEquals('entity', $request->target()->entity());
|
||||||
|
$this->assertEquals('field', $request->target()->field());
|
||||||
|
|
||||||
|
$items = iterator_to_array($request->items()->getIterator());
|
||||||
|
$this->assertCount(2, $items);
|
||||||
|
$this->assertEquals('key1', $items[0]->key());
|
||||||
|
$this->assertEquals('alias1', $items[0]->alias());
|
||||||
|
$this->assertEquals('key2', $items[1]->key());
|
||||||
|
$this->assertEquals('alias2', $items[1]->alias());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseThrowsExceptionOnEmptyTarget(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidRequest::class);
|
||||||
|
|
||||||
|
$parser = new Parser();
|
||||||
|
$parser->parse('key1+alias1,key2+alias2@');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseThrowsExceptionOnInvalidTarget(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidRequest::class);
|
||||||
|
|
||||||
|
$parser = new Parser();
|
||||||
|
$parser->parse('key1+alias1,key2+alias2@entity');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseThrowsExceptionOnEmptyInput(): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidRequest::class);
|
||||||
|
|
||||||
|
$parser = new Parser();
|
||||||
|
$parser->parse('');
|
||||||
|
}
|
||||||
|
}
|
||||||
55
tests/Service/SerializerTest.php
Normal file
55
tests/Service/SerializerTest.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Service;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Serializer as SerializerInterface;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Item;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Service\Serializer;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Serializer::class)]
|
||||||
|
#[CoversMethod(Serializer::class, 'toString')]
|
||||||
|
class SerializerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testImplementsSerializerInterface(): void
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf(SerializerInterface::class, new Serializer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToString(): void
|
||||||
|
{
|
||||||
|
$itemsBag = new ItemsBag();
|
||||||
|
$itemsBag->push(new Item('key1', 'alias1'));
|
||||||
|
$itemsBag->push(new Item('key2', 'alias2'));
|
||||||
|
|
||||||
|
$target = new Target('entity', 'field');
|
||||||
|
$request = new Request($itemsBag, $target);
|
||||||
|
|
||||||
|
$serializer = new Serializer();
|
||||||
|
$result = $serializer->toString($request);
|
||||||
|
|
||||||
|
$this->assertEquals('key1+alias1,key2+alias2@entity,field', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testToStringWithEmptyAlias(): void
|
||||||
|
{
|
||||||
|
$itemsBag = new ItemsBag();
|
||||||
|
$itemsBag->push(new Item('key1'));
|
||||||
|
$itemsBag->push(new Item('key2', 'alias2'));
|
||||||
|
|
||||||
|
$target = new Target('entity', 'field');
|
||||||
|
$request = new Request($itemsBag, $target);
|
||||||
|
|
||||||
|
$serializer = new Serializer();
|
||||||
|
$result = $serializer->toString($request);
|
||||||
|
|
||||||
|
$this->assertEquals('key1,key2+alias2@entity,field', $result);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
tests/Storage/RepositoriesTest.php
Normal file
44
tests/Storage/RepositoriesTest.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Storage;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Exception\RepositoryNotFound;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Interface\Repository;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Repositories;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Repositories::class)]
|
||||||
|
#[CoversMethod(Repositories::class, 'set')]
|
||||||
|
#[CoversMethod(Repositories::class, 'get')]
|
||||||
|
class RepositoriesTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testSetAndGet(): void
|
||||||
|
{
|
||||||
|
$repositories = new Repositories();
|
||||||
|
$mockRepository = new class implements Repository {
|
||||||
|
public function getByFieldValues(string $field, array $values): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['field' => $field, 'value' => $values[0]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$repositories->set('target', $mockRepository);
|
||||||
|
|
||||||
|
$retrievedRepository = $repositories->get('target');
|
||||||
|
$this->assertSame($mockRepository, $retrievedRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetThrowsExceptionWhenRepositoryNotFound(): void
|
||||||
|
{
|
||||||
|
$this->expectException(RepositoryNotFound::class);
|
||||||
|
|
||||||
|
$repositories = new Repositories();
|
||||||
|
$repositories->get('nonexistent_target');
|
||||||
|
}
|
||||||
|
}
|
||||||
45
tests/Storage/RequestsTest.php
Normal file
45
tests/Storage/RequestsTest.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Storage;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\ItemsBag;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Request;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Object\Target;
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Storage\Requests;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Requests::class)]
|
||||||
|
#[CoversMethod(Requests::class, 'append')]
|
||||||
|
#[CoversMethod(Requests::class, 'count')]
|
||||||
|
#[CoversMethod(Requests::class, 'requests')]
|
||||||
|
class RequestsTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testAppendAndCount(): void
|
||||||
|
{
|
||||||
|
$requests = new Requests();
|
||||||
|
$this->assertEquals(0, $requests->count());
|
||||||
|
|
||||||
|
$request = new Request(new ItemsBag(), new Target('entity', 'field'));
|
||||||
|
$requests->append($request);
|
||||||
|
|
||||||
|
$this->assertEquals(1, $requests->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRequestsAreSame(): void
|
||||||
|
{
|
||||||
|
$request1 = new Request(new ItemsBag(), new Target('entity', 'field'));
|
||||||
|
$request2 = new Request(new ItemsBag(), new Target('entity2', 'field2'));
|
||||||
|
|
||||||
|
$requests = new Requests([$request1, $request2]);
|
||||||
|
|
||||||
|
$requestsArray = iterator_to_array($requests);
|
||||||
|
|
||||||
|
$this->assertCount(2, $requestsArray);
|
||||||
|
$this->assertSame($request1, $requestsArray[0]);
|
||||||
|
$this->assertSame($request2, $requestsArray[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
322
tests/Utility/ArrTest.php
Normal file
322
tests/Utility/ArrTest.php
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Diffhead\PHP\DataEnrichmentKit\Tests\Utility;
|
||||||
|
|
||||||
|
use Diffhead\PHP\DataEnrichmentKit\Utility\Arr;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[CoversClass(Arr::class)]
|
||||||
|
#[CoversMethod(Arr::class, 'get')]
|
||||||
|
#[CoversMethod(Arr::class, 'set')]
|
||||||
|
class ArrTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetExistingItems(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'John Doe',
|
||||||
|
'address' => [
|
||||||
|
'city' => 'New York',
|
||||||
|
'zip' => '10001',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'posts' => [
|
||||||
|
['id' => 101, 'title' => 'First Post'],
|
||||||
|
['id' => 102, 'title' => 'Second Post'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals(1, Arr::get('user.id', $array));
|
||||||
|
$this->assertEquals('John Doe', Arr::get('user.name', $array));
|
||||||
|
$this->assertEquals('New York', Arr::get('user.address.city', $array));
|
||||||
|
$this->assertEquals('10001', Arr::get('user.address.zip', $array));
|
||||||
|
|
||||||
|
$this->assertEquals(101, Arr::get('posts.0.id', $array));
|
||||||
|
$this->assertEquals('First Post', Arr::get('posts.0.title', $array));
|
||||||
|
|
||||||
|
$this->assertEquals(102, Arr::get('posts.1.id', $array));
|
||||||
|
$this->assertEquals('Second Post', Arr::get('posts.1.title', $array));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetExistingItemsFromNestedArray(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
[
|
||||||
|
'orders' => [
|
||||||
|
['id' => 201, 'amount' => 150],
|
||||||
|
['id' => 202, 'amount' => 200],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'orders' => [
|
||||||
|
['id' => 203, 'amount' => 250],
|
||||||
|
['id' => 204, 'amount' => 300],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals(201, Arr::get('0.orders.0.id', $array));
|
||||||
|
$this->assertEquals(200, Arr::get('0.orders.1.amount', $array));
|
||||||
|
$this->assertEquals(203, Arr::get('1.orders.0.id', $array));
|
||||||
|
$this->assertEquals(300, Arr::get('1.orders.1.amount', $array));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMultipleExistingItems(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
'users' => [
|
||||||
|
['name' => 'John Doe'],
|
||||||
|
['name' => 'Jane Smith'],
|
||||||
|
['name' => 'Alice Johnson'],
|
||||||
|
],
|
||||||
|
'posts' => [
|
||||||
|
'categories' => [
|
||||||
|
[
|
||||||
|
'name' => 'Tech',
|
||||||
|
'posts' => [
|
||||||
|
['title' => 'Latest Tech Trends'],
|
||||||
|
['title' => 'AI Innovations']
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Health',
|
||||||
|
'posts' => [
|
||||||
|
['title' => 'Wellness Tips'],
|
||||||
|
['title' => 'Nutrition Advice']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'John Doe',
|
||||||
|
'Jane Smith',
|
||||||
|
'Alice Johnson'
|
||||||
|
],
|
||||||
|
Arr::get('users.*.name', $array)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
'Latest Tech Trends',
|
||||||
|
'AI Innovations',
|
||||||
|
'Wellness Tips',
|
||||||
|
'Nutrition Advice'
|
||||||
|
],
|
||||||
|
Arr::get('posts.categories.*.posts.*.title', $array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMultipleExistingItemsInsideNestedArray(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
[
|
||||||
|
'orders' => [
|
||||||
|
['id' => 201, 'amount' => 150],
|
||||||
|
['id' => 202, 'amount' => 200],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'orders' => [
|
||||||
|
['id' => 203, 'amount' => 250],
|
||||||
|
['id' => 204, 'amount' => 300],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
[
|
||||||
|
150,
|
||||||
|
200,
|
||||||
|
250,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
Arr::get('*.orders.*.amount', $array)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetNonExistingItems(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'John Doe',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertNull(Arr::get('user.age', $array));
|
||||||
|
$this->assertEquals('Unknown', Arr::get('user.age', $array, 'Unknown'));
|
||||||
|
$this->assertNull(Arr::get('user.address.city', $array));
|
||||||
|
$this->assertEquals('N/A', Arr::get('user.address.city', $array, 'N/A'));
|
||||||
|
$this->assertNull(Arr::get('posts.0.id', $array));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetMultipleNonExistingItems(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
'users' => [
|
||||||
|
['name' => 'John Doe'],
|
||||||
|
['name' => 'Jane Smith'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame([], Arr::get('users.*.age', $array));
|
||||||
|
$this->assertSame([], Arr::get('users.*.address.*.city', $array));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetValuesAtExistingPaths(): void
|
||||||
|
{
|
||||||
|
$origin = [
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'John Doe',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$updated = Arr::set('user.name', $origin, 'Jane Smith');
|
||||||
|
$updated = Arr::set('user.id', $updated, 2);
|
||||||
|
|
||||||
|
$this->assertEquals('Jane Smith', $updated['user']['name']);
|
||||||
|
$this->assertEquals(2, $updated['user']['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetMultipleValuesAtExistingPaths(): void
|
||||||
|
{
|
||||||
|
$origin = [
|
||||||
|
'users' => [
|
||||||
|
['name' => 'John Doe'],
|
||||||
|
['name' => 'Jane Smith'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$updated = Arr::set('users.*.age', $origin, 29);
|
||||||
|
$updated = Arr::set('users.*.source', $updated, 'facebook');
|
||||||
|
|
||||||
|
$this->assertEquals(29, $updated['users'][0]['age']);
|
||||||
|
$this->assertEquals(29, $updated['users'][1]['age']);
|
||||||
|
|
||||||
|
$this->assertEquals('facebook', $updated['users'][0]['source']);
|
||||||
|
$this->assertEquals('facebook', $updated['users'][1]['source']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetValuesAtNonExistingPaths(): void
|
||||||
|
{
|
||||||
|
$origin = [
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'John Doe',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$updated = Arr::set('user.address.city', $origin, 'New York');
|
||||||
|
$updated = Arr::set('user.address.zip', $updated, '10001');
|
||||||
|
$updated = Arr::set('posts.0.id', $updated, 101);
|
||||||
|
$updated = Arr::set('posts.0.title', $updated, 'First Post');
|
||||||
|
|
||||||
|
$this->assertEquals('New York', $updated['user']['address']['city']);
|
||||||
|
$this->assertEquals('10001', $updated['user']['address']['zip']);
|
||||||
|
$this->assertEquals(101, $updated['posts'][0]['id']);
|
||||||
|
$this->assertEquals('First Post', $updated['posts'][0]['title']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetMultipleValuesAtNonExistingPaths(): void
|
||||||
|
{
|
||||||
|
$origin = [
|
||||||
|
'users' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
$updated = Arr::set('users.*.name', $origin, 'John Doe');
|
||||||
|
$updated = Arr::set('users.*.age', $updated, 30);
|
||||||
|
|
||||||
|
$this->assertCount(1, $updated['users']);
|
||||||
|
$this->assertEquals('John Doe', $updated['users'][0]['name']);
|
||||||
|
$this->assertEquals(30, $updated['users'][0]['age']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHavingValueAtExistingPath(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
'user' => [
|
||||||
|
'id' => 1,
|
||||||
|
'name' => 'John Doe',
|
||||||
|
'address' => [
|
||||||
|
'city' => 'New York',
|
||||||
|
'zip' => '10001',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertTrue(Arr::has('user.id', $array));
|
||||||
|
$this->assertTrue(Arr::has('user.name', $array));
|
||||||
|
$this->assertTrue(Arr::has('user.address.city', $array));
|
||||||
|
$this->assertTrue(Arr::has('user.address.zip', $array));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHavingValueAtExistingPathUsingWildcardNonStrictMode(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
'users' => [
|
||||||
|
['age' => 25],
|
||||||
|
['name' => 'Jane Smith'],
|
||||||
|
[
|
||||||
|
'name' => 'Alice Johnson',
|
||||||
|
'phones' => [
|
||||||
|
['type' => 'mobile', 'number' => '123-456-7890'],
|
||||||
|
['type' => 'home', 'number' => '098-765-4321'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertTrue(Arr::has('users.*.name', $array));
|
||||||
|
$this->assertTrue(Arr::has('users.*.phones.*.number', $array));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHavingValueAtExistingPathUsingWildcardStrictMode(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
'users' => [
|
||||||
|
['age' => 25],
|
||||||
|
['name' => 'Alice Brandon'],
|
||||||
|
],
|
||||||
|
'users_with_phones' => [
|
||||||
|
[
|
||||||
|
'name' => 'Bob Brown',
|
||||||
|
'phones' => [
|
||||||
|
['type' => 'mobile', 'number' => '555-555-5555'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Carol White',
|
||||||
|
'phones' => [
|
||||||
|
['type' => 'home', 'number' => '444-444-4444'],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertFalse(Arr::has('users.*.name', $array, true));
|
||||||
|
$this->assertTrue(Arr::has('users_with_phones.*.phones.*.number', $array, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHavingValueAtNonExistingPathUsingWildcard(): void
|
||||||
|
{
|
||||||
|
$array = [
|
||||||
|
'users' => [
|
||||||
|
['name' => 'John Doe'],
|
||||||
|
['name' => 'Jane Smith'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertFalse(Arr::has('users.*.age', $array));
|
||||||
|
$this->assertFalse(Arr::has('users.*.address.city', $array));
|
||||||
|
$this->assertFalse(Arr::has('users.*.phones.*.type', $array));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user