PageRenderTime 68ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/atk4/lib/AbstractObject.php

https://github.com/intuititve/lostandfound
PHP | 537 lines | 370 code | 44 blank | 123 comment | 66 complexity | de37ba559d3e29ae0956c7cd62dc3c0a 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 <romans@agiletoolkit.org>
  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. /** Reference to the current model. Read only. Use setModel() */
  19. public $model;
  20. /** Reference to the current controller. Read only. Use setController() */
  21. public $controller;
  22. /** Exception class to use when $this->exception() is called */
  23. public $default_exception='BaseException';
  24. /** Default controller to initialize when calling setModel() */
  25. public $default_controller=null;
  26. // {{{ Object hierarchy management: http://agiletoolkit.org/learn/understand/base/adding
  27. /** Unique object name */
  28. public $name;
  29. /** Name of the object in owner's element array */
  30. public $short_name;
  31. /** short_name => object hash of children objects */
  32. public $elements = array ();
  33. /** Link to object into which we added this object */
  34. public $owner;
  35. /** Always points to current API */
  36. public $api;
  37. /** When this object is added, owner->elements[$this->short_name] will be == $this; */
  38. public $auto_track_element=false;
  39. public $_initialized=false;
  40. /** Initialize object. Always call parent */
  41. function init() {
  42. /**
  43. * This method is called for initialization
  44. */
  45. $this->_initialized=true;
  46. }
  47. function __clone(){
  48. // fix short name and add ourselves to the parent
  49. //$this->short_name=$this->_unique($this->owner->elements,$this->short_name);
  50. //$this->owner->add($this);
  51. // Clone controller and model
  52. if($this->model && is_object($this->model))$this->model=clone $this->model;
  53. if($this->controller && is_object($this->controller))$this->controller=clone $this->controller;
  54. }
  55. function __toString() {
  56. return "Object " . get_class($this) . "(" . $this->name . ")";
  57. }
  58. /** Removes object from parent and prevents it from renedring */
  59. function destroy(){
  60. foreach($this->elements as $el)if($el instanceof AbstractObject){
  61. $el->destroy();
  62. }
  63. if(@$this->model && $this->model instanceof AbstractObject){
  64. $this->model->destroy();
  65. unset($this->model);
  66. }
  67. if(@$this->controller && $this->controller instanceof AbstractObject){
  68. $this->controller->destroy();
  69. unset($this->controller);
  70. }
  71. $this->owner->_removeElement($this->short_name);
  72. }
  73. /** Remove child element if it exists */
  74. function removeElement($short_name){
  75. if(is_object($this->elements[$short_name])){
  76. $this->elements[$short_name]->destroy();
  77. }
  78. else unset($this->elements[$short_name]);
  79. return $this;
  80. }
  81. function _removeElement($short_name){
  82. unset($this->elements[$short_name]);
  83. return $this;
  84. }
  85. function newInstance(){
  86. return $this->owner->add(get_class($this));
  87. }
  88. /** Creates new object and adds it as a child. Returns new object
  89. * http://agiletoolkit.org/learn/understand/base/adding */
  90. function add($class, $short_name = null, $template_spot = null, $template_branch = null) {
  91. if(is_array($short_name)){
  92. $di_config=$short_name;
  93. $short_name=@$di_config['name'];unset($di_config['name']);
  94. }else $di_config=array();
  95. if (is_object($class)) {
  96. // Object specified, just add the object, do not create anything
  97. if (!($class instanceof AbstractObject)) {
  98. throw $this->exception('You may only add objects based on AbstractObject');
  99. }
  100. if (!$class->short_name) {
  101. throw $this->exception('Cannot add existing object, without short_name');
  102. }
  103. $this->elements[$class->short_name] = $class;
  104. if($class instanceof AbstractView){
  105. $class->owner->elements[$class->short_name]=true;
  106. }
  107. $class->owner = $this;
  108. return $class;
  109. }elseif($class[0]=='.'){
  110. $tmp=explode('\\',get_class($this));
  111. if(!$tmp[1]){
  112. $ns='';
  113. }else{
  114. $ns=$tmp[0];
  115. }
  116. $class=$ns.'/'.substr($class,2);
  117. }
  118. if (!$short_name)
  119. $short_name = strtolower($class);
  120. $short_name=$this->_unique($this->elements,$short_name);
  121. if (isset ($this->elements[$short_name])) {
  122. if ($this->elements[$short_name] instanceof AbstractView) {
  123. // AbstractView classes shouldn't be created with the same name. If someone
  124. // would still try to do that, it should generate error. Obviously one of
  125. // those wouldn't be displayed or other errors would occur
  126. throw $this->exception("Element with name already exists")
  127. ->addMoreInfo('name',$short_name)
  128. ->addThis($this);
  129. }
  130. }
  131. if(!is_string($class) || !$class)throw $this->exception("Class is not valid")
  132. ->addMoreInfo('class',$class);
  133. // Separate out namespace
  134. $class_name=str_replace('/','\\',$class);
  135. if(!class_exists($class_name,false) && isset($this->api->pathfinder))$this->api->pathfinder->loadClass($class_name);
  136. $element = new $class_name();
  137. if (!($element instanceof AbstractObject)) {
  138. throw $this->exception("You can add only classes based on AbstractObject");
  139. }
  140. foreach($di_config as $key=>$val){
  141. $element->$key=$val;
  142. }
  143. $element->owner = $this;
  144. $element->api = $this->api;
  145. $this->elements[$short_name]=$element;
  146. if(!$element->auto_track_element)
  147. $this->elements[$short_name]=true; // dont store extra reference to models and controlers
  148. // for purposes of better garbage collection
  149. $element->name = $this->name . '_' . $short_name;
  150. $element->short_name = $short_name;
  151. // Initialize template before init() starts
  152. if ($element instanceof AbstractView) {
  153. $element->initializeTemplate($template_spot, $template_branch);
  154. }
  155. // Avoid using this hook. Agile Toolkit creates LOTS of objects, so you'll get significantly
  156. // slower code if you try to use this
  157. $this->api->hook('beforeObjectInit',array(&$element));
  158. $element->init();
  159. // Make sure init()'s parent was called. Popular coder's mistake
  160. if(!$element->_initialized)throw $element->exception('You should call parent::init() when you override it')
  161. ->addMoreInfo('object_name',$element->name)
  162. ->addMoreInfo('class',get_class($element));
  163. return $element;
  164. }
  165. /** Find child element by their short name. Use in chaining. Exception if not found. */
  166. function getElement($short_name) {
  167. if (!isset ($this->elements[$short_name]))
  168. throw $this->exception("Child element not found")
  169. ->addMoreInfo('element',$short_name);
  170. return $this->elements[$short_name];
  171. }
  172. /** Find child element. Use in condition. */
  173. function hasElement($name){
  174. return isset($this->elements[$name])?$this->elements[$name]:false;
  175. }
  176. /** Names object accordingly. May not work on some objects */
  177. function rename($short_name){
  178. unset($this->owner->elements[$this->short_name]);
  179. $this->name = $this->name . '_' . $short_name;
  180. $this->short_name = $short_name;
  181. $this->owner->elements[$short_name]=$this;
  182. if(!$this->auto_track_element)
  183. $this->owner->elements[$short_name]=true;
  184. return $this;
  185. }
  186. // }}}
  187. // {{{ Model and Controller handling
  188. function setController($controller){
  189. if(is_string($controller)&&substr($controller,0,strlen('Controller'))!='Controller')
  190. $controller=preg_replace('|^(.*/)?(.*)$|','\1Controller_\2',$controller);
  191. return $this->add($controller);
  192. }
  193. function setModel($model){
  194. if(is_string($model)&&substr($model,0,strlen('Model'))!='Model'){
  195. $model=preg_replace('|^(.*/)?(.*)$|','\1Model_\2',$model);
  196. }
  197. $this->model=$this->add($model);
  198. return $this->model;
  199. }
  200. function getModel(){
  201. return $this->model;
  202. }
  203. // }}}
  204. // {{{ Session management: http://agiletoolkit.org/doc/session
  205. /** Remember object-relevant session data */
  206. function memorize($name, $value) {
  207. if (!isset ($value))
  208. return $this->recall($name);
  209. $this->api->initializeSession();
  210. return $_SESSION['o'][$this->name][$name] = $value;
  211. }
  212. /** Remember one of the supplied arguments, which is not-null */
  213. function learn($name, $value1 = null, $value2 = null, $value3 = null) {
  214. if (isset ($value1))
  215. return $this->memorize($name, $value1);
  216. if (isset ($value2))
  217. return $this->memorize($name, $value2);
  218. return $this->memorize($name, $value3);
  219. }
  220. /** Forget session data for arg $name. Null forgets all data relevant to this object */
  221. function forget($name = null) {
  222. $this->api->initializeSession();
  223. if (isset ($name)) {
  224. unset ($_SESSION['o'][$this->name][$name]);
  225. } else {
  226. unset ($_SESSION['o'][$this->name]);
  227. }
  228. }
  229. /** Returns session data for this object. If not set, $default is returned */
  230. function recall($name, $default = null) {
  231. $this->api->initializeSession(false);
  232. if (!isset ($_SESSION['o'][$this->name][$name])||is_null($_SESSION['o'][$this->name][$name])) {
  233. return $default;
  234. } else {
  235. return $_SESSION['o'][$this->name][$name];
  236. }
  237. }
  238. // }}}
  239. // {{{ Exception handling: http://agiletoolkit.org/doc/exception
  240. function exception($message,$type=null){
  241. if(!$type){
  242. $type=$this->default_exception;
  243. }elseif($type[0]=='_'){
  244. $type=$this->default_exception.'_'.substr($type,1);
  245. }else{
  246. $type='Exception_'.$type;
  247. }
  248. // Localization support
  249. $message=$this->api->_($message);
  250. if($type=='Exception')$type='BaseException';
  251. $e=new $type($message);
  252. $e->owner=$this;
  253. $e->api=$this->api;
  254. $e->init();
  255. return $e;
  256. }
  257. // }}}
  258. // {{{ Code which can be potentially obsoleted
  259. /** @obsolete */
  260. function fatal($error, $shift = 0) {
  261. /**
  262. * If you have fatal error in your object use the following code:
  263. *
  264. * return $this->fatal("Very serious problem!");
  265. *
  266. * This line will notify parent about fatal error and return null to
  267. * the caller. Caller don't have to handle error messages, just throw
  268. * everything up.
  269. *
  270. * Fatal calls are intercepted by API. Or if you want you can intercept
  271. * them yourself.
  272. *
  273. * TODO: record debug_backtrace depth so we could point acurately at
  274. * the function/place where fatal is called from.
  275. */
  276. return $this->upCall('outputFatal', array (
  277. $error,
  278. $shift
  279. ));
  280. }
  281. /** @obsolete */
  282. function info($msg) {
  283. /**
  284. * Call this function to send some information to API. Example:
  285. *
  286. * $this->info("User tried buying traffic without enough money in bank");
  287. */
  288. if(!$this->api->hook('outputInfo',array($msg,$this)))
  289. $this->upCall('outputInfo', $msg);
  290. }
  291. /** @obsolete */
  292. function debug($msg, $file = null, $line = null) {
  293. /**
  294. * Use this function to send debug information. Information will only
  295. * be sent if you enable debug localy (per-object) by setting
  296. * $this->debug=true or per-apllication by setting $api->debug=true;
  297. *
  298. * You also may enable debug globaly:
  299. * $this->api->debug=true;
  300. * but disable for object
  301. * $object->debug=false;
  302. */
  303. if ((isset ($this->debug) && $this->debug) || (isset ($this->api->debug) && $this->api->debug)) {
  304. $this->upCall('outputDebug', array (
  305. $msg,
  306. $file,
  307. $line
  308. ));
  309. }
  310. }
  311. /** @obsolete */
  312. function warning($msg, $shift = 0) {
  313. $this->upCall('outputWarning', array (
  314. $msg,
  315. $shift
  316. ));
  317. }
  318. /////////////// C r o s s c a l l s ///////////////////////
  319. function upCall($type, $args = array ()) {
  320. /**
  321. * Try to handle something on our own and in case we are not
  322. * able, pass to parent. Such as messages, notifications and request
  323. * for additional info or descriptions are passed this way.
  324. */
  325. if (method_exists($this, $type)) {
  326. return call_user_func_array(array (
  327. $this,
  328. $type
  329. ), $args);
  330. }
  331. if (!$this->owner)
  332. return false;
  333. return $this->owner->upCall($type, $args);
  334. }
  335. // }}}
  336. // {{{ Hooks: http://agiletoolkit.org/doc/hooks
  337. public $hooks = array ();
  338. /** If priority is negative, then hooks will be executed in reverse order */
  339. function addHook($hook_spot, $callable, $arguments=array(), $priority = 5) {
  340. if(!is_array($arguments)){
  341. // Backwards compatibility
  342. $priority=$arguments;
  343. $arguments=array();
  344. }
  345. if(is_string($hook_spot) && strpos($hook_spot,',')!==false)$hook_spot=explode(',',$hook_spot);
  346. if(is_array($hook_spot)){
  347. foreach($hook_spot as $h){
  348. $this->addHook($h,$callable,$arguments, $priority);
  349. }
  350. return $this;
  351. }
  352. if(is_object($callable) && !is_callable($callable)){
  353. $callable=array($callable,$hook_spot); // short for addHook('test',$this); to call $this->test();
  354. }
  355. if($priority>=0){
  356. $this->hooks[$hook_spot][$priority][] = array($callable,$arguments);
  357. }else{
  358. if(!$this->hooks[$hook_spot][$priority])
  359. $this->hooks[$hook_spot][$priority]=array();
  360. array_unshift($this->hooks[$hook_spot][$priority],array($callable,$arguments));
  361. }
  362. return $this;
  363. }
  364. function removeHook($hook_spot) {
  365. unset($this->hooks[$hook_spot]);
  366. return $this;
  367. }
  368. function hook($hook_spot, $arg = array ()) {
  369. $return=array();
  370. try{
  371. if (isset ($this->hooks[$hook_spot])) {
  372. if (is_array($this->hooks[$hook_spot])) {
  373. foreach ($this->hooks[$hook_spot] as $prio => $_data) {
  374. foreach ($_data as $data) {
  375. // Our extentsion.
  376. if (is_string($data[0]) && !preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $data[0])) {
  377. $result = eval ($data[0]);
  378. } elseif (is_callable($data[0])) {
  379. $result = call_user_func_array($data[0], array_merge(array($this),$arg,$data[1]));
  380. } else {
  381. if (!is_array($data[0]))
  382. $data[0] = array (
  383. 'STATIC',
  384. $data[0]
  385. );
  386. throw $this->exception("Cannot call hook. Function might not exist")
  387. ->addMoreInfo('hook',$hook_spot)
  388. ->addMoreInfo('arg1',$data[0][0])
  389. ->addMoreInfo('arg2',$data[0][1]);
  390. }
  391. $return[]=$result;
  392. }
  393. }
  394. }
  395. }
  396. }catch(Exception_Hook $e){
  397. return $e->return_value;
  398. }
  399. return $return;
  400. }
  401. function breakHook($return){
  402. $e=$this->exception(null,'Hook');
  403. $e->return_value=$return;
  404. throw $e;
  405. }
  406. // }}}
  407. // {{{ Dynamic Methods: http://agiletoolkit.org/learn/dynamic
  408. function __call($method,$arguments){
  409. if($ret=$this->tryCall($method,$arguments))return $ret[0];
  410. throw $this->exception("Method is not defined for this object",'Logic')
  411. ->addMoreInfo('class',get_class($this))
  412. ->addMoreInfo("method",$method)
  413. ->addMoreInfo("arguments",$arguments);
  414. }
  415. /** [private] attempts to call method, returns array containing result or false */
  416. function tryCall($method,$arguments){
  417. if($ret=$this->hook('method-'.$method,$arguments))return $ret;
  418. array_unshift($arguments,$this);
  419. if($ret=$this->api->hook('global-method-'.$method,$arguments))return $ret;
  420. }
  421. /** Add new method for this object */
  422. function addMethod($name,$callable){
  423. if(is_string($name) && strpos($name,',')!==false)$name=explode(',',$name);
  424. if(is_array($name)){
  425. foreach($name as $h){
  426. $this->addMethod($h,$callable);
  427. }
  428. return $this;
  429. }
  430. if(is_object($callable) && !is_callable($callable)){
  431. $callable=array($callable,$name);
  432. }
  433. if($this->hasMethod($name))
  434. throw $this->exception('Registering method twice');
  435. $this->addHook('method-'.$name,$callable);
  436. }
  437. /** Return if this object have specified method */
  438. function hasMethod($name){
  439. return method_exists($this,$name)
  440. || isset($this->hooks['method-'.$name])
  441. || isset($this->api->hooks['global-method-'.$name]);
  442. }
  443. function removeMethod($name){
  444. $this->removeHook('method-'.$name);
  445. }
  446. // }}}
  447. // {{{ Logger: to be moved out
  448. function logVar($var,$msg=""){
  449. $this->api->getLogger()->logVar($var,$msg);
  450. }
  451. function logInfo($info,$msg=""){
  452. $this->api->getLogger()->logLine($msg.' '.$info."\n");
  453. }
  454. function logError($error,$msg=""){
  455. if(is_object($error)){
  456. // we got exception object obviously
  457. $error=$error->getMessage();
  458. }
  459. $this->api->getLogger()->logLine($msg.' '.$error."\n",null,'error');
  460. }
  461. // }}}
  462. /**
  463. * This funcion given the associative $array and desired new key will return
  464. * the best matching key which is not yet in the arary. For example if you have
  465. * array('foo'=>x,'bar'=>x) and $desired is 'foo' function will return 'foo_2'. If
  466. * 'foo_2' key also exists in that array, then 'foo_3' is returned and so on.
  467. */
  468. function _unique(&$array,$desired=null){
  469. $postfix=1;$attempted_key=$desired;
  470. if(!is_array($array))throw $this->exception('not array');
  471. while(array_key_exists($attempted_key,$array)){
  472. // already used, move on
  473. $attempted_key=($desired?$desired:'undef').'_'.(++$postfix);
  474. }
  475. return $attempted_key;
  476. }
  477. /** Always call parent if you redefine this */
  478. function __destruct(){
  479. }
  480. function __sleep(){
  481. return array('name');
  482. }
  483. }