aol/transformers

View on GitHub
lib/Transformer.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php

namespace Aol\Transformers;

class Transformer
{
    use AbstractDefinitionTrait;

    const APP = 'app';
    const EXT = 'ext';

    const DEFINITION_KEY = 'key';
    const DEFINITION_MAP = 'map';
    const DEFINITION_FUNC = 'func';
    const DEFINITION_ARGS = 'args';

    /** @var array Transformation definitions */
    private $definitions = [];

    /** @var array Virtual fields */
    private $virtual_fields = [];

    /**
     * Saves field definitions.
     *
     * @param string   $app_name Property name in application context.
     * @param string   $ext_name Property name storage context.
     * @param callable $app_func Callable for transforming property to app context.
     * @param callable $ext_func Callable for transforming property to storage context.
     * @param array    $app_args Arguments for app callback.
     * @param array    $ext_args Arguments for storage callback.
     */
    public function define(
        $app_name,
        $ext_name,
        callable $app_func = null,
        callable $ext_func = null,
        $app_args = [],
        $ext_args = []
    ) {
        $this->definitions[self::EXT][$app_name] = [
            self::DEFINITION_KEY  => $ext_name,
            self::DEFINITION_MAP  => $app_name,
            self::DEFINITION_FUNC => $ext_func,
            self::DEFINITION_ARGS => $ext_args
        ];
        $this->definitions[self::APP][$ext_name] = [
            self::DEFINITION_KEY  => $app_name,
            self::DEFINITION_MAP  => $ext_name,
            self::DEFINITION_FUNC => $app_func,
            self::DEFINITION_ARGS => $app_args,
        ];
    }

    /**
     * Defines a virtual field that simply acts as a passthru and does not
     * appear in getKeys().
     *
     * @param string $key Virtual field key name
     */
    public function defineVirtual($key)
    {
        $this->virtual_fields[$key] = true;
    }

    /**
     * Transforms data for app.
     *
     * @param mixed $data Data for transformation.
     * @param null  $key
     * @param bool  $array
     * @return array
     */
    public function toApp($data, $key = null, $array = false)
    {
        return $this->to(self::APP, $data, $key, $array);
    }

    /**
     * Transforms data for storage.
     *
     * @param mixed $data Data for transformation.
     * @param null  $key
     * @param bool  $array
     * @return array
     */
    public function toExt($data, $key = null, $array = false)
    {
        return $this->to(self::EXT, $data, $key, $array);
    }

    /**
     * Transforms data for a specific environment.
     *
     * @param string      $env  Environment name.
     * @param mixed       $data Data for transformation.
     * @param null|string $key
     * @param bool        $array
     * @return array
     */
    public function to($env, $data, $key = null, $array = false)
    {
        $this->validateEnvironment($env);

        if (!is_array($data) && empty($data) && $data !== false) {
            return null;
        }

        // Handle arrays by recursion
        if ($array) {
            $map = function ($data) use ($env, $key) {
                return $this->to($env, $data, $key);
            };

            return array_map($map, $data);
        }

        // Handle keys
        if (!is_null($key)) {
            if (!$def = $this->getDefinition($env, $key)) {
                throw new \InvalidArgumentException('Unknown key: ' . $key);
            }

            return $this->parseDefinitionValue($def, $data);
        }

        // Transform a data set
        $method_before = 'before' . ucfirst($env);
        $method_after  = 'after' . ucfirst($env);
        $ret           = [];

        $data = $this->$method_before($data);
        foreach ($data as $key => $value) {
            if ($def = $this->getDefinition($env, $key)) {
                $ret[$def[self::DEFINITION_KEY]] = $this->parseDefinitionValue($def, $value);
            } elseif (array_key_exists($key, $this->virtual_fields)) {
                $ret[$key] = $value;
            }
        }
        $ret = $this->$method_after($ret);

        return $ret;
    }

    /**
     * Returns all of the defined keys for the given environment.
     *
     * @param string      $env    The environment from which to retrieve the keys
     * @param null|string $prefix Prefix will be applied to all key names
     * @return array
     */
    public function getKeys($env, $prefix = null)
    {
        $this->validateEnvironment($env);

        $env  = $env === self::APP ? self::EXT : self::APP;
        $keys = array_keys($this->definitions[$env]);

        if ($prefix !== null) {
            $keys = array_map(function ($key) use ($prefix) {
                return $prefix . $key;
            }, $keys);
        }

        return $keys;
    }

    /**
     * Returns all of the defined keys for the APP environment.
     *
     * @param null $prefix
     * @return array
     */
    public function getKeysApp($prefix = null)
    {
        return $this->getKeys(self::APP, $prefix);
    }

    /**
     * Returns all of the defined keys for the EXT environment.
     * @param null $prefix
     * @return array
     */
    public function getKeysExt($prefix = null)
    {
        return $this->getKeys(self::EXT, $prefix);
    }

    /**
     * Look up a specific environment key by its mapped key.
     *
     * @param string $env
     * @param string $key
     * @return null|string
     */
    public function getKey($env, $key)
    {
        $map = $this->getMap($env);

        return isset($map[$key]) ? $map[$key] : null;
    }

    /**
     * Gets a mapped key name using the APP environment.
     *
     * @param $key
     * @return null|string
     */
    public function getKeyApp($key)
    {
        return $this->getKey(self::APP, $key);
    }

    /**
     * Gets a mapped key name using the EXT environment.
     * 
     * @param $key
     * @return null|string
     */
    public function getKeyExt($key)
    {
        return $this->getKey(self::EXT, $key);
    }

    /**
     * Returns a map of all defined key names for the given environment.
     *
     * @param string $keys The environment to use for array keys
     * @return array
     */
    public function getMap($keys = self::APP)
    {
        return array_column($this->definitions[$keys], self::DEFINITION_MAP, self::DEFINITION_KEY);
    }

    protected function beforeApp($data)
    {
        return $data;
    }

    protected function beforeExt($data)
    {
        return $data;
    }

    protected function afterApp($data)
    {
        return $data;
    }

    protected function afterExt($data)
    {
        return $data;
    }

    /**
     * Gets a property definition by key for a specific environment.
     *
     * @param string $env Environment name.
     * @param string $key Key name.
     * @return array|null
     */
    private function getDefinition($env, $key)
    {
        return isset($this->definitions[$env][$key])
            ? $this->definitions[$env][$key]
            : null;
    }

    private function validateEnvironment($env)
    {
        if (!in_array($env, [self::APP, self::EXT])) {
            throw new \InvalidArgumentException('Unknown environment: ' . $env);
        }
    }

    /**
     * Parses a value based on a definition.
     *
     * @param array $definition Definition array.
     * @param mixed $value      Value.
     * @return mixed
     */
    private function parseDefinitionValue($definition, $value)
    {
        if (is_callable($definition[self::DEFINITION_FUNC])) {
            array_unshift($definition[self::DEFINITION_ARGS], $value);
            $value = call_user_func_array($definition[self::DEFINITION_FUNC], $definition[self::DEFINITION_ARGS]);
        }

        return $value;
    }
}