On Mon, Sep 17, 2012 at 02:11:43PM +0200, mario@xenji.com wrote:
From: "Mario (xenji) Mueller" <mario@xenji.com>
Signed-off-by: Mario (xenji) Mueller <mario@xenji.com> --- web/lib/acctfuncs.inc.php | 5 ++ web/lib/aur.inc.php | 4 ++ web/lib/hookdef.php | 113 ++++++++++++++++++++++++++++++++++++++++++++++ web/lib/hooks/account.php | 4 ++ 4 files changed, 126 insertions(+) create mode 100644 web/lib/hookdef.php create mode 100644 web/lib/hooks/account.php
Before reviewing this, could you please explain why hooks are advantageous here? Hooks are nice if you want to change entry points of a function invocation dynamically, such as in extensions or complex configurations, but I don't see how a hook framework is supposed to be superior to a simple function call here.
diff --git a/web/lib/acctfuncs.inc.php b/web/lib/acctfuncs.inc.php index 6f7c98a..0ccd235 100644 --- a/web/lib/acctfuncs.inc.php +++ b/web/lib/acctfuncs.inc.php @@ -188,6 +188,11 @@ function process_account_form($UTYPE,$TYPE,$A,$U="",$T="",$S="",$E="", } else { # account created/modified, tell them so. # + + // trigger hook and pass the fully escaped array to it. + // see lib/hookdef.php for details. + trigger_hook('newAccount', $escaped); + print __("The account, %s%s%s, has been successfully created.", "<b>", htmlspecialchars($U,ENT_QUOTES), "</b>"); print "<p>\n"; diff --git a/web/lib/aur.inc.php b/web/lib/aur.inc.php index 6dcbb34..50c4bce 100644 --- a/web/lib/aur.inc.php +++ b/web/lib/aur.inc.php @@ -11,6 +11,10 @@ include_once('translator.inc.php'); set_lang();
include_once("config.inc.php"); + +include_once("hookdef.php"); +include_once("hooks/account.php"); + include_once("routing.inc.php"); include_once("version.inc.php"); include_once("acctfuncs.inc.php"); diff --git a/web/lib/hookdef.php b/web/lib/hookdef.php new file mode 100644 index 0000000..19d93d5 --- /dev/null +++ b/web/lib/hookdef.php @@ -0,0 +1,113 @@ +<?php +/* + * This hook system is similar to the ones in wordpress or durpal. + * + * It is meant to be simple and effective, whithout any overengineered stuff. + * If you think about making it "better" by creating a OOP version with + * interfaces, please take a look at the rest of the system and ask yourself + * if this really fits in here. + */ + +// LIST OF EXISTING HOOKS +/* + * USE THIS PATTERN: + * + * filename.php hookname (payload1, payload2, ...) + * + * acctfuncs.inc.php newAccount (array($U, $E, $P, $salt, $R, $L, $I, $K)) + */ + +/** + * Triggers the hook by it's name and passes the given payload to the hook. + * + * The trigger does not return anything as they must be used in a fire-and- + * forget manner. + * + * The functions for the hooks must follow a naming convention to be triggered + * by this function. Keep in mind that the weight goes from 1 (lowest) to + * PHP_INT_MAX (highest). The higher the number the earlier it will be executed. + * + * The pattern is: + * + * <code>hook_$hookname_$weight_uniqueId</code> + * + * @example <code> + * function hook_registerUser_15_SendmailOnReg($sUsername) { do something } + * // or + * function hook_updatePackage_1_SendmailOnUpdt($sPkgName, $sUsername) { + * do something + * } + * </code> + * + * @param string $sHookName The hook name to be triggered + * @param mixed $mPayload A nullable payload to pass to the hook. + * + * @return void + * @author xenji <mario@xenji.com> + * @since 2012-09-17 + */ +function trigger_hook($sHookName, $mPayload) +{ + error_log('Started ' . __FUNCTION__); + $aUserDefFunctions = get_defined_functions()['user']; + error_log('Got defined fncs: ' . implode(', ', $aUserDefFunctions)); + + // We just want to process the relevant hooks, no other hooks or fnc. + $oFilter = new \CallbackFilterIterator( + new \ArrayIterator($aUserDefFunctions), + function ($mCurr, $iKey, $oIter) use ($sHookName) + { + // keep this with three eq. signs, it is needed! + if ( + strpos($mCurr, 'hook_') !== false + && strpos(strtolower($mCurr), strtolower($sHookName)) !== false + ) + { + error_log('Hookname OK: '. $sHookName . ' in ' . $mCurr); + return true; + } + return false; + }); + + + $oHookQueue = new \HookQueue(); + + foreach ($oFilter as $sFunction) + { + // $_ => unused declaration, borrowed from scala's convention + list($_, $_, $iWeight, $_) = explode('_', $sFunction); + error_log("Inserting $sFunction into Queue with weight $iWeight"); + $oHookQueue->insert($sFunction, (int)$iWeight); + } + + // We just want to have the function name, not the prio + $oHookQueue->setExtractFlags(\SplPriorityQueue::EXTR_DATA); + if ($oHookQueue->count() > 0) + { + $oHookQueue->top(); + + foreach ($oHookQueue as $sFunction) + { + call_user_func($sFunction, $mPayload); + } + } +} + +/** + * This is an implementation of the prio queue, which fits perfectly for our + * need to sort the hooks in relation to their weight. + * + * Taken from the example on php.net, because I was too lazy to type the text. + * + * @see http://php.net/manual/de/class.splpriorityqueue.php + * @author xenji <mario@xenji.com> + * @since 2012-09-17 + */ +class HookQueue extends SplPriorityQueue +{ + public function compare($priority1, $priority2) + { + if ($priority1 === $priority2) return 0; + return $priority1 < $priority2 ? -1 : 1; + } +} diff --git a/web/lib/hooks/account.php b/web/lib/hooks/account.php new file mode 100644 index 0000000..aea57b5 --- /dev/null +++ b/web/lib/hooks/account.php @@ -0,0 +1,4 @@ +<?php +function hook_newAccount_1_SendMailForNewAccount($aAccountData) { + // send a mail if you want to... +} \ No newline at end of file -- 1.7.12