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

/classes/formo/container/core.php

https://github.com/justus/kohana-formo
PHP | 724 lines | 363 code | 110 blank | 251 comment | 56 complexity | 81b0facdf8c65e0da4efe553b13f2331 MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * This class makes storing and retrieving objects neat
  4. *
  5. * @package Formo
  6. */
  7. abstract class Formo_Container_Core {
  8. /**
  9. * Class-specific settings
  10. *
  11. * (default value: array())
  12. *
  13. * @var array
  14. * @access protected
  15. */
  16. protected $_settings = array();
  17. /**
  18. * Where custom vars are stored
  19. *
  20. * (default value: array())
  21. *
  22. * @var array
  23. * @access protected
  24. */
  25. protected $_customs = array();
  26. protected $_loaded = array
  27. (
  28. 'orm' => FALSE,
  29. 'driver' => FALSE
  30. );
  31. /**
  32. * Container settings
  33. *
  34. * @var mixed
  35. * @access protected
  36. */
  37. protected $_defaults = array
  38. (
  39. 'alias' => NULL,
  40. 'parent' => FALSE,
  41. 'fields' => array(),
  42. 'driver_instance' => NULL,
  43. 'label' => NULL,
  44. 'order' => FALSE,
  45. 'type' => NULL,
  46. );
  47. /**
  48. * Fetch a field or return a driver object
  49. *
  50. * @access public
  51. * @param mixed $variable
  52. * @return object or void
  53. */
  54. public function __get($variable)
  55. {
  56. return $this->get_field($variable);
  57. }
  58. public function __isset($variable)
  59. {
  60. if (array_key_exists($variable, $this->_loaded))
  61. return (bool) $this->_loaded[$variable];
  62. }
  63. public function __invoke()
  64. {
  65. $args = func_get_args();
  66. if (func_num_args() === 0)
  67. return $this->val();
  68. if (func_num_args() === 1)
  69. return $this->get($args[0]);
  70. if (func_num_args() === 2)
  71. return $this->set($args[0], $args[1]);
  72. }
  73. /**
  74. * Returns all fields in order
  75. *
  76. * @access public
  77. * @param mixed $field. (default: NULL)
  78. * @return array
  79. */
  80. public function fields($field = NULL)
  81. {
  82. $unordered = array();
  83. $ordered = array();
  84. foreach ($this->get('fields') as $field)
  85. {
  86. $alias = $field->alias();
  87. $ordered[$alias] = $field;
  88. }
  89. return $ordered;
  90. }
  91. /**
  92. * Fetch a field directly within its container
  93. *
  94. * @access public
  95. * @param mixed $search
  96. * @param mixed $option. (default: FALSE)
  97. * @return object or void
  98. */
  99. public function get_field($search, $option = FALSE)
  100. {
  101. if (is_array($search))
  102. {
  103. $fields = array();
  104. foreach ($search as $_search)
  105. {
  106. $field = $this->get_field($_search);
  107. $fields[$field->alias()] = $field;
  108. }
  109. return $fields;
  110. }
  111. // If $search is an int, search by key
  112. $find_by = is_string($search) ? 'item' : 'key';
  113. foreach ($this->_defaults['fields'] as $key => $field)
  114. {
  115. if ($find_by == 'key' AND $key === $search)
  116. return $field;
  117. if ($find_by == 'item' AND $search == $field->alias())
  118. return $field;
  119. if ($option AND $find_by == 'item' AND call_user_func($option, $search) == $field->alias())
  120. return $field;
  121. }
  122. }
  123. /**
  124. * Runs the method through the driver
  125. *
  126. * @access public
  127. * @param mixed $func
  128. * @param mixed $args
  129. * @return void
  130. */
  131. public function __call($func, $args)
  132. {
  133. return call_user_func_array(array($this->driver(), $func), $args);
  134. }
  135. /**
  136. * Set variables
  137. *
  138. * @access public
  139. * @param mixed $variable
  140. * @param mixed $value
  141. * @return object
  142. */
  143. public function set($variable, $value, $force_into_field = FALSE)
  144. {
  145. // Support array of key => values
  146. if (is_array($variable))
  147. {
  148. foreach ($variable as $_variable => $_value)
  149. {
  150. $this->set($_variable, $_value);
  151. }
  152. return $this;
  153. }
  154. // Allow the driver to alter the variable beforehand
  155. // but obviously it can't happen when setting the driver instance
  156. if ($variable != 'driver_instance' AND method_exists($this->driver(), "set_$variable"))
  157. {
  158. $value = $this->driver()->{'set_'.$variable}($value);
  159. }
  160. if (array_key_exists($variable, $this->_defaults))
  161. {
  162. $this->_defaults[$variable] = $value;
  163. return $this;
  164. }
  165. if (array_key_exists($variable, $this->_settings))
  166. {
  167. // First look for variables in $_settings
  168. $this->_settings[$variable] = $value;
  169. return $this;
  170. }
  171. // Otherwise let the driver do the setting
  172. $this->driver()->set($variable, $value);
  173. return $this;
  174. }
  175. /**
  176. * Load construct options
  177. *
  178. * @access public
  179. * @param mixed $option
  180. * @param mixed $value. (default: NULL)
  181. * @return object
  182. */
  183. public function load_options($option, $value = NULL)
  184. {
  185. // Support array of options
  186. if (is_array($option))
  187. {
  188. foreach ($option as $_option => $_value)
  189. {
  190. $this->load_options($_option, $_value);
  191. }
  192. return $this;
  193. }
  194. // Otherwise just set the variable
  195. $this->set($option, $value);
  196. return $this;
  197. }
  198. /**
  199. * Pass variable by reference
  200. *
  201. * @access public
  202. * @param mixed $variable
  203. * @param mixed $key
  204. * @param mixed & $value
  205. * @return object
  206. */
  207. public function bind($variable, $key, & $value)
  208. {
  209. if ($key)
  210. {
  211. $this->{$variable}[$key] &= $value;
  212. }
  213. else
  214. {
  215. $this->{$variable} &= $key;
  216. }
  217. return $this;
  218. }
  219. /**
  220. * Fetch variable(s)
  221. *
  222. * @access public
  223. * @param mixed $variable
  224. * @param mixed $default. (default: FALSE)
  225. * @return mixed
  226. */
  227. public function get($variable, $default = FALSE)
  228. {
  229. $arrays = array('_defaults', '_settings', '_customs');
  230. foreach ($arrays as $array)
  231. {
  232. if (array_key_exists($variable, $this->$array))
  233. {
  234. return $this->{$array}[$variable];
  235. }
  236. }
  237. // Otherwise run get through the driver
  238. return $this->driver()->get($variable, $default);
  239. }
  240. /**
  241. * Return the model
  242. *
  243. * @access public
  244. * @return void
  245. */
  246. public function model($return_driver = FALSE)
  247. {
  248. if (isset($this->orm))
  249. {
  250. return ($return_driver === TRUE)
  251. ? $this->orm_driver()
  252. : $this->orm_driver()->model;
  253. }
  254. if ($this->parent() !== FALSE)
  255. return $this->parent()->model($return_driver);
  256. return FALSE;
  257. }
  258. /**
  259. * Create a subform from fields already in the Container object
  260. *
  261. * @access public
  262. * @param mixed $alias
  263. * @param mixed $driver
  264. * @param mixed array $fields
  265. * @param mixed $order. (default: NULL)
  266. * @return object
  267. */
  268. public function create_sub($alias, $driver, array $fields, $order = NULL)
  269. {
  270. // Create the empty subform object
  271. $subform = Formo::form($alias, $driver);
  272. foreach ($fields as $key => $field)
  273. {
  274. if (is_string($key) AND ! ctype_digit($key))
  275. {
  276. // Pull fields "as" a new alias
  277. $new_alias = $field;
  278. $field = $key;
  279. }
  280. // Find each field
  281. $_field = $this->find($field);
  282. if ( ! $_field)
  283. // Throw an exception if the field doesn't exist
  284. throw new Kohana_Exception("Formo_Container: Field $field is not in form");
  285. if ( ! empty($new_alias))
  286. {
  287. // Set the new alias
  288. $_field->alias($new_alias);
  289. }
  290. // Remember the field's original parent
  291. $last_parent = $_field->parent();
  292. // Add the field to the new subform
  293. $subform->append($_field);
  294. // Remove the field from its original parent
  295. $last_parent->remove($_field->alias());
  296. }
  297. // If the parent has a model, copy it to the new subform
  298. $subform->set('model', $this->get('model'));
  299. // Add the order if applicable
  300. ($order AND $subform->set('order', $order));
  301. // Append the new subform
  302. $this->append($subform);
  303. return $this;
  304. }
  305. /**
  306. * Stores an item in the container
  307. *
  308. * @access public
  309. * @param mixed $field
  310. * @return object
  311. */
  312. public function append($field)
  313. {
  314. // Set the field's parent
  315. $field->set('parent', $this);
  316. $field->set('type', $this->get('type'));
  317. $this->_defaults['fields'][] = $field;
  318. // Look for order and process it for ordering this field
  319. if ($field->get('order') !== FALSE)
  320. {
  321. $order = $field->get('order');
  322. $args = array($field);
  323. if (is_array($order))
  324. {
  325. $args[] = key($order);
  326. $args[] = current($order);
  327. }
  328. else
  329. {
  330. $args[] = $order;
  331. }
  332. call_user_func_array(array($this, 'order'), $args);
  333. }
  334. $field->driver()->append();
  335. return $this;
  336. }
  337. /**
  338. * Add an item to the beginning of a container
  339. *
  340. * @access public
  341. * @param mixed $item
  342. * @return object
  343. */
  344. public function prepend($item)
  345. {
  346. $item->_defaults['parent'] = $this;
  347. $item->set('type', $this->get('type'));
  348. array_unshift($this->_defaults['fields'], $item);
  349. return $this;
  350. }
  351. /**
  352. * Removes a field from its container
  353. *
  354. * @access public
  355. * @param mixed $alias
  356. * @return object
  357. */
  358. public function remove($alias)
  359. {
  360. // Support an array of fields
  361. if (is_array($alias))
  362. {
  363. foreach ($alias as $_alias)
  364. {
  365. $this->remove($_alias);
  366. }
  367. return $this;
  368. }
  369. foreach ($this->_defaults['fields'] as $key => $item)
  370. {
  371. if ($item->alias() == $alias)
  372. {
  373. unset($this->_defaults['fields'][$key]);
  374. }
  375. }
  376. return $this;
  377. }
  378. /**
  379. * Return array of fields with the specified value for each
  380. *
  381. * @access public
  382. * @param mixed $value. (default: NULL)
  383. * @return array
  384. */
  385. public function as_array($value = NULL)
  386. {
  387. // Create the empty array to fill
  388. $array = array();
  389. foreach ($this->_defaults['fields'] as $field)
  390. {
  391. $alias = $field->alias();
  392. // Make concession for grabbing 'value' as that one characteristic
  393. if ($value == 'value')
  394. {
  395. $array[$alias] = $field->val();
  396. continue;
  397. }
  398. // By default, return name => element
  399. $array[$alias] = ($value !== NULL)
  400. ? $field->get($value)
  401. : $field;
  402. }
  403. return $array;
  404. }
  405. /**
  406. * Retrieve a field's parent
  407. *
  408. * @access public
  409. * @param mixed $search. (default: NULL)
  410. * @return mixed
  411. */
  412. public function parent($search = NULL)
  413. {
  414. $this_parent = $this->_defaults['parent'];
  415. // If not searching, return this parent
  416. if ($search === NULL)
  417. return $this_parent;
  418. // If searching for the topmost parent, return it if this is
  419. if ($search === Formo::PARENT AND ! $this_parent)
  420. return $this;
  421. // If this parent doesn't exist, return FALSE
  422. if ( ! $this_parent)
  423. return FALSE;
  424. // If the parent's alias matches the search term
  425. if ($this_parent AND $this_parent->alias() == $search)
  426. return $this_parent;
  427. // Recursively search for the correct parent
  428. return $this_parent->parent($search);
  429. }
  430. // Convenience method for fetching/setting alias
  431. public function alias($alias = NULL)
  432. {
  433. if (func_num_args() == 0)
  434. return $this->_defaults['alias'];
  435. $this->_defaults['alias'] = $alias;
  436. return $this;
  437. }
  438. /**
  439. * Look through a container object for a field object by alias
  440. *
  441. * @access public
  442. * @param mixed $alias
  443. * @return mixed
  444. */
  445. public function find($alias)
  446. {
  447. // If an array wasn't entered, look everywhere
  448. if ( ! is_array($alias))
  449. {
  450. // Whenever a match is found, return it
  451. if ($field = $this->get_field($alias))
  452. return $field;
  453. // Recursively look as deep as necessary
  454. foreach ($this->_defaults['fields'] as $field)
  455. {
  456. if ($found_field = $field->find($alias))
  457. return $found_field;
  458. }
  459. }
  460. // If an array was entered, follow the exact path of the array
  461. else
  462. {
  463. // Start with the first item
  464. $field = $this->get_field($alias[0]);
  465. // Go deeper for each item entered
  466. for($i=1; $i<count($alias); $i++)
  467. {
  468. $field = $field->field($alias[$i]);
  469. }
  470. return $field;
  471. }
  472. }
  473. /**
  474. * Return the order a field is in
  475. *
  476. * @access protected
  477. * @param mixed $search
  478. * @return mixed
  479. */
  480. protected function find_order($search)
  481. {
  482. $i = 0;
  483. foreach ($this->_defaults['fields'] as $field)
  484. {
  485. // Return the order if we just found it
  486. if ($field->alias() == $search)
  487. return $i;
  488. $i++;
  489. }
  490. // Return false upon failing
  491. return FALSE;
  492. }
  493. /**
  494. * Get the key of a specific field
  495. *
  496. * @access protected
  497. * @param mixed $field
  498. * @return mixed
  499. */
  500. protected function find_fieldkey($field)
  501. {
  502. foreach ($this->_defaults['fields'] as $key => $value)
  503. {
  504. if ($value->alias() == $field)
  505. return $key;
  506. }
  507. return FALSE;
  508. }
  509. /**
  510. * Set the order of a new field
  511. *
  512. * @access public
  513. * @param mixed $field
  514. * @param mixed $new_order
  515. * @param mixed $relative_field. (default: NULL)
  516. * @return object
  517. */
  518. public function order($field, $new_order, $relative_field = NULL)
  519. {
  520. // Find the field if necessary
  521. $field = (is_object($field) === FALSE) ? $this->find($field) : $field;
  522. // Pull out all the fields
  523. $fields = $field->parent()->get('fields');
  524. // Delete the current place
  525. unset($fields[$this->find_fieldkey($field->alias())]);
  526. // If the new order is a string, it's a comparative order
  527. if ( ! ctype_digit($new_order) AND is_string($new_order))
  528. {
  529. $position = $this->find_order($relative_field);
  530. // If the place wasn't found, do nothing
  531. if ($position === FALSE)
  532. return $this;
  533. $new_order = ($new_order == 'after') ? $position + 1 : $position;
  534. }
  535. // Make the insertion
  536. array_splice($fields, $new_order, 0, array($field));
  537. // Save the new order
  538. $field->parent()->set('fields', $fields);
  539. return $this;
  540. }
  541. /**
  542. * Return or create a new driver instance
  543. *
  544. * @access public
  545. * @param mixed $save_instance. (default: FALSE)
  546. * @return Formo_Driver
  547. */
  548. public function driver($save_instance = TRUE)
  549. {
  550. // Fetch the current settings
  551. $driver = $this->get('driver');
  552. $instance = $this->get('driver_instance');
  553. $class = 'Formo_Driver_'.ucfirst($driver);
  554. // If the instance is the correct driver for the field, return it
  555. if ($instance AND $instance instanceof $class)
  556. return $instance;
  557. // Build the class name
  558. $driver_class_name = Kohana::config('formo')->driver_prefix.UTF8::ucfirst($driver);
  559. // Create the new instance
  560. $instance = new $driver_class_name($this);
  561. if ($save_instance === TRUE)
  562. {
  563. // Save the instance if asked to
  564. $this->set('driver_instance', $instance);
  565. }
  566. $this->_loaded['driver'] = TRUE;
  567. // Return the new driver instance
  568. return $instance;
  569. }
  570. /**
  571. * Load an orm driver instance
  572. *
  573. * @access public
  574. * @param mixed $save_instance. (default: FALSE)
  575. * @return Formo_ORM object
  576. */
  577. public function orm_driver($save_instance = TRUE)
  578. {
  579. if ( ! $this instanceof Formo_Form)
  580. return $this->parent()->orm_driver(TRUE);
  581. if ($instance = $this->get('orm_driver_instance'))
  582. // If the instance exists, return it
  583. return $instance;
  584. // Get the driver neame
  585. $driver = Kohana::config('formo')->orm_driver;
  586. // Create the new instance
  587. $instance = new $driver($this);
  588. if ($save_instance === TRUE)
  589. {
  590. // Save the instance if asked to
  591. $this->set('orm_driver_instance', $instance);
  592. }
  593. $this->_loaded['orm'] = TRUE;
  594. // REturn the new orm driver instance
  595. return $instance;
  596. }
  597. /**
  598. * Changes decorator object to new type for all fields within the
  599. * field or subform
  600. *
  601. * @access public
  602. * @param mixed $type
  603. * @return void
  604. */
  605. public function type($type)
  606. {
  607. $this->driver()->decorator($type);
  608. foreach ($this->fields() as $field)
  609. {
  610. $field->decorator($type);
  611. }
  612. return $this;
  613. }
  614. }