PHP Classes

File: src/InputFilterContainer.php

Recommend this page to a friend!
  Packages of Scott Arciszewski   Ionizer PHP Filter Input   src/InputFilterContainer.php   Download  
File: src/InputFilterContainer.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Ionizer PHP Filter Input
Filter input values by chaining filter objects
Author: By
Last change:
Date: 5 years ago
Size: 5,338 bytes
 

Contents

Class file image Download
<?php
declare(strict_types=1);
namespace ParagonIE\Ionizer;

use ParagonIE\Ionizer\Contract\{
    FilterInterface,
    FilterContainerInterface
};

/**
 * Class InputFilterContainer
 *
 * Contains a set of filter rules, useful for enforcing a strict type on
 * unstructured data (e.g. HTTP POST parameters).
 *
 * @package ParagonIE\Ionizer
 */
abstract class InputFilterContainer implements FilterContainerInterface
{
    /** @const string SEPARATOR */
    const SEPARATOR = '.';

    /**
     * @var array<string, array<mixed, FilterInterface>>
     */
    protected $filterMap = [];

    /**
     * InputFilterContainer constructor.
     */
    abstract public function __construct();

    /**
     * Add a new filter to this input value
     *
     * @param string $path
     * @param FilterInterface $filter
     * @return FilterContainerInterface
     */
    public function addFilter(
        string $path,
        FilterInterface $filter
    ): FilterContainerInterface {
        if (!isset($this->filterMap[$path])) {
            $this->filterMap[$path] = [];
        }
        /** @psalm-suppress MixedArrayAssignment */
        $this->filterMap[$path][] = $filter->setIndex($path);
        return $this;
    }

    /**
     * @param string $path
     * @return array<array-key, FilterInterface>
     */
    public function getFiltersForPath(string $path)
    {
        if (!isset($this->filterMap[$path])) {
            $this->filterMap[$path] = [];
        }
        return $this->filterMap[$path];
    }

    /**
     * Use firstlevel.second_level.thirdLevel to find indices in an array
     *
     * @param string $key
     * @param mixed $multiDimensional
     * @return mixed
     * @throws \Error
     * @throws InvalidDataException
     *
     * @psalm-suppress UnusedVariable
     */
    public function filterValue(string $key, $multiDimensional)
    {
        /** @var array<int, string> $pieces */
        $pieces = Util::chunk($key, (string) static::SEPARATOR);
        /** @var array|string $filtered */
        $filtered =& $multiDimensional;

        /**
         * @security This shouldn't be escapable. We know eval is evil, but
         *           there's not a more elegant way to process this in PHP.
         */
        if (\is_array($multiDimensional)) {
            $var = '$multiDimensional';
            foreach ($pieces as $piece) {
                $append = '[' . self::sanitize($piece) . ']';

                // Alphabetize the parent array
                eval(
                    'if (!isset(' . $var . $append . ')) {' . "\n" .
                    '    ' . $var . $append . ' = null;' . "\n" .
                    '}' . "\n" .
                    '\ksort(' . $var . ');' . "\n"
                );
                $var .= $append;
            }
            eval('$filtered =& ' . $var. ';');
        }

        // If we have filters, let's apply them:
        if (isset($this->filterMap[$key])) {
            /** @var object|null $filter */
            foreach ($this->filterMap[$key] as $filter) {
                if ($filter instanceof FilterInterface) {
                    /** @var string|int|bool|float|array $filtered */
                    $filtered = $filter->process($filtered);
                }
            }
        }
        return $multiDimensional;
    }

    /**
     * Use firstlevel.second_level.thirdLevel to find indices in an array
     *
     * Doesn't apply filters
     *
     * @param string $key
     * @param array<string, string|array> $multiDimensional
     * @return mixed
     * @psalm-suppress PossiblyInvalidArrayOffset
     */
    public function getUnfilteredValue(string $key, array $multiDimensional = [])
    {
        /** @var array<string, string> $pieces */
        $pieces = Util::chunk($key, (string) static::SEPARATOR);

        /** @var string|array<string, string|array> $value */
        $value = $multiDimensional;

        /**
         * @var array<string, string> $pieces
         * @var string $piece
         */
        foreach ($pieces as $piece) {
            if (!isset($value[$piece])) {
                return null;
            }
            /** @var string|array<string, string|array> $next */
            $next = $value[$piece];

            /** @var string|array<string, string|array> $value */
            $value = $next;
        }
        return $value;
    }

    /**
     * Only allow allow printable ASCII characters:
     *
     * @param string $input
     * @return string
     * @throws \Error
     */
    protected static function sanitize(string $input): string
    {
        /** @var string|bool $sanitized */
        $sanitized = \json_encode(
            \preg_replace('#[^\x20-\x7e]#', '', $input)
        );
        if (!\is_string($sanitized)) {
            throw new \Error('Could not sanitize string');
        }
        return $sanitized;
    }

    /**
     * Process the input array.
     *
     * @param array $dataInput
     * @return array
     * @throws \Error
     * @throws InvalidDataException
     */
    public function __invoke(array $dataInput = []): array
    {
        /** @var string $key */
        foreach (\array_keys($this->filterMap) as $key) {
            /** @var array $dataInput */
            $dataInput = $this->filterValue($key, $dataInput);
        }
        return $dataInput;
    }
}