root/fORM.php

Revision 499, 28.8 kB (checked in by wbond, 2 years ago)

BackwardsCompatibilityBreak - renamed fORM::addCustomTableClassMapping() to fORM::addCustomClassTableMapping() and swapped the parameters

LineHide Line Numbers
1 <?php
2 /**
3  * Dynamically handles many centralized object-relational mapping tasks
4  *
5  * @copyright  Copyright (c) 2007-2009 Will Bond
6  * @author     Will Bond [wb] <will@flourishlib.com>
7  * @license    http://flourishlib.com/license
8  *
9  * @package    Flourish
10  * @link       http://flourishlib.com/fORM
11  *
12  * @version    1.0.0b5
13  * @changes    1.0.0b5  Backwards compatibility break - renamed ::addCustomTableClassMapping() to ::addCustomClassTableMapping() and swapped the parameters [wb, 2009-01-26]
14  * @changes    1.0.0b4  Fixed a bug with retrieving fActiveRecord methods registered for all classes [wb, 2009-01-14]
15  * @changes    1.0.0b3  Fixed a static method callback constant [wb, 2008-12-17]
16  * @changes    1.0.0b2  Added ::replicate() and ::registerReplicateCallback() for fActiveRecord::replicate() [wb, 2008-12-12]
17  * @changes    1.0.0b   The initial implementation [wb, 2007-08-04]
18  */
19 class fORM
20 {
21     // The following constants allow for nice looking callbacks to static methods
22     const addCustomClassTableMapping = 'fORM::addCustomClassTableMapping';
23     const callHookCallbacks          = 'fORM::callHookCallbacks';
24     const callReflectCallbacks       = 'fORM::callReflectCallbacks';
25     const checkHookCallback          = 'fORM::checkHookCallback';
26     const classize                   = 'fORM::classize';
27     const defineActiveRecordClass    = 'fORM::defineActiveRecordClass';
28     const getActiveRecordMethod      = 'fORM::getActiveRecordMethod';
29     const getClass                   = 'fORM::getClass';
30     const getColumnName              = 'fORM::getColumnName';
31     const getRecordName              = 'fORM::getRecordName';
32     const getRecordSetMethod         = 'fORM::getRecordSetMethod';
33     const objectify                  = 'fORM::objectify';
34     const overrideColumnName         = 'fORM::overrideColumnName';
35     const overrideRecordName         = 'fORM::overrideRecordName';
36     const parseMethod                = 'fORM::parseMethod';
37     const registerActiveRecordMethod = 'fORM::registerActiveRecordMethod';
38     const registerHookCallback       = 'fORM::registerHookCallback';
39     const registerObjectifyCallback  = 'fORM::registerObjectifyCallback';
40     const registerRecordSetMethod    = 'fORM::registerRecordSetMethod';
41     const registerReflectCallback    = 'fORM::registerReflectCallback';
42     const registerReplicateCallback  = 'fORM::registerReplicateCallback';
43     const registerScalarizeCallback  = 'fORM::registerScalarizeCallback';
44     const replicate                  = 'fORM::replicate';
45     const reset                      = 'fORM::reset';
46     const scalarize                  = 'fORM::scalarize';
47     const tablize                    = 'fORM::tablize';
48    
49    
50     /**
51     * An array of `{method} => {callback}` mappings for fActiveRecord
52     *
53     * @var array
54     */
55     static private $active_record_method_callbacks = array();
56    
57     /**
58     * Custom mappings for class <-> table
59     *
60     * @var array
61     */
62     static private $class_table_map = array();
63    
64     /**
65     * Custom column names for columns in fActiveRecord classes
66     *
67     * @var array
68     */
69     static private $column_names = array();
70    
71     /**
72     * Tracks callbacks registered for various fActiveRecord hooks
73     *
74     * @var array
75     */
76     static private $hook_callbacks = array();
77    
78     /**
79     * Callbacks for ::objectify()
80     *
81     * @var array
82     */
83     static private $objectify_callbacks = array();
84    
85     /**
86     * Custom record names for fActiveRecord classes
87     *
88     * @var array
89     */
90     static private $record_names = array();
91    
92     /**
93     * An array of `{method} => {callback}` mappings for fRecordSet
94     *
95     * @var array
96     */
97     static private $record_set_method_callbacks = array();
98    
99     /**
100     * Callbacks for ::reflect()
101     *
102     * @var array
103     */
104     static private $reflect_callbacks = array();
105    
106     /**
107     * Callbacks for ::replicate()
108     *
109     * @var array
110     */
111     static private $replicate_callbacks = array();
112    
113     /**
114     * Callbacks for ::scalarize()
115     *
116     * @var array
117     */
118     static private $scalarize_callbacks = array();
119    
120    
121     /**
122     * Allows non-standard class to table mapping
123     *
124     * By default, all database tables are assumed to be plural nouns in
125     * `underscore_notation` and all class names are assumed to be singular
126     * nouns in `UpperCamelCase`. This method allows arbitrary class to
127     * table mapping.
128     *
129     * @param  string $class  The name of the class
130     * @param  string $table  The name of the database table
131     * @return void
132     */
133     static public function addCustomClassTableMapping($class, $table)
134     {
135         self::$class_table_map[$class] = $table;
136     }
137    
138    
139     /**
140     * Calls the hook callbacks for the class and hook specified
141     *
142     * @internal
143      *
144     * @param  fActiveRecord $object            The instance of the class to call the hook for
145     * @param  string        $hook              The hook to call
146     * @param  array         &$values           The current values of the record
147     * @param  array         &$old_values       The old values of the record
148     * @param  array         &$related_records  Records related to the current record
149     * @param  array         &$cache            The cache array of the record
150     * @param  mixed         &$parameter        The parameter to send the callback
151     * @return void
152     */
153     static public function callHookCallbacks($object, $hook, &$values, &$old_values, &$related_records, &$cache, &$parameter=NULL)
154     {
155         $class = self::getClass($object);
156        
157         if (empty(self::$hook_callbacks[$class][$hook]) && empty(self::$hook_callbacks['*'][$hook])) {
158             return;
159         }
160        
161         // Get all of the callbacks for this hook, both for this class or all classes
162         $callbacks = array();
163        
164         if (isset(self::$hook_callbacks[$class][$hook])) {
165             $callbacks = array_merge($callbacks, self::$hook_callbacks[$class][$hook]);
166         }
167        
168         if (isset(self::$hook_callbacks['*'][$hook])) {
169             $callbacks = array_merge($callbacks, self::$hook_callbacks['*'][$hook]);
170         }
171        
172         foreach ($callbacks as $callback) {
173             call_user_func_array(
174                 $callback,
175                 // This is the only way to pass by reference
176                 array(
177                     $object,
178                     &$values,
179                     &$old_values,
180                     &$related_records,
181                     &$cache,
182                     &$parameter
183                 )
184             );
185         }
186     }
187    
188    
189     /**
190     * Calls all reflect callbacks for the object passed
191     *
192     * @internal
193      *
194     * @param  fActiveRecord $object                The instance of the class to call the hook for
195     * @param  array         &$signatures           The associative array of `{method_name} => {signature}`
196     * @param  boolean       $include_doc_comments  If the doc comments should be included in the signature
197     * @return void
198     */
199     static public function callReflectCallbacks($object, &$signatures, $include_doc_comments)
200     {
201         $class = self::getClass($object);
202        
203         if (!isset(self::$reflect_callbacks[$class]) && !isset(self::$reflect_callbacks['*'])) {
204             return;
205         }
206        
207         if (!empty(self::$reflect_callbacks['*'])) {
208             foreach (self::$reflect_callbacks['*'] as $callback) {
209                 // This is the only way to pass by reference
210                 $parameters = array(
211                     $class,
212                     &$signatures,
213                     $include_doc_comments
214                 );
215                 call_user_func_array($callback, $parameters);
216             }   
217         }
218        
219         if (!empty(self::$reflect_callbacks[$class])) {
220             foreach (self::$reflect_callbacks[$class] as $callback) {
221                 // This is the only way to pass by reference
222                 $parameters = array(
223                     $class,
224                     &$signatures,
225                     $include_doc_comments
226                 );
227                 call_user_func_array($callback, $parameters);
228             }
229         }
230     }
231    
232    
233     /**
234     * Checks to see if any (or a specific) callback has been registered for a specific hook
235     *
236     * @internal
237      *
238     * @param  mixed  $class     The name of the class, or an instance of it
239     * @param  string $hook      The hook to check
240     * @param  array  $callback  The specific callback to check for
241     * @return boolean  If the specified callback exists
242     */
243     static public function checkHookCallback($class, $hook, $callback=NULL)
244     {
245         $class = self::getClass($class);
246        
247         if (empty(self::$hook_callbacks[$class][$hook]) && empty(self::$hook_callbacks['*'][$hook])) {
248             return FALSE;
249         }
250        
251         if (!$callback) {
252             return TRUE;
253         }
254        
255         if (is_string($callback) && strpos($callback, '::') !== FALSE) {
256             $callback = explode('::', $callback);   
257         }
258        
259         if (!empty(self::$hook_callbacks[$class][$hook]) && in_array($callback, self::$hook_callbacks[$class][$hook])) {
260             return TRUE;   
261         }
262        
263         if (!empty(self::$hook_callbacks['*'][$hook]) && in_array($callback, self::$hook_callbacks['*'][$hook])) {
264             return TRUE;   
265         }
266        
267         return FALSE;
268     }
269    
270    
271     /**
272     * Takes a table and turns it into a class name - uses custom mapping if set
273     *
274     * @param  string $table  The table name
275     * @return string  The class name
276     */
277     static public function classize($table)
278     {
279         if (!$class = array_search($table, self::$class_table_map)) {
280             $class = fGrammar::camelize(fGrammar::singularize($table), TRUE);
281             self::$class_table_map[$class] = $table;
282         }
283        
284         return $class;
285     }
286    
287    
288     /**
289     * Will dynamically create an fActiveRecord-based class for a database table
290     *
291     * Normally this would be called from an `__autoload()` function
292     *
293     * @param  string $class  The name of the class to create
294     * @return void
295     */
296     static public function defineActiveRecordClass($class)
297     {
298         if (class_exists($class, FALSE)) {
299             return;
300         }
301         $tables = fORMSchema::retrieve()->getTables();
302         $table = self::tablize($class);
303         if (in_array($table, $tables)) {
304             eval('class ' . $class . ' extends fActiveRecord { };');
305             return;
306         }
307        
308         throw new fProgrammerException(
309             'The class specified, %s, does not correspond to a database table',
310             $class
311         );
312     }
313    
314    
315     /**
316     * Returns a matching callback for the class and method specified
317     *
318     * The callback returned will be determined by the following logic:
319     *
320     *  1. If an exact callback has been defined for the method, it will be returned
321     *  2. If a callback in the form `{action}*` has been defined that matches the method, it will be returned
322     *  3. `NULL` will be returned
323     *
324     * @internal
325      *
326     * @param  mixed  $class   The name of the class, or an instance of it
327     * @param  string $method  The method to get the callback for
328     * @return string|null  The callback for the method or `NULL` if none exists - see method description for details
329     */
330     static public function getActiveRecordMethod($class, $method)
331     {
332         $class = self::getClass($class);
333        
334         if (isset(self::$active_record_method_callbacks[$class][$method])) {
335             return self::$active_record_method_callbacks[$class][$method];   
336         }
337        
338         if (isset(self::$active_record_method_callbacks['*'][$method])) {
339             return self::$active_record_method_callbacks['*'][$method];   
340         }
341        
342         if (preg_match('#[A-Z0-9]#', $method)) {
343             list($action, $subject) = self::parseMethod($method);
344             if (isset(self::$active_record_method_callbacks[$class][$action . '*'])) {
345                 return self::$active_record_method_callbacks[$class][$action . '*'];   
346             }   
347         }
348        
349         return NULL;   
350     }
351    
352    
353     /**
354     * Takes a class name or class and returns the class name
355     *
356     * @internal
357      *
358     * @param  mixed $class  The object to get the name of, or possibly a string already containing the class
359     * @return string  The class name
360     */
361     static public function getClass($class)
362     {
363         if (is_object($class)) { return get_class($class); }
364         return $class;
365     }
366    
367    
368     /**
369     * Returns the column name
370     *
371     * The default column name is the result of calling fGrammar::humanize()
372     * on the column.
373     *
374     * @internal
375      *
376     * @param  mixed  $class   The class name or instance of the class the column is part of
377     * @param  string $column  The database column
378     * @return string  The column name for the column specified
379     */
380     static public function getColumnName($class, $column)
381     {
382         $class = self::getClass($class);
383        
384         if (!isset(self::$column_names[$class])) {
385             self::$column_names[$class] = array();
386         }
387        
388         if (!isset(self::$column_names[$class][$column])) {
389             self::$column_names[$class][$column] = fGrammar::humanize($column);
390         }
391        
392         return self::$column_names[$class][$column];
393     }
394    
395    
396     /**
397     * Returns the record name for a class
398     *
399     * The default record name is the result of calling fGrammar::humanize()
400     * on the class.
401     *
402     * @internal
403      *
404     * @param  mixed $class  The class name or instance of the class to get the record name of
405     * @return string  The record name for the class specified
406     */
407     static public function getRecordName($class)
408     {
409         $class = self::getClass($class);
410        
411         if (!isset(self::$record_names[$class])) {
412             self::$record_names[$class] = fGrammar::humanize($class);
413         }
414        
415         return self::$record_names[$class];
416     }
417    
418    
419     /**
420     * Returns a matching callback for the method specified
421     *
422     * The callback returned will be determined by the following logic:
423     *
424     *  1. If an exact callback has been defined for the method, it will be returned
425     *  2. If a callback in the form `{action}*` has been defined that matches the method, it will be returned
426     *  3. `NULL` will be returned
427     *
428     * @internal
429      *
430     * @param  string $method  The method to get the callback for
431     * @return string|null  The callback for the method or `NULL` if none exists - see method description for details
432     */
433     static public function getRecordSetMethod($method)
434     {
435         if (isset(self::$record_set_method_callbacks[$method])) {
436             return self::$record_set_method_callbacks[$method];   
437         }
438        
439         if (preg_match('#[A-Z0-9]#', $method)) {
440             list($action, $subject) = self::parseMethod($method);
441             if (isset(self::$record_set_method_callbacks[$action . '*'])) {
442                 return self::$record_set_method_callbacks[$action . '*'];   
443             }   
444         }
445        
446         return NULL;   
447     }
448    
449    
450     /**
451     * Takes a scalar value and turns it into an object if applicable
452     *
453     * @internal
454      *
455     * @param  mixed  $class   The class name or instance of the class the column is part of
456     * @param  string $column  The database column
457     * @param  mixed  $value   The value to possibly objectify
458     * @return mixed  The scalar or object version of the value, depending on the column type and column options
459     */
460     static public function objectify($class, $column, $value)
461     {
462         $class = self::getClass($class);
463        
464         if (!empty(self::$objectify_callbacks[$class][$column])) {
465             return call_user_func(self::$objectify_callbacks[$class][$column], $class, $column, $value);
466         }
467        
468         $table = self::tablize($class);
469        
470         // Turn date/time values into objects
471         $column_type = fORMSchema::retrieve()->getColumnInfo($table, $column, 'type');
472        
473         if ($value !== NULL && in_array($column_type, array('date', 'time', 'timestamp'))) {
474             try {
475                
476                 // Explicit calls to the constructors are used for dependency detection
477                 switch ($column_type) {
478                     case 'date':      $value = new fDate($value);      break;
479                     case 'time':      $value = new fTime($value);      break;
480                     case 'timestamp': $value = new fTimestamp($value); break;
481                 }
482                
483             } catch (fValidationException $e) {
484                 // Validation exception results in the raw value being saved
485             }
486         }
487        
488         return $value;
489     }
490    
491    
492     /**
493     * Allows overriding of default column names
494     *
495     * By default a column name is the result of fGrammar::humanize() called
496     * on the column.
497     *
498     * @param  mixed  $class        The class name or instance of the class the column is located in
499     * @param  string $column       The database column
500     * @param  string $column_name  The name for the column
501     * @return void
502     */
503     static public function overrideColumnName($class, $column, $column_name)
504     {
505         $class = self::getClass($class);
506        
507         if (!isset(self::$column_names[$class])) {
508             self::$column_names[$class] = array();
509         }
510        
511         self::$column_names[$class][$column] = $column_name;
512     }
513    
514    
515     /**
516     * Allows overriding of default record names
517     *
518     * By default a record name is the result of fGrammar::humanize() called
519     * on the class.
520     *
521     * @param  mixed  $class        The class name or instance of the class to override the name of
522     * @param  string $record_name  The human version of the record
523     * @return void
524     */
525     static public function overrideRecordName($class, $record_name)
526     {
527         $class = self::getClass($class);
528         self::$record_names[$class] = $record_name;
529     }
530    
531    
532     /**
533     * Parses a `camelCase` method name for an action and subject in the form `actionSubject()`
534     *
535     * @internal
536      *
537     * @param  string $method  The method name to parse
538     * @return array  An array of `0 => {action}, 1 => {subject}`
539     */
540     static public function parseMethod($method)
541     {
542         if (!preg_match('#^([a-z]+)(.*)$#D', $method, $matches)) {
543             throw new fProgrammerException(
544                 'Invalid method, %s(), called',
545                 $method
546             );   
547         }
548         return array($matches[1], fGrammar::underscorize($matches[2]));
549     }
550    
551    
552     /**
553     * Registers a callback for an fActiveRecord method that falls through to fActiveRecord::__call() or hits a predefined method hook
554    
555     * The callback should accept the following parameters:
556     *
557     *  - **`$object`**:           The fActiveRecord instance
558     *  - **`&$values`**:          The values array for the record
559     *  - **`&$old_values`**:      The old values array for the record
560     *  - **`&$related_records`**: The related records array for the record
561     *  - **`&$cache`**:           The cache array for the record
562     *  - **`$method_name`**:      The method that was called
563     *  - **`&$parameters`**:      The parameters passed to the method
564     *
565     * @param  mixed    $class     The class name or instance of the class to register for, `'*'` will register for all classes
566     * @param  string   $method    The method to hook for
567     * @param  callback $callback  The callback to execute - see method description for parameter list
568     * @return void
569     */
570     static public function registerActiveRecordMethod($class, $method, $callback)
571     {
572         $class = self::getClass($class);
573        
574         if (!isset(self::$active_record_method_callbacks[$class])) {
575             self::$active_record_method_callbacks[$class] = array();   
576         }
577        
578         if (is_string($callback) && strpos($callback, '::') !== FALSE) {
579             $callback = explode('::', $callback);   
580         }
581        
582         self::$active_record_method_callbacks[$class][$method] = $callback;
583     }
584    
585    
586     /**
587     * Registers a callback for one of the various fActiveRecord hooks - multiple callbacks can be registered for each hook
588     *
589     * The method signature should include the follow parameters:
590     *
591     *  - **`$object`**:           The fActiveRecord instance
592     *  - **`&$values`**:          The values array for the record
593     *  - **`&$old_values`**:      The old values array for the record
594     *  - **`&$related_records`**: The related records array for the record
595     *  - **`&$cache`**:           The cache array for the record
596     *
597     * The `'pre::validate()'` and `'post::validate()'` hooks have an extra parameter:
598     *
599     *  - **`&$validation_messages`**: An ordered array of validation errors that will be returned or tossed as an fValidationException
600    
601     * Below is a list of all valid hooks:
602     *
603     *  - `'post::__construct()'`
604     *  - `'pre::delete()'`
605     *  - `'post-begin::delete()'`
606     *  - `'pre-commit::delete()'`
607     *  - `'post-commit::delete()'`
608     *  - `'post-rollback::delete()'`
609     *  - `'post::delete()'`
610     *  - `'post::loadFromResult()'`
611     *  - `'pre::populate()'`
612     *  - `'post::populate()'`
613     *  - `'pre::store()'`
614     *  - `'post-begin::store()'`
615     *  - `'post-validate::store()'`
616     *  - `'pre-commit::store()'`
617     *  - `'post-commit::store()'`
618     *  - `'post-rollback::store()'`
619     *  - `'post::store()'`
620     *  - `'pre::validate()'`
621     *  - `'post::validate()'`
622     *
623     * @param  mixed    $class     The class name or instance of the class to hook, `'*'` will hook all classes
624     * @param  string   $hook      The hook to register for
625     * @param  callback $callback  The callback to register - see the method description for details about the method signature
626     * @return void
627     */
628     static public function registerHookCallback($class, $hook, $callback)
629     {
630         $class = self::getClass($class);
631        
632         static $valid_hooks = array(
633             'post::__construct()',
634             'pre::delete()',
635             'post-begin::delete()',
636             'pre-commit::delete()',
637             'post-commit::delete()',
638             'post-rollback::delete()',
639             'post::delete()',
640             'post::loadFromResult()',
641             'pre::populate()',
642             'post::populate()',
643             'pre::store()',
644             'post-begin::store()',
645             'post-validate::store()',
646             'pre-commit::store()',
647             'post-commit::store()',
648             'post-rollback::store()',
649             'post::store()',
650             'pre::validate()',
651             'post::validate()'
652         );
653        
654         if (!in_array($hook, $valid_hooks)) {
655             throw new fProgrammerException(
656                 'The hook specified, %1$s, should be one of: %2$s.',
657                 $hook,
658                 join(', ', $valid_hooks)
659             );
660         }
661        
662         if (!isset(self::$hook_callbacks[$class])) {
663             self::$hook_callbacks[$class] = array();
664         }
665        
666         if (!isset(self::$hook_callbacks[$class][$hook])) {
667             self::$hook_callbacks[$class][$hook] = array();
668         }
669        
670         if (is_string($callback) && strpos($callback, '::') !== FALSE) {
671             $callback = explode('::', $callback);   
672         }
673        
674         self::$hook_callbacks[$class][$hook][] = $callback;
675     }
676    
677    
678     /**
679     * Registers a callback for when ::objectify() is called on a specific column
680     *
681     * @param  mixed    $class     The class name or instance of the class to register for
682     * @param  string   $column    The column to register for
683     * @param  callback $callback  The callback to register. Callback should accept a single parameter, the value to objectify and should return the objectified value.
684     * @return void
685     */
686     static public function registerObjectifyCallback($class, $column, $callback)
687     {
688         $class = self::getClass($class);
689        
690         if (!isset(self::$objectify_callbacks[$class])) {
691             self::$objectify_callbacks[$class] = array();
692         }
693        
694         if (is_string($callback) && strpos($callback, '::') !== FALSE) {
695             $callback = explode('::', $callback);   
696         }
697        
698         self::$objectify_callbacks[$class][$column] = $callback;
699     }
700    
701    
702     /**
703     * Registers a callback for an fRecordSet method that fall through to fRecordSet::__call()
704    
705     * The callback should accept the following parameters:
706     *
707     *  - **`$object`**:      The actual record set
708     *  - **`$class`**:       The class of each record
709     *  - **`&$records`**:    The ordered array of fActiveRecord objects
710     *  - **`&$pointer`**:    The current array pointer for the records array
711     *  - **`&$associate`**:  If the record should be associated with an fActiveRecord holding it
712     *
713     * @param  string   $method    The method to hook for
714     * @param  callback $callback  The callback to execute - see method description for parameter list
715     * @return void
716     */
717     static public function registerRecordSetMethod($method, $callback)
718     {
719         if (is_string($callback) && strpos($callback, '::') !== FALSE) {
720             $callback = explode('::', $callback);   
721         }
722         self::$record_set_method_callbacks[$method] = $callback;
723     }
724    
725    
726     /**
727     * Registers a callback to modify the results of fActiveRecord::reflect()
728     *
729     * Callbacks registered here can override default method signatures and add
730     * method signatures, however any methods that are defined in the actual class
731     * will override these signatures.
732     *
733     * The callback should accept three parameters:
734     *
735     *  - **`$class`**: the class name
736     *  - **`&$signatures`**: an associative array of `{method_name} => {signature}`
737     *  - **`$include_doc_comments`**: a boolean indicating if the signature should include the doc comment for the method, or just the signature
738     *
739     * @param  mixed    $class     The class name or instance of the class to register for, `'*'` will register for all classes
740     * @param  callback $callback  The callback to register. Callback should accept a three parameters - see method description for details.
741     * @return void
742     */
743     static public function registerReflectCallback($class, $callback)
744     {
745         $class = self::getClass($class);
746        
747         if (!isset(self::$reflect_callbacks[$class])) {
748             self::$reflect_callbacks[$class] = array();
749         } elseif (in_array($callback, self::$reflect_callbacks[$class])) {
750             return;
751         }
752        
753         if (is_string($callback) && strpos($callback, '::') !== FALSE) {
754             $callback = explode('::', $callback);   
755         }
756        
757         self::$reflect_callbacks[$class][] = $callback;
758     }
759    
760    
761     /**
762     * Registers a callback for when a value is replicated for a specific column
763     *
764     * @param  mixed    $class     The class name or instance of the class to register for
765     * @param  string   $column    The column to register for
766     * @param  callback $callback  The callback to register. Callback should accept a single parameter, the value to replicate and should return the replicated value.
767     * @return void
768     */
769     static public function registerReplicateCallback($class, $column, $callback)
770     {
771         $class = self::getClass($class);
772        
773         if (!isset(self::$replicate_callbacks[$class])) {
774             self::$replicate_callbacks[$class] = array();
775         }
776        
777         if (is_string($callback) && strpos($callback, '::') !== FALSE) {
778             $callback = explode('::', $callback);   
779         }
780        
781         self::$replicate_callbacks[$class][$column] = $callback;
782     }
783    
784    
785     /**
786     * Registers a callback for when ::scalarize() is called on a specific column
787     *
788     * @param  mixed    $class     The class name or instance of the class to register for
789     * @param  string   $column    The column to register for
790     * @param  callback $callback  The callback to register. Callback should accept a single parameter, the value to scalarize and should return the scalarized value.
791     * @return void
792     */
793     static public function registerScalarizeCallback($class, $column, $callback)
794     {
795         $class = self::getClass($class);
796        
797         if (!isset(self::$scalarize_callbacks[$class])) {
798             self::$scalarize_callbacks[$class] = array();
799         }
800        
801         if (is_string($callback) && strpos($callback, '::') !== FALSE) {
802             $callback = explode('::', $callback);   
803         }
804        
805         self::$scalarize_callbacks[$class][$column] = $callback;
806     }
807    
808    
809     /**
810     * Takes and value and returns a copy is scalar or a clone if an object
811     *
812     * The ::registerReplicateCallback() allows for custom replication code
813     *
814     * @internal
815      *
816     * @param  mixed  $class   The class name or instance of the class the column is part of
817     * @param  string $column  The database column
818     * @param  mixed  $value   The value to copy/clone
819     * @return mixed  The copied/cloned value
820     */
821     static public function replicate($class, $column, $value)
822     {
823         $class = self::getClass($class);
824        
825         if (!empty(self::$replicate_callbacks[$class][$column])) {
826             return call_user_func(self::$replicate_callbacks[$class][$column], $class, $column, $value);
827         }
828        
829         if (!is_object($value)) {
830             return $value;   
831         }
832        
833         return clone $value;
834     }
835    
836    
837     /**
838     * Resets the configuration of the class
839     *
840     * @internal
841      *
842     * @return void
843     */
844     static public function reset()
845     {
846         self::$class_table_map                = array();
847         self::$active_record_method_callbacks = array();
848         self::$column_names                   = array();
849         self::$hook_callbacks                 = array();
850         self::$objectify_callbacks            = array();
851         self::$record_names                   = array();
852         self::$record_set_method_callbacks    = array();
853         self::$reflect_callbacks              = array();
854         self::$replicate_callbacks            = array();
855         self::$scalarize_callbacks            = array();
856     }
857    
858    
859     /**
860     * If the value passed is an object, calls `__toString()` on it
861     *
862     * @internal
863      *
864     * @param  mixed  $class   The class name or instance of the class the column is part of
865     * @param  string $column  The database column
866     * @param  mixed  $value   The value to get the scalar value of
867     * @return mixed  The scalar value of the value
868     */
869     static public function scalarize($class, $column, $value)
870     {
871         $class = self::getClass($class);
872        
873         if (!empty(self::$scalarize_callbacks[$class][$column])) {
874             return call_user_func(self::$scalarize_callbacks[$class][$column], $class, $column, $value);
875         }
876        
877         if (is_object($value) && is_callable(array($value, '__toString'))) {
878             return $value->__toString();
879         } elseif (is_object($value)) {
880             return (string) $value;
881         }
882        
883         return $value;
884     }
885    
886    
887     /**
888     * Takes a class name (or class) and turns it into a table name - Uses custom mapping if set
889     *
890     * @param  mixed $class  he class name or instance of the class
891     * @return string  The table name
892     */
893     static public function tablize($class)
894     {
895         $class = self::getClass($class);
896        
897         if (!isset(self::$class_table_map[$class])) {
898             self::$class_table_map[$class] = fGrammar::underscorize(fGrammar::pluralize($class));
899         }
900         return self::$class_table_map[$class];
901     }
902    
903    
904     /**
905     * Forces use as a static class
906     *
907     * @return fORM
908     */
909     private function __construct() { }
910 }
911  
912  
913  
914 /**
915  * Copyright (c) 2007-2009 Will Bond <will@flourishlib.com>
916  *
917  * Permission is hereby granted, free of charge, to any person obtaining a copy
918  * of this software and associated documentation files (the "Software"), to deal
919  * in the Software without restriction, including without limitation the rights
920  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
921  * copies of the Software, and to permit persons to whom the Software is
922  * furnished to do so, subject to the following conditions:
923  *
924  * The above copyright notice and this permission notice shall be included in
925  * all copies or substantial portions of the Software.
926  *
927  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
928  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
929  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
930  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
931  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
932  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
933  * THE SOFTWARE.
934  */