Factory and Facade wrapper for lazy loading


/ Published in: PHP
Save to your folder(s)

This is a set of classes that work with each other to make a lazy object loader factory.


Copy this code and paste it in your HTML
  1. <?php
  2.  
  3. /*
  4. This code implements a sort of super late binding using a
  5. factory and a facade.
  6. The factory creates facade objects which are just wrappers
  7. around the real object. When I ask the factory for an object
  8. it gives me the facade. Only when I try to access the object
  9. methods does it create the actual object.
  10.  
  11. The facade behaves (or is supposed to) the same as the object
  12. it is wrapping.
  13.  
  14. One shortcoming of this implementation is that it only intercepts
  15. method calls. Attempts to access member variables will fail.
  16. Need to implement __get and __set to handle this.
  17.  
  18. */
  19.  
  20. /*
  21.  * I made a simple log/output class so I don't use echo/print
  22.  * in the actual code.
  23.  */
  24. class Log{
  25. static function head($s){
  26. echo "------ $s ------\n";
  27. }
  28. static function debug($s){
  29. echo sprintf("<< %s >>\n", $s);
  30. }
  31. static function out($s){
  32. echo sprintf(" %s\n", $s);
  33. }
  34. static function br(){
  35. echo "--------------\n";
  36. }
  37. }
  38.  
  39. /*
  40.  * A wrapper around an actual class.
  41.  * Will pass through all functions and variables
  42.  * as if it is the real underlying object.
  43.  */
  44. class Facade {
  45.  
  46. private $object = null;
  47. private $wrappedClass;
  48. private $constructorArgs;
  49. private $properties;
  50.  
  51. public function __construct($cls, $args = null){
  52. Log::debug(__METHOD__ . "($cls)");
  53.  
  54. $this->wrappedClass = $cls;
  55. $this->constructorArgs = $args;
  56. }
  57.  
  58. //
  59. private function makeDynamicObjectInstance(){
  60. if ( ! $this->object ){
  61. if ( ! $this->constructorArgs ){
  62. Log::debug('Facade constructing new '.$this->wrappedClass);
  63. $this->object = new $this->wrappedClass();
  64. }
  65. else{
  66. Log::debug('Facade constructing new '.$this->wrappedClass . '(' . implode(', ', $this->constructorArgs) . ')');
  67. $reflect = new ReflectionClass($this->wrappedClass);
  68. $this->object = $reflect->newInstanceArgs($this->constructorArgs);
  69. }
  70. if ( $this->properties && is_array($this->properties) ){
  71. foreach($this->properties as $varname => $value){
  72. $this->object->$varname = $value;
  73. }
  74. }
  75. }
  76. }
  77.  
  78. // I have to make this funciton public to allow the factory to set it if needed.
  79. public function facade_setProperties($properties){
  80. $this->properties = $properties;
  81. }
  82.  
  83. public function __call($name, $args){
  84. if ( ! $this->object ) $this->makeDynamicObjectInstance();
  85. Log::debug('invoking ' . $this->wrappedClass . '::' . $name . "() via Facade") ;
  86.  
  87. if ( ! method_exists($this->object, $name) ){
  88. $trace = debug_backtrace();
  89. trigger_error('Call to undefined method ' . $this->wrappedClass . '::' . $name . '() in ' . $trace[1]['file'] . ' on line ' . $trace[1]['line'],
  90. E_USER_ERROR);
  91. }
  92.  
  93. return call_user_func_array (array($this->object, $name), $args);
  94.  
  95. }
  96.  
  97. public function __get($name){
  98. Log::debug(__METHOD__."($name)");
  99. if ( ! $this->object ) $this->makeDynamicObjectInstance();
  100.  
  101. return $this->object->$name; // FIXME: will crash if doesn't exist
  102. }
  103. public function __set($name, $val){
  104. Log::debug(__METHOD__."($name)");
  105. if ( ! $this->object ) $this->makeDynamicObjectInstance();
  106. $this->object->$name = $val;
  107. }
  108.  
  109. }
  110.  
  111. class Factory{
  112.  
  113. /*
  114.   I am telling the factory what objects I want it to make for me.
  115.   I can put any objects in here I want. They do not need to extend
  116.   any base class.
  117.   */
  118. static $def = array(
  119. 'simple1' => array('class' => 'Simple'),
  120. 'simple2' => array('class' => 'Simple'), // I can make another one with a different reference name
  121. 'simple3' => array('class' => 'Simple',
  122. // I can add any properties I want
  123. 'properties' => array(
  124. 'myvar1' => 'myvar1-value',
  125. 'temp' => '56',
  126. 'humidity' => 53,
  127. )
  128. ),
  129. 'memnon' => array('class' => 'Greek',
  130. 'constructorArgs' => array('Memnon') // I can make it call the constructor with arguments
  131. ),
  132. 'achilles' => array('class' => 'Greek',
  133. 'constructorArgs' => array('Achilles')
  134. ),
  135. 'imhotep' => array('class' => 'Egyptian',
  136. 'constructorArgs' => array('Imhotep')
  137. ),
  138. 'jesse' => array('class' => 'Friend',
  139. 'constructorArgs' => array('Jesse'),
  140. 'properties' => array(
  141. 'car' => array('@ref' => 'jesse-car'),
  142. 'length' => 6.5,
  143. 'status' => 1,
  144. 'sex' => 'm',
  145. 'weight' => 150,
  146. )
  147. ),
  148. 'aldo' => array(
  149. ),
  150. 'car1' => array('class' => 'Car',
  151. 'constructorArgs' => array('blue')
  152. ),
  153. 'car2' => array('class' => 'Car',
  154. 'constructorArgs' => array('blue')
  155. ),
  156. 'jesse-car' => array('class' => 'Car',
  157. 'constructorArgs' => array('red')
  158. ),
  159. 'memnons-car' => array('class' => 'Car',
  160. 'constructorArgs' => array('purple')
  161. ),
  162. );
  163. static $objects = array();
  164.  
  165. // I will call this method when I want an object
  166. // I don't have to care if it is instantiated yet.
  167. // FIXME: calling this with a name that isn't in the factory will fail.
  168. static function getObject($name){
  169. Log::debug(__METHOD__."($name)");
  170. if ( isset(self::$objects[$name]) ) return self::$objects[$name];
  171. $class = self::$def[$name]['class'];
  172.  
  173. if ( isset(self::$def[$name]['constructorArgs']) ){
  174. self::$objects[$name] = new Facade($class, self::$def[$name]['constructorArgs']);
  175. }
  176. else{
  177. self::$objects[$name] = new Facade($class);
  178. }
  179. if ( isset(self::$def[$name]['properties']) && self::$def[$name]['properties'] ){
  180. $props = array();
  181. foreach (self::$def[$name]['properties'] as $key => $value) {
  182. if ( is_array($value) && count($value) > 0 && array_key_exists('@ref', $value) ){
  183. Log::debug('Reference ' . $value['@ref']);
  184. // is it a reference to self?
  185. if ( $value['@ref'] == $name ){
  186. Log::debug('reference to self');
  187. $props[$key] = self::$objects[$name];
  188. }
  189. else{
  190. Log::debug('reference to other object ' . $value['@ref'] );
  191. $props[$key] = self::getObject($value['@ref']);
  192. }
  193. }
  194. else $props[$key] = $value;
  195. }
  196. self::$objects[$name]->facade_setProperties($props);
  197. }
  198. return self::$objects[$name];
  199. }
  200. }
  201.  
  202. class Simple{
  203. // a simple class to demonstrate classes with no constructor
  204. public $var = 'something';
  205. }
  206.  
  207. // Now I will make some classes to play with the factory/facade
  208. abstract class Person{
  209. protected $name = null;
  210. public $sex;
  211. private $car = null;
  212. public $weight;
  213. public function __construct($name = null, $carId=null){
  214. $this->name = ($name) ? $name : null;
  215. $this->car = ( $carId ) ? Factory::getObject($carId) : null;
  216. }
  217. public function car(){
  218. if ( $this->car )
  219. Log::out("My car is " . $this->car->getColor() );
  220. else
  221. Log::out("I have no car");
  222. }
  223.  
  224. public function outName(){
  225. Log::debug(__METHOD__." [$this->name]");
  226. if ( $this->name )
  227. Log::out( "My name is " . $this->name);
  228. else
  229. Log::out("I have no name");
  230. }
  231. public function getName(){
  232. Log::debug(__METHOD__." [$this->name]");
  233. //Log::debug('Returning : '.$this->name);
  234. return $this->name;
  235. }
  236. }
  237.  
  238. class Greek extends Person{
  239.  
  240. public function __construct($name){
  241. Log::debug(__METHOD__."($name)");
  242. parent::__construct($name);
  243. }
  244.  
  245. function outClassName(){
  246. Log::out( __METHOD__ . ": My name is ". __CLASS__ );
  247. }
  248.  
  249. }
  250.  
  251. class Egyptian extends Person{
  252. protected $enemy;
  253. public function __construct($name){
  254. Log::debug(__METHOD__);
  255. parent::__construct($name);
  256. $this->enemy = Factory::getObject('memnon');
  257. }
  258. function attack($s){
  259. Log::out("Attacking $s") ;
  260. }
  261. function enemy(){
  262. Log::debug(__METHOD__ ." [$this->name]");
  263. //Log::debug(print_r($this->enemy, true));
  264. $enemyName = ($this->enemy) ? $this->enemy->getName() : 'no enemy';
  265. if ( $this->enemy ) Log::out('My enemy is '. $enemyName);
  266. else Log::out("I have no enemy");
  267. }
  268. }
  269.  
  270. class Friend extends Person{
  271. public $length;
  272. public $status;
  273. public function __construct($name){
  274. Log::debug(__METHOD__);
  275. parent::__construct($name);
  276. }
  277.  
  278. public function outStats(){
  279. Log::out(sprintf('sex=%s length=%s status=%s weight=%s', $this->sex, $this->length, $this->status, $this->weight));
  280. }
  281. }
  282.  
  283. class Car{
  284.  
  285. private $color;
  286. public function __construct($color){
  287. Log::debug(__METHOD__);
  288. $this->color = $color;
  289. }
  290.  
  291. function color(){
  292. Log::out( __METHOD__ . ": My color is ". $this->color );
  293. return $this->color;
  294. }
  295.  
  296. function getColor(){
  297. return $this->color();
  298. }
  299.  
  300. }
  301.  
  302.  
  303.  
  304. Log::head('Start');
  305. Log::out('Get memnon object');
  306. $memnon = Factory::getObject('memnon');
  307.  
  308. Log::out('Get achilles object');
  309. $achilles = Factory::getObject('achilles');
  310. Log::out('Get car1 object');
  311. $car1 = Factory::getObject('car1');
  312. Log::out('Get imhotep object');
  313. $imhotep = Factory::getObject('imhotep');
  314. $simple1 = Factory::getObject('simple1');
  315. $simple3 = Factory::getObject('simple3');
  316.  
  317. Log::head('Finished getting objects');
  318.  
  319. Log::out('Call memnon\'s function');
  320. $memnon->outName();
  321. Log::out('Call car1\'s function');
  322. $car1->color();
  323. Log::out('Call imhotep\'s function');
  324. $imhotep->enemy();
  325.  
  326.  
  327. Log::out('try to get a property from simple1');
  328. Log::out('var = '.$simple1->var);
  329.  
  330. Log::out('try to get a property from simple3');
  331. Log::out('myvar1 = '.$simple3->myvar1);
  332. Log::out('temp = '.$simple3->temp);
  333. Log::out('humidity = '.$simple3->humidity);
  334. //the following will fail
  335. //Log::out('myvar1 = '.$simple3->myvar2);
  336.  
  337. // Let's put it in a non-factory object
  338. class Plain{
  339. public $friend;
  340.  
  341. public function __construct(){
  342.  
  343. }
  344. public function setFriend($name){
  345. $this->friend = Factory::getObject($name);
  346. }
  347. public function outFriendName(){
  348. if ( $this->friend ) Log::out('Friend\'s name ' . $this->friend->getName());
  349. }
  350. }
  351. $plain = new Plain();
  352. $plain->setFriend('jesse');
  353. $plain->outFriendName();
  354.  
  355. $friend = $plain->friend;
  356. $friend->outStats();

Report this snippet


Comments

RSS Icon Subscribe to comments

You need to login to post a comment.