diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a9875b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/README.md b/README.md new file mode 100644 index 0000000..b07c5b0 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# Array mapping + +The goal of the library is to map two arrays (in and out) with an YAML file config. + +## Install + +WIP, not official lib yet + +```bash +composer require valouleloup/array-mapping +``` + +## Usage + +#### Yaml Mapping + +```yaml +transformer: + mapping: + civilite: + from: 'demandeur.civilite' + to: 'civ' + function: + name: 'buildCivilite' + params: + - 'demandeur.civilite' + nom: + from: 'demandeur.nomUsage' + to: 'nom' + required: true + prenom: + from: 'demandeur.prenom' + to: 'prenom' + required: true + dateNaissance: + from: 'demandeur.dateNaissance' + to: 'dnat' + function: + name: 'convertDate' + params: + - 'demandeur.dateNaissance' + villeNaissance: + from: 'demandeur.naissanceVille' + to: 'villenaiss' +``` + +* ` from ` : string, the key's path of the ` in ` array +* ` to ` : string, the key's path of the ` out ` array +* ` function ` : array, the php function to use to transform the data + * ` name ` : string, the function's name + * ` params ` : array, the function's parameters +* ` required ` : boolean, if the key is ` required ` +* WIP ` condition ` : array, the conditions that specify if the key is required or not + +#### Transformer + +Your transformer needs to extends the ` MappingTransformer ` class : + +```php += 3.4 + $config = Yaml::parse(file_get_contents(__DIR__ . '/mapping.yml')); + $result = $this->transformFromMapping($config['transformer']['mapping'], $dataBefore); + ... + } + + /** + * @param $value + * + * @return string + */ + public function convertDate($value) + { + $date = new DateTime($value); + + return $date->format('d/m/Y'); + } + +} +``` + +The ` $result ` variable now contains the new array. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..42f1965 --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "valouleloup/array-mapping", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Valentin Faugeroux", + "email": "faugerouxvalentin@gmail.com" + } + ], + "autoload": { + "psr-4": { + "Valouleloup\\ArrayMapping\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Valouleloup\\ArrayMapping\\Tests\\": "tests/" + } + }, + "require": { + "php": "^5.6|>=7.0.8", + "symfony/property-access": "^3.4" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..3daac85 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,15 @@ + + + + + + ./tests + + + + + + ./src + + + diff --git a/src/AbstractMappingTransformer.php b/src/AbstractMappingTransformer.php new file mode 100644 index 0000000..ba9f91c --- /dev/null +++ b/src/AbstractMappingTransformer.php @@ -0,0 +1,175 @@ +accessor = $accessor ? $accessor : PropertyAccess::createPropertyAccessor(); + } + + /** + * Transform the array given in parameter to an output array + * Method goal: + * - Load transformer config (yaml file) + * - Transform input array to output formatted array + * + * @param array $data + * @return mixed + */ + abstract protected function transform(array $data); + + /** + * Generate output array by given mapping & input array + * + * @param array $mapping The mapping to use to transform input array + * @param array $data The input array + * @return array The output array + * @throws \Exception + */ + protected function transformFromMapping(array $mapping, array $data) + { + $result = []; + + foreach ($mapping as $node) { + // Only transform current node if no condition prevent it + if ($this->areConditionsValidated($node, $data)) { + // Get our node value + $nodeValue = $this->getNodeValue($node, $data); + + // If we didn't get value but field was required, throw Exception + if (isset($node['required']) && true === $node['required'] && null === $nodeValue) { + throw new \Exception('Field ' . $node['from'] . ' required.'); + } + + if (isset($node['dependencies'])) { + $dependenciesExist = true; + + foreach ($node['dependencies'] as $dependency) { + if (null === $this->getValue($result, $mapping[$dependency]['to'])) { + $dependenciesExist = false; + } + } + + if ($dependenciesExist && null === $nodeValue) { + throw new \Exception('Field ' . $node['from'] . ' required if dependencies true.'); + } + } + + // nodeValue was found && acceptable, add value to result array + if (null !== $nodeValue) { + $this->accessor->setValue($result, $this->convertToBrackets($node['to']), $nodeValue); + } + } + } + + return $result; + } + + /** + * Check if a node can be transformed + * + * @param array $node The node to check + * @param array $data The input date to parse + * @return bool True if node is transformable, False if node isn't transformable + */ + private function areConditionsValidated(array $node, array $data) + { + // If node doesn't have condition, execute transformation + if (!isset($node['condition'])) { + return true; + } + + // Only transform node if field exists in array to parse + if (isset($node['condition']['exists']) + && \is_null($this->getValue($data, $node['condition']['exists']))) { + return false; + } + + return true; + } + + /** + * Get the value related to a node + * + * @param array $node + * @param array $data + * + * @return mixed + */ + private function getNodeValue(array $node, array $data) + { + //1. Get function value if is set so + //2. Else get raw value is no function is defined + //3. If keyValue is null and a default value is set, return default value + $nodeValue = null; + + if (isset($node['function'])) { + $params = []; + + foreach ($node['function']['params'] as $param) { + $params[] = $this->getValue($data, $param); + } + + $nodeValue = call_user_func_array([$this, $node['function']['name']], $params); + } else { + if (isset($node['from'])) { + $nodeValue = $this->getValue($data, $node['from']); + } + } + + if (null === $nodeValue && isset($node['default'])) { + // If default value is an array, try to get it node value + // Else return default value which is static + if (\is_array($node['default'])) { + $nodeValue = $this->getNodeValue($node['default'], $data); + } else { + $nodeValue = $node['default']; + } + } + + return $nodeValue; + } + + /** + * Get value set in the input array by finding them with key + * + * @param array $data The input array + * @param string $key The key which is the location of the value in the input array + * + * @return mixed|null The related value, if value doesn't exists return null + */ + private function getValue(array $data, $key) + { + return $this->accessor->getValue($data, $this->convertToBrackets($key)); + } + + /** + * Convert path separated with dot to path separated with brackets + * ie. path.to.convert will produce [path][to][convert] + * + * @param string $path The path to convert + * @return string the converted path + */ + private function convertToBrackets($path) + { + $keys = explode('.', $path); + + $bracketPath = ''; + + foreach ($keys as $key) { + $bracketPath .= '[' . $key . ']'; + } + + return $bracketPath; + } +} diff --git a/tests/AbstractMappingTransformerTest.php b/tests/AbstractMappingTransformerTest.php new file mode 100644 index 0000000..e20fb78 --- /dev/null +++ b/tests/AbstractMappingTransformerTest.php @@ -0,0 +1,136 @@ + [ + 'from' => 'foo', + 'to' => 'bar', + ], + ]; + $data = [ + 'foo' => 'baz', + ]; + $expected = [ + 'bar' => 'baz', + ]; + + $transformer = new ProxyDummyTransformer(); + $result = $transformer->transformFromMapping($mapping, $data); + + $this->assertEquals($expected, $result); + } + + public function testTransformFromToWithDepth() + { + $mapping = [ + 'node' => [ + 'from' => 'foo1.foo2', + 'to' => 'bar1.bar2.bar3', + ], + ]; + $data = [ + 'foo1' => ['foo2' => 'foo'] + ]; + $expected = [ + 'bar1' => ['bar2' => ['bar3' => 'foo']] + ]; + + $transformer = new ProxyDummyTransformer(); + $result = $transformer->transformFromMapping($mapping, $data); + + $this->assertEquals($expected, $result); + } + + public function testTransformToFunction() + { + $mapping = [ + 'node' => [ + 'to' => 'bar', + 'function' => [ + 'name' => 'getString', + 'params' => ['foo'] + ] + ], + ]; + $data = [ + 'foo' => 'baz', + ]; + $expected = [ + 'bar' => 'baz', + ]; + + $transformer = new ProxyDummyTransformer(); + $result = $transformer->transformFromMapping($mapping, $data); + + $this->assertEquals($expected, $result); + } + + public function testTransformFromToDefaultConstant() + { + $mapping = [ + 'node' => [ + 'from' => 'foo', + 'to' => 'bar', + 'default' => 'default', + ], + ]; + $data = []; + $expected = [ + 'bar' => 'default', + ]; + + $transformer = new ProxyDummyTransformer(); + $result = $transformer->transformFromMapping($mapping, $data); + + $this->assertEquals($expected, $result); + } + + public function testTransformFromToDefaultFunction() + { + $mapping = [ + 'node' => [ + 'to' => 'bar', + 'default' => [ + 'function' => [ + 'name' => 'getString', + 'params' => ['foo'] + ], + ], + ], + ]; + $data = [ + 'foo' => 'I am a string' + ]; + $expected = [ + 'bar' => 'I am a string', + ]; + + $transformer = new ProxyDummyTransformer(); + $result = $transformer->transformFromMapping($mapping, $data); + + $this->assertEquals($expected, $result); + } +} + +class ProxyDummyTransformer extends AbstractMappingTransformer +{ + protected function transform(array $data) { return null; } + + public function transformFromMapping(array $mapping, array $data) + { + return parent::transformFromMapping($mapping, $data); + } + + public function getString($value) + { + return $value; + } +}