root/fORM.php

Revision 517, 29.0 kB (checked in by wbond, 2 years ago)

Added the ability to pass a class instance to fORM::addCustomClassTableMapping()

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