/src/Container.class.php

https://github.com/mikesir87/PHP-Container · PHP · 250 lines · 150 code · 18 blank · 82 comment · 34 complexity · 463694b39b2a5ad2c7749072d9675ca9 MD5 · raw file

  1. <?php
  2. /**
  3. * Creates a container that allows for dependency injection in the application.
  4. * In order to work with the container, classes can use the following
  5. * annotations:
  6. *
  7. * - Class Level Annotations
  8. * - @ManagedClass - reference defaults to the name of the class
  9. * - @ManagedClass("referenceName") - define the reference name
  10. * - @SharedClass - defines the class as a Singleton in the Container (only
  11. * one instantiation of the object will be performed
  12. * - Property level
  13. * - NOTE: In order for a class to use Autowiring, the @ManagedClass
  14. * annotation must be used.
  15. * - @Autowired - signifies that the property is to be autowired, using a
  16. * reference with the same name as the property.
  17. * - @Autowired("referenceName") - defines the property to be autowired
  18. * using a given referenceName
  19. * - @ResourceSetting("class.variable") - defines an autowiring using a
  20. * property from a class.
  21. *
  22. * @author Michael Irwin
  23. * @package container
  24. */
  25. class Container {
  26. /**
  27. * The ClassInfoSet, which has all information about the classes to be
  28. * managed.
  29. * @var ClassInfoSet
  30. */
  31. protected $classes;
  32. /**
  33. * @var ResourceInfoSet
  34. */
  35. protected $resources;
  36. /**
  37. * The singleton instance of the Container.
  38. * @var Container
  39. */
  40. protected static $instance;
  41. /**
  42. * Works in a Singleton manner.
  43. * @return Container The current, and only, instance of Container.
  44. */
  45. public static function getInstance() {
  46. if (self::$instance == null) {
  47. self::$instance = new Container();
  48. }
  49. return self::$instance;
  50. }
  51. /**
  52. * Given an object, inject dependencies into it.
  53. * @param Object $object An object to injuect dependencies for.
  54. * @return Object The same object, with dependencies injected into it.
  55. */
  56. public function injectDependencies($object) {
  57. $classInfo = new ClassInfo();
  58. $classInfo->setClassName(get_class($object));
  59. $classInfo->setAutowiringProperties($this->getManagedProperties(new ReflectionClass(get_class($object))));
  60. return $this->setupClass($classInfo, $object);
  61. }
  62. /**
  63. * Instantiates the Container. Loads all classes currently in the system
  64. * and figures out what needs autowiring, etc.
  65. */
  66. private function Container() {
  67. $this->classes = new ClassInfoSet();
  68. $this->resources = new ResourceInfoSet();
  69. $this->loadAllClasses();
  70. foreach($this->classes->getAllClasses() as $class) {
  71. /* @var $class ClassInfo */
  72. if ($class->getAutoCreate()) {
  73. $this->setupClass($class);
  74. }
  75. }
  76. }
  77. /**
  78. * Get a wired component from the Container. If a class is a shared class,
  79. * it returns the stored copy of that object.
  80. *
  81. * For example, to retrieve a DB, you can call Container::getInstance()->db
  82. * It will, if needed, create the object and inject any needed dependencies
  83. * into it.
  84. *
  85. * @param string $className The class to retrieve.
  86. * @return type
  87. */
  88. public function __get($className) {
  89. $classReference = $this->classes->findClass($className);
  90. if ($classReference == null)
  91. throw new NoSuchObjectException("Class reference for " . $className
  92. . " could not be found.", $className);
  93. $class = $this->classes->getClass($classReference);
  94. if ($class->isSingleton() == true) {
  95. if ($class->getObject() == null) {
  96. $obj = $this->setupClass($class);
  97. $this->classes->assignObjectToClass($classReference, $obj);
  98. return $obj;
  99. }
  100. return $class->getObject();
  101. }
  102. else {
  103. return $this->setupClass($class);
  104. }
  105. return null;
  106. }
  107. /**
  108. * Function that navigates through all declared classes and determines
  109. * autowiring needs and ManagedClasses.
  110. * @return ClassInfoSet Array of ClassInfo, which contains information about
  111. * the properties of each class.
  112. */
  113. private function loadAllClasses() {
  114. $classes = get_declared_classes();
  115. $objs = array();
  116. foreach ($classes as $class) {
  117. $objs[] = $this->evaluateClass($class);
  118. }
  119. foreach ($objs as $obj) {
  120. if (method_exists($obj, "postConstruct"))
  121. $obj->postConstruct();
  122. }
  123. }
  124. /**
  125. * Evaluates a class and determines if it is managed, has autowiring, etc.
  126. * @param string $className The name of the class to evaluate.
  127. */
  128. private function evaluateClass($className) {
  129. $class = new ReflectionClass($className);
  130. $docComment = $class->getDocComment();
  131. if (preg_match('/\*\s+@ManagedClass(\("(\w+)"\))?/', $docComment, $matches)) {
  132. $reference = (count($matches) == 1) ? lcfirst($className) : $matches[2];
  133. $single = (preg_match('/\*\s+@(SharedClass|RequestScoped)/', $docComment,
  134. $matches)) ? true : false;
  135. $autoCreate = (preg_match('/\*\s+@(AutoCreate)/', $docComment,
  136. $matches)) ? true : false;
  137. $thisClass = new ClassInfo();
  138. $thisClass->setReference($reference);
  139. $thisClass->setClassName($className);
  140. $thisClass->setAutowiringProperties($this->getManagedProperties($class));
  141. $thisClass->setObject(null);
  142. $thisClass->setIsSingleton($single);
  143. $thisClass->setAutoCreate($autoCreate);
  144. $this->classes->addClass($thisClass);
  145. }
  146. else if (preg_match('/\*\s+@ResourceBundle(\("(\w+)"\))?/', $docComment, $matches)) {
  147. $reference = (count($matches) == 1) ? lcfirst($className) : $matches[2];
  148. $resource = new ResourceInfo();
  149. $resource->setClassName($className);
  150. $resource->setReference($reference);
  151. $this->resources->addResource($resource);
  152. }
  153. return null;
  154. }
  155. /**
  156. * Look at a class and determine what properties are to be autowired.
  157. * @param ReflectionClass $class The class to evaluate.
  158. * @return Array An array of the properties that are to be autowired.
  159. */
  160. private function getManagedProperties(ReflectionClass $class) {
  161. $managedProperties = array();
  162. $properties = $class->getProperties();
  163. foreach ($properties as $property) {
  164. $docComment = $property->getDocComment();
  165. if (preg_match('/\*\s+@Autowired(\("(\w+)"\))?/i', $docComment, $matches)) {
  166. $reference = (count($matches) == 1) ? lcfirst($property->getName()) : $matches[2];
  167. $autowire = new AutowireInfo();
  168. $autowire->setAutowireType(AutowireTypes::OBJECT);
  169. $autowire->setPropertyName($property->getName());
  170. $autowire->setReference($reference);
  171. $managedProperties[] = $autowire;
  172. }
  173. else if (preg_match('/\*\s+@ResourceSetting\("(.+)"\)/i', $docComment, $matches)) {
  174. $reference = $matches[1];
  175. $autowire = new AutowireInfo();
  176. $autowire->setAutowireType(AutowireTypes::RESOURCE);
  177. $autowire->setPropertyName($property->getName());
  178. $autowire->setReference($reference);
  179. $managedProperties[] = $autowire;
  180. }
  181. }
  182. if ($class->getParentClass() != null) { // Check for autowiring on parent
  183. $managedProperties = array_merge($managedProperties,
  184. $this->getManagedProperties($class->getParentClass()));
  185. }
  186. return $managedProperties;
  187. }
  188. /**
  189. * Creates a new instantation of a class and performs any needed dependency
  190. * injections on it.
  191. * @param ClassInfo $class The class to base off of and build with.
  192. * @return Class An instantiated object of the class represented by $class.
  193. */
  194. private function setupClass($class, $obj = null) {
  195. $className = $class->getClassName();
  196. if ($obj == null)
  197. $obj = new $className();
  198. if ($class->isSingleton())
  199. $this->classes->assignObjectToClass($class->getReference(), $obj);
  200. foreach($class->getAutowiringProperties() as $wire) {
  201. /* @var $wire AutowireInfo */
  202. if ($wire->getAutowireType() == AutowireTypes::OBJECT) {
  203. $setVariable = "set" . ucfirst($wire->getPropertyName());
  204. if (method_exists($obj, $setVariable)) {
  205. if ($this->classes->getClass($wire->getReference()) == null) {
  206. throw new InvalidArgumentException("No autowiring candidate found for " . $wire->getReference());
  207. }
  208. if ($this->classes->getClass($wire->getReference())->getObject() != "") {
  209. $obj->$setVariable($this->classes->getClass($wire->getReference())->getObject());
  210. }
  211. else
  212. $obj->$setVariable( $this->{$wire->getReference()} );
  213. }
  214. else
  215. throw new InvalidArgumentException ("Function $setVariable doesn't
  216. exist on " . $class->getClassName());
  217. }
  218. else if ($wire->getAutowireType() == AutowireTypes::RESOURCE) {
  219. $setVariable = "set" . ucfirst($wire->getPropertyName());
  220. if (preg_match_all("/(\w+)/", $wire->getReference(), $matches)) {
  221. if (count($matches) == 2) {
  222. $class = $matches[0][0]; $variable = $matches[0][1];
  223. $resource = $this->resources->getResource($class)->getClassName();
  224. $value = call_user_func(array($resource, "get"), $variable);
  225. $obj->$setVariable($value);
  226. }
  227. }
  228. }
  229. }
  230. return $obj;
  231. }
  232. }