1: <?php
2: 3: 4: 5: 6:
7:
8: namespace Ark\Database;
9:
10: 11: 12:
13: class Model
14: {
15: private $_table;
16:
17: private $_pk;
18:
19: private $_relations;
20:
21: protected $_db;
22:
23: protected $_data = array();
24:
25: protected $_safe;
26:
27: protected $_dirty = array();
28:
29: protected $_isNew;
30:
31: 32: 33: 34: 35: 36:
37: public static function config(){
38: return array(
39:
40: 'pk' => 'id',
41:
42: );
43: }
44:
45: 46: 47: 48: 49: 50:
51: final public static function getConfig($key = null){
52: $config = static::config();
53: if(null === $key){
54: return $config;
55: }
56: elseif(isset($config[$key])){
57: return $config[$key];
58: }
59: else{
60: return null;
61: }
62: }
63:
64: 65: 66:
67: public function getTable(){
68: return $this->_table;
69: }
70:
71: 72: 73:
74: public function getPK(){
75: return $this->_pk;
76: }
77:
78: 79: 80:
81: public function getRelations(){
82: return $this->_relations;
83: }
84:
85: 86: 87: 88: 89: 90: 91: 92:
93: static public function entityNameToDBName($class){
94:
95: if(false !== $pos = strrpos($class, '\\')){
96: $class = substr($class, $pos + 1);
97: }
98:
99: return strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $class));
100: }
101:
102: 103: 104: 105: 106: 107: 108:
109: public function __construct($db, $data = [], $isNew = true){
110: $this->_db = $db;
111:
112: if($isNew){
113: $this->_dirty = $data;
114: }
115: else{
116: $this->_data = $data;
117: }
118: $this->_isNew = $isNew;
119:
120: $this->setAttributes(static::config());
121: }
122:
123: 124: 125: 126: 127:
128: public function setAttribute($attribute, $value){
129: if($attribute === 'table'){
130: $this->_table = $value;
131: }
132: elseif($attribute === 'pk'){
133: $this->_pk = $value;
134: }
135: elseif($attribute === 'relations'){
136: $this->_relations = $value;
137: }
138:
139: return $this;
140: }
141:
142: 143: 144: 145: 146: 147:
148: public function setAttributes($attributes){
149: foreach($attributes as $k => $v){
150: $this->setAttribute($k, $v);
151: }
152:
153: return $this;
154: }
155:
156: 157: 158: 159: 160:
161: public function isNew(){
162: return $this->_isNew;
163: }
164:
165: 166: 167: 168: 169:
170: public function isDirty(){
171: return !empty($this->_dirty);
172: }
173:
174: 175: 176: 177: 178: 179:
180: public function getRaw($key = null){
181: if(null === $key){
182: return $this->_data;
183: }
184: else{
185: return isset($this->_data[$key])?$this->_data[$key]:null;
186: }
187: }
188:
189: 190: 191: 192: 193: 194:
195: public function get($key = null){
196: if(null === $key){
197: return array_merge($this->_data, $this->_dirty);
198: }
199: else{
200: if(isset($this->_dirty[$key])){
201: return $this->_dirty[$key];
202: }
203: elseif(isset($this->_data[$key])){
204: return $this->_data[$key];
205: }
206: else{
207: return null;
208: }
209: }
210: }
211:
212: 213: 214: 215: 216:
217: public function __get($key){
218: $relations = $this->getRelations();
219: if(isset($relations[$key])){
220: return $this->getWithRelation($key);
221: }
222: return $this->get($key);
223: }
224:
225: 226: 227: 228: 229:
230: public function getWithRelation($name){
231: $relations = $this->getRelations();
232: if(isset($relations[$name])){
233: $relation = $relations[$name];
234: if($relation['relation'] === 'OTO'){
235: return $this->getOneToOne($relation['target'], isset($relation['key'])?$relation['key']:null, isset($relation['target_key'])?$relation['target_key']:null);
236: }
237: elseif($relation['relation'] == 'OTM'){
238: return $this->getOneToMany($relation['target'], isset($relation['key'])?$relation['key']:null, isset($relation['target_key'])?$relation['target_key']:null);
239: }
240: elseif($relation['relation'] == 'MTO'){
241: return $this->getManyToOne($relation['target'], isset($relation['key'])?$relation['key']:null, isset($relation['target_key'])?$relation['target_key']:null);
242: }
243: elseif($relation['relation'] == 'MTM'){
244: return $this->getManyToMany($relation['target'], $relation['through'], isset($relation['key'])?$relation['key']:null, isset($relation['target_key'])?$relation['target_key']:null);
245: }
246: else{
247: throw new Exception('Invalid relation "'.$relation['relation'].'"');
248: }
249: }
250: else{
251: return false;
252: }
253: }
254:
255: 256: 257: 258: 259: 260: 261: 262:
263: public function getOneToOne($target, $key = null, $target_key = null){
264: $factory = $this->_db->factory($target);
265: if(null === $key){
266: $key = $this->getPK();
267: }
268: if(null === $target_key){
269: $target_key = $factory->getPK();
270: }
271:
272: return $factory->findOneBy($target_key, $this->get($key));
273: }
274:
275: 276: 277: 278: 279: 280: 281: 282:
283: public function getOneToMany($target, $key = null, $target_key = null){
284: $factory = $this->_db->factory($target);
285: if(null === $key){
286: $key = $this->getPK();
287: }
288: if(null === $target_key){
289: $target_key = $key;
290: }
291:
292: return $factory->findManyBy($target_key, $this->get($key));
293: }
294:
295: 296: 297: 298: 299: 300: 301: 302:
303: public function getManyToOne($target, $key = null, $target_key = null){
304: $factory = $this->_db->factory($target);
305: if(null === $target_key){
306: $target_key = $factory->getPK();
307: }
308: if(null === $key){
309: $key = $target_key;
310: }
311:
312: return $factory->findOneBy($target_key, $this->get($key));
313: }
314:
315: 316: 317: 318: 319: 320: 321: 322: 323:
324: public function getManyToMany($target, $through, $key = null, $target_key = null){
325: $factory = $this->_db->factory($target);
326:
327: if(null === $key){
328: $key = $this->getPK();
329: }
330: if(null === $target_key){
331: $target_key = $factory->getPK();
332: }
333:
334: $through = $this->parseThrough($through);
335: if(!$through[1]){
336: $through[1] = $key;
337: }
338: if(!$through[2]){
339: $through[2] = $target_key;
340: }
341:
342: $rows = $this->_db->builder()
343: ->select('t.*')
344: ->from($factory->getTable().' t')
345: ->leftJoin($through[0].' m', 'm.'.$through[2].'=t.'.$target_key)
346: ->where('m.'.$through[1].'=:value', array(
347: ':value' => $this->get($key)
348: ))
349: ->queryAll();
350:
351: if(false === $rows){
352: return false;
353: }
354:
355: return $factory->mapModels($rows);
356: }
357:
358: protected function parseThrough($through){
359: $through = explode(',', $through);
360: $table = trim($through[0]);
361: $key = isset($through[1])?trim($through[1]):null;
362: $target_key = isset($through[2])?trim($through[2]):null;
363:
364: return array($table, $key, $target_key);
365: }
366:
367: 368: 369: 370: 371: 372: 373:
374: public function set($key, $value = null){
375: if(is_array($key)){
376: $this->_dirty = $key;
377: }
378: else{
379: $this->_dirty[$key] = $value;
380: }
381:
382: return $this;
383: }
384:
385: public function __set($key, $value){
386: $this->_dirty[$key] = $value;
387: }
388:
389: public function __isset($key){
390: return isset($this->_dirty[$key]) || isset($this->_data[$key]);
391: }
392:
393: public function __unset($key){
394: if(isset($this->_dirty[$key])){
395: unset($this->_dirty[$key]);
396: }
397: }
398:
399: protected function buildPKConditions(){
400: $pk = $this->getPK();
401: if(is_string($pk)){
402: $pks = array($pk);
403: }
404: else{
405: $pks = $pk;
406: }
407: $params = array();
408: foreach($pks as $k => $pk){
409: $pks[$k] = $pk.'=:pk'.$k;
410: $params[':pk'.$k] = $this->_data[$pk];
411: }
412: array_unshift($pks, 'AND');
413:
414: return array($pks, $params);
415: }
416:
417: 418: 419: 420: 421:
422: public function save(){
423: if($this->beforeSave()){
424: if($this->isNew()){
425: $data = $this->_dirty;
426:
427:
428: if(false !== $rst = $this->_db->builder()->insert($this->getTable(), $data))
429: {
430: if(is_string($this->getPK()) && $id = $this->_db->lastInsertId()){
431: $data[$this->getPK()] = $id;
432: }
433: $this->_data = $data;
434: $this->_dirty = array();
435: $this->_isNew = false;
436: $this->afterSave();
437: return $rst;
438: }
439: }
440: else{
441: if($this->isDirty()){
442:
443: $pkConditions = $this->buildPKConditions();
444: if(false !== $rst = $this->_db->builder()->update($this->getTable(), $this->_dirty, $pkConditions[0], $pkConditions[1])){
445: $this->_data = array_merge($this->_data, $this->_dirty);
446: $this->_dirty = array();
447: $this->afterSave();
448: return $rst;
449: }
450: }
451: }
452: }
453:
454: return false;
455: }
456:
457: 458: 459: 460:
461: public function delete(){
462: if($this->beforeDelete()){
463: $pkConditions = $this->buildPKConditions();
464: if(false !== $rst = ($this->isNew() || $this->_db->builder()->delete($this->getTable(), $pkConditions[0], $pkConditions[1]))){
465: $this->_data = array();
466: $this->_dirty = array();
467: $this->afterDelete();
468:
469: return $rst;
470: }
471: }
472:
473: return false;
474: }
475:
476: protected function beforeSave(){
477: return true;
478: }
479:
480: protected function afterSave(){
481: return true;
482: }
483:
484: protected function beforeDelete(){
485: return true;
486: }
487:
488: protected function afterDelete(){
489: return true;
490: }
491:
492: }