1: <?php
2:
3: namespace ZCL\DB;
4:
5: abstract class TreeEntity extends Entity
6: {
7:
8: /**
9: * Возврашает метаданные для выборки из БД
10: * Реализуется конкретными сущностями имплементирующими класс Entity
11: * Метаданные содержат имя таблицы, имя ключевого поля
12: * а также имя представления если такое существует в БД
13: * Например array('table' => 'system_users','view' => 'system_users_view', 'keyfield' => 'user_id')
14: * Для работы с данными иерархическогго типа вводятся дополнительные параметрыЖ
15: * 'parentfield' - поле с родительскич id и 'pathfield' - строчное поле хранения материализованного
16: * пути
17: *
18: * Вместо испоользования метода можно импользоввать аннтации возде определения класса
19: * анноации именуются аналогично ключам массива метаданных.
20: */
21: protected static function getMetadata() {
22:
23: $class = new \ReflectionClass(get_called_class());
24: $doc = $class->getDocComment();
25: preg_match_all('/@([a-z0-9_-]+)=([^\n]+)/is', $doc, $arr);
26: if (is_array($arr)) {
27: $reg_arr = array_combine($arr[1], $arr[2]);
28:
29: $table = trim($reg_arr['table']);
30: $view = trim($reg_arr['view']);
31: $keyfield = trim($reg_arr['keyfield']);
32: $parentfield = trim($reg_arr['parentfield']);
33: $pathfield = trim($reg_arr['pathfield']);
34:
35:
36: if (strlen($table) > 0 && strlen($keyfield) > 0 && strlen($parentfield) > 0 && strlen($pathfield) > 0) {
37: $retarr = array();
38: $retarr['table'] = $table;
39: $retarr['keyfield'] = $keyfield;
40: $retarr['parentfield'] = $parentfield;
41: $retarr['pathfield'] = $pathfield;
42: if (strlen($view) > 0) {
43: $retarr['view'] = $view;
44: }
45:
46: return $retarr;
47: }
48: }
49: throw new \Zippy\Exception('getMetadata должен быть перегружен');
50: }
51:
52:
53: /**
54: * @see Entity
55: */
56: public static function delete($id) {
57: $class = get_called_class();
58: $meta = $class::getMetadata();
59:
60: if (is_numeric($id)) {
61:
62:
63: $obj = $class::load($id);
64: } else {
65: $obj = $id;
66: }
67:
68: if ($obj instanceof TreeEntity) {
69: $alowdelete = $obj->beforeDelete();
70:
71: if (strlen($allowdelete)>0) {
72:
73: return $allowdelete;
74: }
75:
76: $alowdelete = $obj->deleteChildren();
77: if (strlen($allowdelete)>0) {
78:
79: return $allowdelete;
80: }
81: $conn = DB::getConnect();
82: $conn->Execute("delete from {$meta['table']} where {$meta['keyfield']} = " . $id);
83: }
84:
85: }
86:
87: /**
88: * Перемешение узла дерева в другой ужел
89: *
90: * @param mixed $pid Id узла перемешения
91: */
92: public function moveTo($pid) {
93: $class = get_called_class();
94: $meta = $class::getMetadata();
95: $old = sprintf('%08s', $this->{$meta['parentfield']});
96: $this->{$meta['parentfield']} = $pid;
97: $new = sprintf('%08s', $this->{$meta['parentfield']});
98: $this->save();
99:
100: $children = $this->getChildren(true);
101: foreach ($children as $child) {
102: $child->{$meta['pathfield']} = str_replace($old, $new, $child->{$meta['pathfield']});
103: $child->save();
104: }
105: }
106:
107:
108: public function getParent() {
109:
110: $class = get_called_class();
111: $meta = $class::getMetadata();
112:
113:
114: return $class::load($this->{$meta['parentfield']});
115: }
116:
117: /**
118: * Получение дочерних узлов
119: *
120: * @param mixed $all Если false получаем только непостредственнвх потомков
121: */
122: public function getChildren($all = false) {
123: $conn = DB::getConnect();
124: $class = get_called_class();
125: $meta = $class::getMetadata();
126:
127: if (!$all) {
128: return self::find($meta['parentfield'] . '=' . $this->fields[$meta['keyfield']]);
129: } else {
130: return self::find($meta['keyfield'] . ' <> ' . $this->fields[$meta['keyfield']] . ' and ' . $meta['pathfield'] . " like " . $this->qstr('%' . sprintf('%08s', $this->fields[$meta['keyfield']]) . '%'));
131: }
132: }
133:
134: /**
135: * Удаление узла
136: *
137: * @param mixed $rec Ксли true дочерние узлы удаляются рекурсивно,
138: * иначе удаляются одним запросом к БД
139: */
140: public function deleteChildren($rec = true) {
141: $conn = DB::getConnect();
142: $class = get_called_class();
143: $meta = $class::getMetadata();
144: if ($rec) {
145: $children = $this->getChildren();
146: foreach ($children as $child) {
147: $b = $class::delete($child->getID());
148: if (strlen($b)>0) {
149: return $b;
150: }
151: }
152: } else {
153: $id = $this->fields[$meta['keyfield']];
154: $conn->Execute("delete from {$meta['table']} where " . $meta['pathfield'] . " like " . $conn->qstr('%' . sprintf('%08s', $id) . '%') . " and {$meta['keyfield']} != " . $id);
155: return "";
156: }
157: return "";
158: }
159:
160: /**
161: *
162: * @see Entity
163: */
164: protected function afterSave($update) {
165: $meta = $this->getMetadata();
166: if (strlen($this->{$meta['pathfield']}) > 0) {
167: return;
168: }
169:
170: $class = get_called_class();
171:
172:
173: $this->{$meta['pathfield']} = sprintf('%08s', $this->{$meta['keyfield']});
174: if ($this->{$meta['parentfield']} > 0) {
175: $parent = $class::load($this->{$meta['parentfield']});
176: $this->{$meta['pathfield']} = $parent->{$meta['pathfield']} . sprintf('%08s', $this->{$meta['keyfield']});
177: }
178: $conn = \ZDB\DB::getConnect();
179: $conn->Execute("UPDATE {$meta['table']} set {$meta['pathfield']} ='" . $this->{$meta['pathfield']} . "' where {$meta['keyfield']} = " . $this->{$meta['keyfield']});
180: }
181:
182:
183: /**
184: * Возвращает цепочку родителей до корня
185: *
186: */
187: public function getParents() {
188: $class = get_called_class();
189: $meta = $class::getMetadata();
190: $path = $this->{$meta['pathfield']};
191: $ap = str_split($path, 8);
192: array_pop($ap);
193: if (count($ap) == 0) {
194: return array();
195: }
196: $ids = implode(",", $ap);
197: $where = $meta["keyfield"] . " in (" . $ids . ")";
198: return self::find($where, $meta["keyfield"] . " desc");
199:
200: }
201: }
202: