<commit_before><?php
namespace Darya\Service;

use \Closure;
use \ReflectionClass;
use \ReflectionMethod;
use \ReflectionFunction;
use \ReflectionParameter;
use Darya\Service\ContainerException;
use Darya\Service\ContainerInterface;

/**
 * Darya's service container.
 * 
 * Service containers can be used to associate interfaces with implementations.
 * They make it painless to interchange the components of an application.
 * 
 * @author <NAME> <<EMAIL>>
 */
class Container implements ContainerInterface {
	
	/**
	 * @var \Darya\Service\Container Singleton instance of the service container
	 */
	protected static $instance;
	
	/**
	 * @var array Set of interfaces as keys and implementations as values
	 */
	protected $services = array();
	
	/**
	 * @var array Set of aliases as keys and interfaces as values
	 */
	protected $aliases = array();
	
	/**
	 * Returns a singleton instance of the container.
	 * 
	 * @return \Darya\Service\Container
	 */
	public static function instance() {
		if (is_null(static::$instance)) {
			static::$instance = new static;
		}
		
		return static::$instance;
	}
	
	/**
	 * Instantiate a service container.
	 * 
	 * @param array $services [optional] Initial set of services and/or aliases
	 */
	public function __construct(array $services = array()) {
		$this->register($services);
	}
	
	/**
	 * Enables shorter syntax for resolving services.
	 * 
	 * @param string $id
	 * @return mixed
	 */
	public function __get($id) {
		if (!property_exists($this, $id)) {
			return $this->resolve($id);
		}
		
		return $this->$id;
	}
	
	/**
	 * Enables shorter syntax for registering a service.
	 * 
	 * @param string $id
	 * @param mixed  $service
	 */
	public function __set($id, $service) {
		$this->register($id, $service);
	}
	
	
	/**
	 * Determine whether the container has a service registered for the given
	 * interface or alias.
	 * 
	 * @param string $abstract
	 * @return bool
	 */
	public function has($abstract) {
		return isset($this->aliases[$abstract]) || isset($this->services[$abstract]);
	}
	
	/**
	 * Get the service associated with the given interface or alias.
	 * 
	 * This method does not resolve dependencies using registered services.
	 * 
	 * Returns null if not found.
	 * 
	 * @param string $abstract
	 * @return mixed
	 */
	public function get($abstract) {
		$abstract = isset($this->aliases[$abstract]) ? $this->aliases[$abstract] : $abstract;
		
		return isset($this->services[$abstract]) ? $this->services[$abstract] : null;
	}
	
	/**
	 * Register an interface and its associated implementation.
	 * 
	 * @param string $abstract
	 * @param mixed  $concrete
	 */
	public function set($abstract, $concrete) {
		$this->services[$abstract] = $concrete;
	}
	
	/**
	 * Register an alias for the given interface.
	 * 
	 * @param string $alias
	 * @param string $abstract
	 */
	public function alias($alias, $abstract) {
		$this->aliases[$alias] = $abstract;
	}
	
	/**
	 * Register interfaces and their concrete implementations, or aliases and
	 * their corresponding interfaces.
	 * 
	 * This method only registers aliases if their interface is already
	 * registered with the container.
	 * 
	 * @param array $services interfaces => concretes or aliases => interfaces
	 */
	public function register(array $services = array()) {
		foreach ($services as $key => $value) {
			if (is_string($value) && isset($this->services[$value])) {
				$this->alias($key, $value);
			} else {
				$this->set($key, $value);
			}
		}
	}

	/**
	 * Resolve a service and its dependencies by interface or alias.
	 * 
	 * @param string $abstract  Interface or alias
	 * @param array  $arguments [optional]
	 * @return mixed
	 */
	public function resolve($abstract, array $arguments = array()) {
		$concrete = $this->get($abstract);
		
		if ($concrete instanceof Closure || is_callable($concrete)) {
			return $this->call($concrete, $arguments);
		} else if (is_string($concrete) && class_exists($concrete)) {
			return $this->create($concrete, $arguments);
		}
		
		return $concrete;
	}
	
	/**
	 * Call a callable and attempt to resolve its parameters using services
	 * registered with the container.
	 * 
	 * @param callable $callable
	 * @param array    $arguments [optional]
	 * @return mixed
	 */
	public function call($callable, array $arguments = array()) {
		$method = is_array($callable) && count($callable) > 1 && method_exists($callable[0], $callable[1]);
		
		if ($method) {
			$reflection = new ReflectionMethod($callable[0], $callable[1]);
		} else {
			$reflection = new ReflectionFunction($callable);
		}
		
		$parameters = $reflection->getParameters();
		$arguments = array_map('is_numeric', array_keys($arguments)) ? $arguments : array_merge($this->resolveParameters($parameters), $arguments);
		
		return $method ? $reflection->invokeArgs($callable[0], $arguments) : $reflection->invokeArgs($arguments);
	}
	
	/**
	 * Instantiate the given class and attempt to resolve its constructor's
	 * parameters using services registered with the container.
	 * 
	 * @param callable $class
	 * @param array    $arguments [optional]
	 * @return object
	 */
	public function create($class, array $arguments = array()) {
		$reflection = new ReflectionClass($class);
		$constructor = $reflection->getConstructor();
		
		if (!$constructor) {
			return $reflection->newInstance();
		}
		
		$parameters = $constructor->getParameters();
		$arguments = array_merge($this->resolveParameters($parameters), $arguments);<commit_msg>Implemented service container self-registration.<commit_after><?php
namespace Darya\Service;

use \Closure;
use \ReflectionClass;
use \ReflectionMethod;
use \ReflectionFunction;
use \ReflectionParameter;
use Darya\Service\ContainerException;
use Darya\Service\ContainerInterface;

/**
 * Darya's service container.
 * 
 * Service containers can be used to associate interfaces with implementations.
 * They make it painless to interchange the components of an application.
 * 
 * @author <NAME> <<EMAIL>>
 */
class Container implements ContainerInterface {
	
	/**
	 * @var \Darya\Service\Container Singleton instance of the service container
	 */
	protected static $instance;
	
	/**
	 * @var array Set of interfaces as keys and implementations as values
	 */
	protected $services = array();
	
	/**
	 * @var array Set of aliases as keys and interfaces as values
	 */
	protected $aliases = array();
	
	/**
	 * Returns a singleton instance of the container.
	 * 
	 * @return \Darya\Service\Container
	 */
	public static function instance() {
		if (is_null(static::$instance)) {
			static::$instance = new static;
		}
		
		return static::$instance;
	}
	
	/**
	 * Instantiate a service container.
	 * 
	 * Registers the service container with itself, as well as registering any
	 * given services and aliases.
	 * 
	 * @param array $services [optional] Initial set of services and/or aliases
	 */
	public function __construct(array $services = array()) {
		$this->register(array(
			'Darya\Service\ContainerInterface' => $this,
			'Darya\Service\Container'          => $this
		));
		
		$this->register($services);
	}
	
	/**
	 * Enables shorter syntax for resolving services.
	 * 
	 * @param string $alias
	 * @return mixed
	 */
	public function __get($alias) {
		return $this->resolve($alias);
	}
	
	/**
	 * Enables shorter syntax for registering a service.
	 * 
	 * @param string $alias
	 * @param mixed  $service
	 */
	public function __set($alias, $service) {
		$this->register($alias, $service);
	}
	
	/**
	 * Determine whether the container has a service registered for the given
	 * interface or alias.
	 * 
	 * @param string $abstract
	 * @return bool
	 */
	public function has($abstract) {
		return isset($this->aliases[$abstract]) || isset($this->services[$abstract]);
	}
	
	/**
	 * Get the service associated with the given interface or alias.
	 * 
	 * This method does not resolve dependencies using registered services.
	 * 
	 * Returns null if not found.
	 * 
	 * @param string $abstract
	 * @return mixed
	 */
	public function get($abstract) {
		$abstract = isset($this->aliases[$abstract]) ? $this->aliases[$abstract] : $abstract;
		
		return isset($this->services[$abstract]) ? $this->services[$abstract] : null;
	}
	
	/**
	 * Register an interface and its associated implementation.
	 * 
	 * @param string $abstract
	 * @param mixed  $concrete
	 */
	public function set($abstract, $concrete) {
		$this->services[$abstract] = $concrete;
	}
	
	/**
	 * Register an alias for the given interface.
	 * 
	 * @param string $alias
	 * @param string $abstract
	 */
	public function alias($alias, $abstract) {
		$this->aliases[$alias] = $abstract;
	}
	
	/**
	 * Register interfaces and their concrete implementations, or aliases and
	 * their corresponding interfaces.
	 * 
	 * This method only registers aliases if their interface is already
	 * registered with the container.
	 * 
	 * @param array $services interfaces => concretes or aliases => interfaces
	 */
	public function register(array $services = array()) {
		foreach ($services as $key => $value) {
			if (is_string($value) && isset($this->services[$value])) {
				$this->alias($key, $value);
			} else {
				$this->set($key, $value);
			}
		}
	}

	/**
	 * Resolve a service and its dependencies by interface or alias.
	 * 
	 * @param string $abstract  Interface or alias
	 * @param array  $arguments [optional]
	 * @return mixed
	 */
	public function resolve($abstract, array $arguments = array()) {
		$concrete = $this->get($abstract);
		
		if ($concrete instanceof Closure || is_callable($concrete)) {
			return $this->call($concrete, $arguments ?: array($this));
		} else if (is_string($concrete) && class_exists($concrete)) {
			return $this->create($concrete, $arguments);
		}
		
		return $concrete;
	}
	
	/**
	 * Call a callable and attempt to resolve its parameters using services
	 * registered with the container.
	 * 
	 * @param callable $callable
	 * @param array    $arguments [optional]
	 * @return mixed
	 */
	public function call($callable, array $arguments = array()) {
		$method = is_array($callable) && count($callable) > 1 && method_exists($callable[0], $callable[1]);
		
		if ($method) {
			$reflection = new ReflectionMethod($callable[0], $callable[1]);
		} else {
			$reflection = new ReflectionFunction($callable);
		}
		
		$parameters = $reflection->getParameters();
		
		if (!array_map('is_numeric', array_keys($arguments))) {
			$arguments = array_merge($this->resolveParameters($parameters), $arguments);
		}
		
		return $method ? $reflection->invokeArgs($callable[0], $arguments) : $reflection->invokeArgs($arguments);
	}
	
	/**
	 * Instantiate the given class and attempt to resolve its constructor's
	 * parameters using services registered with the container.
	 * 
	 * @param callable $class
	 * @param array    $arguments [optional]
	 * @return object
	 */
	public function create($class, array $arguments = array()) {
		$reflection = new ReflectionClass($class);
		$constructor = $reflection->getConstructor();
		
		if (!$constructor) {
			return $reflection->newInstance();
		}
		
		$parameters = $constructor->getParameters();
		$arguments = array_merge($this->resolveParameters($parameters), $arguments);
{'fraction_non_alphanumeric': 0.1334527541424093, 'fraction_numerical': 0.0010747872816838334, 'mean_word_length': 3.7575628461866213, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '904513', 'n_tokens_mistral': 3532, 'n_tokens_neox': 3066, 'n_words': 966}