<?php
/**
 * Trait ClassLoader
 *
 * @filesource   ClassLoader.php
 * @created      13.11.2017
 * @package      chillerlan\Traits
 * @author       Smiley <smiley@chillerlan.net>
 * @copyright    2017 Smiley
 * @license      MIT
 */

namespace chillerlan\Traits;

use Exception, ReflectionClass;

trait ClassLoader {

	/**
	 * Instances an object of $class/$type with an arbitrary number of $params
	 *
	 * @param string $class  class FQCN
	 * @param string $type   class/parent/interface FQCN
	 *
	 * @param mixed $params [optional] the following arguments will be passed to the $class constructor
	 *
	 * @return mixed of type $type
	 * @throws \Exception
	 */
	public function loadClass( string $class, string $type = null, ...$params) {
		$type = $type ?? $class;

		try {
			$reflectionClass = new ReflectionClass($class);
			$reflectionType  = new ReflectionClass($type);
		} catch (Exception $e) {
			throw new TraitException('ClassLoader: ' . $e->getMessage());
		}


		if ($reflectionType->isTrait()) {
			throw new TraitException($class . ' cannot be an instance of trait ' . $type);
		}

		if ($reflectionClass->isAbstract()) {
			throw new TraitException('cannot instance abstract class ' . $class);
		}

		if ($reflectionClass->isTrait()) {
			throw new TraitException('cannot instance trait ' . $class);
		}

		if ($class !== $type) {

			if ($reflectionType->isInterface() && !$reflectionClass->implementsInterface($type)) {
				throw new TraitException($class . ' does not implement ' . $type);
			} elseif (!$reflectionClass->isSubclassOf($type)) {
				throw new TraitException($class . ' does not inherit ' . $type);
			}

		}

		try {
			$object = $reflectionClass->newInstanceArgs($params);

			if (!$object instanceof $type) {
				throw new TraitException('how did u even get here?'); // @codeCoverageIgnore
			}

			return $object;
		}
		// @codeCoverageIgnoreStart
		// here be dragons
		catch (Exception $e) {
			throw new TraitException('ClassLoader: ' . $e->getMessage());
		}
		// @codeCoverageIgnoreEnd

	}

}
