/[drupal]/contributions/modules/record/record.inc
ViewVC logotype

Contents of /contributions/modules/record/record.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.4 - (show annotations) (download) (as text)
Tue Nov 27 06:42:04 2007 UTC (2 years ago) by recidive
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +229 -103 lines
File MIME type: text/x-php
Lots of improvements:
 - Changed how arguments are processed.
 - Improved JOIN clause generation.
 - Fetched data are now the same (reference) for the main object or a sub-object, no object copy/processing is done when fetching results.
 - Adding field labels to users table schema.
 - And more.
1 <?php
2 /**
3 * Creates a new record.
4 *
5 * @param $type
6 * Record type, usually a table name.
7 * @param $data
8 * Optional, the array with data to fill up the new object.
9 *
10 * @return
11 * New Record Object.
12 */
13 function record_new($type, $data = array()) {
14 return new record($type, $data);
15 }
16
17 /**
18 * Load an record.
19 *
20 * @param $type
21 * Object type, usually a table name.
22 * @param $args
23 * Array with arguments to search for.
24 *
25 * @return
26 * Record Object.
27 */
28 function record_load($type, $args = array()) {
29 $records = record_find($type, $args);
30
31 return isset($records[0]) ? $records[0] : NULL;
32 }
33
34 /**
35 * Find records.
36 * TODO: Add static cache of record objects.
37 *
38 * @param $type
39 * Record type, usually a table name.
40 * @param $args
41 * Array with arguments to search for.
42 *
43 * @return
44 * Record Object.
45 */
46 function record_find($type, $args = array()) {
47 record_process_arguments($type, $args);
48
49 //print_r($args);
50
51 $query = 'SELECT '. implode(', ', $args['#fields']) ." FROM $type". (isset($args['#alias']) ? ' '. $args['#alias'] : '');
52
53 if (isset($args['#join'])) {
54 $query .= ' '. implode(' ', $args['#join']);
55 }
56
57 if (isset($args['#where'])) {
58 $query .= ' WHERE '. implode(' AND ', $args['#where']);
59 }
60
61 if (isset($args['#order_by'])) {
62 $query .= ' ORDER BY '. $args['#order_by'];
63 }
64
65 //echo $query;
66
67 if (isset($args['#limit']) && is_array($args['#limit'])) {
68 $result = db_query_range($query, $args['#limit'][0], $args['#limit'][1], isset($args['#values']) ? $args['#values'] : NULL);
69 }
70 else {
71 $result = db_query($query, isset($args['#values']) ? $args['#values'] : NULL);
72 }
73
74 // TODO: Allow returning one object at once, to be used in a while loop.
75 $records = array();
76 while ($data = db_fetch_array($result)) {
77 $records[] = new record($type, $data, (isset($args['#alias']) ? $type : NULL), (isset($args['#include']) && is_array($args['#include']) ? $args['#include'] : array()));
78 }
79
80 // TODO: This may not work for looping using while.
81 return $records;
82 }
83
84 /**
85 * Save a record.
86 *
87 * @param $object
88 * Record object to save.
89 *
90 * @return
91 * Record Object.
92 */
93 function record_save($object) {
94 return $object->save();
95 }
96
97 /**
98 * Delete a record.
99 *
100 * @param $object
101 * Record object to delete.
102 *
103 * @return
104 * Record Object with primary keys deleted and flagged as new.
105 */
106 function record_delete($object) {
107 return $object->delete();
108 }
109
110 /**
111 * Validates a record.
112 *
113 * @param $object
114 * Record object to validate.
115 *
116 * @return
117 * TRUE if successful validate, FALSE otherwise.
118 */
119 function record_validate($object) {
120 return $object->validate();
121 }
122
123 /**
124 * Get latest validation errors, usually called after a
125 * record_validate() call.
126 *
127 * @param $object
128 * Record object to get errors from.
129 *
130 * @return
131 * An array of error messages.
132 */
133 function record_get_errors($object) {
134 return $object->get_errors();
135 }
136
137 function record_process_arguments($type, &$args) {
138 $schema = drupal_get_schema($type);
139
140 // Build where clause.
141 // $args is an integer, possibly a primary key.
142 if (is_int($args)) {
143 $key_value = $args;
144 $args = array();
145 // Search for primary key.
146 if (isset($schema['primary key']) && count($schema['primary key']) == 1) {
147 $args['#where'][] = "$type.". $schema['primary key'][0] .' = %d';
148 $args['#values'][] = $key_value;
149 }
150 }
151 // $args is an array, might contains field => value pairs and/or where fragments.
152 elseif (is_array($args) && !empty($args)) {
153 // Add where fragments.
154 if (isset($args['#where'])) {
155 // Make sure WHERE fragments are arrays.
156 $args['#where'] = (array) $args['#where'];
157 if (isset($args['#where values'])) {
158 $args['#values'] = $args['#where values'];
159 }
160 }
161 else {
162 $args['#where'] = array();
163 $args['#values'] = array();
164 }
165
166 // Add field => value pairs.
167 foreach(element_children($args) as $field) {
168 if (in_array($field, array_keys($schema['fields']))) {
169 $args['#where'][] = "$field = ". db_type_placeholder($schema['fields'][$field]['type']);
170 $args['#values'][] = $args[$field];
171 }
172 }
173 }
174
175 // If '#join' is set, make sure it is an array.
176 if (isset($args['#join'])) {
177 $args['#join'] = (array) $args['#join'];
178 }
179
180 // If '#fields' is set, make sure it is an array.
181 if (isset($args['#fields'])) {
182 $args['#fields'] = (array) $args['#fields'];
183 }
184 // Otherwise, declare it.
185 else {
186 $args['#fields'] = array();
187 }
188
189 if (isset($args['#include']) && is_array($args['#include'])) {
190 if (!isset($args['#join'])) {
191 $args['#join'] = array();
192 }
193
194 $args['#alias'] = isset($args['#alias']) ? $args['#alias'] : $type;
195
196 //TODO: Improve this to allow nested '#include' arrays.
197 $references = $schema['references'];
198 foreach ($args['#include'] as $ref_name) {
199 $args['#join'] = array_merge($args['#join'], record_schema_joins_sql($type, $ref_name));
200 if (isset($references[$ref_name])) {
201 $reference = $schema['references'][$ref_name];
202 foreach ($reference['join'] as $ref_table => $ref_fields) {
203 if ($reference['type'] == 'one') {
204 $alias = $type .'_'. $ref_name;
205 $args['#fields'] = array_merge($args['#fields'], record_schema_fields_sql($ref_table, $alias, TRUE));
206 }
207 }
208 }
209 }
210 }
211
212 $args['#fields'] = array_merge(record_schema_fields_sql($type, $type, isset($args['#alias'])), $args['#fields']);
213 }
214
215 /**
216 * Build the JOIN part of a SQL query.
217 *
218 * @param $args
219 * Arguments
220 * @return
221 * The JOIN string.
222 */
223 function record_build_join_clause($type, $args) {
224
225 }
226
227 /**
228 * Build the WHERE part of a SQL query.
229 *
230 * @param $args
231 * Arguments
232 * @return
233 * Array, first argument is the WHERE string, and second is
234 * an array of releated values.
235 */
236 function record_build_where_clause($type, $args) {
237 $schema = drupal_get_schema($type);
238
239 return array(implode(' AND ', $wheres), $values);
240 }
241
242 /**
243 * Retrieve a list of fields from a table schema. The list is suitable for use
244 * in a SQL query.
245 *
246 * Copied from drupal_schema_fields_sql() and modified to allow field aliasing.
247 *
248 * @param $table
249 * The name of the table from which to retrieve fields.
250 * @param $prefix
251 * An optional prefix to to all fields.
252 * @param $alias
253 * If set to TRUE, fields will be aliased as tablename_fieldname
254 * or prefix_fieldname if a $prefix is given as well.
255 *
256 * @return An array of fields.
257 */
258 function record_schema_fields_sql($table, $prefix = NULL, $alias = FALSE) {
259 $fields = array_keys(record_get_schema($table, 'fields'));
260 if ($prefix) {
261 $columns = array();
262 foreach ($fields as $field) {
263 $columns[] = "$prefix.$field". ($alias ? ($prefix ? ' AS '. $prefix .'_'. $field : ' AS '. $table .'_'. $field) : '');
264 }
265 return $columns;
266 }
267 else {
268 return $fields;
269 }
270 }
271
272 /**
273 * Retrieve a list of referenced tables from a table schema. The list is suitable for use
274 * in a SQL query on the JOIN clause.
275 *
276 * @param $table
277 * The name of the table from which to retrieve fields.
278 * @param $ref_name
279 * The name of the reference.
280 *
281 * @return
282 * An array with JOIN fragments.
283 */
284 function record_schema_joins_sql($table, $ref_name, $table_alias = NULL) {
285 $schema = drupal_get_schema($table);
286 //$reference = record_schema_references($table, $ref_name);
287 $reference = record_get_schema($table, 'references', $ref_name);
288 $joins = array();
289 foreach ($reference['join'] as $join_table => $join_fields) {
290 if (!isset($left)) {
291 // Set first 'left' table to main table.
292 $left = $table;
293 $left_alias = isset($table_alias) ? $table_alias : $table;
294 }
295 $right = $join_table;
296 $right_alias = $table .'_'. $ref_name;
297
298 $ons = array();
299 foreach ($join_fields as $left_field => $right_field) {
300 $ons[] = "$left_alias.$left_field = $right_alias.$right_field";
301 }
302 $joins[] = "INNER JOIN {$right} $right_alias ON ". implode(' ', $ons);
303
304 // Set next 'left' table to current 'right' table.
305 $left = $right;
306 $left_alias = $right_alias;
307 }
308 return $joins;
309 }
310
311 /**
312 * Get references information from table schema.
313 *
314 * @param $table
315 * The name of the table from which to retrieve references.
316 * @param $ref_name
317 * If not given, all references for that table is returned.
318 */
319 function record_schema_references($table, $ref_name = NULL) {
320 $schema = drupal_get_schema($table);
321 if ($ref_name) {
322 return isset($schema['references'][$ref_name]) ? $schema['references'][$ref_name] : NULL;
323 }
324 else {
325 return isset($schema['references']) ? $schema['references'] : NULL;
326 }
327 }
328
329 function record_get_schema($table, $section = NULL, $fragment = NULL) {
330 $schema = drupal_get_schema($table);
331 if ($section) {
332 if ($fragment) {
333 return $schema[$section][$fragment];
334 }
335 else {
336 return $schema[$section];
337 }
338 }
339 else {
340 return $schema;
341 }
342 }
343
344 /**
345 * Record class: implements a simple Active Records pattern
346 * using PHP5's magic methods.
347 *
348 * @param $object
349 * Record object to get errors from.
350 *
351 * @return
352 * An array of error messages.
353 */
354 class record {
355
356 /**
357 * Record type (table name).
358 */
359 private $__type = NULL;
360
361 /**
362 * Table alias.
363 */
364 private $__alias = NULL;
365
366 /**
367 * Table schema information.
368 */
369 private $__schema = array();
370
371 /**
372 * Data array.
373 */
374 private $__data = array();
375
376 /**
377 * Validation errors array.
378 */
379 private $__errors = array();
380
381 /**
382 * Constructor.
383 */
384 public function __construct($type, $data = array(), $alias = NULL, $include = array()) {
385 //echo "$type -> $alias\n";
386 $this->__type = $type;
387 $this->__schema = drupal_get_schema($type);
388 if (!empty($data)) {
389 $this->set_data($data, $alias, $include);
390 }
391 }
392
393 /**
394 * Public methods.
395 */
396
397 public function set_data($data, $alias = NULL, $include = NULL) {
398 if (isset($alias)) {
399 $this->__alias = $alias;
400 }
401
402 $this->__data = $data;
403
404 if (isset($include) && is_array($include)) {
405 foreach ($include as $ref_name => $ref_includes) {
406 // Allow nested includes.
407 if (!is_array($ref_includes)) {
408 $ref_name = $ref_includes;
409 $ref_includes = NULL;
410 }
411 $ref_table = array_pop(array_keys($this->__schema['references'][$ref_name]['join']));
412 $ref_alias = $this->field_alias($ref_name);
413 $this->__data[$ref_alias] = new record($ref_table, $data, $ref_alias, $ref_includes);
414 }
415 }
416 }
417
418 public function get_data($normalized = FALSE) {
419 return $this->__data;
420 }
421
422 public function save() {
423 // Notify modules so they can perform additional operations.
424 $this->invoke('before save');
425
426 $schema = drupal_get_schema($this->__type);
427
428 // Check if it is a update by checking if the primary keys are set.
429 $update = (array_intersect($schema['primary key'], array_keys($this->__data)) == $schema['primary key']) ? $schema['primary key'] : array();
430
431 // Save object to database.
432 drupal_write_record($this->__type, &$this, $update);
433
434 // Notify modules so they can perform additional operations.
435 $this->invoke('save');
436 }
437
438 public function delete() {
439 // Notify modules so they can perform additional operations.
440 $this->invoke('before delete');
441
442 // Delete record from database.
443
444 // Notify modules so they can perform additional operations.
445 $this->invoke('delete');
446 }
447
448 public function validate($deep = FALSE) {
449 // Validate object.
450
451 // Reset errors array.
452 $this->__errors = array();
453
454 // TODO: output human readable labels instead of field names on error messages.
455 foreach ($this->__schema['fields'] as $field_name => $field) {
456 $field_alias = $this->field_alias($field_name);
457
458 if (isset($this->__data[$field_alias])) {
459 // Simple datatype validation.
460 switch ($field['type']) {
461 case 'serial':
462 case 'int':
463 if (!is_int($this->__data[$field_alias])) {
464 $this->__errors[$field_name][] = t('The field %field must be an integer.', array('%field' => $field_name));
465 }
466 break;
467 case 'numeric':
468 case 'float':
469 if (!is_numeric($this->__data[$field_alias])) {
470 $this->__errors[$field_name][] = t('The field %field must be numeric.', array('%field' => $field_name));
471 }
472 if (isset($field['unsigned']) && $field['unsigned'] && $this->__data[$field_alias] < 0) {
473 $this->__errors[$field_name][] = t('The field %field must be greater than zero.', array('%field' => $field_name));
474 }
475 break;
476 case 'varchar':
477 case 'text':
478 if (isset($field['length']) && strlen($this->__data[$field_alias]) > $field['length']) {
479 $this->__errors[$field_name][] = t('The field %field has to be less than %length characters long.', array('%field' => $field_name, '%length' => $field['length']));
480 }
481 break;
482 }
483 }
484 elseif ($field['not null'] && $field['type'] != 'serial') {
485 $this->__errors[$field_name][] = t('The field %field is required.', array('%field' => $field_name));
486 }
487 }
488
489 // Notify modules so they can perform additional validations.
490 array_merge_recursive($this->__errors, $this->invoke('validate'));
491
492 return empty($this->__errors) ? TRUE : FALSE;
493 }
494
495 public function get_errors($type = NULL) {
496 return (empty($type) ? $this->__errors : $this->__errors[$type]);
497 }
498
499 /**
500 * Private methods.
501 */
502
503 public function invoke($op) {
504 $return = array();
505 foreach (module_implements('record') as $module) {
506 $function = $module .'_record';
507 $result = $function($op, $this->__type, $this);
508 if (isset($result) && is_array($result)) {
509 $return = array_merge($return, $result);
510 }
511 elseif (isset($result)) {
512 $return[] = $result;
513 }
514 }
515
516 return $return;
517 }
518
519 /**
520 * Magic methods.
521 */
522
523 public function __set($name, $value) {
524 $field_alias = $this->field_alias($name);
525 $this->__data[$field_alias] = $value;
526 }
527
528 public function __get($name) {
529 $field_alias = $this->field_alias($name);
530 return isset($this->__data[$field_alias]) ? $this->__data[$field_alias] : NULL;
531 }
532
533 public function __isset($name) {
534 $field_alias = $this->field_alias($name);
535 return isset($this->__data[$field_alias]);
536 }
537
538 public function __unset($name) {
539 $field_alias = $this->field_alias($name);
540 unset($this->__data[$field_alias]);
541 }
542
543 public function __destruct() {
544 $this->__data = array();
545 }
546
547 public function __toString() {
548 static $level = 0;
549 $output = "\n";
550 foreach($this->__data as $key => $value) {
551 if (in_array($this->field_name($key), array_keys($this->__schema['fields']))) {
552 $output .= str_repeat(' ', $level);
553 $output .= $this->field_name($key) .': '. (string) $value ."\n";
554 if ($level > 0) {
555 $level--;
556 }
557 }
558 elseif (in_array($this->field_name($key), array_keys($this->__schema['references']))) {
559 if (is_array($value)) {
560 $output .= str_repeat(' ', $level);
561 foreach($value as $sub_value) {
562 $output .= $this->field_name($key) .': '. (string) $sub_value ."\n";
563 }
564 }
565 else {
566 $output .= str_repeat(' ', $level);
567 $output .= $this->field_name($key) .': '. (string) $value ."\n";
568 }
569 $level++;
570 }
571 }
572 return $output;
573 }
574
575 /**
576 * Return aliased field name.
577 */
578 public function field_alias($field) {
579 return isset($this->__alias) ? $this->__alias .'_'. $field : $field;
580 }
581
582 /**
583 * Return real (dealiased) field name.
584 */
585 public function field_name($alias) {
586 return isset($this->__alias) ? substr($alias, strlen($this->__alias) + 1) : $alias;
587 }
588 }
589
590
591 class record_iterator implements Iterator {
592 protected $result;
593 protected $row;
594
595 public function __construct($result) {
596 $this->result = $result;
597 }
598
599 public function key() {
600 // Not Implemented.
601 }
602
603 public function current() {
604 return $this->row;
605 }
606
607 public function next() {
608 $this->row = db_fetch_array($this->result);
609 return $this->row;
610 }
611
612 public function rewind() {
613 // Not Implemented.
614 }
615
616 public function valid() {
617 return ($this->row !== FALSE);
618 }
619 }

  ViewVC Help
Powered by ViewVC 1.1.2