Giter Site home page Giter Site logo

hashmapper's Introduction

Build Status Maintainability Test Coverage

Objective

This is a minimalistic library aimed to reuse logic on HashTables mapping, that i use over and over i.e. consuming API results to pass to Twig views.

Makes easy to do filtering/renaming of unwanted keys through simple dictionary of keys of source to target. Through callbacks supports any transformation, and passing it another callable or another instance of HashMapper, pretty complex transformation of associative arrays, a.k.a as Hashmaps for friends.

use function Apantle\HashMapper\hashMapper;

$target = hashMapper($specs, $options)($source);

Installation

You can install the package via composer:

composer require apantle/hashmapper

Simple key mapping

Change one key in input, only output that in target.

Input Mapper Output
['origin' => 'Africa']; hashMapper(['origin' => 'roots]) ['roots' => 'Africa']

Callback key mapping

For somewhat complex transforms, you can use a function that will receive as arguments:

  • the value of the source hashmap specified at key:
  • the whole hashmap if you need other values of it
assert(hashMapper([
    'place' => 'Caso CIDH',
    'date' => [
        'fecha',
        function($date, $source) {
            extract($date);
            $date = date_create_from_format('Y/m/d', "{$year}/{$month}/{$day}");
            return $date->format('Y-m-d');
        }
    ],
])([
    'date' => [
        'year' => 2006,
        'month' => 5,
        'day' => 4,
    ],
    'place' => 'San Salvador Atenco',
]) === [
    'Caso CIDH' => 'San Salvador Atenco',
    'fecha' => '2006-05-04'
]);

Use another HashMapper

If you have a complex subkey that is not easily mapped with a simple function, you could use another HashMapper with the spec for that subkey, as the mapper for that key.

assert(hashMapper([
    'sourceKey' => hashMapper([ 'value' => 'legend' ])
])([
    'sourceKey' => [
        'value' => 'to pass to HashMapper',
        'ignored' => 'it should not appear'
    ],
]) === [
    'sourceKey => [
        'legend' => 'to pass to HashMapper'
    ]
]);

Spread Operator Mapping with Callable

If you want to take a key with subkeys of the source and spread it (copy the dictionary of key and values it contains) on the target hashmap, you can pass a tuple with the string '...' as the target key, and your chosen callable.

assert(hashMapper([
    'wp:term' => ['...', 'Apantle\FunPHP\identity']
])([
    'wp:term' => [
         'id' => 31925,
         'link' => 'http://example.com/category/test-term/',
         'name' => 'Test term',
         'slug' => 'test-term',
         'taxonomy' => 'category',
    ],
    'ignored' => 'right'
]) === [
     'id' => 31925,
     'link' => 'http://example.com/category/test-term/',
     'name' => 'Test term',
     'slug' => 'test-term',
     'taxonomy' => 'category',
]);

Implicit spread (not specifying '...' key)

If you need all the dictionaries inside top level keys be spread into the target, rather than writing your mapping spec as a tuple, you can give it only a callable, specifying the option implicitSpread => true in the constructor to the Mapper functor.

assert(hashMaper(
    [
        'wp:term' => compose('Apantle\FunPHP\head', 'Apantle\FunPHP\identity'),
    ],
    [
        'implicitSpread' => true
    ]
)([
    'wp:term' => [
        [
            'id' => 31925,
            'link' => 'http://example.com/category/test-term/',
            'name' => 'Test term',
            'slug' => 'test-term',
            'taxonomy' => 'category',
        ]
    ],
]) === [
    'id' => 31925,
    'link' => 'http://example.com/category/test-term/',
    'name' => 'Test term',
    'slug' => 'test-term',
    'taxonomy' => 'category',
]);

Call a HashMapper as Functor object

For better reuse, now offers through the __invoke magic, a simpler way to use it to map a collection of associative arrays, as array_map, array_reduce or Collection::map (from Illuminate\Support).

Reuse a HashMapper to transform an array of associative arrays

Instead of using the HashMapper as the function for array_map or Collection::map, you can use our own helper, that applies the same set of transformations to every array passed.

$collectionTransformed = collection(hashMapper($specs))($arrayOfAssociativeArrays);

See issue:1 for more complete examples in test sources.

hashmapper's People

Contributors

tezcatl avatar tzkmx avatar

Watchers

 avatar

hashmapper's Issues

Documentar más casos de uso

Los ejemplos dados son más bien simples para facilitar la comprensión de la plataforma, sin embargo al ser utilizados en otros proyectos pueden mostrar mejor su utilidad.

OptionsInterface / constantes de configuración en lugar de array de opciones

Pasar las opciones con que se puede configurar la función se implementa mediante un array associativo de opciones, lo que no facilita recibir ayuda del IDE para implementar rápidamente la opción requerida.

Las alternativas con una serie de constantes de clase que sean pasadas como segundo argumento y funciones como una serie de bits independientes entre sí, como funcionan actualmente las opciones, o bien un objeto que implemente una interfaz que devuelva las opciones de configuración.

Mejorar documentación mostrando más claramente entrada/salida y estilo funcional

La mayoría de la documentación está hecha en un momento en que esta librería se facilitaba solo mediante un objeto mapper a inicializar con los spec en el constructor. Sin embargo ahora se promueve más su uso como una función de filtro, que internamente construye un objeto HashmapMapperInterface. Asimismo el propio objeto se exporta como un Functor que permite pasarle directamente el array a mapear.

Así, en aras de facilitar documentar más casos de uso (#2) y el uso del collectionMapper (#1), cambiaré el formato de la documentación a tablas que muestren más claramente la entrada, salida y qué mapper produce dicha transformación, así como preferir crear el objeto Mapper con las funciones facilitadas en lugar de la construcción de objetos manualmente.

Verificar que chequeo de función unary es solo llamado una vez por instancia

Debido a que la verificación de que una función mapper es unary introducido en #7, se realiza con Reflection, como salvaguarda para próximas optimizaciones, como dar soporte a callables en objetos (actualmente solo soporta funciones como cadena), deberíamos introducir una prueba y corregir si no pasa, de que la verificación de función unary solo se llama una única vez por instancia (y no por cada invocación cuando se reusa el hashmapper varias veces).

Ver apantle/fun-php#3

Compatibilidad con funciones unary

Algunas funciones de uso simple como strval arrojan errores al pasarles además del dato a mapear, el hashmap siendo iterado. Por lo que hace falta algo de Reflection para poder ubicar dicho caso y evitarnos funciones adaptadores.

Object factory?

Convendrá una opción o parámetro o helper adicional para hacer del hashmap devuelto un objeto?

Leer de un objeto como si fuese un hashMap?

También, valdría la pena hacer de la Collection un Iterable? Ya sea un objeto Collection (como el de Illuminate), o solo una lista de objetos de determinado tipo?

Traducir documentación al español

Para facilitar su uso en una comunidad más amplia, la documentación en español sería de gran ayuda pues al final somos la segunda lengua más hablada del mundo, pero tenemos muy poca documentación en nuestra lengua nativa.

Documentar método y usos de getCollectionMapper

La clase que provee este paquete, proporciona un método para obtener un CollectionMapper, con el que se pueden aplicar los mapeos configurados para un solo array asociativo, a un array de array asociativo con la misma estructura.

Ver las pruebas en:

foreach ($source as $key => $item) {
$this->assertEquals($expected[$key], $mapper->apply($item));
}
$collectionMapper = $mapper->getCollectionMapper();
$this->assertEquals($expected, $collectionMapper->apply($source));

public function testHashMapperFunctor()
{
$list = [
[
'head' => 1,
'tail' => 2,
],
[
'head' => 3,
'tail' => 4,
],
];
$mapper = hashMapper(['head' => 'fst', 'tail' => 'snd']);
$expectedFirstItemMapped = ['fst' => 1, 'snd' => 2];
$this->assertEquals($expectedFirstItemMapped, $mapper($list[0]));
$expectedTransformedList = [['fst' => 1, 'snd' => 2], ['fst' => 3, 'snd' => 4]];
$listMapper = $mapper->getCollectionMapper();
$this->assertEquals($expectedTransformedList, $listMapper($list));
}

Input / Output filter options

Para ampliar un poco el alcance de la aplicabilidad de esta librería, podemos proporcionar simplemente un par de opciones a cada hashMapper, que funcionen como filtros de entrada, y así pudiendo implementar fácilmente algoritmos más complejos como transformar y validar un objeto mediante un simple helper, y sanitizar la salida como excluir claves con informes de error que sería deseable loggear en un servicio externo pero no devolver al usuario final en el frontend, por poner un ejemplo.

Esto puede ser conseguido con relativa facilidad mediante un par de opciones para el hashMapper, sería útil para otros casos, poder obtener un collectionMapper con filtros para toda la colección, distintos a los que se aplicarían a cada elemento de la colección recibida.

hashMapperPipe

El patrón en que operar una key puede ser pasado a otro hashMapper (decorador), es muy útil, pero recientemente se hace más pertinente aplicar una serie de hashMapper en patrón pipeline, permitiéndoles a cada uno operar una transformación distinta, haciendo efectivamente posible expresar algoritmos cada vez más complejos solo mediante la trasformación de arrays associativos.

Actualmente implementar un algoritmo así es muy sencillo combinando una serie de hashMapper con un pipe de apantle/fun-php#4

$results = pipe(
    hashMapper($transformsSpec, $options),
    hashMapper($transformsSpec, $options),
    hashMapper($transformsSpec, $options)
)( $inputArray );

Sin embargo si requerimos que no cada hashMapper filtre toda la entrada, sino digamos, agregue o aplique funciones al array de entrada, especificar en cada $options se vuelve repetitivo y se pierde la intención de las transformaciones entre boilerplate:

// ...
hashMapper($validateQueriesSpec, ['passThroughNoMatched' => true]),
hashMapper($resolveQueriesSpec, ['passThroughNoMatched' => true]),
hashMapper($captureAndFilterErrors. ['passThroughNoMatched' => true])

A pesar de poder resumir las opciones en una variable en este caso, rápidamente vuelve a explotar la complejidad al requerir, por ejemplo, que algunos mappers tengan unas opciones distintas.

Pero además apuntamos siempre a que esta librería facilite patrones funcionales donde la intención del programador al escribir las transformaciones a una colección de datos de entrada, no quede oscurecida en un mar de sintaxis, además de que la lógica de las funciones sea fácil de componer y mantener.

Así pues, poder especificar con una utilidad de esta misma librería, una serie de transformaciones a aplicar a un array de entrada, con el mínimo ruido extra posible, podría plantear un consumo así:

hashMapperPipe(
   $validateQueriesSpecArray,
   $resolveQueriesSpecArray,
   $captureAndFilterErrors,
   [ $logStatistics, $logOptions ]
   $commonOptions
)

Facilitar uso de getCollectionMapper

Relacionado con #1 , una función para obtener el collection Mapper desde un hashMapper ya creado puede facilitar aún más su uso en pipes, pienso en algo como:

collection(hashMapper($rules))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.