/** * @constructor * * @param {array} $rules Redirect rules * @param {callable|string} $rules[][source] Regex, plain string startsWith() or callback matcher func, * @param {string} $rules[][target] String for redirection, can use backreference on regex, * @param {?int} $rules[][options] Redirection $options, or internal by default, * @param {?string} $options[source] Base path to match against requests, defaults to root. * @param {string|callable} $options[target] Redirects to a static target, or function($request) returns a string; */ public function __construct($rules) { // rewrite all URLs if (is_string($rules)) { $rules = array('*' => $rules); } $rules = util::wrapAssoc($rules); $this->rules = array_reduce($rules, function ($result, $rule) { $rule = array_select($rule, array('source', 'target', 'options')); // note: make sure source is callback if (is_string($rule['source'])) { // regex if (@preg_match($rule['source'], null) !== false) { $rule['source'] = matches($rule['source']); if (is_string($rule['target'])) { $rule['target'] = compose(invokes('uri', array('path')), replaces($rule['source'], $rule['target'])); } } else { if (!is_callable($rule['source'])) { $rule['source'] = startsWith($rule['source']); if (is_string($rule['target'])) { $rule['target'] = compose(invokes('uri', array('path')), replaces('/^' . preg_quote($rule['source']) . '/', $rule['target'])); } } } } if (!is_callable($rule['source'])) { throw new InvalidArgumentException('Source must be string, regex or callable.'); } $result[] = $rule; return $result; }, array()); }
/** * @param $handlers An array of template handler options, with the following format: * [{ render: // Template rendering function: function($template, $resource); * , extensions: // File extensions to match against handling templates. * }] */ public function __construct($handlers = array()) { $handlers = Utility::wrapAssoc($handlers); foreach ($handlers as &$handler) { if (!is_callable($handler['render'])) { throw new FrameworkException('Please specify a valid render function.'); } if (!@$handler['extensions']) { throw new FrameworkException('Please specify file extensions this handler handles.'); } if (is_string($handler['extensions'])) { $handler['extensions'] = preg_split('/\\s+/', $handler['extensions']); } $handler['extensions'] = Utility::wrapAssoc($handler['extensions']); } $this->handlers = $handlers; }
/** * Perform cURL requests and throw appropriate exceptions. * * An array of parameters used in a curl_setopt_array, * multiple calls can be passed in. * * This function make use of curl_multi no matter it is * single request or not. * * Callbacks are used to handle results inside the array. * * $option['callbacks'] = array( * 'progress' => [Function] * , 'success' => [Function] * , 'failure' => [Function] * , 'always' => [Function] * ); * * @return void */ public static function curlRequest($options) { $options = Utility::wrapAssoc(array_values((array) $options)); $multiHandle = curl_multi_init(); // Initialize cUrl options array_walk($options, function (&$option) { // 1. Request headers $option['response'] = array('headers' => ''); $option[CURLOPT_HEADERFUNCTION] = function ($curl, $data) use(&$option) { $option['response']['headers'] .= $data; return strlen($data); }; // 2. Progress function $progressCallback =& $option['callbacks']['progress']; if ($progressCallback) { $option[CURLOPT_NOPROGRESS] = false; $option[CURLOPT_PROGRESSFUNCTION] = function () use(&$progressCallback) { if (func_num_args() == 4) { list($dSize, $dLen, $uSize, $uLen) = func_get_args(); } else { list($req, $dSize, $dLen, $uSize, $uLen) = func_get_args(); } if ($dSize || $dLen) { static $_dLen = 0; if ($_dLen != $dLen) { $_dLen = $dLen; /*! Note by Vicary @ 2.Oct.2012 * Total download size is often 0 if server doesn't * response with a Content-Length header. * * Total size guessing logic: * 1. if $dLen < 1M, assume 1M. * 2. if $dLen < 10M, assume 10M. * 3. if $dLen < 100M, assume 100M. * 4. if $dLen < 1G, assume 1G. */ if (!$dSize) { // Do not assume when size under 1K if ($dLen < 5000) { return; } elseif ($dLen < 10000000) { $dSize = 20000000; } elseif ($dLen < 100000000) { $dSize = 200000000; } elseif ($dLen < 1000000000) { $dSize = 2000000000; } else { $dSize = 20000000000; } // $dSize = $dLen / .05; } // Download progress, from 0 to 1. $progressArgs = array($dLen / $dSize, $dLen, $dSize); } } else { if ($uSize) { static $_uLen = 0; if ($_uLen != $uLen) { $_uLen = $uLen; $uSize *= -1; $uLen += $uSize; // Upload progress, from -1 to 0. $progressArgs = array($uLen / $uSize, $uLen, $uSize); } } } // Fire the event for each µSeconds. static $_tOffset = 0; $tOffset = microtime(1); if (isset($progressArgs) && $tOffset - $_tOffset > self::progressInterval()) { $_tOffset = $tOffset; Utility::forceInvoke($progressCallback, $progressArgs); } }; } unset($progressCallback); // 3. Apply cUrl options, numeric keys only. $option['handle'] = curl_init(); curl_setopt_array($option['handle'], array_filter_keys($option, 'is_int')); }); $requestIndex = 0; while ($requestIndex < self::$maximumRequests && isset($options[$requestIndex])) { curl_multi_add_handle($multiHandle, $options[$requestIndex++]['handle']); } // Start the multi request do { $status = curl_multi_exec($multiHandle, $active); /* Added by Vicary @ 6.Nov.2012 Blocks until there is a message arrives. */ curl_multi_select($multiHandle); do { $info = curl_multi_info_read($multiHandle, $queueLength); if ($info === FALSE) { continue; } $optionIndex = array_search($info['handle'], array_map(prop('handle'), $options)); if ($optionIndex === FALSE) { continue; } $curlOption =& $options[$optionIndex]; $callbacks =& $curlOption['callbacks']; // Success handler if ($info['result'] === CURLE_OK) { // Fire a 100% downloaded event. if (@$callbacks['progress']) { Utility::forceInvoke($callbacks['progress'], array(1, 1, 1)); usleep(self::progressInterval() * 1000000); } // Append HTTP status code $curlOption['status'] = curl_getinfo($info['handle'], CURLINFO_HTTP_CODE); Utility::forceInvoke(@$callbacks['success'], array(curl_multi_getcontent($info['handle']), $curlOption)); } else { $errorNumber = curl_errno($info['handle']); $errorMessage = curl_error($info['handle']); // libcurl errors, try to parse it. if ($errorNumber === 0) { if (preg_match('/errno: (\\d+)/', $errorMessage, $matches)) { $errorNumber = (int) $matches[1]; $curlErrors = unserialize(FRAMEWORK_NET_CURL_ERRORS); if (isset($curlErrors[$errorNumber])) { $errorMessage = $curlErrors[$errorNumber]; } } } Utility::forceInvoke(@$callbacks['failure'], array($errorNumber, $errorMessage, $curlOption)); unset($errorNumber, $errorMessage); } // Always handler Utility::forceInvoke(@$callbacks['always'], array($curlOption)); if (isset($options[$requestIndex])) { curl_multi_add_handle($multiHandle, $options[$requestIndex++]['handle']); // Keep the loop alive. $active = TRUE; } curl_multi_remove_handle($multiHandle, $info['handle']); curl_close($info['handle']); unset($info, $callbacks, $curlOption, $options[$optionIndex], $optionIndex); } while ($queueLength > 0); } while ($status === CURLM_CALL_MULTI_PERFORM || $active); curl_multi_close($multiHandle); }
/** * Instead of chaining together .alias().demand().default(), * you can specify keys in opt for each of the chainable methods. * * Optionally options() can take an object that maps * keys to opt parameters. */ public function options($key, $opt) { $key = Utility::wrapAssoc($key); foreach ($key as $_key) { foreach ($opt as $option => $value) { if (method_exists($this, $option)) { $this->{$option}($_key, $value); } } } return $this; // chainable }
/** * This differs from propIn() only when target property * is an array, this returns true when at least one of * the contents in targert property matches $values, * while propIn() does full array equality comparison. * * @param {string} $prop Target property. * @param {array} $values Array of values to match against. * @param {bool} $strict Whether to perform a strict comparison or not. * * @returns {Closure} A function that returns true on * at least one matches, false othereise. */ function propHas($prop, array $values, $strict = false) { return function ($object) use($prop, $values, $strict) { $prop = Utility::wrapAssoc(@$object[$prop]); $prop = array_map(function ($prop) use($values, $strict) { return in_array($prop, $values, $strict); }, $prop); return in_array(true, $prop, true); }; }