Data mapping

Updated on @May 7, 2022

Most data gets to our apps in the form of an associative array of key/value pairs. For example, when we receive an HTTP request, it will be an array of data.

If we want to use that data, we can access it by its key, but we have to ensure that:

  1. the key exists
  2. the value is of the correct type, or optionally cast it to the correct one

Let’s see an example of getting a string:

/** @param array<mixed> $data */
private static function getString(array $data, string $key, string $default = ''): string
{
    $value = $data[$key] ?? null;

    if (!is_scalar($value)) {
        return $default;
    }

    return (string) $value;
}

Now, we can call that function this way:

/** @var array<string, mixed> $requestData */

$title = self::getString($requestData, 'title');

Generally, we put these utility functions inside a trait called DataMapping, which will be inserted, for example, in our controller classes.

<?php

declare(strict_types=1);

namespace Example\Shared\Domain\Data;

use Example\Shared\Domain\Exception\InvalidDataMappingException;
use Symfony\Component\HttpFoundation\File\UploadedFile;

use function Lambdish\Phunctional\filter;

trait DataMapping
{
    /**
     * @param array<mixed> $data
     * @param array<string> $default
     * @return array<string>
     */
    private static function getArrayOfStrings(array $data, string $key, array $default = []): array
    {
        $value = $data[$key] ?? null;

        if (!\is_array($value)) {
            return $default;
        }

        return filter(
            static fn ($item) => \is_string($item),
            $value,
        );
    }

    /** @param array<mixed> $data */
    private static function getBool(array $data, string $key, bool $default = false): bool
    {
        $value = self::getBoolOrNull($data, $key);

        if (null === $value) {
            return $default;
        }

        return $value;
    }

    /** @param array<mixed> $data */
    private static function getBoolOrNull(array $data, string $key): ?bool
    {
        $value = $data[$key] ?? null;

        if (null === $value) {
            return null;
        }

        return filter_var($value, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE);
    }

    /** @param array<mixed> $data */
    private static function getInt(array $data, string $key, int $default = 0): int
    {
        $value = self::getIntOrNull($data, $key);

        if (null === $value) {
            return $default;
        }

        return $value;
    }

    /** @param array<mixed> $data */
    private static function getIntOrNull(array $data, string $key): ?int
    {
        $value = $data[$key] ?? null;

        if (null === $value) {
            return null;
        }

        if (\is_bool($value)) {
            return (int) $value;
        }

        return filter_var($value, \FILTER_VALIDATE_INT, \FILTER_NULL_ON_FAILURE);
    }

    /** @param array<mixed> $data */
    private static function getString(array $data, string $key, string $default = ''): string
    {
        $value = $data[$key] ?? null;

        if (!is_scalar($value)) {
            return $default;
        }

        if (true === $value) {
            return 'true';
        }

        if (false === $value) {
            return 'false';
        }

        return (string) $value;
    }

    /** @param array<mixed> $data */
    private static function getNonEmptyStringOrNull(array $data, string $key): ?string
    {
        $value = self::getString($data, $key);

        if ('' === $value) {
            return null;
        }

        return $value;
    }

    /**
     * @param array<mixed> $data
     * @throws InvalidDataMappingException
     */
    private static function getStringOrFail(array $data, string $key, ?string $arrayName = null): string
    {
        if (!\array_key_exists($key, $data)) {
            throw InvalidDataMappingException::fromMissingKey($key, $arrayName);
        }

        $value = $data[$key];

        if (!is_scalar($value)) {
            throw InvalidDataMappingException::fromNonScalar($key, $arrayName);
        }

        if (true === $value) {
            return 'true';
        }

        if (false === $value) {
            return 'false';
        }

        return (string) $value;
    }

    /** @param array<mixed> $data */
    private static function getUploadedFileOrNull(array $data, string $key): ?UploadedFile
    {
        $value = $data[$key] ?? null;

        if (!($value instanceof UploadedFile)) {
            return null;
        }

        return $value;
    }
}
Bibliography