Overview

Namespaces

  • Ark
    • Database

Classes

  • Ark\Database\Connection
  • Ark\Database\Model
  • Ark\Database\ModelFactory
  • Ark\Database\QueryBuilder

Interfaces

  • Ark\Database\Exception
  • Overview
  • Namespace
  • Class
  1: <?php
  2: /**
  3:  * ark.database
  4:  * @copyright 2015 Liu Dong <ddliuhb@gmail.com>
  5:  * @license MIT
  6:  */
  7: 
  8: namespace Ark\Database;
  9: 
 10: /**
 11:  * Model
 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:      * Model configuration
 33:      * 
 34:      * Sub classes should extend this static method
 35:      * @return array
 36:      */
 37:     public static function config(){
 38:         return array(
 39:             //'table' => 'table',
 40:             'pk' => 'id',
 41:             //'relations' => array(),
 42:         );
 43:     }
 44:     
 45:     /**
 46:      * Get model configuration
 47:      * 
 48:      * @param string $key
 49:      * @return mixed
 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:      * Get model table
 66:      */
 67:     public function getTable(){
 68:         return $this->_table;
 69:     }
 70: 
 71:     /**
 72:      * Get model pk
 73:      */
 74:     public function getPK(){
 75:         return $this->_pk;
 76:     }
 77:         
 78:     /**
 79:      * Get model relations
 80:      */
 81:     public function getRelations(){
 82:         return $this->_relations;
 83:     }
 84:     
 85:     /**
 86:      * Translate class name to table name.
 87:      * example:
 88:      *  BlogPost => blog_post
 89:      *  Acme\BlogPost => blog_post
 90:      * @param string $class Class name
 91:      * @return string
 92:      */
 93:     static public function entityNameToDBName($class){
 94:         //namespace
 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:      * Model constructor
104:      * 
105:      * @param \Ark\Database\Connection $db
106:      * @param array $data
107:      * @param boolean $isNew Is it a new model or a model loaded from database.
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:      * Set attribute
125:      *
126:      * @return \Ark\Database\Model
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:      * Set attributes
144:      * 
145:      * @param array $attributes
146:      * @return \Ark\Database\Model
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:      * Is it a new model
158:      * 
159:      * @return boolean
160:      */
161:     public function isNew(){
162:         return $this->_isNew;
163:     }
164:     
165:     /**
166:      * Is it synced with database
167:      * 
168:      * @return boolean
169:      */
170:     public function isDirty(){
171:         return !empty($this->_dirty);
172:     }
173:     
174:     /**
175:      * Get raw data
176:      * 
177:      * @param string $key
178:      * @return mixed
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:      * Get data
191:      * 
192:      * @param string $key
193:      * @return mixed Find in dirty data first, return all data if key is not specified
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:      * Magic method to fetch a model field
214:      * @param  string $key
215:      * @return mixed
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:      * Get a field with relation
227:      * @param  string $name
228:      * @return mixed
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:      * Has one
257:      * 
258:      * @param string $target target model or @table
259:      * @param string $key
260:      * @param string $target_key
261:      * @return \Ark\Database\Model
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:      * Has many
277:      * 
278:      * @param string $target
279:      * @param string $key
280:      * @param string $target_key
281:      * @return array
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:      * Belongs to
297:      * 
298:      * @param string $target
299:      * @param string $key
300:      * @param string $target_key
301:      * @return \Ark\Database\Model
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:      * Many to many
317:      * 
318:      * @param string $target
319:      * @param string $through
320:      * @param string $key
321:      * @param string $target_key
322:      * @return array
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:      * Set data
369:      * 
370:      * @param string|array $key
371:      * @param mixed $value
372:      * @return \Ark\Database\Model
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:      * Save modified data to db
419:      * 
420:      * @return int|boolean
421:      */
422:     public function save(){
423:         if($this->beforeSave()){
424:             if($this->isNew()){
425:                 $data = $this->_dirty;
426:                 
427:                 //insert
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:                     //update
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:      * Delete model
459:      * @return int|boolean
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: }
API documentation generated by ApiGen