Pluf Framework

Pluf Framework Git Source Tree

Root/src/Pluf/Model.php

1<?php
2/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3/*
4# ***** BEGIN LICENSE BLOCK *****
5# This file is part of Plume Framework, a simple PHP Application Framework.
6# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
7#
8# Plume Framework is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License as published by
10# the Free Software Foundation; either version 2.1 of the License, or
11# (at your option) any later version.
12#
13# Plume Framework is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21#
22# ***** END LICENSE BLOCK ***** */
23
24
25/**
26 * Sort of Active Record Class
27 *
28 */
29class Pluf_Model
30{
31 public $_model = __CLASS__; //set it to your model name
32
33 /** Database connection. */
34 public $_con = null;
35
36 /**
37 * Store the attributes of the model. To minimize pollution of the
38 * property space, all the attributes are stored in this array.
39 *
40 * Description of the keys:
41 * 'table': The table in which the model is stored.
42 * 'model': The name of the model.
43 * 'cols': The definition of the columns.
44 * 'idx': The definition of the indexes.
45 * 'views': The definition of the views.
46 * 'verbose': The verbose name of the model.
47 */
48 public $_a = array('table' => 'model',
49 'model' => 'Pluf_Model',
50 'cols' => array(),
51 'idx' => array(),
52 'views' => array(),
53 );
54
55 /** Storage of the data.
56 *
57 * The object data are stored in an associative array. Each key
58 * corresponds to a column and stores a Pluf_DB_Field_* variable.
59 */
60 protected $_data = array();
61
62 /**
63 * Storage cached data for methods_get
64 */
65 protected $_cache = array(); // We should use a global cache.
66
67 /** List of the foreign keys.
68 *
69 * Set by the init() method from the definition of the columns.
70 */
71 protected $_fk = array();
72
73 /**
74 * Methods available, this array is dynamically populated by init
75 * method.
76 */
77 protected $_m = array('list' => array(), // get_*_list methods
78 'many' => array(), // many to many
79 'get' => array(), // foreign keys
80 'extra' => array(), // added by some fields
81 );
82
83 function __construct($pk=null, $values=array())
84 {
85 $this->_init();
86 if ((int) $pk > 0) {
87 $this->get($pk); //Should not have a side effect
88 }
89 }
90
91
92 function init()
93 {
94 // Define it yourself.
95 }
96
97 /**
98 * Define the list of methods for the model from the available
99 * model relationship.
100 */
101 function _init()
102 {
103 $this->_getConnection();
104 if (isset($GLOBALS['_PX_models_init_cache'][$this->_model])) {
105 $init_cache = $GLOBALS['_PX_models_init_cache'][$this->_model];
106 $this->_cache = $init_cache['cache'];
107 $this->_m = $init_cache['m'];
108 $this->_a = $init_cache['a'];
109 $this->_fk = $init_cache['fk'];
110 $this->_data = $init_cache['data'];
111 return;
112 }
113 $this->init();
114 foreach ($this->_a['cols'] as $col => $val) {
115 $field = new $val['type']('', $col);
116 $col_lower = strtolower($col);
117
118 $type = 'foreignkey';
119 if ($type === $field->type) {
120 $this->_m['get']['get_'.$col_lower] = array($val['model'], $col);
121 $this->_cache['fk'][$col] = $type;
122 $this->_fk[$col] = $type;
123 }
124
125 $type = 'manytomany';
126 if ($type === $field->type) {
127 $this->_m['list']['get_'.$col_lower.'_list'] = $val['model'];
128 $this->_m['many'][$val['model']] = $type;
129 }
130
131 foreach ($field->methods as $method) {
132 $this->_m['extra'][$method[0]] = array($col_lower, $method[1]);
133 }
134
135 if (array_key_exists('default', $val)) {
136 $this->_data[$col] = $val['default'];
137 } else {
138 $this->_data[$col] = '';
139 }
140 }
141
142 $this->_setupAutomaticListMethods('foreignkey');
143 $this->_setupAutomaticListMethods('manytomany');
144
145 $GLOBALS['_PX_models_init_cache'][$this->_model] = array(
146 'cache' => $this->_cache,
147 'm' => $this->_m,
148 'a' => $this->_a,
149 'fk' => $this->_fk,
150 'data' => $this->_data,
151 );
152 }
153
154 /**
155 * Retrieve key relationships of a given model.
156 *
157 * @param string $model
158 * @param string $type Relation type: 'foreignkey' or 'manytomany'.
159 * @return array Key relationships.
160 */
161 public function getRelationKeysToModel($model, $type)
162 {
163 $keys = array();
164 foreach ($this->_a['cols'] as $col => $val) {
165 if (isset($val['model']) && $model === $val['model']) {
166 $field = new $val['type']();
167 if ($type === $field->type) {
168 $keys[$col] = $val;
169 }
170 }
171 }
172
173 return $keys;
174 }
175
176 /**
177 * Get the foreign keys relating to a given model.
178 *
179 * @deprecated Use {@link self::getRelationKeysToModel()} instead.
180 * @param string Model
181 * @return array Foreign keys
182 */
183 function getForeignKeysToModel($model)
184 {
185 return $this->getRelationKeysToModel($model, 'foreignkey');
186 }
187
188 /**
189 * Get the raw data of the object.
190 *
191 * For the many to many relations, the value is an array of ids.
192 *
193 * @return array Associative array of the data.
194 */
195 function getData()
196 {
197 foreach ($this->_a['cols'] as $col=>$val) {
198 $field = new $val['type']();
199 if ($field->type == 'manytomany') {
200 $this->_data[$col] = array();
201 $method = 'get_'.strtolower($col).'_list';
202 foreach ($this->$method() as $item) {
203 $this->_data[$col][] = $item->id;
204 }
205 }
206 }
207 return $this->_data;
208 }
209
210 /**
211 * Set the association of a model to another in many to many.
212 *
213 * @param object Object to associate to the current object
214 */
215 function setAssoc($model)
216 {
217 if (!$this->delAssoc($model)) {
218 return false;
219 }
220 $hay = array(strtolower($model->_a['model']), strtolower($this->_a['model']));
221 sort($hay);
222 $table = $hay[0].'_'.$hay[1].'_assoc';
223 $req = 'INSERT INTO '.$this->_con->pfx.$table."\n";
224 $req .= '('.$this->_con->qn(strtolower($this->_a['model']).'_id').', '
225 .$this->_con->qn(strtolower($model->_a['model']).'_id').') VALUES '."\n";
226 $req .= '('.$this->_toDb($this->_data['id'], 'id').', ';
227 $req .= $this->_toDb($model->id, 'id').')';
228 $this->_con->execute($req);
229 return true;
230 }
231
232 /**
233 * Set the association of a model to another in many to many.
234 *
235 * @param object Object to associate to the current object
236 */
237 function delAssoc($model)
238 {
239
240 //check if ok to make the association
241 //current model has a many to many key with $model
242 //$model has a many to many key with current model
243 if (!isset($this->_m['many'][$model->_a['model']])
244 or strlen($this->_data['id']) == 0
245 or strlen($model->id) == 0) {
246 return false;
247 }
248 $hay = array(strtolower($model->_a['model']), strtolower($this->_a['model']));
249 sort($hay);
250 $table = $hay[0].'_'.$hay[1].'_assoc';
251 $req = 'DELETE FROM '.$this->_con->pfx.$table.' WHERE'."\n";
252 $req .= $this->_con->qn(strtolower($this->_a['model']).'_id').' = '.$this->_toDb($this->_data['id'], 'id');
253 $req .= ' AND '.$this->_con->qn(strtolower($model->_a['model']).'_id').' = '.$this->_toDb($model->id, 'id');
254 $this->_con->execute($req);
255 return true;
256 }
257
258 /**
259 * Bulk association of models to the current one.
260 *
261 * @param string Model name
262 * @param array Ids of Model name
263 * @return bool Success
264 */
265 function batchAssoc($model_name, $ids)
266 {
267 $currents = $this->getRelated($model_name);
268 foreach ($currents as $cur) {
269 $this->delAssoc($cur);
270 }
271 foreach ($ids as $id) {
272 $m = new $model_name($id);
273 if ($m->id == $id) {
274 $this->setAssoc($m);
275 }
276 }
277 return true;
278 }
279
280 /**
281 * Get a database connection.
282 */
283 function _getConnection()
284 {
285 static $con = null;
286 if ($this->_con !== null) {
287 return $this->_con;
288 }
289 if ($con !== null) {
290 $this->_con = $con;
291 return $this->_con;
292 }
293 $this->_con = &Pluf::db($this);
294 $con = $this->_con;
295 return $this->_con;
296 }
297
298 /**
299 * Get a database connection.
300 */
301 function getDbConnection()
302 {
303 return $this->_getConnection();
304 }
305
306 /**
307 * Get the table of the model.
308 *
309 * Avoid doing the concatenation of the prefix and the table
310 * manually.
311 */
312 function getSqlTable()
313 {
314 return $this->_con->pfx.$this->_a['table'];
315 }
316
317 /**
318 * Overloading of the get method.
319 *
320 * @param string Property to get
321 */
322 function __get($prop)
323 {
324 return (array_key_exists($prop, $this->_data)) ?
325 $this->_data[$prop] : $this->__call($prop, array());
326 }
327
328 /**
329 * Overloading of the set method.
330 *
331 * @param string Property to set
332 * @param mixed Value to set
333 */
334 function __set($prop, $val)
335 {
336 if (null !== $val and isset($this->_cache['fk'][$prop])) {
337 $this->_data[$prop] = $val->id;
338 unset($this->_cache['get_'.$prop]);
339 } else {
340 $this->_data[$prop] = $val;
341 }
342 }
343
344 /**
345 * Overloading of the method call.
346 *
347 * @param string Method
348 * @param array Arguments
349 */
350 function __call($method, $args)
351 {
352 // The foreign keys of the current object.
353 if (isset($this->_m['get'][$method])) {
354 if (isset($this->_cache[$method])) {
355 return $this->_cache[$method];
356 } else {
357 $this->_cache[$method] = Pluf::factory($this->_m['get'][$method][0], $this->_data[$this->_m['get'][$method][1]]);
358 if ($this->_cache[$method]->id == '') $this->_cache[$method] = null;
359 return $this->_cache[$method];
360 }
361 }
362 // Many to many or foreign keys on the other objects.
363 if (isset($this->_m['list'][$method])) {
364 if (is_array($this->_m['list'][$method])) {
365 $model = $this->_m['list'][$method][0];
366 } else {
367 $model = $this->_m['list'][$method];
368 }
369 $args = array_merge(array($model, $method), $args);
370 return call_user_func_array(array($this, 'getRelated'), $args);
371 }
372 // Extra methods added by fields
373 if (isset($this->_m['extra'][$method])) {
374 $args = array_merge(array($this->_m['extra'][$method][0], $method, $this), $args);
375 Pluf::loadFunction($this->_m['extra'][$method][1]);
376 return call_user_func_array($this->_m['extra'][$method][1], $args);
377 }
378 throw new Exception(sprintf('Method "%s" not available.', $method));
379 }
380
381 /**
382 * Get a given item.
383 *
384 * @param int Id of the item.
385 * @return mixed Item or false if not found.
386 */
387 function get($id)
388 {
389 $req = 'SELECT * FROM '.$this->getSqlTable().' WHERE id='.$this->_toDb($id, 'id');
390 if (false === ($rs = $this->_con->select($req))) {
391 throw new Exception($this->_con->getError());
392 }
393 if (count($rs) == 0) {
394 return false;
395 }
396 foreach ($this->_a['cols'] as $col => $val) {
397 $field = new $val['type']();
398 if ($field->type != 'manytomany' && array_key_exists($col, $rs[0])) {
399 $this->_data[$col] = $this->_fromDb($rs[0][$col], $col);
400 }
401 }
402 $this->restore();
403 return $this;
404 }
405
406 /**
407 * Get one item.
408 *
409 * The parameters are the same as the ones of the getList method,
410 * but, the return value is either:
411 *
412 * - The object
413 * - null if no match
414 * - Exception if the match results in more than one item.
415 *
416 * Usage:
417 *
418 * <pre>
419 * $m = Pluf::factory('My_Model')->getOne(array('filter' => 'id=1'));
420 * </pre>
421 * <pre>
422 * $m = Pluf::factory('My_Model')->getOne('id=1');
423 * </pre>
424 *
425 * @param array|string Filter string or array given to getList
426 * @see self::getList
427 */
428 public function getOne($p=array())
429 {
430 if (!is_array($p)) {
431 $p = array('filter' => $p);
432 }
433 $items = $this->getList($p);
434 if ($items->count() == 1) {
435 return $items[0];
436 }
437 if ($items->count() == 0) {
438 return null;
439 }
440 throw new Exception(__('Error: More than one matching item found.'));
441 }
442
443 /**
444 * Get a list of items.
445 *
446 * The filter should be used only for simple filtering. If you want
447 * a complex query, you should create a new view.
448 * Both filter and order accept an array or a string in case of multiple
449 * parameters:
450 * Filter:
451 * array('col1=toto', 'col2=titi') will be used in a AND query
452 * or simply 'col1=toto'
453 * Order:
454 * array('col1 ASC', 'col2 DESC') or 'col1 ASC'
455 *
456 * This is modelled on the DB_Table pear module interface.
457 *
458 * @param array Associative array with the possible following
459 * keys:
460 * 'view': The view to use
461 * 'filter': The where clause to use
462 * 'order': The ordering of the result set
463 * 'start': The number of skipped rows in the result set
464 * 'nb': The number of items to get in the result set
465 * 'count': Run a count query and not a select if set to true
466 * @return ArrayObject of items or through an exception if
467 * database failure
468 */
469 function getList($p=array())
470 {
471 $default = array('view' => null,
472 'filter' => null,
473 'order' => null,
474 'start' => null,
475 'select' => null,
476 'nb' => null,
477 'count' => false);
478 $p = array_merge($default, $p);
479 if (!is_null($p['view']) && !isset($this->_a['views'][$p['view']])) {
480 throw new Exception(sprintf(__('The view "%s" is not defined.'), $p['view']));
481 }
482 $query = array(
483 'select' => $this->getSelect(),
484 'from' => $this->_a['table'],
485 'join' => '',
486 'where' => '',
487 'group' => '',
488 'having' => '',
489 'order' => '',
490 'limit' => '',
491 'props' => array(),
492 );
493 if (!is_null($p['view'])) {
494 $query = array_merge($query, $this->_a['views'][$p['view']]);
495 }
496 if (!is_null($p['select'])) {
497 $query['select'] = $p['select'];
498 }
499 if (!is_null($p['filter'])) {
500 if (is_array($p['filter'])) {
501 $p['filter'] = implode(' AND ', $p['filter']);
502 }
503 if (strlen($query['where']) > 0) {
504 $query['where'] .= ' AND ';
505 }
506 $query['where'] .= ' ('.$p['filter'].') ';
507 }
508 if (!is_null($p['order'])) {
509 if (is_array($p['order'])) {
510 $p['order'] = implode(', ', $p['order']);
511 }
512 if (strlen($query['order']) > 0 and strlen($p['order']) > 0) {
513 $query['order'] .= ', ';
514 }
515 $query['order'] .= $p['order'];
516 }
517 if (!is_null($p['start']) && is_null($p['nb'])) {
518 $p['nb'] = 10000000;
519 }
520 if (!is_null($p['start'])) {
521 if ($p['start'] != 0) {
522 $p['start'] = (int) $p['start'];
523 }
524 $p['nb'] = (int) $p['nb'];
525 $query['limit'] = 'LIMIT '.$p['nb'].' OFFSET '.$p['start'];
526 }
527 if (!is_null($p['nb']) && is_null($p['start'])) {
528 $p['nb'] = (int) $p['nb'];
529 $query['limit'] = 'LIMIT '.$p['nb'];
530 }
531 if ($p['count'] == true) {
532 if (isset($query['select_count'])) {
533 $query['select'] = $query['select_count'];
534 } else {
535 $query['select'] = 'COUNT(*) as nb_items';
536 }
537 $query['order'] = '';
538 $query['limit'] = '';
539 }
540 $req = 'SELECT '.$query['select'].' FROM '
541 .$this->_con->pfx.$query['from'].' '.$query['join'];
542 if (strlen($query['where'])) {
543 $req .= "\n".'WHERE '.$query['where'];
544 }
545 if (strlen($query['group'])) {
546 $req .= "\n".'GROUP BY '.$query['group'];
547 }
548 if (strlen($query['having'])) {
549 $req .= "\n".'HAVING '.$query['having'];
550 }
551 if (strlen($query['order'])) {
552 $req .= "\n".'ORDER BY '.$query['order'];
553 }
554 if (strlen($query['limit'])) {
555 $req .= "\n".$query['limit'];
556 }
557 if (false === ($rs=$this->_con->select($req))) {
558 throw new Exception($this->_con->getError());
559 }
560 if (count($rs) == 0) {
561 return new ArrayObject();
562 }
563 if ($p['count'] == true) {
564 return $rs;
565 }
566 $res = new ArrayObject();
567 foreach ($rs as $row) {
568 $this->_reset();
569 foreach ($this->_a['cols'] as $col => $val) {
570 if (isset($row[$col])) $this->_data[$col] = $this->_fromDb($row[$col], $col);
571 }
572 // FIXME: The associated properties need to be converted too.
573 foreach ($query['props'] as $prop => $key) {
574 $this->_data[$key] = (isset($row[$prop])) ? $row[$prop] : null;
575 }
576 $this->restore();
577 $res[] = clone($this);
578 }
579 return $res;
580 }
581
582 /**
583 * Get the number of items.
584 *
585 * @see getList() for definition of the keys
586 *
587 * @param array with associative keys 'view' and 'filter'
588 * @return int The number of items
589 */
590 function getCount($p=array())
591 {
592 $p['count'] = true;
593 $count = $this->getList($p);
594 if (empty($count) or count($count) == 0) {
595 return 0;
596 } else {
597 return (int) $count[0]['nb_items'];
598 }
599 }
600
601 /**
602 * Get a list of related items.
603 *
604 * See the getList() method for usage of the view and filters.
605 *
606 * @param string Class of the related items
607 * @param string Method call in a many to many related
608 * @param array Parameters, see getList() for the definition of
609 * the keys
610 * @return array Array of items
611 */
612 function getRelated($model, $method=null, $p=array())
613 {
614 $default = array('view' => null,
615 'filter' => null,
616 'order' => null,
617 'start' => null,
618 'nb' => null,
619 'count' => false);
620 $p = array_merge($default, $p);
621 if ('' == $this->_data['id']) {
622 return new ArrayObject();
623 }
624 $m = new $model();
625 if (isset($this->_m['list'][$method])
626 and is_array($this->_m['list'][$method])) {
627 $foreignkey = $this->_m['list'][$method][1];
628 if (strlen($foreignkey) == 0) {
629 throw new Exception(sprintf(__('No matching foreign key found in model: %s for model %s'), $model, $this->_a['model']));
630 }
631 if (!is_null($p['filter'])) {
632 if (is_array($p['filter'])) {
633 $p['filter'] = implode(' AND ', $p['filter']);
634 }
635 $p['filter'] .= ' AND ';
636 } else {
637 $p['filter'] = '';
638 }
639 $p['filter'] .= $this->_con->qn($foreignkey).'='.$this->_toDb($this->_data['id'], 'id');
640 } else {
641 // Many to many: We generate a special view that is making
642 // the join
643 $hay = array(strtolower(Pluf::factory($model)->_a['model']),
644 strtolower($this->_a['model']));
645 sort($hay);
646 $table = $hay[0].'_'.$hay[1].'_assoc';
647 if (isset($m->_a['views'][$p['view']])) {
648 $m->_a['views'][$p['view'].'__manytomany__'] = $m->_a['views'][$p['view']];
649 if (!isset($m->_a['views'][$p['view'].'__manytomany__']['join'])) {
650 $m->_a['views'][$p['view'].'__manytomany__']['join'] = '';
651 }
652 if (!isset($m->_a['views'][$p['view'].'__manytomany__']['where'])) {
653 $m->_a['views'][$p['view'].'__manytomany__']['where'] = '';
654 }
655 } else {
656 $m->_a['views']['__manytomany__'] = array('join' => '',
657 'where' => '');
658 $p['view'] = '';
659 }
660 $m->_a['views'][$p['view'].'__manytomany__']['join'] .=
661 ' LEFT JOIN '.$this->_con->pfx.$table.' ON '
662 .$this->_con->qn(strtolower($m->_a['model']).'_id').' = '.$this->_con->pfx.$m->_a['table'].'.id';
663
664 $m->_a['views'][$p['view'].'__manytomany__']['where'] = $this->_con->qn(strtolower($this->_a['model']).'_id').'='.$this->_data['id'];
665 $p['view'] = $p['view'].'__manytomany__';
666 }
667 return $m->getList($p);
668 }
669
670 /**
671 * Generate the SQL select from the columns
672 */
673 function getSelect()
674 {
675 if (isset($this->_cache['getSelect'])) return $this->_cache['getSelect'];
676 $select = array();
677 $table = $this->getSqlTable();
678 foreach ($this->_a['cols'] as $col=>$val) {
679 if ($val['type'] != 'Pluf_DB_Field_Manytomany') {
680 $select[] = $table.'.'.$this->_con->qn($col).' AS '.$this->_con->qn($col);
681 }
682 }
683 $this->_cache['getSelect'] = implode(', ', $select);
684 return $this->_cache['getSelect'];
685 }
686
687 /**
688 * Update the model into the database.
689 *
690 * If no where clause is provided, the index definition is used to
691 * find the sequence. These are used to limit the update
692 * to the current model.
693 *
694 * @param string Where clause to update specific items. ('')
695 * @return bool Success
696 */
697 function update($where='')
698 {
699 $this->preSave();
700 $req = 'UPDATE '.$this->getSqlTable().' SET'."\n";
701 $fields = array();
702 $assoc = array();
703 foreach ($this->_a['cols'] as $col=>$val) {
704 $field = new $val['type']();
705 if ($col == 'id') {
706 continue;
707 } elseif ($field->type == 'manytomany') {
708 if (is_array($this->$col)) {
709 $assoc[$val['model']] = $this->$col;
710 }
711 continue;
712 }
713 $fields[] = $this->_con->qn($col).' = '.$this->_toDb($this->$col, $col);
714 }
715 $req .= implode(','."\n", $fields);
716 if (strlen($where) > 0) {
717 $req .= ' WHERE '.$where;
718 } else {
719 $req .= ' WHERE id = '.$this->_toDb($this->_data['id'], 'id');
720 }
721 $this->_con->execute($req);
722 if (false === $this->get($this->_data['id'])) {
723 return false;
724 }
725 foreach ($assoc as $model=>$ids) {
726 $this->batchAssoc($model, $ids);
727 }
728 $this->postSave();
729 return true;
730 }
731
732 /**
733 * Create the model into the database.
734 *
735 * If raw insert is requested, the preSave/postSave methods are
736 * not called and the current id of the object is directly
737 * used. This is particularily used when doing backup/restore of
738 * data.
739 *
740 * @param bool Raw insert (false)
741 * @return bool Success
742 */
743 function create($raw=false)
744 {
745 if (!$raw) {
746 $this->preSave(true);
747 }
748 $req = 'INSERT INTO '.$this->getSqlTable()."\n";
749 $icols = array();
750 $ivals = array();
751 $assoc = array();
752 foreach ($this->_a['cols'] as $col=>$val) {
753 $field = new $val['type']();
754 if ($col == 'id' and !$raw) {
755 continue;
756 } elseif ($field->type == 'manytomany') {
757 // If is a defined array, we need to associate.
758 if (is_array($this->_data[$col])) {
759 $assoc[$val['model']] = $this->_data[$col];
760 }
761 continue;
762 }
763 $icols[] = $this->_con->qn($col);
764 $ivals[] = $this->_toDb($this->_data[$col], $col);
765 }
766 $req .= '('.implode(', ', $icols).') VALUES ';
767 $req .= '('.implode(','."\n", $ivals).')';
768 $this->_con->execute($req);
769 if (!$raw) {
770 if (false === ($id=$this->_con->getLastID())) {
771 throw new Exception($this->_con->getError());
772 }
773 $this->_data['id'] = $id;
774 }
775 foreach ($assoc as $model=>$ids) {
776 $this->batchAssoc($model, $ids);
777 }
778 if (!$raw) {
779 $this->postSave(true);
780 }
781 return true;
782 }
783
784 /**
785 * Get models affected by delete.
786 *
787 * @return array Models deleted if deleting current model.
788 */
789 function getDeleteSideEffect()
790 {
791 $affected = array();
792 foreach ($this->_m['list'] as $method=>$details) {
793 if (is_array($details)) {
794 // foreignkey
795 $related = $this->$method();
796 $affected = array_merge($affected, (array) $related);
797 foreach ($related as $rel) {
798 if ($details[0] == $this->_a['model']
799 and $rel->id == $this->_data['id']) {
800 continue; // $rel == $this
801 }
802 $affected = array_merge($affected, (array) $rel->getDeleteSideEffect());
803 }
804 }
805 }
806 return Pluf_Model_RemoveDuplicates($affected);
807 }
808
809 /**
810 * Delete the current model from the database.
811 *
812 * If another model link to the current model through a foreign
813 * key, find it and delete it. If this model is linked to other
814 * through a many to many, delete the association.
815 *
816 * FIXME: No real test of circular references. It can break.
817 */
818 function delete()
819 {
820 if (false === $this->get($this->_data['id'])) {
821 return false;
822 }
823 $this->preDelete();
824 // Drop the row level permissions if we are using them
825 if (Pluf::f('pluf_use_rowpermission', false)) {
826 $_rpt = Pluf::factory('Pluf_RowPermission')->getSqlTable();
827 $sql = new Pluf_SQL('model_class=%s AND model_id=%s',
828 array($this->_a['model'], $this->_data['id']));
829 $this->_con->execute('DELETE FROM '.$_rpt.' WHERE '.$sql->gen());
830 }
831 // Find the models linking to the current one through a foreign key.
832 foreach ($this->_m['list'] as $method=>$details) {
833 if (is_array($details)) {
834 // foreignkey
835 $related = $this->$method();
836 foreach ($related as $rel) {
837 if ($details[0] == $this->_a['model']
838 and $rel->id == $this->_data['id']) {
839 continue; // $rel == $this
840 }
841 // We do not really control if it can be deleted
842 // as we can find many times the same to delete.
843 $rel->delete();
844 }
845 } else {
846 // manytomany
847 $related = $this->$method();
848 foreach ($related as $rel) {
849 $this->delAssoc($rel);
850 }
851 }
852 }
853 $req = 'DELETE FROM '.$this->getSqlTable().' WHERE id = '.$this->_toDb($this->_data['id'], 'id');
854 $this->_con->execute($req);
855 $this->_reset();
856 return true;
857 }
858
859 /**
860 * Reset the fields to default values.
861 */
862 function _reset()
863 {
864 foreach ($this->_a['cols'] as $col => $val) {
865 if (isset($val['default'])) {
866 $this->_data[$col] = $val['default'];
867 } elseif (isset($val['is_null'])) {
868 $this->_data[$col] = null;
869 } else {
870 $this->_data[$col] = '';
871 }
872 }
873 }
874
875
876 /**
877 * Represents the model in auto generated lists.
878 *
879 * You need to overwrite this method to have a nice display of
880 * your objects in the select boxes, logs.
881 */
882 function __toString()
883 {
884 return $this->_a['model'].'('.$this->_data['id'].')';
885 }
886
887
888 /**
889 * Hook run just after loading a model from the database.
890 *
891 * Just overwrite it into your model to perform custom actions.
892 */
893 function restore()
894 {
895 }
896
897 /**
898 * Hook run just before saving a model in the database.
899 *
900 * Just overwrite it into your model to perform custom actions.
901 *
902 * @param bool Create.
903 */
904 function preSave($create=false)
905 {
906 }
907
908 function postSave($create=false)
909 {
910 }
911
912 /**
913 * Hook run just before deleting a model from the database.
914 *
915 * Just overwrite it into your model to perform custom actions.
916 */
917 function preDelete()
918 {
919 }
920
921 /**
922 * Set the values from form data.
923 */
924 function setFromFormData($cleaned_values)
925 {
926 foreach ($cleaned_values as $key=>$val) {
927 $this->_data[$key] = $val;
928 }
929 }
930
931 /**
932 * Set a view.
933 *
934 * @param string Name of the view.
935 * @param array Definition of the view.
936 */
937 function setView($view, $def)
938 {
939 $this->_a['views'][$view] = $def;
940 }
941
942 /**
943 * Prepare the value to be put in the DB.
944 *
945 * @param mixed Value.
946 * @param string Column name.
947 * @return string SQL ready string.
948 */
949 function _toDb($val, $col)
950 {
951 $m = $this->_con->type_cast[$this->_a['cols'][$col]['type']][1];
952 return $m($val, $this->_con);
953 }
954
955 /**
956 * Get the value from the DB.
957 *
958 * @param mixed Value.
959 * @param string Column name.
960 * @return mixed Value.
961 */
962 function _fromDb($val, $col)
963 {
964 $m = $this->_con->type_cast[$this->_a['cols'][$col]['type']][0];
965 return ($m == 'Pluf_DB_IdentityFromDb') ? $val : $m($val);
966 }
967
968 /**
969 * Display value.
970 *
971 * When you have a list of choices for a field and you want to get
972 * the display value of the current stored value.
973 *
974 * @param string Field to display the value.
975 * @return mixed Display value, if not available default to the value.
976 */
977 function displayVal($col)
978 {
979 if (!isset($this->_a['cols'][$col]['choices'])) {
980 return $this->_data[$col]; // will on purposed failed if not set
981 }
982 $val = array_search($this->_data[$col], $this->_a['cols'][$col]['choices']);
983 if ($val !== false) {
984 return $val;
985 }
986 return $this->_data[$col];
987 }
988
989 /**
990 * Build the automatic methods for the relations of given type.
991 *
992 * Adds the get_xx_list method when the methods of the model
993 * contains custom names.
994 *
995 * @param string $type Relation type: 'foreignkey' or 'manytomany'.
996 */
997 protected function _setupAutomaticListMethods($type)
998 {
999 $current_model = $this->_a['model'];
1000 if (isset($GLOBALS['_PX_models_related'][$type][$current_model])) {
1001 $relations = $GLOBALS['_PX_models_related'][$type][$current_model];
1002 foreach ($relations as $related) {
1003 if ($related != $current_model) {
1004 $model = new $related();
1005 } else $model = clone $this;
1006 $fkeys = $model->getRelationKeysToModel($current_model, $type);
1007 foreach ($fkeys as $fkey => $val) {
1008 $mname = (isset($val['relate_name'])) ? $val['relate_name'] : $related;
1009 $mname = 'get_'.strtolower($mname).'_list';
1010 if ('foreignkey' === $type) {
1011 $this->_m['list'][$mname] = array($related, $fkey);
1012 } else {
1013 $this->_m['list'][$mname] = $related;
1014 $this->_m['many'][$related] = $type;
1015 }
1016 }
1017 }
1018 }
1019 }
1020
1021}
1022
1023
1024/**
1025 * Check if a model is already in an array of models.
1026 *
1027 * It is not possible to override the == function in PHP to directly
1028 * use in_array.
1029 *
1030 * @param Pluf_Model The model to test
1031 * @param Array The models
1032 * @return bool
1033 */
1034function Pluf_Model_InArray($model, $array)
1035{
1036 if ($model->id == '') {
1037 return false;
1038 }
1039 foreach ($array as $modelin) {
1040 if ($modelin->_a['model'] == $model->_a['model']
1041 and $modelin->id == $model->id) {
1042 return true;
1043 }
1044 }
1045 return false;
1046}
1047
1048/**
1049 * Return a list of unique models.
1050 *
1051 * @param array Models with duplicates
1052 * @return array Models with duplicates.
1053 */
1054function Pluf_Model_RemoveDuplicates($array)
1055{
1056 $res = array();
1057 foreach ($array as $model) {
1058 if (!Pluf_Model_InArray($model, $res)) {
1059 $res[] = $model;
1060 }
1061 }
1062 return $res;
1063}
1064
1065

Archive Download this file

Branches

Tags