diff --git a/fActiveRecord.php b/fActiveRecord.php index 8b7c6bc9..9310693f 100644 --- a/fActiveRecord.php +++ b/fActiveRecord.php @@ -10,12 +10,14 @@ * @copyright Copyright (c) 2007-2011 Will Bond, others * @author Will Bond [wb] * @author Will Bond, iMarc LLC [wb-imarc] + * @author Jeff Turcotte [jt] * @license http://flourishlib.com/license * * @package Flourish * @link http://flourishlib.com/fActiveRecord * - * @version 1.0.0b81 + * @version 1.0.0b82 + * @changes 1.0.0b82 Added support for registering methods for __callStatic() [jt, 2011-07-25] * @changes 1.0.0b81 Fixed a bug with updating a record that contains only an auto-incrementing primary key [wb, 2011-09-06] * @changes 1.0.0b80 Added support to ::checkCondition() for the `^~` and `$~` operators [wb, 2011-06-20] * @changes 1.0.0b79 Fixed some bugs in handling relationships between PHP 5.3 namespaced classes [wb, 2011-05-26] @@ -157,6 +159,14 @@ abstract class fActiveRecord */ static protected $replicate_map = array(); + + /** + * Caches callbacks for static methods + * + * @var array + **/ + static protected $static_callback_cache = array(); + /** * Contains a list of what columns in each class need to be unescaped and what data type they are * @@ -164,7 +174,52 @@ abstract class fActiveRecord */ static protected $unescape_map = array(); - + + /** + * Handles dynamically registered static method callbacks + * + * Static method callbacks registered through fORM::registerActiveRecordStaticMethod() + * will be delegated via this method. Both this and fORM::registerActiveRecordStaticMethod + * are available to PHP 5.3+ only. + * + * @throws fProgrammerException When the method cannot be found + * @param string $method_name The name of the method called + * @param array $parameters The parameters passed + * @return mixed The value returned by the method called + */ + static public function __callStatic($method_name, $parameters) + { + $class = get_called_class(); + + self::forceConfigure($class); + + if (!isset(self::$static_callback_cache[$class][$method_name])) { + if (!isset(self::$static_callback_cache[$class])) { + self::$static_callback_cache[$class] = array(); + } + $callback = fORM::getActiveRecordStaticMethod($class, $method_name); + self::$static_callback_cache[$class][$method_name] = $callback ? $callback : FALSE; + } + + if ($callback = self::$static_callback_cache[$class][$method_name]) { + return call_user_func_array( + $callback, + array( + $class, + $method_name, + $parameters + ) + ); + } + + // Error handler + throw new fProgrammerException( + 'Unknown static method, %s(), called', + $method_name + ); + } + + /** * Sets a value to the `$values` array, preserving the old value in `$old_values` * @@ -2981,4 +3036,4 @@ public function validate($return_messages=FALSE, $remove_column_names=FALSE) * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. - */ \ No newline at end of file + */ diff --git a/fORM.php b/fORM.php index f8c5bbcb..09e6e643 100644 --- a/fORM.php +++ b/fORM.php @@ -4,12 +4,14 @@ * * @copyright Copyright (c) 2007-2011 Will Bond * @author Will Bond [wb] + * @author Jeff Turcotte [jt] * @license http://flourishlib.com/license * * @package Flourish * @link http://flourishlib.com/fORM * - * @version 1.0.0b28 + * @version 1.0.0b29 + * @changes 1.0.0b29 Added ::registerActiveRecordStaticMethod() for static hooks in PHP 5.3 [jt, 2012-07-25] * @changes 1.0.0b28 Updated ::getColumnName() and ::getRecordName() to use fText if loaded [wb, 2011-02-02] * @changes 1.0.0b27 Added links to the detailed documentation for the parameters passed to hooks [wb, 2010-11-27] * @changes 1.0.0b26 Added ::getRelatedClass() for handling related classes in PHP 5.3 namespaces [wb, 2010-11-17] @@ -42,39 +44,40 @@ class fORM { // The following constants allow for nice looking callbacks to static methods - const callHookCallbacks = 'fORM::callHookCallbacks'; - const callInspectCallbacks = 'fORM::callInspectCallbacks'; - const callReflectCallbacks = 'fORM::callReflectCallbacks'; - const checkHookCallback = 'fORM::checkHookCallback'; - const classize = 'fORM::classize'; - const defineActiveRecordClass = 'fORM::defineActiveRecordClass'; - const enableSchemaCaching = 'fORM::enableSchemaCaching'; - const getActiveRecordMethod = 'fORM::getActiveRecordMethod'; - const getClass = 'fORM::getClass'; - const getColumnName = 'fORM::getColumnName'; - const getDatabaseName = 'fORM::getDatabaseName'; - const getRecordName = 'fORM::getRecordName'; - const getRecordSetMethod = 'fORM::getRecordSetMethod'; - const getRelatedClass = 'fORM::getRelatedClass'; - const isClassMappedToTable = 'fORM::isClassMappedToTable'; - const mapClassToDatabase = 'fORM::mapClassToDatabase'; - const mapClassToTable = 'fORM::mapClassToTable'; - const objectify = 'fORM::objectify'; - const overrideColumnName = 'fORM::overrideColumnName'; - const overrideRecordName = 'fORM::overrideRecordName'; - const parseMethod = 'fORM::parseMethod'; - const registerActiveRecordMethod = 'fORM::registerActiveRecordMethod'; - const registerHookCallback = 'fORM::registerHookCallback'; - const registerInspectCallback = 'fORM::registerInspectCallback'; - const registerObjectifyCallback = 'fORM::registerObjectifyCallback'; - const registerRecordSetMethod = 'fORM::registerRecordSetMethod'; - const registerReflectCallback = 'fORM::registerReflectCallback'; - const registerReplicateCallback = 'fORM::registerReplicateCallback'; - const registerScalarizeCallback = 'fORM::registerScalarizeCallback'; - const replicate = 'fORM::replicate'; - const reset = 'fORM::reset'; - const scalarize = 'fORM::scalarize'; - const tablize = 'fORM::tablize'; + const callHookCallbacks = 'fORM::callHookCallbacks'; + const callInspectCallbacks = 'fORM::callInspectCallbacks'; + const callReflectCallbacks = 'fORM::callReflectCallbacks'; + const checkHookCallback = 'fORM::checkHookCallback'; + const classize = 'fORM::classize'; + const defineActiveRecordClass = 'fORM::defineActiveRecordClass'; + const enableSchemaCaching = 'fORM::enableSchemaCaching'; + const getActiveRecordMethod = 'fORM::getActiveRecordMethod'; + const getClass = 'fORM::getClass'; + const getColumnName = 'fORM::getColumnName'; + const getDatabaseName = 'fORM::getDatabaseName'; + const getRecordName = 'fORM::getRecordName'; + const getRecordSetMethod = 'fORM::getRecordSetMethod'; + const getRelatedClass = 'fORM::getRelatedClass'; + const isClassMappedToTable = 'fORM::isClassMappedToTable'; + const mapClassToDatabase = 'fORM::mapClassToDatabase'; + const mapClassToTable = 'fORM::mapClassToTable'; + const objectify = 'fORM::objectify'; + const overrideColumnName = 'fORM::overrideColumnName'; + const overrideRecordName = 'fORM::overrideRecordName'; + const parseMethod = 'fORM::parseMethod'; + const registerActiveRecordMethod = 'fORM::registerActiveRecordMethod'; + const registerActiveRecordStaticMethod = 'fORM::registerActiveRecordStaticMethod'; + const registerHookCallback = 'fORM::registerHookCallback'; + const registerInspectCallback = 'fORM::registerInspectCallback'; + const registerObjectifyCallback = 'fORM::registerObjectifyCallback'; + const registerRecordSetMethod = 'fORM::registerRecordSetMethod'; + const registerReflectCallback = 'fORM::registerReflectCallback'; + const registerReplicateCallback = 'fORM::registerReplicateCallback'; + const registerScalarizeCallback = 'fORM::registerScalarizeCallback'; + const replicate = 'fORM::replicate'; + const reset = 'fORM::reset'; + const scalarize = 'fORM::scalarize'; + const tablize = 'form::tablize'; /** @@ -83,7 +86,14 @@ class fORM * @var array */ static private $active_record_method_callbacks = array(); - + + /** + * An array of `{static_method} => {callback}` mappings for fActiveRecord + * + * @var array + */ + static private $active_record_static_method_callbacks = array(); + /** * Cache for repetitive computation * @@ -468,8 +478,53 @@ static public function getActiveRecordMethod($class, $method) self::$cache['getActiveRecordMethod'][$class . '::' . $method] = ($callback === NULL) ? FALSE : $callback; return $callback; } + + /** + * Returns a matching callback for the class and static method specified + * + * The callback returned will be determined by the following logic: + * + * 1. If an exact callback has been defined for the method, it will be returned + * 2. If a callback in the form `{prefix}*` has been defined that matches the method, it will be returned + * 3. `NULL` will be returned + * + * @internal + * + * @param string $class The name of the class + * @param string $method The method to get the callback for + * @return string|null The callback for the method or `NULL` if none exists - see method description for details + */ + static public function getActiveRecordStaticMethod($class, $method) + { + // This caches method lookups, providing a significant performance + // boost to pages with lots of method calls that get passed to + // fActiveRecord::__callStatic() + if (isset(self::$cache['getActiveRecordStaticMethod'][$class . '::' . $method])) { + return (!$method = self::$cache['getActiveRecordStaticMethod'][$class . '::' . $method]) ? NULL : $method; + } + + $callback = NULL; + + if (isset(self::$active_record_static_method_callbacks[$class][$method])) { + $callback = self::$active_record_static_method_callbacks[$class][$method]; + + } elseif (isset(self::$active_record_static_method_callbacks['*'][$method])) { + $callback = self::$active_record_static_method_callbacks['*'][$method]; + + } elseif (preg_match('#[A-Z0-9]#', $method)) { + list($action, $subject) = self::parseMethod($method); + if (isset(self::$active_record_static_method_callbacks[$class][$action . '*'])) { + $callback = self::$active_record_static_method_callbacks[$class][$action . '*']; + } elseif (isset(self::$active_record_static_method_callbacks['*'][$action . '*'])) { + $callback = self::$active_record_static_method_callbacks['*'][$action . '*']; + } + } + + self::$cache['getActiveRecordStaticMethod'][$class . '::' . $method] = ($callback === NULL) ? FALSE : $callback; + return $callback; + } - + /** * Takes a class name or class and returns the class name * @@ -839,6 +894,48 @@ static public function registerActiveRecordMethod($class, $method, $callback) self::$cache['getActiveRecordMethod'] = array(); } + + /** + * Registers a callback for an fActiveRecord method that falls through to fActiveRecord::__callStatic() or hits a predefined method hook + * + * Only available to PHP 5.3+ which supports the __callStatic magic method. + * + * The callback should accept the following parameters: + * + * - **`&$class`**: The class calling the static method + * - **`$method_name`**: The method that was called + * - **`&$parameters`**: The parameters passed to the method + * + * @throws fProgrammerException When the PHP version less than 5.3 + * + * @param mixed $class The class name or instance of the class to register for, `'*'` will register for all classes + * @param string $method The method to hook for - this can be a complete method name or `{prefix}*` where `*` will match any column name + * @param callback $callback The callback to execute - see method description for parameter list + * @return void + */ + static public function registerActiveRecordStaticMethod($class, $method, $callback) + { + if (!fCore::checkVersion('5.3')) { + throw new fProgrammerException( + 'fORM::registerActiveRecordStaticMethod is only available to PHP 5.3+', + $method_name + ); + }; + + $class = self::getClass($class); + + if (!isset(self::$active_record_static_method_callbacks[$class])) { + self::$active_record_static_method_callbacks[$class] = array(); + } + + if (is_string($callback) && strpos($callback, '::') !== FALSE) { + $callback = explode('::', $callback); + } + + self::$active_record_static_method_callbacks[$class][$method] = $callback; + self::$cache['getActiveRecordStaticMethod'] = array(); + } + /** * Registers a callback for one of the various fActiveRecord hooks - multiple callbacks can be registered for each hook