Skip to content

Instantly share code, notes, and snippets.

@mvaled
Created December 16, 2010 16:25
Show Gist options
  • Save mvaled/743599 to your computer and use it in GitHub Desktop.
Save mvaled/743599 to your computer and use it in GitHub Desktop.
A basic PHP IoC implementation.
<?php
class ClaseX {
private static $info;
public static function RegistrarInformacion($info) {
echo "Me estan poniendo informacion $info \n";
self::$info = $info;
}
}
?>
<?php
/*
* container.php
*
* (c) 2010 Manuel Vázquez.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
// If this constant is false, the Cointaner will try to emulate
// the behavior of an unimplemented (but defined) contract.
//
// It is useful when developing. Set it to true when release-testing and
// in production.
if (!defined('CONTAINER_FORCE_METHODS'))
define('CONTAINER_FORCE_METHODS', false);
function getAttribute($node, $name) {
$attrs = $node->attributes();
return (string)$attrs[$name];
}
/**
* Returns the real path of file/dir given a context.
*
* @param string $path The path to be resolved.
* @param string $context A context to be prefixed to path before resolving.
*/
function resolvepath($path, $context = '.') {
$context = trim($context);
if ($context === '.' or $context === '') {
return realpath($path);
}
else {
if ($context[strlen($context)-1] === '/') {
return realpath($context.$path);
}
else {
return realpath($context.'/'.$path);
}
}
}
class ContractViolation extends Exception {
}
class UnknownContractName extends Exception {
}
/**
* The IoC container.
*
* @see test2.php for usage example.
*/
class Container {
public function __construct($filename, $context = '.') {
$contents = simplexml_load_file(resolvepath($filename, $context));
foreach($contents->modulo as $which) {
$proxy = new ModuleProxy(realpath(getAttribute($which, 'src')));
if(!isset($this->{$proxy->getname()})) {
$this->{$proxy->getname()} = $proxy;
}
else {
$this->{$proxy->getname()} = ModuleProxy::MergeProxies($this->{$proxy->getname()}, $proxy);
}
}
}
}
/**
* Represents the signature of a method in a contract
*/
class Prototype {
public $params;
public $result;
public function __construct($params, $result) {
/* Los parámetros son un arreglo de tuplas: llave, valor
*
* XXX No usar arreglos asociativos, pues nosotros necesitamos
* el orden de los parámetros.
*/
$this->params = $params;
$this->result = $result;
}
}
/**
* An unbounded method proxy.
*
* Represents an indirection to a method (implementation) within a
* contract.
*
* The contructor takes four arguments:
*
* $desc A string describing the method. TODO: PHPDoc?
* $controller The name of the class implementing the method. It
* should be a public static method.
* $action The name of the method.
* $prototype A Prototype instance describing the signature of the method.
*
*/
class UnboundMethodProxy {
private $description = '';
private $controller = '';
private $action = '';
private $prototype;
private $method = null;
public function __construct($desc, $controller, $action, $prototype) {
$this->description = $desc;
$this->controller = $controller;
$this->action = $action;
$this->prototype = $prototype;
}
private function InitMethod() {
$class = new ReflectionClass($this->controller);
try {
$this->method = $class->getMethod($this->action);
} catch (ReflectionException $exc) {
// El método aun no está implementado.
// Verificamos si debemos simular el método o relanzar la excepción
if (!CONTAINER_FORCE_METHODS) {
$type = ucfirst($this->prototype->result);
try {
$factory = new ReflectionClass("{$type}Factory");
} catch(Exception $devnull) {
$factory = new ReflectionClass("ObjectFactory");
}
$this->method = $factory->getMethod('getDefault');
}
else
throw $exc;
}
}
/*
private function CheckArguments($arguments) {
if (!isset($this->method)) {
$this->InitMethod();
}
$result = true;
$i = 0;
while($result and $which = each($arguments)) {
$typeincontract = $this->prototype->params[$i][1];
// TODO: PHP es tan malito que no puedo obtener fiablemente el tipo
$i++;
}
return $result;
}
*/
public function execute() {
$arguments = func_get_args();
// TODO: CheckArguments?
if (!isset($this->method))
$this->InitMethod();
/*
eval("\$controller = new {$this->controller}();");
*/
return $this->method->invokeArgs(null, $arguments);
}
}
class VoidFactory {
public static function getDefault() {
return null;
}
}
class IntegerFactory {
public static function getDefault() {
return 0;
}
}
class StringFactory {
public static function getDefault() {
return '';
}
}
class BooleanFactory {
public static function getDefault() {
return false;
}
}
class DoubleFactory {
public static function getDefault() {
return 0.0;
}
}
class ArrayFactory {
public static function getDefault() {
return array();
}
}
class ObjectFactory {
public static function getDefault() {
return null;
}
}
/**
* Represents a module which. A module is basically a namespace
* for methods within a Container.
*
* You can define a module in several XML files and they will be merged.
*/
class ModuleProxy {
private $name;
private $methods = array();
public function getname() {
return $this->name;
}
public static function MergeProxies($pivot, $added) {
assert($pivot->name == $added->name);
$result = clone $pivot;
foreach($added->methods as $which => $what) {
$result->methods[$which] = $what;
}
return $result;
}
private static function ProcessPrototype($which) {
$params = array();
foreach($which->parametro as $paramdef) {
$name = getAttribute($paramdef, 'nombre');
$type = getAttribute($paramdef, 'tipo');
$params[] = array($name, $type);
}
$resultype = getAttribute($which->resultado[0], 'tipo');
$prototype = new Prototype($params, $resultype);
return $prototype;
}
public function __construct($filename, $context = '.') {
$contents = simplexml_load_file(resolvepath($filename, $context));
$this->name = getAttribute($contents, 'nombre');
foreach($contents->contrato as $which)
{
$prototype = self::ProcessPrototype($which->prototipo[0]);
$desc = getAttribute($which->inyector[0], 'nombre');
$controller = getAttribute($which->inyector[0], 'controlador');
$action = getAttribute($which->inyector[0], 'accion');
$methodproxy = new UnboundMethodProxy($desc, $controller, $action, $prototype);
$this->methods[getAttribute($which, 'nombre')] = $methodproxy;
}
}
private function __call($action, $args) {
if (isset($this->methods[$action])) {
$result = call_user_method_array("execute", $this->methods[$action], $args);
return $result;
}
else {
throw new UnknownContractName(sprintf('Contrato %s indefinido para el módulo %s', $action, $this->name));
}
}
}
?>
<?xml version="1.0" encoding="utf-8"?>
<container>
<modulo src="moduloX.xml" />
</container>
<?xml version="1.0" encoding="utf-8"?>
<modulo nombre="ModuloX">
<contrato nombre="Uno">
<inyector nombre="Gestor de Informacion" controlador="ClaseX" accion="RegistrarInformacion" />
<prototipo>
<parametro nombre="Informacion" tipo="integer" />
<resultado tipo="void" />
</prototipo>
</contrato>
<contrato nombre="Unimpl">
<inyector nombre="Gestor de Informacion" controlador="ClaseX" accion="Unimpl" />
<prototipo>
<resultado tipo="integer" />
</prototipo>
</contrato>
</modulo>
<?php
require_once 'container.php';
// So the IoC could find the classes
function __autoload($class_name) {
@include_once($class_name.".php");
}
$container = new Container('container.xml');
$container->ModuloX->Uno(55);
// The following sentence either return the default value for the result's
// type; or raises a ReflectionException. Check the CONTAINER_FORCE_METHODS constant.
echo $container->ModuloX->Unimpl();
// The following sentence raises an UnknownContractName. Uncomment at will.
//$container->ModuloX->Yme(55);
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment