src/Reference/ReferenceTrait.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

/**
 * Phoole (PHP7.2+)
 *
 * @category  Library
 * @package   Phoole\Base
 * @copyright Copyright (c) 2019 Hong Zhang
 */
declare(strict_types=1);

namespace Phoole\Base\Reference;

/**
 * ReferenceTrait
 *
 * @package Phoole\Base
 */
trait ReferenceTrait
{
    /**
     * default is '${' and '}'
     *
     * @var string
     */
    protected $ref_pattern = '~(\$\{(\w((?!\$\{|\}).)*)\})~';

    /**
     * @inheritDoc
     */
    public function setReferencePattern(string $start, string $end): object
    {
        $s = preg_quote($start);
        $e = preg_quote($end);
        $this->ref_pattern = sprintf("~(%s(\w((?!%s|%s).)*)%s)~", $s, $s, $e, $e);
        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function deReference(&$subject): void
    {
        if (is_string($subject)) {
            $subject = $this->deReferenceString($subject);
        }

        if (!is_array($subject)) {
            return;
        }

        foreach ($subject as &$data) {
            $this->dereference($data);
        }
    }

    /**
     * Replace all references in the target string (recursively)
     *
     * @param  string $subject
     * @return mixed
     * @throws \RuntimeException if bad thing happens
     */
    protected function deReferenceString(string $subject)
    {
        $loop = 0;
        // recursive matching in string
        while (preg_match($this->ref_pattern, $subject, $matched)) {
            if ($loop++ > 20) {
                throw new \RuntimeException("Loop in resolving $subject");
            }

            $ref = $this->resolveReference($matched[2]);
            if (is_string($ref)) {
                $subject = str_replace($matched[1], $ref, $subject);
            } else {
                return $this->checkValue($ref, $subject, $matched[1]);
            }
        }
        return $subject;
    }

    /**
     * Check dereferenced value
     *
     * @param  mixed  $value
     * @param  string $subject  the subject to dereference
     * @param  string $matched  the matched reference
     * @return mixed
     * @throws \RuntimeException if $subject malformed
     */
    protected function checkValue($value, string $subject, string $matched)
    {
        // reference not resolved
        if (is_null($value)) {
            return $this->resolveUnknown($subject, $matched);
        }

        // malformed subject
        if ($subject != $matched) {
            throw new \RuntimeException("Error in resolving $matched");
        }

        // good match
        return $value;
    }

    /**
     * resolving the references
     *
     * @param  mixed $name
     * @return mixed
     * @throws \RuntimeException if bad thing happens
     */
    protected function resolveReference(string $name)
    {
        if (is_string($name)) {
            return $this->getReference($name);
        }
        return $name;
    }

    /**
     * Dealing with unknown reference, may leave it untouched
     *
     * @param  string $subject  the subject to dereference
     * @param  string $matched  the matched reference
     * @return mixed
     * @throws \RuntimeException if $subject malformed
     */
    protected function resolveUnknown(string $subject, string $matched)
    {
        throw new \RuntimeException("Unable to resolve $matched in $subject");
    }

    /**
     * resolve the references
     *
     * @param  string $name
     * @return mixed
     * @throws \RuntimeException if bad thing happens
     */
    abstract protected function getReference(string $name);
}