diff --git a/src/Container.php b/src/Container.php
index 5eed081..0bb6362 100644
--- a/src/Container.php
+++ b/src/Container.php
@@ -15,89 +15,82 @@ namespace Twister;
* Along with the closures, it also contains arrays and instanciated objects.
* All these capabilities are accessed in the form of a dynamic property (using __get and __set);
* eg. '$c->db' gives you the current database class; you can also use '$c->db()' if you prefer.
+ * Laravel Container: https://laravel.com/docs/5.4/container
+ * Symfony Container: http://symfony.com/doc/current/service_container.html
*/
-class Container
+class Container implements ArrayAccess
{
- protected $_container = null;
+ /**
+ * The current globally available container (if any).
+ *
+ * @var static
+ */
+ protected static $instance = null;
+
+ /**
+ * The container's bindings.
+ *
+ * @var array
+ */
+ protected $bindings = null;
+
+ /**
+ * The registered type aliases.
+ *
+ * @var array
+ */
+ protected $aliases = null;
function __construct(array $c = [])
{
- $this->_container = $c;
- }
+ if (static::$instance)
+ throw new \Exception('Cannot create another Container instance!');
+ static::$instance = $this;
- function __set($key, $value)
- {
- /**
- * do we really need to stop the variables from being set???
- * currently we are protecting anything that is not a callable function.
- * the reason why I don't protect a callable, is because many of the callables
- * will set the same value to an instantaited object. It just saves us using `unset()` first
- * The alternative is to do something like Symfony or other frameworks,
- * where we call a `protect()` or `singleton()` methods etc.
- * I just hate calling yet another method for every occasion or fringe case!
- */
- if (isset($this->_container[$key]) && ! is_callable($this->_container[$key]))
+ $this->bindings = $c;
+ if (isset($c['aliases']))
{
- $trace = debug_backtrace();
- trigger_error(__CLASS__ . " container property `{$key}` has already been set and cannot be changed in {$trace[0]['file']} on line {$trace[0]['line']}. Please unset() and re-set the value!", E_USER_ERROR);
+ $this->aliases = $c['aliases'];
+ unset($c['aliases']);
}
- else
- return $this->_container[$key] = $value;
+ $this->aliases[__CLASS__] = $this;
}
- function __get($key)
+ /**
+ * Set the globally available instance of the container.
+ *
+ * @return static
+ */
+ public static function getInstance()
{
- if (isset($this->_container[$key]))
- {
- $value = $this->_container[$key];
- return is_callable($value) ? $value($this) : $value;
- }
-
- /**
- * Examples of official PHP error messages when a property cannot be found
- * Notice: Undefined index: config in C:\...\app.php on line 34
- * Undefined property: Container::$config in C:\...\app.php on line 111
- */
- $trace = debug_backtrace();
- trigger_error('Undefined container property: ' . __CLASS__ . "->{$key} in {$trace[0]['file']} on line {$trace[0]['line']}; thrown", E_USER_ERROR);
- return null;
- }
-
- function __isset($key)
- {
- return isset($this->_container[$key]);
- }
-
- function __unset($key)
- {
- unset($this->_container[$key]);
+ return static::$instance;
}
function &__call($method, $args)
{
- if (isset($this->_container[$method]))
- if (is_callable($this->_container[$method]))
+ if (isset($this->bindings[$method]))
+ if (is_callable($this->bindings[$method]))
{
array_unshift($args, $this); // Prepend $this container to the beginning of the function call arguments!
- $result = call_user_func_array($this->_container[$method], $args);
+ $result = call_user_func_array($this->bindings[$method], $args);
return $result;
}
else
- return $this->_container[$method];
+ return $this->bindings[$method];
else
{
if (preg_match('/^([gs]et|has|isset|unset)([A-Z])(.*)$/', $method, $match))
{
$property = strtolower($match[2]). $match[3];
- if (isset($this->_container[$property]))
+ if (isset($this->bindings[$property]))
{
switch($match[1])
{
- case 'get': return $this->_container[$property];
- case 'set': return $this->_container[$property] = $args[0];
+ case 'get': return $this->bindings[$property];
+ case 'set': return $this->bindings[$property] = $args[0];
case 'has': // fallthrough vvv alias for `isset`
- case 'isset': $result = isset($this->_container[$property]); return $result;
- case 'unset': $result = null; unset($this->_container[$property]); return $result;
+ case 'isset': $result = isset($this->bindings[$property]); return $result;
+ case 'unset': $result = null; unset($this->bindings[$property]); return $result;
}
}
throw new \InvalidArgumentException("Property {$property} doesn't exist");
@@ -113,20 +106,129 @@ class Container
}
function &get($key, $default = null) // similar to __get()
{
- return $this->_container[$key] ?? $default;
+ return $this->bindings[$key] ?? $default;
}
function has($key) // alias for __isset()
{
- return isset($this->_container[$key]);
+ return isset($this->bindings[$key]);
}
function &merge($key, array $arr) // we need this function because we cannot (re)`set` the arrays to new values without unsetting the old values first! ie. __set() will fail because it already exists!
{
// TODO: Add is_array() checks to the container, and add variable number of array inputs!
- $this->_container[$key] = array_merge($this->_container[$key], $arr);
- return $this->_container[$key];
+ $this->bindings[$key] = array_merge($this->bindings[$key], $arr);
+ return $this->bindings[$key];
}
function remove($key) // alias for __unset()
{
- unset($this->_container[$key]);
+ unset($this->bindings[$key]);
+ }
+
+
+
+ /**
+ * Determine if a given offset exists.
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ return $this->__isset($key);
}
+
+ /**
+ * Get the value at a given offset.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ return $this->__get($key);
+ }
+
+ /**
+ * Set the value at a given offset.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($key, $value)
+ {
+ $this->__set($key, $value);
+ }
+
+ /**
+ * Unset the value at a given offset.
+ *
+ * @param string $key
+ * @return void
+ */
+ public function offsetUnset($key)
+ {
+ unset($this->bindings[$key]);
+ }
+
+ /**
+ * Dynamically access container services.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if (isset($this->bindings[$key]))
+ {
+ $value = $this->bindings[$key];
+ return is_callable($value) ? $value($this) : $value; // $value instanceof Closure
+ }
+
+ /**
+ * Examples of official PHP error messages when a property cannot be found
+ * Notice: Undefined index: config in C:\...\app.php on line 34
+ * Undefined property: Container::$config in C:\...\app.php on line 111
+ */
+ $trace = debug_backtrace();
+ trigger_error('Undefined container property: ' . __CLASS__ . "->{$key} in {$trace[0]['file']} on line {$trace[0]['line']}; thrown", E_USER_ERROR);
+ return null;
+ }
+
+ /**
+ * Dynamically set container services.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ /**
+ * do we really need to stop the variables from being set???
+ * currently we are protecting anything that is not a callable function.
+ * the reason why I don't protect a callable, is because many of the callables
+ * will set the same value to an instantaited object. It just saves us using `unset()` first
+ * The alternative is to do something like Symfony or other frameworks,
+ * where we call a `protect()` or `singleton()` methods etc.
+ * I just hate calling yet another method for every occasion or fringe case!
+ */
+ if (isset($this->bindings[$key]) && ! is_callable($this->bindings[$key]))
+ {
+ $trace = debug_backtrace();
+ trigger_error(__CLASS__ . " container property `{$key}` has already been set and cannot be changed in {$trace[0]['file']} on line {$trace[0]['line']}. Please unset() and re-set the value!", E_USER_ERROR);
+ }
+ else
+ return $this->bindings[$key] = $value;
+ }
+
+ function __isset($key)
+ {
+ return isset($this->bindings[$key]);
+ }
+
+ function __unset($key)
+ {
+ unset($this->bindings[$key]);
+ }
+
}