PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/server/vpn.51sync.com/library/orm/activerecord/meta.php

https://github.com/qibinghua/vpn.51sync
PHP | 1438 lines | 799 code | 128 blank | 511 comment | 100 complexity | 1119c036fde672fa6f03b02d9b3be441 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * 定义 QDB_ActiveRecord_Meta 类
  4. *
  5. * @link http://qeephp.com/
  6. * @copyright Copyright (c) 2006-2009 Qeeyuan Inc. {@link http://www.qeeyuan.com}
  7. * @license New BSD License {@link http://qeephp.com/license/}
  8. * @package orm
  9. */
  10. /**
  11. * QDB_ActiveRecord_Meta 类封装了 QDB_ActiveRecord_Abstract 继承类的元信息
  12. *
  13. * @author YuLei Liao <liaoyulei@qeeyuan.com>
  14. * @package orm
  15. */
  16. class QDB_ActiveRecord_Meta implements QDB_ActiveRecord_Callbacks
  17. {
  18. /**
  19. * ID 属性名
  20. *
  21. * @var array
  22. */
  23. public $idname;
  24. /**
  25. * ID 属性包含多少个属性
  26. *
  27. * @var int
  28. */
  29. public $idname_count;
  30. /**
  31. * 数据表的元信息
  32. *
  33. * @var array
  34. */
  35. public $table_meta;
  36. /**
  37. * 验证规则
  38. *
  39. * @var array
  40. */
  41. public $validations = array();
  42. /**
  43. * 允许使用 mass-assignment 方式赋值的属性
  44. *
  45. * 如果指定了 attr_accessible,则忽略 attr_protected 的设置。
  46. *
  47. * @var array
  48. */
  49. public $attr_accessible = array();
  50. /**
  51. * 拒绝使用 mass-assignment 方式赋值的属性
  52. *
  53. * @var array
  54. */
  55. public $attr_protected = array();
  56. /**
  57. * 创建时要过滤的属性
  58. *
  59. * @var array
  60. */
  61. public $create_reject = array();
  62. /**
  63. * 更新时要过滤的属性
  64. *
  65. * @var array
  66. */
  67. public $update_reject = array();
  68. /**
  69. * 创建时要自动填充的属性
  70. *
  71. * @var array
  72. */
  73. public $create_autofill = array();
  74. /**
  75. * 更新时要自动填充的属性
  76. *
  77. * @var array
  78. */
  79. public $update_autofill = array();
  80. /**
  81. * 属性到字段名的映射
  82. *
  83. * @var array
  84. */
  85. public $props2fields = array();
  86. /**
  87. * 字段名到属性的映射
  88. *
  89. * @var array
  90. */
  91. public $fields2props = array();
  92. /**
  93. * 所有属性的元信息
  94. *
  95. * @var array of properties meta
  96. */
  97. public $props = array();
  98. /**
  99. * 所有属性的默认值,用于初始化一个新的 ActiveRecord 实例
  100. *
  101. * @var array
  102. */
  103. public $default_props = array();
  104. /**
  105. * ActiveRecord 之间的关联
  106. *
  107. * @code php
  108. * array (
  109. * 'prop_name' => $assoc
  110. * )
  111. * @endcode
  112. *
  113. * 如果关联已经初始化,则 $assoc 是一个 QDB_ActiveRecord_Association_Abstract 继承类实例。
  114. * 否则 $assoc 为 false。
  115. *
  116. * @var array of QDB_ActiveRecord_Association_Abstract
  117. */
  118. public $associations = array();
  119. /**
  120. * 事件钩子
  121. *
  122. * @var array of callbacks
  123. */
  124. public $callbacks = array();
  125. /**
  126. * 扩展的方法
  127. *
  128. * @var array of callbacks
  129. */
  130. public $methods = array();
  131. /**
  132. * 扩展的静态方法
  133. *
  134. * @var array of callbacks
  135. */
  136. public $static_methods = array();
  137. /**
  138. * 表数据入口
  139. *
  140. * @var QDB_Table
  141. */
  142. public $table;
  143. /**
  144. * Meta 对应的 ActiveRecord 继承类
  145. *
  146. * @var string
  147. */
  148. public $class_name;
  149. /**
  150. * ActiveRecord 的基础类
  151. *
  152. * @var string
  153. */
  154. public $inherit_base_class;
  155. /**
  156. * 用于指定继承类名称的字段名
  157. *
  158. * @var string
  159. */
  160. public $inherit_type_field;
  161. /**
  162. * BELONGS_TO 关联的 source_key
  163. *
  164. * @var array
  165. */
  166. public $belongsto_props = array();
  167. /**
  168. * 行为插件对象
  169. *
  170. * @var array of QDB_ActiveRecord_Behavior_Abstract objects
  171. */
  172. protected $_behaviors = array();
  173. /**
  174. * 指示是否已经初始化了对象的关联
  175. *
  176. * @var boolean
  177. */
  178. protected $_associations_inited = false;
  179. /**
  180. * 可用的对象聚合类型
  181. *
  182. * @var array
  183. */
  184. protected static $_assoc_types = array(
  185. QDB::HAS_ONE,
  186. QDB::HAS_MANY,
  187. QDB::BELONGS_TO,
  188. QDB::MANY_TO_MANY
  189. );
  190. /**
  191. * 验证策略可用的选项
  192. *
  193. * @var array
  194. */
  195. protected static $_validation_policy_options = array(
  196. 'allow_null' => false,
  197. 'check_all_rules' => false,
  198. );
  199. /**
  200. * 所有 ActiveRecord 继承类的 Meta 对象
  201. *
  202. * @var array of QDB_ActiveRecord_Meta
  203. */
  204. protected static $_metas = array();
  205. /**
  206. * 构造函数
  207. *
  208. * @param string $class
  209. */
  210. protected function __construct($class)
  211. {
  212. $this->_init1($class);
  213. }
  214. /**
  215. * 获得指定指定 ActiveRecord 继承类的元对象唯一实例
  216. *
  217. * @param string $class
  218. *
  219. * @return QDB_ActiveRecord_Meta
  220. */
  221. static function instance($class)
  222. {
  223. if (!isset(self::$_metas[$class]))
  224. {
  225. self::$_metas[$class] = new QDB_ActiveRecord_Meta($class);
  226. self::$_metas[$class]->_init2();
  227. }
  228. return self::$_metas[$class];
  229. }
  230. /**
  231. * 返回一个根据 $data 数组构造的 ActiveRecord 继承类实例
  232. *
  233. * @return QDB_ActiveRecord_Abstract
  234. */
  235. function newObject(array $data = null)
  236. {
  237. return new $this->class_name($data);
  238. }
  239. /**
  240. * 根据表名构造一个元对象
  241. *
  242. *
  243. */
  244. function createFromTable($table_name, $class_name = null)
  245. {
  246. // TODO!
  247. }
  248. /**
  249. * 开启一个查询
  250. *
  251. * @return QDB_Select
  252. */
  253. function find()
  254. {
  255. return $this->findByArgs(func_get_args());
  256. }
  257. /**
  258. * 开启一个查询,并根据提供的参数设置查询对象
  259. *
  260. * @param array $args
  261. *
  262. * @return QDB_Select
  263. */
  264. function findByArgs(array $args = array())
  265. {
  266. $select = new QDB_Select($this->table->getConn());
  267. $select->asColl()->from($this->table)->asObject($this->class_name);
  268. $c = count($args);
  269. if ($c > 0)
  270. {
  271. if ($c == 1 && is_int($args[0]) && $this->idname_count == 1)
  272. {
  273. $select->where(array(reset($this->idname) => $args[0]));
  274. }
  275. else
  276. {
  277. call_user_func_array(array($select, 'where'), $args);
  278. }
  279. }
  280. if ($this->inherit_base_class && $this->inherit_type_field)
  281. {
  282. // 如果是来自某个继承类的查询,则限定只能查询该类型的对象
  283. $select->where(array($this->inherit_type_field => $this->class_name));
  284. }
  285. return $select;
  286. }
  287. /**
  288. * 更新符合条件的对象
  289. *
  290. * @param array $attribs
  291. */
  292. function updateWhere(array $attribs)
  293. {
  294. $args = func_get_args();
  295. array_shift($args);
  296. $objs = $this->findByArgs($args)->all()->query();
  297. foreach ($objs as $obj)
  298. {
  299. /* @var $obj QDB_ActiveRecord_Abstract */
  300. $obj->changeProps($attribs);
  301. $obj->save(0, 'update');
  302. unset($obj);
  303. }
  304. }
  305. /**
  306. * 更新符合条件的记录
  307. */
  308. function updateDbWhere()
  309. {
  310. $args = func_get_args();
  311. call_user_func_array(array($this->table, 'update'), $args);
  312. }
  313. /**
  314. * 实例化符合条件的对象,并调用这些对象的 destroy() 方法,返回被删除的对象数
  315. *
  316. * @return int
  317. */
  318. function destroyWhere()
  319. {
  320. $objs = $this->findByArgs(func_get_args())->all()->query();
  321. $c = count($objs);
  322. foreach ($objs as $obj)
  323. {
  324. $obj->destroy();
  325. unset($obj);
  326. }
  327. return $c;
  328. }
  329. /**
  330. * 从数据库中直接删除符合条件的对象
  331. *
  332. * 与 destroyWhere() 不同,deleteWhere() 会直接从数据库中删除符合条件的记录。
  333. * 而不是先把符合条件的对象查询出来再调用对象的 destroy() 方法进行删除。
  334. *
  335. * 因此,deleteWhere() 速度更快,但无法处理对象间的关联关系。
  336. */
  337. function deleteWhere()
  338. {
  339. $args = func_get_args();
  340. call_user_func_array(array($this->table, 'delete'), $args);
  341. }
  342. /**
  343. * 对数据进行验证,返回所有未通过验证数据的错误信息
  344. *
  345. * @param array $data 要验证的数据
  346. * @param array|string $props 指定仅仅验证哪些属性
  347. * @param string $mode 验证模式
  348. *
  349. * @return array 所有没有通过验证的属性名称及验证规则
  350. */
  351. function validate(array $data, $props = null, $mode = 'general')
  352. {
  353. if (!is_null($props))
  354. {
  355. $props = array_flip(Q::normalize($props));
  356. }
  357. else
  358. {
  359. $props = $this->props2fields;
  360. }
  361. $error = array();
  362. $mode = 'on_' . strtolower($mode);
  363. foreach ($this->validations as $prop => $policy)
  364. {
  365. if (!isset($props[$prop]))
  366. {
  367. continue;
  368. }
  369. if (!isset($data[$prop]))
  370. {
  371. $data[$prop] = null;
  372. }
  373. if (isset($this->belongsto_props[$prop]) && empty($policy['rules']))
  374. {
  375. continue;
  376. }
  377. if (isset($policy[$mode]))
  378. {
  379. $policy = $policy[$mode];
  380. }
  381. if (is_null($data[$prop]))
  382. {
  383. // 对于 null 数据,如果指定了自动填充,则跳过对该数据的验证
  384. switch ($mode)
  385. {
  386. case 'update':
  387. if (isset($this->update_autofill[$prop]))
  388. {
  389. continue 2;
  390. }
  391. break;
  392. default:
  393. if (isset($this->create_autofill[$prop]))
  394. {
  395. continue 2;
  396. }
  397. break;
  398. }
  399. if (!$policy['policy']['allow_null'])
  400. {
  401. // allow_null 为 false 时,如果数据为 null,则视为验证失败
  402. //$error[$prop]['not_null'] = '未被设置';
  403. }
  404. elseif (empty($policy['rules']))
  405. {
  406. continue;
  407. }
  408. }
  409. foreach ($policy['rules'] as $index => $rule)
  410. {
  411. $validation = array_shift($rule);
  412. $msg = array_pop($rule);
  413. array_unshift($rule, $data[$prop]);
  414. $ret = QValidator::validateByArgs($validation, $rule);
  415. if ($ret === QValidator::SKIP_OTHERS)
  416. {
  417. break;
  418. }
  419. elseif (!$ret)
  420. {
  421. $error[$prop][$index] = $msg;
  422. if (isset($policy['policy']) && !$policy['policy']['check_all_rules'])
  423. {
  424. break;
  425. }
  426. }
  427. }
  428. }
  429. return $error;
  430. }
  431. /**
  432. * 获得对象的关联对象
  433. *
  434. * @param QDB_ActiveRecord_Abstract $obj
  435. * @param string $prop_name
  436. *
  437. * @return QDB_ActiveRecord_Abstract|QDB_ActiveRecord_Association_Coll
  438. */
  439. function relatedObjects(QDB_ActiveRecord_Abstract $obj, $prop_name)
  440. {
  441. /**
  442. * @var QDB_ActiveRecord_Meta
  443. */
  444. $target_meta = self::instance($this->props[$prop_name]['assoc_class']);
  445. $assoc = $this->assoc($prop_name)->init();
  446. $source_key_value = $obj->{$assoc->source_key};
  447. if (empty($source_key_value))
  448. {
  449. if ($assoc->one_to_one)
  450. {
  451. return $target_meta->newObject();
  452. }
  453. else
  454. {
  455. return new QDB_ActiveRecord_Association_Coll($target_meta->class_name);
  456. }
  457. }
  458. switch ($assoc->type)
  459. {
  460. case QDB::HAS_ONE:
  461. case QDB::HAS_MANY:
  462. case QDB::BELONGS_TO:
  463. /**
  464. * @var QDB_Select
  465. */
  466. $select = $target_meta->find(array($assoc->target_key => $source_key_value));
  467. if (strlen($assoc->source_key_2nd) && strlen($assoc->target_key_2nd)){
  468. $select->where(array($assoc->target_key_2nd=>$obj->{$assoc->source_key_2nd}));
  469. }
  470. break;
  471. case QDB::MANY_TO_MANY:
  472. $assoc->mid_table->init();
  473. /* @var $assoc QDB_ActiveRecord_Association_ManyToMany */
  474. if (strlen($assoc->mid_common_key)){
  475. #第二关联键
  476. $select = $target_meta->find("[{$assoc->target_key}] = [m.{$assoc->mid_target_key}]")
  477. ->joinInner(array('m' => $assoc->mid_table), null,
  478. "[{$assoc->mid_source_key}] = ? and [{$assoc->mid_common_key}] =?", $source_key_value,$obj->{$assoc->mid_common_key});
  479. }else {
  480. $select = $target_meta->find("[{$assoc->target_key}] = [m.{$assoc->mid_target_key}]")
  481. ->joinInner(array('m' => $assoc->mid_table), null,
  482. "[{$assoc->mid_source_key}] = ?", $source_key_value);
  483. }
  484. break;
  485. default:
  486. // LC_MSG: 无效的关联类型 "%s".
  487. throw new QDB_ActiveRecord_Association_Exception(__('无效的关联类型 "%s".', $assoc->type));
  488. }
  489. /* @var $select QDB_Select */
  490. if (!empty($assoc->on_find_where))
  491. {
  492. call_user_func_array(array($select, 'where'), $assoc->on_find_where);
  493. }
  494. if (!empty($assoc->on_find_order))
  495. {
  496. $select->order($assoc->on_find_order);
  497. }
  498. if ($assoc->on_find === 'all' || $assoc->on_find === true)
  499. {
  500. $select->all();
  501. }
  502. elseif (is_int($assoc->on_find))
  503. {
  504. $select->limit(0, $assoc->on_find);
  505. }
  506. elseif (is_array($assoc->on_find))
  507. {
  508. $select->limit($assoc->on_find[0], $assoc->on_find[1]);
  509. }
  510. if ($assoc->one_to_one)
  511. {
  512. $objects = $select->query();
  513. if (count($objects))
  514. {
  515. return (is_object($objects)) ? $objects->first() : reset($objects);
  516. }
  517. else
  518. {
  519. return $target_meta->newObject();
  520. }
  521. }
  522. else
  523. {
  524. return $select->asColl()->query();
  525. }
  526. }
  527. /**
  528. * 检查是否绑定了指定的行为插件
  529. *
  530. */
  531. function hasBindBehavior($name)
  532. {
  533. return isset($this->_behaviors[$name]) ? true : false;
  534. }
  535. /**
  536. * 绑定行为插件
  537. *
  538. * @param string|array $behaviors
  539. * @param array $config
  540. *
  541. * @return QDB_ActiveRecord_Meta
  542. */
  543. function bindBehaviors($behaviors, array $config = null)
  544. {
  545. $behaviors = Q::normalize($behaviors);
  546. if (!is_array($config))
  547. {
  548. $config = array();
  549. }
  550. else
  551. {
  552. $config = array_change_key_case($config, CASE_LOWER);
  553. }
  554. foreach ($behaviors as $name)
  555. {
  556. $name = strtolower($name);
  557. // 已经绑定过的插件不再绑定
  558. if (isset($this->_behaviors[$name]))
  559. {
  560. continue;
  561. }
  562. // 载入插件
  563. $class = 'Model_Behavior_' . ucfirst($name);
  564. // 构造行为插件
  565. $settings = (!empty($config[$name])) ? $config[$name] : array();
  566. $this->_behaviors[$name] = new $class($this, $settings);
  567. }
  568. return $this;
  569. }
  570. /**
  571. * 撤销与指定行为插件的绑定
  572. *
  573. * @param string|array $behaviors
  574. *
  575. * @return QDB_ActiveRecord_Meta
  576. */
  577. function unbindBehaviors($behaviors)
  578. {
  579. $behaviors = Q::normalize($behaviors);
  580. foreach ($behaviors as $name)
  581. {
  582. $name = strtolower($name);
  583. if (!isset($this->_behaviors[$name]))
  584. {
  585. continue;
  586. }
  587. $this->_behaviors[$name]->unbind();
  588. unset($this->_behaviors[$name]);
  589. }
  590. return $this;
  591. }
  592. /**
  593. * 添加一个动态方法
  594. *
  595. * @param string $method_name
  596. * @param callback $callback
  597. * @param array $custom_parameters
  598. *
  599. * @return QDB_ActiveRecord_Meta
  600. */
  601. function addDynamicMethod($method_name, $callback, array $custom_parameters = array())
  602. {
  603. if (!empty($this->methods[$method_name]))
  604. {
  605. // LC_MSG: 指定的动态方法名 "%s" 已经存在于 "%s" 对象中.
  606. throw new QDB_ActiveRecord_Meta_Exception(__('指定的动态方法名 "%s" 已经存在于 "%s" 对象中.', $method_name, $this->class_name));
  607. }
  608. $this->methods[$method_name] = array($callback, $custom_parameters);
  609. return $this;
  610. }
  611. /**
  612. * 删除指定的动态方法
  613. *
  614. * @param string $method_name
  615. *
  616. * @return QDB_ActiveRecord_Meta
  617. */
  618. function removeDynamicMethod($method_name)
  619. {
  620. unset($this->methods[$method_name]);
  621. return $this;
  622. }
  623. /**
  624. * 添加一个静态方法
  625. *
  626. * @param string $method_name
  627. * @param callback $callback
  628. * @param array $custom_parameters
  629. *
  630. * @return QDB_ActiveRecord_Meta
  631. */
  632. function addStaticMethod($method_name, $callback, array $custom_parameters = array())
  633. {
  634. if (!empty($this->static_methods[$method_name]))
  635. {
  636. // LC_MSG: 指定的静态方法名 "%s" 已经存在于 "%s" 对象中.
  637. throw new QDB_ActiveRecord_Meta_Exception(__('指定的静态方法名 "%s" 已经存在于 "%s" 对象中.', $method_name, $this->class_name));
  638. }
  639. $this->static_methods[$method_name] = array($callback, $custom_parameters);
  640. return $this;
  641. }
  642. /**
  643. * 删除指定的静态方法
  644. *
  645. * @param string $method_name
  646. *
  647. * @return QDB_ActiveRecord_Meta
  648. */
  649. function removeStaticMethod($method_name)
  650. {
  651. unset($this->static_methods[$method_name]);
  652. return $this;
  653. }
  654. /**
  655. * 设置属性的 setter 方法
  656. *
  657. * @param string $prop_name
  658. * @param callback $callback
  659. * @param array $custom_parameters
  660. *
  661. * @return QDB_ActiveRecord_Meta
  662. */
  663. function setPropSetter($prop_name, $callback, array $custom_parameters = array())
  664. {
  665. if (isset($this->props[$prop_name]))
  666. {
  667. $this->props[$prop_name]['setter'] = array($callback, $custom_parameters);
  668. }
  669. else
  670. {
  671. $this->addProp($prop_name, array('setter' => $callback, 'setter_params'=> $custom_parameters));
  672. }
  673. return $this;
  674. }
  675. /**
  676. * 取消属性的 setter 方法
  677. *
  678. * @param string $prop_name
  679. *
  680. * @return QDB_ActiveRecord_Meta
  681. */
  682. function unsetPropSetter($prop_name)
  683. {
  684. if (isset($this->props[$prop_name]))
  685. {
  686. unset($this->props[$prop_name]['setter']);
  687. }
  688. return $this;
  689. }
  690. /**
  691. * 设置属性的 getter 方法
  692. *
  693. * @param string $name
  694. * @param callback $callback
  695. * @param array $custom_parameters
  696. *
  697. * @return QDB_ActiveRecord_Meta
  698. */
  699. function setPropGetter($name, $callback, array $custom_parameters = array())
  700. {
  701. if (isset($this->props[$name]))
  702. {
  703. $this->props[$name]['getter'] = array($callback, $custom_parameters);
  704. }
  705. else
  706. {
  707. $this->addProp($name, array('getter' => $callback, 'getter_params'=>$custom_parameters));
  708. }
  709. }
  710. /**
  711. * 取消属性的 getter 方法
  712. *
  713. * @param string $prop_name
  714. *
  715. * @return QDB_ActiveRecord_Meta
  716. */
  717. function unsetPropGetter($prop_name)
  718. {
  719. if (isset($this->props[$prop_name]))
  720. {
  721. unset($this->props[$prop_name]['getter']);
  722. }
  723. return $this;
  724. }
  725. /**
  726. * 为指定事件添加处理方法
  727. *
  728. * @param int $event_type
  729. * @param callback $callback
  730. * @param array $custom_parameters
  731. *
  732. * @return QDB_ActiveRecord_Meta
  733. */
  734. function addEventHandler($event_type, $callback, array $custom_parameters = array())
  735. {
  736. $this->callbacks[$event_type][] = array($callback, $custom_parameters);
  737. return $this;
  738. }
  739. /**
  740. * 为指定对象添加异常捕捉器
  741. *
  742. * @param QDB_ActiveRecord_Abstract $obj
  743. * @param int $exception_type
  744. * @param callback $callback
  745. * @param array $custom_parameters
  746. *
  747. * @return QDB_ActiveRecord_Meta
  748. */
  749. function addExceptionTrap(QDB_ActiveRecord_Abstract $obj, $exception_type, $callback, array $custom_parameters = array())
  750. {
  751. $obj->__exception_trap[$exception_type][] = array($callback, $custom_parameters);
  752. return $this;
  753. }
  754. /**
  755. * 删除指定事件的一个处理方法
  756. *
  757. * @param int $event_type
  758. * @param callback $callback
  759. *
  760. * @return QDB_ActiveRecord_Meta
  761. */
  762. function removeEventHandler($event_type, $callback)
  763. {
  764. if (empty($this->callbacks[$event_type]))
  765. {
  766. return $this;
  767. }
  768. foreach ($this->callbacks[$event_type] as $offset => $arr)
  769. {
  770. if ($arr[0] == $callback)
  771. {
  772. unset($this->callbacks[$event_type][$offset]);
  773. return $this;
  774. }
  775. }
  776. return $this;
  777. }
  778. /**
  779. * 添加一个属性
  780. *
  781. * @param string $prop_name
  782. * @param array $config
  783. *
  784. * @return QDB_ActiveRecord_Meta
  785. */
  786. function addProp($prop_name, array $config)
  787. {
  788. if (isset($this->props[$prop_name]))
  789. {
  790. // LC_MSG: 尝试添加的属性 "%s" 已经存在.
  791. throw new QDB_ActiveRecord_Meta_Exception(__('尝试添加的属性 "%s" 已经存在.', $prop_name));
  792. }
  793. $config = array_change_key_case($config, CASE_LOWER);
  794. $params = array('assoc' => false);
  795. $params['readonly'] = isset($config['readonly']) ? (bool)$config['readonly'] : false;
  796. // 确定属性和字段名之间的映射关系
  797. if (!empty($config['field_name']))
  798. {
  799. $field_name = $config['field_name'];
  800. }
  801. else
  802. {
  803. $field_name = isset($this->table_meta[$prop_name]) ? $this->table_meta[$prop_name]['name'] : $prop_name;
  804. }
  805. $this->props2fields[$prop_name] = $field_name;
  806. $this->fields2props[$field_name] = $prop_name;
  807. // 根据数据表的元信息确定属性是否是虚拟属性
  808. $meta_key_name = strtolower($field_name);
  809. if (!empty($this->table_meta[$meta_key_name]))
  810. {
  811. // 如果是非虚拟属性,则根据数据表的元信息设置属性的基本验证策略
  812. $params['virtual'] = false;
  813. $field_meta = $this->table_meta[$meta_key_name];
  814. $params['default_value'] = $field_meta['default'];
  815. $params['ptype'] = $field_meta['ptype'];
  816. }
  817. else
  818. {
  819. $params['virtual'] = true;
  820. $params['default_value'] = null;
  821. $params['ptype'] = 'varchar'; // TODO! 这个不能够在配置指定?是bug吗?
  822. }
  823. // 处理对象聚合
  824. foreach (self::$_assoc_types as $type)
  825. {
  826. if (empty($config[$type]))
  827. {
  828. continue;
  829. }
  830. $params['assoc'] = $type;
  831. $params['assoc_class'] = $config[$type];
  832. $params['assoc_params'] = $config;
  833. $this->associations[$prop_name] = $params;
  834. }
  835. // 设置属性信息
  836. if (!$params['virtual'] || $params['assoc'])
  837. {
  838. $this->default_props[$prop_name] = $params['default_value'];
  839. }
  840. $this->props[$prop_name] = $params;
  841. // 设置 getter 和 setter
  842. if (!empty($config['setter']))
  843. {
  844. $setter_params = !empty($config['setter_params']) ? (array)$config['setter_params'] : array();
  845. if (is_array($config['setter']))
  846. {
  847. $this->setPropSetter($prop_name, $config['setter'], $setter_params);
  848. }
  849. else
  850. {
  851. if (strpos($config['setter'], '::'))
  852. {
  853. $config['setter'] = explode('::', $config['setter']);
  854. }
  855. $this->setPropSetter($prop_name, $config['setter'], $setter_params);
  856. }
  857. }
  858. if (!empty($config['getter']))
  859. {
  860. $getter_params = !empty($config['getter_params']) ? (array)$config['getter_params'] : array();
  861. if (is_array($config['getter']))
  862. {
  863. $this->setPropGetter($prop_name, $config['getter'], $getter_params);
  864. }
  865. else
  866. {
  867. if (strpos($config['getter'], '::'))
  868. {
  869. $config['getter'] = explode('::', $config['getter']);
  870. }
  871. $this->setPropGetter($prop_name, $config['getter'], $getter_params);
  872. }
  873. }
  874. return $this;
  875. }
  876. /**
  877. * 添加一个对象关联
  878. *
  879. * $prop_name 参数指定使用 ActiveRecord 对象的什么属性来引用关联的对象。
  880. * 例如“文章”对象的 comments 属性引用了多个关联的“评论”对象。
  881. *
  882. * $assoc_type 指定了关联的类型,可以是 QDB::BELONGS_TO、QDB::HAS_MANY、QDB::HAS_ONE 或 QDB::MANY_TO_MANY。
  883. *
  884. * $config 指定了关联的属性,可用的属性有多项。
  885. *
  886. * @param string $prop_name
  887. * @param int $assoc_type
  888. * @param array $config
  889. *
  890. * @return QDB_ActiveRecord_Meta
  891. */
  892. function addAssoc($prop_name, $assoc_type, array $config)
  893. {
  894. switch ($assoc_type)
  895. {
  896. case QDB::HAS_ONE:
  897. case QDB::HAS_MANY:
  898. if (empty($config['target_key']))
  899. {
  900. $config['target_key'] = strtolower($this->class_name) . '_id';
  901. }
  902. break;
  903. case QDB::BELONGS_TO:
  904. if (empty($config['source_key']))
  905. {
  906. $config['source_key'] = strtolower($config['assoc_class']) . '_id';
  907. }
  908. break;
  909. case QDB::MANY_TO_MANY:
  910. if (empty($config['mid_source_key']))
  911. {
  912. $config['mid_source_key'] = strtolower($this->class_name) . '_id';
  913. }
  914. if (empty($config['mid_target_key']))
  915. {
  916. $config['mid_target_key'] = strtolower($config['assoc_class']) . '_id';
  917. }
  918. }
  919. $assoc = $config['assoc_params'];
  920. $assoc['mapping_name'] = $prop_name;
  921. $assoc['target_class'] = $config['assoc_class'];
  922. unset($assoc[$assoc_type]);
  923. $association = QDB_ActiveRecord_Association_Abstract::create($assoc_type, $assoc, $this);
  924. $association->registerCallbacks($assoc);
  925. $this->associations[$prop_name] = $association;
  926. if ($association->type == QDB::BELONGS_TO)
  927. {
  928. $association->init();
  929. $this->belongsto_props[$association->source_key] = $association;
  930. }
  931. return $association;
  932. }
  933. /**
  934. * 判断是否有指定关联
  935. *
  936. * @return boolean
  937. */
  938. function hasAssoc($prop_name)
  939. {
  940. return isset($this->associations[$prop_name]);
  941. }
  942. /**
  943. * 访问指定属性对应的关联
  944. *
  945. * @param string $prop_name
  946. *
  947. * @return QDB_ActiveRecord_Association_Abstract
  948. */
  949. function assoc($prop_name)
  950. {
  951. if (!isset($this->associations[$prop_name]))
  952. {
  953. throw new QDB_ActiveRecord_Association_NotDefinedException($this->class_name, $prop_name);
  954. }
  955. return $this->associations[$prop_name];
  956. }
  957. /**
  958. * 移除一个关联
  959. *
  960. * @param string $prop_name
  961. *
  962. * @return QDB_ActiveRecord_Meta
  963. */
  964. function removeAssoc($prop_name)
  965. {
  966. unset($this->props[$prop_name]);
  967. unset($this->associations[$prop_name]);
  968. return $this;
  969. }
  970. /**
  971. * 为指定属性添加一个验证规则
  972. *
  973. * @param string $prop_name
  974. * @param mixed $validation
  975. *
  976. * @return QDB_ActiveRecord_Meta
  977. */
  978. function addValidation($prop_name, $validation)
  979. {
  980. $p = array($prop_name => array($validation));
  981. $r = $this->_prepareValidationRules($p);
  982. if (!empty($r[$prop_name]['rules']))
  983. {
  984. foreach ($r[$prop_name]['rules'] as $rule)
  985. {
  986. $this->validations[$prop_name]['rules'][] = $rule;
  987. }
  988. }
  989. return $this;
  990. }
  991. /**
  992. * 取得指定属性的所有验证规则
  993. *
  994. * @param string $prop_name
  995. *
  996. * @return array
  997. */
  998. function propValidations($prop_name)
  999. {
  1000. if (isset($this->validations[$prop_name]))
  1001. {
  1002. return $this->validations[$prop_name];
  1003. }
  1004. return array('policy' => self::$_validation_policy_options, 'rules' => array());
  1005. }
  1006. /**
  1007. * 取得所有属性的所有验证规则
  1008. *
  1009. * @return array
  1010. */
  1011. function allValidations()
  1012. {
  1013. return $this->validations;
  1014. }
  1015. /**
  1016. * 清除指定属性的所有验证规则
  1017. *
  1018. * @param string $prop_name
  1019. *
  1020. * @return QDB_ActiveRecord_Meta
  1021. */
  1022. function removePropValidations($prop_name)
  1023. {
  1024. if (isset($this->validations[$prop_name]))
  1025. {
  1026. unset($this->validations[$prop_name]);
  1027. }
  1028. return $this;
  1029. }
  1030. /**
  1031. * 清除所有属性的所有验证规则
  1032. *
  1033. * @return QDB_ActiveRecord_Meta
  1034. */
  1035. function removeAllValidations()
  1036. {
  1037. $this->validations = array();
  1038. return $this;
  1039. }
  1040. /**
  1041. * 调用 ActiveRecord 继承类定义的自定义静态方法
  1042. *
  1043. * @param string $method_name
  1044. * @param array $args
  1045. *
  1046. * @return mixed
  1047. */
  1048. function __call($method_name, array $args)
  1049. {
  1050. if (isset($this->static_methods[$method_name]))
  1051. {
  1052. $callback = $this->static_methods[$method_name];
  1053. foreach ($args as $arg)
  1054. {
  1055. array_push($callback[1], $arg);
  1056. }
  1057. return call_user_func_array($callback[0], $callback[1]);
  1058. }
  1059. throw new QDB_ActiveRecord_Meta_Exception(__('未定义的方法 "%s".', $method_name));
  1060. }
  1061. /**
  1062. * 准备验证策略
  1063. *
  1064. * @param array $policies 要解析的策略
  1065. * @param array $ref 用于 include 参考的策略
  1066. * @param boolean $set_policy 是否指定验证策略
  1067. */
  1068. protected function _prepareValidationRules($policies, array $ref = array(), $set_policy = true)
  1069. {
  1070. $validation = $this->validations;
  1071. foreach ($policies as $prop_name => $policy)
  1072. {
  1073. if (!is_array($policy))
  1074. {
  1075. continue;
  1076. }
  1077. $validation[$prop_name] = array(
  1078. 'policy' => self::$_validation_policy_options,
  1079. 'rules' => array()
  1080. );
  1081. if (isset($this->props2fields[$prop_name]))
  1082. {
  1083. $fn = $this->props2fields[$prop_name];
  1084. if (isset($this->table_meta[$fn]))
  1085. {
  1086. $validation[$prop_name]['policy']['allow_null'] = ! $this->table_meta[$fn]['not_null'];
  1087. }
  1088. }
  1089. if (!$set_policy)
  1090. {
  1091. unset($validation[$prop_name]['policy']);
  1092. }
  1093. foreach ($policy as $option => $rule)
  1094. {
  1095. if (isset($validation[$prop_name]['policy'][$option]))
  1096. {
  1097. // 设置一个验证选项
  1098. $validation[$prop_name]['policy'][$option] = $rule;
  1099. }
  1100. elseif ($option === 'on_create' || $option === 'on_update')
  1101. {
  1102. // 解析 on_create 和 on_update 规则
  1103. $rule = array($option => (array) $rule);
  1104. $ret = $this->_prepareValidationRules($rule, $validation[$prop_name]['rules'], false);
  1105. $validation[$prop_name][$option] = $ret[$option];
  1106. }
  1107. elseif ($option === 'include')
  1108. {
  1109. // 解析规则包含
  1110. $include = Q::normalize($rule);
  1111. foreach ($include as $rule_name)
  1112. {
  1113. if (isset($ref[$rule_name]))
  1114. {
  1115. $validation[$prop_name]['rules'][$rule_name] = $ref[$rule_name];
  1116. }
  1117. }
  1118. }
  1119. elseif (is_array($rule))
  1120. {
  1121. // $rule 是验证规则
  1122. if (is_string($option))
  1123. {
  1124. $rule_name = $option;
  1125. }
  1126. else
  1127. {
  1128. $rule_name = $rule[0];
  1129. if (is_array($rule_name))
  1130. {
  1131. $rule_name = $rule_name[count($rule_name) - 1];
  1132. }
  1133. if (isset($validation[$prop_name]['rules'][$rule_name]))
  1134. {
  1135. $rule_name .= '_' . ($option + 1);
  1136. }
  1137. }
  1138. $validation[$prop_name]['rules'][$rule_name] = $rule;
  1139. }
  1140. else
  1141. {
  1142. // LC_MSG: 指定了无效的验证规则 "%s".
  1143. throw new QDB_ActiveRecord_Meta_Exception(__('指定了无效的验证规则 "%s".', $option . ' - ' . $rule));
  1144. }
  1145. }
  1146. }
  1147. return $validation;
  1148. }
  1149. /**
  1150. * 根据数据表名称获得表数据入口对象
  1151. *
  1152. * @param string $table_name
  1153. * @param array $table_config
  1154. *
  1155. * @return QDB_Table
  1156. */
  1157. protected function _tableByName($table_name, array $table_config = array())
  1158. {
  1159. $obj_id = 'activerecord_table_' . strtolower($table_name);
  1160. if (Q::isRegistered($obj_id))
  1161. {
  1162. return Q::registry($obj_id);
  1163. }
  1164. else
  1165. {
  1166. $table_config['name'] = $table_name;
  1167. $table = new QDB_Table($table_config);
  1168. Q::register($table, $obj_id);
  1169. return $table;
  1170. }
  1171. }
  1172. /**
  1173. * 根据类名称获得表数据入口对象
  1174. *
  1175. * @param string $table_class
  1176. * @param array $table_config
  1177. *
  1178. * @return QDB_Table
  1179. */
  1180. protected function _tableByClass($table_class, array $table_config = array())
  1181. {
  1182. $obj_id = 'activerecord_table_' . strtolower($table_class);
  1183. if (Q::isRegistered($obj_id))
  1184. {
  1185. return Q::registry($obj_id);
  1186. }
  1187. else
  1188. {
  1189. $table = new $table_class($table_config);
  1190. Q::register($table, $obj_id);
  1191. return $table;
  1192. }
  1193. }
  1194. /**
  1195. * 第一步初始化
  1196. *
  1197. * @param string $class
  1198. */
  1199. protected function _init1($class)
  1200. {
  1201. // 从指定类获得初步的定义信息
  1202. Q::loadClass($class);
  1203. $this->class_name = $class;
  1204. $ref = (array) call_user_func(array($class, '__define'));
  1205. /**
  1206. * 检查是否是继承
  1207. */
  1208. if (!empty($ref['inherit']))
  1209. {
  1210. $this->inherit_base_class = $ref['inherit'];
  1211. /**
  1212. * 继承类的 __define() 方法只需要指定与父类不同的内容
  1213. */
  1214. $base_ref = (array) call_user_func(array($this->inherit_base_class, '__define'));
  1215. $ref = array_merge_recursive($base_ref, $ref);
  1216. }
  1217. // 被继承的类
  1218. $this->inherit_type_field = !empty($ref['inherit_type_field'])
  1219. ? $ref['inherit_type_field']
  1220. : null;
  1221. // 设置表数据入口对象
  1222. $table_config = !empty($ref['table_config']) ? (array)$ref['table_config'] : array();
  1223. if (!empty($ref['table_class']))
  1224. {
  1225. $this->table = $this->_tableByClass($ref['table_class'], $table_config);
  1226. }
  1227. else
  1228. {
  1229. $this->table = $this->_tableByName($ref['table_name'], $table_config);
  1230. }
  1231. $this->table_meta = $this->table->columns();
  1232. // 根据字段定义确定字段属性
  1233. if (empty($ref['props']) || ! is_array($ref['props']))
  1234. {
  1235. $ref['props'] = array();
  1236. }
  1237. foreach ($ref['props'] as $prop_name => $config)
  1238. {
  1239. $this->addProp($prop_name, $config);
  1240. }
  1241. // 将没有指定的字段也设置为对象属性
  1242. foreach ($this->table_meta as $prop_name => $field)
  1243. {
  1244. if (isset($this->props2fields[$prop_name])) continue;
  1245. $this->addProp($prop_name, $field);
  1246. }
  1247. // 设置其他选项
  1248. if (!empty($ref['create_reject']))
  1249. {
  1250. $this->create_reject = array_flip(Q::normalize($ref['create_reject']));
  1251. }
  1252. if (!empty($ref['update_reject']))
  1253. {
  1254. $this->update_reject = array_flip(Q::normalize($ref['update_reject']));
  1255. }
  1256. if (!empty($ref['create_autofill']) && is_array($ref['create_autofill']))
  1257. {
  1258. $this->create_autofill = $ref['create_autofill'];
  1259. }
  1260. if (!empty($ref['update_autofill']) && is_array($ref['update_autofill']))
  1261. {
  1262. $this->update_autofill = $ref['update_autofill'];
  1263. }
  1264. if (!empty($ref['attr_accessible']))
  1265. {
  1266. $this->attr_accessible = array_flip(Q::normalize($ref['attr_accessible']));
  1267. }
  1268. if (!empty($ref['attr_protected']))
  1269. {
  1270. $this->attr_protected = array_flip(Q::normalize($ref['attr_protected']));
  1271. }
  1272. // 准备验证规则
  1273. if (empty($ref['validations']) || ! is_array($ref['validations']))
  1274. {
  1275. $ref['validations'] = array();
  1276. }
  1277. $this->validations = $this->_prepareValidationRules($ref['validations']);
  1278. // 设置对象 ID 属性名
  1279. $pk = $this->table->getPK();
  1280. $this->idname = array();
  1281. foreach ($this->table->getPK() as $pk)
  1282. {
  1283. $pn = $this->fields2props[$pk];
  1284. $this->idname[$pn] = $pn;
  1285. }
  1286. $this->idname_count = count($this->idname);
  1287. // 绑定行为插件
  1288. if (isset($ref['behaviors']))
  1289. {
  1290. $config = isset($ref['behaviors_settings']) ? $ref['behaviors_settings'] : array();
  1291. $this->bindBehaviors($ref['behaviors'], $config);
  1292. }
  1293. }
  1294. /**
  1295. * 第二步初始化
  1296. *
  1297. * 避免因为关联到自身造成循环引用。
  1298. */
  1299. protected function _init2()
  1300. {
  1301. foreach (array_keys($this->associations) as $prop_name)
  1302. {
  1303. $config = $this->associations[$prop_name];
  1304. if (is_array($config))
  1305. {
  1306. $this->addAssoc($prop_name, $config['assoc'], $config);
  1307. }
  1308. }
  1309. }
  1310. }