phossa2/shared

View on GitHub
src/Shared/Reference/ReferenceTrait.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php
/**
 * Phossa Project
 *
 * PHP version 5.4
 *
 * @category  Library
 * @package   Phossa2\Shared
 * @copyright Copyright (c) 2016 phossa.com
 * @license   http://mit-license.org/ MIT License
 * @link      http://www.phossa.com/
 */
/*# declare(strict_types=1); */

namespace Phossa2\Shared\Reference;

use Phossa2\Shared\Message\Message;
use Phossa2\Shared\Exception\RuntimeException;

/**
 * ReferenceTrait
 *
 * Provides reference & dereference methods
 *
 * @package Phossa2\Shared
 * @author  Hong Zhang <phossa@126.com>
 * @see     ReferenceInterface
 * @version 2.1.0
 * @since   2.0.4 added
 * @since   2.0.6 added reference cache support
 * @since   2.0.8 added delegator support, changed to LocaclCache
 * @since   2.0.12 removed LocalCache
 * @since   2.0.15 removed delegator support, moved to individual library
 * @since   2.1.0 added `enableDeRefence()`
 */
trait ReferenceTrait
{
    /**
     * refernece start chars
     *
     * @var    string
     * @access protected
     */
    protected $ref_start = '${';

    /**
     * reference ending chars
     *
     * @var    string
     * @access protected
     */
    protected $ref_end = '}';

    /**
     * cached pattern to match
     *
     * @var    string
     * @access protected
     */
    protected $ref_pattern = '~(\$\{((?:(?!\$\{|\}).)+?)\})~';

    /**
     * dereference enabled
     *
     * @var    bool
     * @access protected
     */
    protected $ref_enabled = true;

    /**
     * {@inheritDoc}
     */
    public function setReferencePattern(
        /*# string */ $start,
        /*# string */ $end
    ) {
        $this->ref_start = $start;
        $this->ref_end = $end;

        // build pattern on the fly
        $s = preg_quote($start);
        $e = preg_quote($end);
        $this->ref_pattern = sprintf(
            "~(%s((?:(?!%s|%s).)+?)%s)~",
            $s,
            $s,
            $e,
            $e
        );
    }

    /**
     * {@inheritDoc}
     */
    public function hasReference(
        /*# string */ $subject,
        array &$matched
    )/*# : bool */ {
        if (is_string($subject) &&
            false !== strpos($subject, $this->ref_start) &&
            preg_match($this->ref_pattern, $subject, $matched)
        ) {
            return true;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function deReference(/*# string */ $subject)
    {
        // @since 2.1.0
        if (!$this->ref_enabled) {
            return $subject;
        }

        $loop = 0;
        $matched = [];
        while ($this->hasReference($subject, $matched)) {
            $this->checkReferenceLoop($loop++, $matched[2]); // avoid looping
            $val = $this->resolveReference($matched[2]);
            if (is_string($val)) {
                $subject = str_replace($matched[1], $val, $subject);
            } else {
                return $this->checkValue($val, $subject, $matched[1]);
            }
        }
        return $subject;
    }

    /**
     * {@inheritDoc}
     */
    public function deReferenceArray(&$dataArray)
    {
        // @since 2.1.0
        if (!$this->ref_enabled) {
            return;
        }

        if (is_string($dataArray)) {
            $dataArray = $this->deReference($dataArray);
        }

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

        foreach ($dataArray as &$data) {
            $this->dereferenceArray($data);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @since  2.1.0
     */
    public function enableDeReference(/*# bool */ $flag = true)
    {
        $this->ref_enabled = (bool) $flag;
        return $this;
    }

    /**
     * Check dereferenced value
     *
     * @param  mixed $value
     * @param  string $subject the subject to dereference
     * @param  string $reference the matched whole reference
     * @return mixed
     * @throws RuntimeException if $subject malformed, like mix string & array
     * @access protected
     */
    protected function checkValue(
        $value,
        /*# string */ $subject,
        /*# string */ $reference
    ) {
        // unknown reference found, leave it alone
        if (is_null($value)) {
            // exception thrown in resolveUnknown() already if wanted to
            return $subject;

        // malformed partial match, partial string, partial non-scalar
        } elseif ($subject != $reference) {
            throw new RuntimeException(
                Message::get(Message::MSG_REF_MALFORMED, $reference),
                Message::MSG_REF_MALFORMED
            );

        // full match, array or object
        } else {
            return $value;
        }
    }

    /**
     * Resolve the reference $name
     *
     * @param  string $name
     * @return mixed
     * @throws RuntimeException if reference unknown
     * @access protected
     * @since  2.0.8 added localCache support
     * @since  2.0.13 removed localCache support
     */
    protected function resolveReference(/*# string */ $name)
    {
        // lookup the reference
        $val = $this->referenceLookup($name);

        // dealing with unknown reference
        if (is_null($val)) {
            $val = $this->resolveUnknown($name);
        }
        return $val;
    }

    /**
     * Throw exception if looped
     *
     * @param  int $loop loop counter
     * @param  string $name reference name
     * @throws RuntimeException if loop found
     * @access protected
     * @since  2.0.6
     */
    protected function checkReferenceLoop(
        /*# int */ $loop,
        /*# string */ $name
    ) {
        if ($loop > 20) {
            throw new RuntimeException(
                Message::get(Message::MSG_REF_LOOP, $name),
                Message::MSG_REF_LOOP
            );
        }
    }

    /**
     * Lookup reference
     *
     * @param  string $name
     * @return mixed
     * @access protected
     * @since  2.0.15 removed delegator support, moved to individual library
     */
    protected function referenceLookup(/*# string */ $name)
    {
        return $this->getReference($name);
    }

    /**
     * For unknown reference $name, normally returns NULL
     *
     * @param  string $name
     * @return mixed
     * @throws \Exception if implementor WANTS TO !!
     * @access protected
     */
    abstract protected function resolveUnknown(/*# string */ $name);

    /**
     * The REAL resolving method. return NULL for unknown reference
     *
     * @param  string $name
     * @return mixed
     * @access protected
     */
    abstract protected function getReference(/*# string */ $name);
}