PageRenderTime 60ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/atk4/lib/AbstractObject.php

https://github.com/git86/todo
PHP | 432 lines | 283 code | 32 blank | 117 comment | 50 complexity | 350117815b1337d09828debdfde838b9 MD5 | raw file
Possible License(s): AGPL-3.0
  1. <?php // vim:ts=4:sw=4:et:fdm=marker
  2. /**
  3. * A base class for all objects/classes in Agile Toolkit.
  4. * Do not directly inherit from this class, instead use one of
  5. * AbstractModel, AbstractController or AbstractView
  6. *
  7. * @link http://agiletoolkit.org/learn/intro
  8. *//*
  9. ==ATK4===================================================
  10. This file is part of Agile Toolkit 4
  11. http://agiletoolkit.org/
  12. (c) 2008-2011 Romans Malinovskis <atk@agiletech.ie>
  13. Distributed under Affero General Public License v3
  14. See http://agiletoolkit.org/about/license
  15. =====================================================ATK4=*/
  16. abstract class AbstractObject {
  17. public $settings=array('extension'=>'.html');
  18. /** Configuration passed as a 2nd argument/array to add. Useful for dependency injection */
  19. public $di_config = array();
  20. // {{{ Object hierarchy management: http://agiletoolkit.org/learn/understand/base/adding
  21. /** Unique object name */
  22. public $name;
  23. /** Name of the object in owner's element array */
  24. public $short_name;
  25. /** short_name => object hash of children objects */
  26. public $elements = array ();
  27. /** Link to object into which we added this object */
  28. public $owner;
  29. /** Always points to current API */
  30. public $api;
  31. public $_initialized=false;
  32. /** Initialize object. Always call parent */
  33. function init() {
  34. /**
  35. * This method is called for initialization
  36. */
  37. $this->_initialized=true;
  38. }
  39. function __clone(){
  40. // fix short name and add ourselves to the parent
  41. $this->short_name=$this->_unique($this->owner->elements,$this->short_name);
  42. $this->owner->add($this);
  43. }
  44. function __toString() {
  45. return "Object " . get_class($this) . "(" . $this->name . ")";
  46. }
  47. /** Removes object from parent and prevents it from renedring */
  48. function destroy(){
  49. foreach($this->elements as $el){
  50. $el->destroy();
  51. }
  52. unset($this->elements);
  53. $this->owner->_removeElement($this->short_name);
  54. }
  55. /** Remove child element if it exists */
  56. function _removeElement($short_name){
  57. unset($this->elements[$short_name]);
  58. return $this;
  59. }
  60. /** Creates new object and adds it as a child. Returns new object
  61. * http://agiletoolkit.org/learn/understand/base/adding */
  62. function add($class, $short_name = null, $template_spot = null, $template_branch = null) {
  63. if(is_array($short_name)){
  64. $di_config=$short_name;
  65. $short_name=@$di_config['name'];
  66. }else $di_config=array();
  67. if (is_object($class)) {
  68. // Object specified, just add the object, do not create anything
  69. if (!($class instanceof AbstractObject)) {
  70. throw $this->exception('You may only add objects based on AbstractObject');
  71. }
  72. if (!$class->short_name) {
  73. throw $this->exception('Cannot add existing object, without short_name');
  74. }
  75. if (isset($this->elements[$class->short_name]))
  76. return $this->elements[$class->short_name];
  77. $this->elements[$class->short_name] = $class;
  78. $class->owner = $this;
  79. $class->di_config = array_merge($class->di_config,$di_config);
  80. return $class;
  81. }
  82. if (!$short_name)
  83. $short_name = strtolower($class);
  84. $short_name=$this->_unique($this->elements,$short_name);
  85. if (isset ($this->elements[$short_name])) {
  86. if ($this->elements[$short_name] instanceof AbstractView) {
  87. // AbstractView classes shouldn't be created with the same name. If someone
  88. // would still try to do that, it should generate error. Obviously one of
  89. // those wouldn't be displayed or other errors would occur
  90. $this->warning("Element with name $short_name already exists in " . ($this->__toString()));
  91. }
  92. if ($this->elements[$short_name] instanceof AbstractController) {
  93. return $this->elements[$short_name];
  94. }
  95. // Model classes may be created several times and we are actually don't care about those.
  96. }
  97. if(!is_string($class) || !$class)throw new BaseException("Class is not valid");
  98. $element = new $class ();
  99. if (!($element instanceof AbstractObject)) {
  100. throw new BaseException("You can add only classes based on AbstractObject (called from " . caller_lookup(1, true) . ")");
  101. }
  102. $element->owner = $this;
  103. $element->api = $this->api;
  104. $this->elements[$short_name] = $element;
  105. $element->name = $this->name . '_' . $short_name;
  106. $element->short_name = $short_name;
  107. $element->di_config=$di_config;
  108. if ($element instanceof AbstractView) {
  109. $element->initializeTemplate($template_spot, $template_branch);
  110. }
  111. $element->init();
  112. if(!$element->_initialized)throw $element->exception('You should call parent::init() when you override it')
  113. ->addMoreInfo('object_name',$element->name)
  114. ->addMoreInfo('class',get_class($element));
  115. return $element;
  116. }
  117. /** Find child element by their short name. Use in chaining. Exception if not found. */
  118. function getElement($short_name, $obligatory = true) {
  119. if (!isset ($this->elements[$short_name]))
  120. if ($obligatory)
  121. throw $this->exception("Child element not found")
  122. ->addMoreInfo('element',$short_name);
  123. else
  124. return null;
  125. return $this->elements[$short_name];
  126. }
  127. /** Find child element. Use in condition. */
  128. function hasElement($name){
  129. return isset($this->elements[$name])?$this->elements[$name]:false;
  130. }
  131. // }}}
  132. // {{{ Session management: http://agiletoolkit.org/doc/session
  133. /** Remember object-relevant session data */
  134. function memorize($name, $value) {
  135. if (!isset ($value))
  136. return $this->recall($name);
  137. $this->api->initializeSession();
  138. return $_SESSION['o'][$this->name][$name] = $value;
  139. }
  140. /** Remember one of the supplied arguments, which is not-null */
  141. function learn($name, $value1 = null, $value2 = null, $value3 = null) {
  142. if (isset ($value1))
  143. return $this->memorize($name, $value1);
  144. if (isset ($value2))
  145. return $this->memorize($name, $value2);
  146. return $this->memorize($name, $value3);
  147. }
  148. /** Forget session data for arg $name. Null forgets all data relevant to this object */
  149. function forget($name = null) {
  150. $this->api->initializeSession();
  151. if (isset ($name)) {
  152. unset ($_SESSION['o'][$this->name][$name]);
  153. } else {
  154. unset ($_SESSION['o'][$this->name]);
  155. }
  156. }
  157. /** Returns session data for this object. If not set, $default is returned */
  158. function recall($name, $default = null) {
  159. $this->api->initializeSession(false);
  160. if (!isset ($_SESSION['o'][$this->name][$name])||is_null($_SESSION['o'][$this->name][$name])) {
  161. return $default;
  162. } else {
  163. return $_SESSION['o'][$this->name][$name];
  164. }
  165. }
  166. // }}}
  167. // {{{ Exception handling: http://agiletoolkit.org/doc/exception
  168. public $default_exception='BaseException';
  169. function exception($message,$type=null){
  170. if(!$type){
  171. $type=$this->default_exception;
  172. }else{
  173. $type='Exception_'.$type;
  174. }
  175. // Localization support
  176. if($this->api->hasMethod('_'))
  177. $message=$this->api->_($message);
  178. $e=new $type($message);
  179. $e->owner=$this;
  180. $e->api=$this->api;
  181. $e->init();
  182. return $e;
  183. }
  184. // }}}
  185. // {{{ Code which can be potentially obsoleted
  186. /** @obsolete */
  187. function fatal($error, $shift = 0) {
  188. /**
  189. * If you have fatal error in your object use the following code:
  190. *
  191. * return $this->fatal("Very serious problem!");
  192. *
  193. * This line will notify parent about fatal error and return null to
  194. * the caller. Caller don't have to handle error messages, just throw
  195. * everything up.
  196. *
  197. * Fatal calls are intercepted by API. Or if you want you can intercept
  198. * them yourself.
  199. *
  200. * TODO: record debug_backtrace depth so we could point acurately at
  201. * the function/place where fatal is called from.
  202. */
  203. return $this->upCall('outputFatal', array (
  204. $error,
  205. $shift
  206. ));
  207. }
  208. /** @obsolete */
  209. function info($msg) {
  210. /**
  211. * Call this function to send some information to API. Example:
  212. *
  213. * $this->info("User tried buying traffic without enough money in bank");
  214. */
  215. if(!$this->api->hook('outputInfo',array($msg,$this)))
  216. $this->upCall('outputInfo', $msg);
  217. }
  218. /** @obsolete */
  219. function debug($msg, $file = null, $line = null) {
  220. /**
  221. * Use this function to send debug information. Information will only
  222. * be sent if you enable debug localy (per-object) by setting
  223. * $this->debug=true or per-apllication by setting $api->debug=true;
  224. *
  225. * You also may enable debug globaly:
  226. * $this->api->debug=true;
  227. * but disable for object
  228. * $object->debug=false;
  229. */
  230. if ((isset ($this->debug) && $this->debug) || (isset ($this->api->debug) && $this->api->debug)) {
  231. $this->upCall('outputDebug', array (
  232. $msg,
  233. $file,
  234. $line
  235. ));
  236. }
  237. }
  238. /** @obsolete */
  239. function warning($msg, $shift = 0) {
  240. $this->upCall('outputWarning', array (
  241. $msg,
  242. $shift
  243. ));
  244. }
  245. /////////////// C r o s s c a l l s ///////////////////////
  246. function upCall($type, $args = array ()) {
  247. /**
  248. * Try to handle something on our own and in case we are not
  249. * able, pass to parent. Such as messages, notifications and request
  250. * for additional info or descriptions are passed this way.
  251. */
  252. if (method_exists($this, $type)) {
  253. return call_user_func_array(array (
  254. $this,
  255. $type
  256. ), $args);
  257. }
  258. if (!$this->owner)
  259. return false;
  260. return $this->owner->upCall($type, $args);
  261. }
  262. function downCall($type, $args = array()) {
  263. /**
  264. * Unlike upCallHandler, this will pass call down to all childs. This
  265. * one is useful for a "render" or "submitted" calls.
  266. */
  267. foreach (array_keys($this->elements) as $key) {
  268. if (!($this->elements[$key] instanceof AbstractController)) {
  269. $this_result = $this->elements[$key]->downCall($type, $args);
  270. if ($this_result === false)
  271. return false;
  272. }
  273. }
  274. if (method_exists($this, $type)) {
  275. return call_user_func_array(array (
  276. $this,
  277. $type
  278. ), $args);
  279. }
  280. return null;
  281. }
  282. // }}}
  283. // {{{ Hooks: http://agiletoolkit.org/doc/hooks
  284. public $hooks = array ();
  285. function addHook($hook_spot, $callable, $arguments=array(), $priority = 5) {
  286. if(!is_array($arguments)){
  287. // Backwards compatibility
  288. $priority=$arguments;
  289. $arguments=array();
  290. }
  291. $this->hooks[$hook_spot][$priority][] = array($callable,$arguments);
  292. return $this;
  293. }
  294. function removeHook($hook_spot) {
  295. unset($this->hooks[$hook_spot]);
  296. return $this;
  297. }
  298. function hook($hook_spot, $arg = array ()) {
  299. $return=array();
  300. try{
  301. if (isset ($this->hooks[$hook_spot])) {
  302. if (is_array($this->hooks[$hook_spot])) {
  303. foreach ($this->hooks[$hook_spot] as $prio => $_data) {
  304. foreach ($_data as $data) {
  305. // Our extentsion.
  306. if (is_string($data[0]) && !preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $data[0])) {
  307. $result = eval ($data[0]);
  308. } elseif (is_callable($data[0])) {
  309. $result = call_user_func_array($data[0], array_merge($arg,$data[1]));
  310. } else {
  311. if (!is_array($data[0]))
  312. $data[0] = array (
  313. 'STATIC',
  314. $data[0]
  315. );
  316. throw $this->exception("Cannot call hook. Function might not exist")
  317. ->addMoreInfo('hook',$hook_spot)
  318. ->addMoreInfo('arg1',$data[0][0])
  319. ->addMoreInfo('arg2',$data[0][1]);
  320. }
  321. $return[]=$result;
  322. }
  323. }
  324. }
  325. }
  326. }catch(Exception_Hook $e){
  327. return $e->return_value;
  328. }
  329. return $return;
  330. }
  331. function breakHook($return){
  332. $e=$this->exception(null,'Hook');
  333. $e->return_value=$return;
  334. throw $e;
  335. }
  336. // }}}
  337. // {{{ Dynamic Methods: http://agiletoolkit.org/learn/dynamic
  338. function __call($method,$arguments){
  339. if($ret=$this->tryCall($method,$arguments))return $ret[0];
  340. throw $this->exception("Method is not defined for this object")
  341. ->addMoreInfo("method",$method)
  342. ->addMoreInfo("arguments",$arguments);
  343. }
  344. /** [private] attempts to call method, returns array containing result or false */
  345. function tryCall($method,$arguments){
  346. array_unshift($arguments,$this);
  347. if($ret=$this->hook('method-'.$method,$arguments))return $ret;
  348. if($ret=$this->api->hook('global-method-'.$method,$arguments))return $ret;
  349. }
  350. /** Add new method for this object */
  351. function addMethod($name,$callable){
  352. if($this->hasMethod($name))
  353. throw $this->exception('Registering method twice');
  354. $this->addHook('method-'.$name,$callable);
  355. }
  356. /** Return if this object have specified method */
  357. function hasMethod($name){
  358. return method_exists($this,$name)
  359. || isset($this->hooks['method-'.$name])
  360. || isset($this->api->hooks['global-method-'.$name]);
  361. }
  362. function removeMethod($name){
  363. $this->removeHook('method-'.$name);
  364. }
  365. // }}}
  366. // {{{ Logger: to be moved out
  367. function logVar($var,$msg=""){
  368. $this->api->getLogger()->logVar($var,$msg);
  369. }
  370. function logInfo($info,$msg=""){
  371. $this->api->getLogger()->logLine($msg.' '.$info."\n");
  372. }
  373. function logError($error,$msg=""){
  374. if(is_object($error)){
  375. // we got exception object obviously
  376. $error=$error->getMessage();
  377. }
  378. $this->api->getLogger()->logLine($msg.' '.$error."\n",null,'error');
  379. }
  380. // }}}
  381. /**
  382. * @private
  383. * DO NOT USE THIS FUNCTION, it might relocate
  384. *
  385. * This funcion given the associative $array and desired new key will return
  386. * the best matching key which is not yet in the arary. For example if you have
  387. * array('foo'=>x,'bar'=>x) and $desired is 'foo' function will return 'foo_2'. If
  388. * 'foo_2' key also exists in that array, then 'foo_3' is returned and so on.
  389. */
  390. function _unique(&$array,$desired=null){
  391. $postfix=1;$attempted_key=$desired;
  392. while(isset($array[$attempted_key])){
  393. // already used, move on
  394. $attempted_key=($desired?$desired:'undef').'_'.(++$postfix);
  395. }
  396. return $attempted_key;
  397. }
  398. }