Ellrion
3/29/2017 - 4:25 PM

ClassMap.php

<?php 

namespace App\Support;

use IteratorAggregate;

class ClassMap implements IteratorAggregate
{
    protected $baseClass;
    protected $classesList = [];
    protected $instances;
    protected $knowledge = [];

    /**
     * Execute a callback over each class names.
     *
     * @param  \Closure $callback
     * @return array
     */
    public function each(\Closure $callback)
    {
        return $this->isInstantiated()
            ? array_map($callback, $this->classesList, $this->instances)
            : array_map($callback, $this->classesList);
    }

    /**
     * Apply a callback to every instance of classes.
     *
     * @param \Closure $callback
     */
    public function walk(\Closure $callback)
    {
        $this->instantiate();
        array_walk($this->instances, $callback);
    }

    /**
     * Reduce the collection of instances to a single value.
     *
     * @param  \Closure $callback
     * @param  mixed    $initial
     * @return mixed
     */
    public function reduce(\Closure $callback, $initial = null)
    {
        $this->instantiate();
        return array_reduce($this->instances, $callback, $initial);
    }

    /**
     * Filters classes or instances using a callback function.
     *
     * @param  \Closure $callback
     * @return $this
     */
    public function filter(\Closure $callback)
    {
        $filtered = $this->isInstantiated()
            ? array_filter($this->instances, $callback)
            : array_filter($this->classesList, $callback);

        $object = clone $this;

        $object->instances = $object->isInstantiated() ? array_intersect_key($object->instances, $filtered) : null;

        $object->classesList = array_intersect_key($object->classesList, $filtered);

        foreach ($this->knowledge as $key => $item) {
            $object->knowledge[$key] = array_intersect_key($item, $filtered);
        }

        return $object;
    }

    /**
     * Make instance for all classes.
     */
    public function instantiate()
    {
        if (!$this->isInstantiated()) {
            $args = func_get_args();
            $this->instances = $this->each(function ($class) use ($args) {
                return (new \ReflectionClass($class))->newInstanceArgs($args);
            });
        }

        return $this;
    }

    /**
     * Get list of values by parameter.
     *
     * @param  string   $parameter
     * @param  \Closure $callback
     * @return mixed
     */
    public function getListOf($parameter, \Closure $callback = null)
    {
        if (!array_key_exists($parameter, $this->knowledge)) {
            $callback = $callback ?: function ($class, $obj = null) use ($parameter) {
                return call_user_func([null !== $obj ? $obj : $class, $parameter]);
            };
            $this->knowledge[$parameter] = $this->each($callback);
        }

        return $this->knowledge[$parameter];
    }

    /**
     * Get list of constants values by constant.
     *
     * @param  string $const
     * @return mixed
     */
    public function getListOfConstant($const)
    {
        if (!array_key_exists($const, $this->knowledge)) {
            $this->knowledge[$const] = $this->each(function ($class) use ($const) {
                return defined($class . '::' . $const) ? constant($class . '::' . $const) : null;
            });
        }

        return $this->knowledge[$const];
    }

    /**
     * Get an instance of a class.
     *
     * @param  string $class
     * @return mixed
     */
    public function getInstance($class)
    {
        if ($class === null) {
            return null;
        }

        $this->instantiate();

        return array_get(array_combine($this->classesList, $this->instances), $class, null);
    }

    /**
     * Get a class by return value of a parameter.
     *
     * @param  string $parameter
     * @param  mixed  $val
     * @return string
     */
    public function getClassBy($parameter, $val)
    {
        return array_get(array_combine($this->getListOf($parameter), $this->classesList), $val, $this->baseClass);
    }

    /**
     * Get an instance of a class by return value of a parameter.
     *
     * @param  string $parameter
     * @param  mixed  $val
     * @return mixed
     */
    public function getInstanceBy($parameter, $val)
    {
        $class = $this->getClassBy($parameter, $val);
        return $this->getInstance($class);
    }

    /**
     * Terminate classes instances created.
     *
     * @return bool
     */
    public function isInstantiated()
    {
        return null !== $this->instances;
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
        $array = $this->isInstantiated()
            ? array_combine($this->classesList, $this->instances)
            : array_fill_keys($this->classesList, null);

        return new \ArrayIterator($array);
    }

    /**
     * @return int
     */
    public function count()
    {
        return $this->isInstantiated()
            ? count($this->instances)
            : count($this->classesList);
    }

}