Git viewing holonet/common / cb6a57b38e9b29f9528949e2770da52beee57610 / src/di/Container.php


Filter

      <?php
/**
 * This file is part of the holonet common library
 * (c) Matthias Lantsch.
 *
 * @license http://opensource.org/licenses/gpl-license.php  GNU Public License
 * @author  Matthias Lantsch <[email protected]>
 */

namespace holonet\common\di;

use TypeError;
use ReflectionClass;
use ReflectionException;
use Psr\Container\ContainerInterface;

/**
 * Dependency Injection container conforming with PSR-11.
 */
class Container implements ContainerInterface {
	/**
	 * @var string DI_PREFIX Prefix value for the injected class properties
	 */
	public const DI_PREFIX = 'di_';

	/**
	 * @var array<string, object> $dependencies A key value storage with dependency objects
	 */
	private array $dependencies = array();

	/**
	 * @var array<string, array> $lazyLoadedDeps Lazily loaded dependency objects
	 */
	private array $lazyLoadedDeps = array();

	/**
	 * {@inheritDoc}
	 * @param string[] $getFor Array used keep track of injections (to prevent recursive dependencies)
	 */
	public function get($id, array $getFor = array()) {
		if (in_array($id, $getFor)) {
			throw new DependencyInjectionException('Recursive dependency definition detected: '.implode(' => ', $getFor));
		}

		if (isset($this->dependencies[$id])) {
			return $this->dependencies[$id];
		}
		if (isset($this->lazyLoadedDeps[$id])) {
			try {
				list('class' => $class, 'args' => $args) = $this->lazyLoadedDeps[$id];
				$rfc = new ReflectionClass($class);
				$value = $rfc->newInstanceWithoutConstructor();
				$getFor[] = $id;
				$this->inject($value, true, $getFor);
				if (method_exists($value, '__construct')) {
					$value->__construct(...$args);
				}
				if (method_exists($value, 'init')) {
					trigger_error('Relying on init() to initialise dependency objects after injecting is no longer required and deprecated', \E_USER_DEPRECATED);
				}
				$this->dependencies[$id] = $value;

				return $value;
			} catch (TypeError | ReflectionException $e) {
				throw new DependencyInjectionException("Cannot initialise dependency '{$id}' on Dependency Container: '{$e->getMessage()}'", (int)($e->getCode()), $e);
			}
		} else {
			throw new DependencyNotFoundException("Dependency '{$id}' does not exist on Dependency Container");
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public function has($id) {
		return isset($this->dependencies[$id]) || isset($this->lazyLoadedDeps[$id]);
	}

	/**
	 * Method used to inject dependencies into an object, here called "the user of the dependencies".
	 * @param object $dependencyUser The object to be injected
	 * @param bool $forceInjection Whether to throw an exception if a dependency cannot be found
	 * @param array $injectFor Array used keep track of injections (to prevent recursive dependencies)
	 */
	public function inject(object $dependencyUser, bool $forceInjection = true, array $injectFor = array()): void {
		foreach (array_keys(get_class_vars(get_class($dependencyUser))) as $propertyName) {
			$propertyName = (string)$propertyName;
			if (mb_strpos($propertyName, self::DI_PREFIX) === 0) {
				$depKey = str_replace(self::DI_PREFIX, '', $propertyName);
				if (!$this->has($depKey) && $forceInjection) {
					try {
						$dependencyUser->{$propertyName} = null;
					} catch (TypeError $e) {
						throw new DependencyNotFoundException("Dependency '{$depKey}' does not exist on Dependency Container");
					}
				} else {
					$dependencyUser->{$propertyName} = $this->get($depKey, $injectFor);
				}
			}
		}
	}

	/**
	 * Method used to set a dependency in this class.
	 * If the given value is an object, it will get injected and saved under the key
	 * If the given value is a string a class name is assumed and the class / argument combination will be saved for later instantiation.
	 * @param string $id The key to save the dependency under
	 * @param mixed $value The dependency to save
	 * @param mixed ...$constructorArgs Arguments for the class instantiation
	 */
	public function set(string $id, $value, ...$constructorArgs): void {
		if (is_string($value) && class_exists($value)) {
			$this->lazyLoadedDeps[$id] = array('class' => $value, 'args' => $constructorArgs);
		} else {
			if (!is_object($value)) {
				throw new DependencyInjectionException("Cannot create dependency '{$id}' on Dependency Container");
			}

			$this->inject($value);
			$this->dependencies[$id] = $value;
		}
	}
}