Newer
Older
<?php
/*********************************************************************
class.orm.php
Simple ORM (Object Relational Mapper) for PHP5 based on Django's ORM,
except that complex filter operations are not supported. The ORM simply
supports ANDed filter operations without any GROUP BY support.
Jared Hancock <jared@osticket.com>
Copyright (c) 2006-2013 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class VerySimpleModel {
static $meta = array(
'table' => false,
'ordering' => false,
'pk' => false
);
var $ht;
var $dirty;
var $__new__ = false;
function __construct($row) {
$this->ht = $row;
$this->__setupForeignLists();
$this->dirty = array();
}
function get($field) {
return $this->ht[$field];
}
function __get($field) {
if (array_key_exists($field, $this->ht))
return $this->ht[$field];
elseif (isset(static::$meta['joins'][$field])) {
// TODO: Support instrumented lists and such
$j = static::$meta['joins'][$field];
$class = $j['fkey'][0];
$v = $this->ht[$field] = $class::lookup($this->ht[$j['local']]);
return $v;
}
// Update of foreign-key by assignment to model instance
if (isset(static::$meta['joins'][$field])) {
$j = static::$meta['joins'][$field];
// XXX: Ensure $value instanceof $j['fkey'][0]
if ($value->__new__)
$value->save();
// Capture the object under the object's field name
$this->ht[$field] = $value;
// Capture the foreign key id value
$field = $j['local'];
$value = $value->{$j['fkey'][1]};
// Fall through to the standard logic below
}
// XXX: Fully support or die if updating pk
// XXX: The contents of $this->dirty should be the value after the
// previous fetch or save. For instance, if the value is changed more
// than once, the original value should be preserved in the dirty list
// on the second edit.
$old = isset($this->ht[$field]) ? $this->ht[$field] : null;
if ($old != $value) {
$this->dirty[$field] = $old;
$this->ht[$field] = $value;
}
}
function __set($field, $value) {
return $this->set($field, $value);
}
function setAll($props) {
foreach ($props as $field=>$value)
$this->set($field, $value);
}
function __setupForeignLists() {
// Construct related lists
if (isset(static::$meta['joins'])) {
foreach (static::$meta['joins'] as $name => $j) {
if (isset($j['list']) && $j['list']) {
$fkey = $j['fkey'];
$this->{$name} = new InstrumentedList(
// Send Model, Foriegn-Field, Local-Id
array($fkey[0], $fkey[1], $this->{$j['local']})
);
}
}
}
}
static function _inspect() {
if (!static::$meta['table'])
throw new OrmConfigurationError(
'Model does not define meta.table', get_called_class());
// Break down foreign-key metadata
foreach (static::$meta['joins'] as $field => &$j) {
if (isset($j['reverse'])) {
list($model, $key) = explode('.', $j['reverse']);
$info = $model::$meta['joins'][$key];
$constraint = array();
foreach ($info['constraint'] as $foreign => $local) {
list(,$field) = explode('.', $local);
$constraint[$field] = "$model.$foreign";
}
$j['constraint'] = $constraint;
$j['list'] = true;
}
// XXX: Make this better (ie. composite keys)
$keys = array_keys($j['constraint']);
$foreign = $j['constraint'][$keys[0]];
$j['fkey'] = explode('.', $foreign);
$j['local'] = $keys[0];
}
}
static function objects() {
return new QuerySet(get_called_class());
}
static function lookup($criteria) {
if (!is_array($criteria))
// Model::lookup(1), where >1< is the pk value
$criteria = array(static::$meta['pk'][0] => $criteria);
$list = static::objects()->filter($criteria)->limit(1);
// TODO: Throw error if more than one result from database
return $list[0];
}
function delete($pk=false) {
$table = static::$meta['table'];
$sql = 'DELETE FROM '.$table;
if (!$pk) $pk = static::$meta['pk'];
if (!is_array($pk)) $pk=array($pk);
foreach ($pk as $p)
$filter[] = $p.' = '.db_input($this->get($p));
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
$sql .= ' WHERE '.implode(' AND ', $filter).' LIMIT 1';
return db_affected_rows(db_query($sql)) == 1;
}
function save($refetch=false) {
$pk = static::$meta['pk'];
if (!$this->isValid())
return false;
if (!is_array($pk)) $pk=array($pk);
if ($this->__new__)
$sql = 'INSERT INTO '.static::$meta['table'];
else
$sql = 'UPDATE '.static::$meta['table'];
$filter = $fields = array();
if (count($this->dirty) === 0)
return;
foreach ($this->dirty as $field=>$old)
if ($this->__new__ or !in_array($field, $pk))
if (@get_class($this->get($field)) == 'SqlFunction')
$fields[] = $field.' = '.$this->get($field)->toSql();
else
$fields[] = $field.' = '.db_input($this->get($field));
foreach ($pk as $p)
$filter[] = $p.' = '.db_input($this->get($p));
$sql .= ' SET '.implode(', ', $fields);
if (!$this->__new__) {
$sql .= ' WHERE '.implode(' AND ', $filter);
$sql .= ' LIMIT 1';
}
if (db_affected_rows(db_query($sql)) != 1) {
throw new Exception(db_error());
return false;
}
if ($this->__new__) {
if (count($pk) == 1)
$this->ht[$pk[0]] = db_insert_id();
$this->__new__ = false;
// Setup lists again
$this->__setupForeignLists();
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
}
# Refetch row from database
# XXX: Too much voodoo
if ($refetch) {
# XXX: Support composite PK
$criteria = array($pk[0] => $this->get($pk[0]));
$self = static::lookup($criteria);
$this->ht = $self->ht;
}
$this->dirty = array();
return $this->get($pk[0]);
}
static function create($ht=false) {
if (!$ht) $ht=array();
$class = get_called_class();
$i = new $class(array());
$i->__new__ = true;
foreach ($ht as $field=>$value)
if (!is_array($value))
$i->set($field, $value);
return $i;
}
/**
* isValid
*
* Validates the contents of $this->ht before the model should be
* committed to the database. This is the validation for the field
* template -- edited in the admin panel for a form section.
*/
function isValid() {
return true;
}
}
class SqlFunction {
function SqlFunction($name) {
$this->func = $name;
$this->args = array_slice(func_get_args(), 1);
}
function toSql() {
$args = (count($this->args)) ? implode(',', db_input($this->args)) : "";
return sprintf('%s(%s)', $this->func, $args);
}
}
class QuerySet implements IteratorAggregate, ArrayAccess {
var $model;
var $constraints = array();
var $exclusions = array();
var $ordering = array();
var $limit = false;
var $offset = 0;
var $related = array();
var $values = array();
var $compiler = 'MySqlCompiler';
var $iterator = 'ModelInstanceIterator';
var $params;
var $query;
function __construct($model) {
$this->model = $model;
}
function filter() {
// Multiple arrays passes means OR
$this->constraints[] = func_get_args();
return $this;
}
function exclude() {
$this->exclusions[] = func_get_args();
return $this;
}
function order_by() {
$this->ordering = array_merge($this->ordering, func_get_args());
return $this;
}
function limit($count) {
$this->limit = $count;
return $this;
}
function offset($at) {
$this->offset = $at;
return $this;
}
function select_related() {
$this->related = array_merge($this->related, func_get_args());
return $this;
}
function values() {
$this->values = func_get_args();
$this->iterator = 'HashArrayIterator';
return $this;
}
function values_flat() {
$this->values = func_get_args();
$this->iterator = 'FlatArrayIterator';
return $this;
}
function all() {
return $this->getIterator()->asArray();
}
function count() {
$compiler = new $this->compiler();
return $compiler->compileCount($this);
}
function exists() {
return $this->count() > 0;
}
// IteratorAggregate interface
function getIterator() {
if (!isset($this->_iterator))
$this->_iterator = new $this->iterator($this);
return $this->_iterator;
}
// ArrayAccess interface
function offsetExists($offset) {
return $this->getIterator()->offsetExists($offset);
}
function offsetGet($offset) {
return $this->getIterator()->offsetGet($offset);
}
function offsetUnset($a) {
throw new Exception('QuerySet is read-only');
}
function offsetSet($a, $b) {
throw new Exception('QuerySet is read-only');
}
function __toString() {
return (string)$this->getQuery();
}
function getQuery($options=array()) {
if (isset($this->query))
return $this->query;
// Load defaults from model
$model = $this->model;
if (!$this->ordering && isset($model::$meta['ordering']))
$this->ordering = $model::$meta['ordering'];
$compiler = new $this->compiler($options);
$this->query = $compiler->compileSelect($this);
return $this->query;
}
}
class ModelInstanceIterator implements Iterator, ArrayAccess {
var $model;
var $resource;
var $cache = array();
var $position = 0;
var $queryset;
function __construct($queryset=false) {
if ($queryset) {
$this->model = $queryset->model;
$this->resource = $queryset->getQuery();
}
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
}
function buildModel($row) {
// TODO: Traverse to foreign keys
return new $this->model($row);
}
function fillTo($index) {
while ($this->resource && $index >= count($this->cache)) {
if ($row = $this->resource->getArray()) {
$this->cache[] = $this->buildModel($row);
} else {
$this->resource->close();
$this->resource = null;
break;
}
}
}
function asArray() {
$this->fillTo(PHP_INT_MAX);
return $this->cache;
}
// Iterator interface
function rewind() {
$this->position = 0;
}
function current() {
$this->fillTo($this->position);
return $this->cache[$this->position];
}
function key() {
return $this->position;
}
function next() {
$this->position++;
}
function valid() {
$this->fillTo($this->position);
return count($this->cache) > $this->position;
}
// ArrayAccess interface
function offsetExists($offset) {
$this->fillTo($offset);
return $this->position >= $offset;
}
function offsetGet($offset) {
$this->fillTo($offset);
return $this->cache[$offset];
}
function offsetUnset($a) {
throw new Exception(sprintf('%s is read-only', get_class($this)));
}
function offsetSet($a, $b) {
throw new Exception(sprintf('%s is read-only', get_class($this)));
}
}
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
class FlatArrayIterator extends ModelInstanceIterator {
function __construct($queryset) {
$this->resource = $queryset->getQuery();
}
function fillTo($index) {
while ($this->resource && $index >= count($this->cache)) {
if ($row = $this->resource->getRow()) {
$this->cache += $row;
} else {
$this->resource->close();
$this->resource = null;
break;
}
}
}
}
class InstrumentedList extends ModelInstanceIterator {
var $key;
var $id;
function __construct($fkey, $queryset=false) {
list($model, $this->key, $this->id) = $fkey;
if (!$queryset)
$queryset = $model::objects()->filter(array($this->key=>$this->id));
parent::__construct($queryset);
if (!$this->id)
$this->resource = null;
}
function add($object) {
$object->{$this->key} = $this->id;
$object->save();
$this->list[] = $object;
}
function remove($object) {
$object->delete();
}
function offsetUnset($a) {
$this->fillTo($a);
$this->cache[$a]->delete();
}
function offsetSet($a, $b) {
$this->fillTo($a);
$this->cache[$a]->delete();
$this->add($b);
}
}
var $joins = array();
var $aliases = array();
var $alias_num = 1;
function __construct($options=false) {
if ($options)
$this->options = array_merge($this->options, $options);
}
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
/**
* Handles breaking down a field or model search descriptor into the
* model search path, field, and operator parts. When used in a queryset
* filter, an expression such as
*
* user__email__hostname__contains => 'foobar'
*
* would be broken down to search from the root model (passed in,
* perhaps a ticket) to the user and email models by inspecting the
* model metadata 'joins' property. The 'constraint' value found there
* will be used to build the JOIN sql clauses.
*
* The 'hostname' will be the field on 'email' model that should be
* compared in the WHERE clause. The comparison should be made using a
* 'contains' function, which in MySQL, might be implemented using
* something like "<lhs> LIKE '%foobar%'"
*
* This function will rely heavily on the pushJoin() function which will
* handle keeping track of joins made previously in the query and
* therefore prevent multiple joins to the same table for the same
* reason. (Self joins are still supported).
*
* Comparison functions supported by this function are defined for each
* respective SqlCompiler subclass; however at least these functions
* should be defined:
*
* function a__function => b
* ----------+------------------------------------------------
* exact | a is exactly equal to b
* gt | a is greater than b
* lte | b is greater than a
* lt | a is less than b
* gte | b is less than a
* ----------+------------------------------------------------
* contains | (string) b is contained within a
* statswith | (string) first len(b) chars of a are exactly b
* endswith | (string) last len(b) chars of a are exactly b
* like | (string) a matches pattern b
* ----------+------------------------------------------------
* in | a is in the list or the nested queryset b
* ----------+------------------------------------------------
* isnull | a is null (if b) else a is not null
*
* If no comparison function is declared in the field descriptor,
* 'exact' is assumed.
*/
function getField($field, $model, $options=array()) {
$joins = array();
// Break apart the field descriptor by __ (double-underbars). The
// first part is assumed to be the root field in the given model.
// The parts after each of the __ pieces are links to other tables.
// The last item (after the last __) is allowed to be an operator
// specifiction.
$parts = explode('__', $field);
$operator = static::$operators['exact'];
if (!isset($options['table'])) {
if (array_key_exists($field, static::$operators)) {
$operator = static::$operators[$field];
$field = array_pop($parts);
}
$path = array();
$crumb = '';
$alias = $this->quote($model::$meta['table']);
// Traverse through the parts and establish joins between the tables
// if the field is joined to a foreign model
if (count($parts) && isset($model::$meta['joins'][$parts[0]])) {
// Call pushJoin for each segment in the join path. A new
// JOIN fragment will need to be emitted and/or cached
$path[] = $p;
$tip = implode('__', $path);
$alias = $this->pushJoin($crumb, $tip, $model, $info);
// Roll to foreign model
foreach ($info['constraint'] as $local => $foreign) {
list($model, $f) = explode('.', $foreign);
if (class_exists($model))
break;
}
}
if (isset($options['table']) && $options['table'])
$field = $alias;
elseif ($alias)
$field = $alias.'.'.$this->quote($field);
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
/**
* Uses the compiler-specific `compileJoin` function to compile the join
* statement fragment, and caches the result in the local $joins list. A
* new alias is acquired using the `nextAlias` function which will be
* associated with the join. If the same path is requested again, the
* algorithm is short-circuited and the originally-assigned table alias
* is returned immediately.
*/
function pushJoin($tip, $path, $model, $info) {
// TODO: Build the join statement fragment and return the table
// alias. The table alias will be useful where the join is used in
// the WHERE and ORDER BY clauses
// If the join already exists for the statement-being-compiled, just
// return the alias being used.
if (isset($this->joins[$path]))
return $this->joins[$path]['alias'];
// TODO: Support only using aliases if necessary. Use actual table
// names for everything except oddities like self-joins
$alias = $this->nextAlias();
// Keep an association between the table alias and the model. This
// will make model construction much easier when we have the data
// and the table alias from the database.
$this->aliases[$alias] = $model;
// TODO: Stash joins and join constraints into local ->joins array.
// This will be useful metadata in the executor to construct the
// final models for fetching
// TODO: Always use a table alias. This will further help with
// coordination between the data returned from the database (where
// table alias is available) and the corresponding data.
$this->joins[$path] = array(
'alias' => $alias,
'sql'=> $this->compileJoin($tip, $model, $alias, $info),
);
return $alias;
}
function compileWhere($where, $model) {
$constrints = array();
foreach ($where as $constraint) {
$filter = array();
foreach ($constraint as $field=>$value) {
list($field, $op) = $this->getField($field, $model);
// Allow operators to be callable rather than sprintf
// strings
if (is_callable($op))
$filter[] = call_user_func($op, $field, $value);
else
$filter[] = sprintf($op, $field, $this->input($value));
}
// Multiple constraints here are ANDed together
$constraints[] = implode(' AND ', $filter);
}
// Multiple constrains here are ORed together
$filter = implode(' OR ', $constraints);
if (count($constraints) > 1)
$filter = '(' . $filter . ')';
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
return $filter;
}
function getParams() {
return $this->params;
}
function getJoins() {
$sql = '';
foreach ($this->joins as $j)
$sql .= $j['sql'];
return $sql;
}
function nextAlias() {
// Use alias A1-A9,B1-B9,...
$alias = chr(65 + (int)($this->alias_num / 9)) . $this->alias_num % 9;
$this->alias_num++;
return $alias;
}
}
class DbEngine {
function __construct($info) {
}
function connect() {
}
// Gets a compiler compatible with this database engine that can compile
// and execute a queryset or DML request.
function getCompiler() {
}
}
class MySqlCompiler extends SqlCompiler {
static $operators = array(
'exact' => '%1$s = %2$s',
'contains' => array('self', '__contains'),
'gt' => '%1$s > %2$s',
'lt' => '%1$s < %2$s',
'isnull' => '%1$s IS NULL',
'like' => '%1$s LIKE %2$s',
'in' => array('self', '__in'),
);
function __contains($a, $b) {
# {%a} like %{$b}%
return sprintf('%s LIKE %s', $a, $this->input("%$b%"));
}
function __in($a, $b) {
if (is_array($b)) {
$vals = array_map(array($this, 'input'), $b);
$b = implode(', ', $vals);
}
else {
$b = $this->input($b);
}
return sprintf('%s IN %s', $a, $b);
}
function compileJoin($tip, $model, $alias, $info) {
$constraints = array();
$join = ' JOIN ';
if (isset($info['null']) && $info['null'])
$join = ' LEFT'.$join;
if (isset($this->joins[$tip]))
$table = $this->joins[$tip]['alias'];
else
$table = $this->quote($model::$meta['table']);
foreach ($info['constraint'] as $local => $foreign) {
list($rmodel, $right) = explode('.', $foreign);
// TODO: Support a constant constraint
$constraints[] = sprintf("%s.%s = %s.%s",
$table, $this->quote($local), $alias,
$this->quote($right)
);
}
return $join.$this->quote($rmodel::$meta['table'])
.' '.$alias.' ON ('.implode(' AND ', $constraints).')';
if ($what instanceof QuerySet) {
$q = $what->getQuery(array('nosort'=>true));
$this->params += $q->params;
return '(' . (string)$q . ')';
}
else {
$this->params[] = $what;
return '?';
}
}
function quote($what) {
return "`$what`";
}
function compileCount($queryset) {
$model = $queryset->model;
$table = $model::$meta['table'];
$where_pos = array();
$where_neg = array();
$joins = array();
foreach ($queryset->constraints as $where) {
$where_pos[] = $this->compileWhere($where, $model);
}
foreach ($queryset->exclusions as $where) {
$where_neg[] = $this->compileWhere($where, $model);
}
$where = '';
if ($where_pos || $where_neg) {
$where = ' WHERE '.implode(' AND ', $where_pos)
.implode(' AND NOT ', $where_neg);
}
$sql = 'SELECT COUNT(*) AS count FROM '.$this->quote($table).$joins.$where;
$exec = new MysqlExecutor($sql, $this->params);
$row = $exec->getArray();
return $row['count'];
}
function compileSelect($queryset) {
$model = $queryset->model;
$where_pos = array();
$where_neg = array();
$joins = array();
foreach ($queryset->constraints as $where) {
$where_pos[] = $this->compileWhere($where, $model);
}
foreach ($queryset->exclusions as $where) {
$where_neg[] = $this->compileWhere($where, $model);
}
$where = '';
if ($where_pos || $where_neg) {
$where = ' WHERE '.implode(' AND ', $where_pos)
.implode(' AND NOT ', $where_neg);
}
$sort = '';
if ($queryset->ordering && !isset($this->options['nosort'])) {
$orders = array();
foreach ($queryset->ordering as $sort) {
$dir = 'ASC';
if (substr($sort, 0, 1) == '-') {
$dir = 'DESC';
$sort = substr($sort, 1);
}
list($field) = $this->getField($sort, $model);
$orders[] = $field.' '.$dir;
}
$sort = ' ORDER BY '.implode(', ', $orders);
}
// Include related tables
$fields = array();
$table = $model::$meta['table'];
if ($queryset->related) {
$fields = array($this->quote($table).'.*');
// XXX: This is ugly
list($t) = $this->getField($rel, $model,
array('table'=>true));
$fields[] = $t.'.*';
}
// Support only retrieving a list of values rather than a model
} elseif ($queryset->values) {
foreach ($queryset->values as $v) {
list($fields[]) = $this->getField($v, $model);
}
} else {
$fields[] = $this->quote($table).'.*';
}
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
$sql = 'SELECT '.implode(', ', $fields).' FROM '
.$this->quote($table).$joins.$where.$sort;
if ($queryset->limit)
$sql .= ' LIMIT '.$queryset->limit;
if ($queryset->offset)
$sql .= ' OFFSET '.$queryset->offset;
return new MysqlExecutor($sql, $this->params);
}
function compileUpdate() {
}
function compileInsert() {
}
function compileDelete() {
}
// Returns meta data about the table used to build queries
function inspectTable($table) {
}
}
class MysqlExecutor {
var $stmt;
var $fields = array();
var $sql;
var $params;
function __construct($sql, $params) {
$this->sql = $sql;
$this->params = $params;
}
function _prepare() {
if (!($this->stmt = db_prepare($this->sql)))
throw new Exception('Unable to prepare query: '.db_error()
.' '.$this->sql);
if (count($this->params))
$this->_bind($this->params);
$this->stmt->execute();
$this->_setup_output();
$this->stmt->store_result();
}
function _bind($params) {
if (count($params) != $this->stmt->param_count)
throw new Exception('Parameter count does not match query');
$types = '';
$ps = array();
if (is_int($p))
$types .= 'i';
elseif (is_string($p))
$types .= 's';
// TODO: Emit error if param is null
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
array_unshift($ps, $types);
call_user_func_array(array($this->stmt,'bind_param'), $ps);
}
function _setup_output() {
$meta = $this->stmt->result_metadata();
while ($f = $meta->fetch_field())
$this->fields[] = $f;
}
// Iterator interface
function rewind() {
if (!isset($this->stmt))
$this->_prepare();
$this->stmt->data_seek(0);
}
function next() {
$status = $this->stmt->fetch();
if ($status === false)
throw new Exception($this->stmt->error_list . db_error());
elseif ($status === null) {
$this->close();
return false;
}
return true;
}
function getArray() {
$output = array();
$variables = array();
if (!isset($this->stmt))
$this->_prepare();
foreach ($this->fields as $f)
$variables[] = &$output[$f->name]; // pass by reference
call_user_func_array(array($this->stmt, 'bind_result'), $variables);
if (!$this->next())
return false;
return $output;
}
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
function getRow() {
$output = array();
$variables = array();
if (!isset($this->stmt))
$this->_prepare();
foreach ($this->fields as $f)
$variables[] = &$output[]; // pass by reference
call_user_func_array(array($this->stmt, 'bind_result'), $variables);
if (!$this->next())
return false;
return $output;
}
function getStruct() {
$output = array();
$variables = array();
if (!isset($this->stmt))
$this->_prepare();
foreach ($this->fields as $f)
$variables[] = &$output[$f->table][$f->name]; // pass by reference
// TODO: Figure out what the table alias for the root model will be
call_user_func_array(array($this->stmt, 'bind_result'), $variables);
if (!$this->next())
return false;
return $output;
}