High-performance function checks whether specified array contains only integer values (or integer-numeric strings):
/**
* Checks whether all elements of an array are integers (integer value or string with integer value).
* Note: this is very efficient method can be used for array with 10000+ items.
*
* @param int[]|string[] $array One dimensional array to check
*
* @return bool
*/
function hasOnlyIntEntries(array $array)
{
return ctype_digit(join('', $array));
}
Often we need remove CSS classes that begin with certain string.
For example: has-state-active
, has-state-pending
, has-state-disabled
.
Easiest way is to use wildcards: has-state-*
.
Following function supports them (requires jQuery):
/**
* Removes CSS classes of element using wildcards:
*
* @example
* <code><pre>
*
* // create some element
* var $element = $('<div id="#some-element"/>')
* .addClass('base-class has-state-active item item-75')
* .appendTo('body');
*
* // remove classes with few prefixes
* removeClassWildcard( $element, 'has-state-* item-*');
*
* // display element classes
* console.log($element.attr('class')); // >> "base-class item"
*
* </pre></code>
*
* @param {jQuery|*} $element
* @param {String} removals Classes to delete, for example: 'foo-* bar-*'
* @returns {jQuery|*} Passed element
*/
function removeClassWildcard($element, removals) {
if (removals.indexOf('*') === -1) {
// Use native jQuery methods if there is no wildcard matching
$element.removeClass(removals);
return $element;
}
var patt = new RegExp('\\s' +
removals.replace(/\*/g, '[A-Za-z0-9-_]+').split(' ').join('\\s|\\s') +
'\\s', 'g');
$element.each(function (i, it) {
var cn = ' ' + it.className + ' ';
while (patt.test(cn)) {
cn = cn.replace(patt, ' ');
}
it.className = $.trim(cn);
});
return $element;
}
Example:
// create some element
var $element = $('<div id="#some-element"/>')
.addClass('base-class has-state-active item item-75')
.appendTo('body');
// remove classes with few prefixes
removeClassWildcard( $element, 'has-state-* item-*');
// display element classes
console.log($element.attr('class')); // >> "base-class item"
"Golden" rule of frontend optimization often recommends to insert all JS scripts on page end.
However usualy we can't remove <script src="//jquery.js">
from page <head>
, because page content can contain many onReady-handlers, for example $(function(){ ... })
.
This short snippet allows move jquery.js
from <head>
to end of <body>
without changing onReady-handlers.
Snippet:
<head>
<script>
// Fallback code for the jQuery inline scripts on pages:
if (window.jQuery === undefined) {
window.jQueryReadyHandlers = [];
window.$ = window.jQuery = function (callback) {
window.jQueryReadyHandlers.push(callback);
return window.$;
};
window.$.ready = window.$;
}
</script>
<!-- ...some head items -->
</head>
<body>
<!-- ...some page content -->
<script>
// some onReady-handlers
$(function(){
console.log('First callback');
});
jQuery(document).ready(function(){
console.log('Second callback');
});
</script>
<!-- ...some page content -->
<script src="//js/jquery.min.js"></script>
<script>
jQuery(window).load(function(){
if(window.jQueryReadyHandlers) {
$.each(window.jQueryReadyHandlers, function(index,func){
$(func);
});
}
});
</script>
</body>
When we work with some API interface, it often have Rate-Limit, usually certain number of http-requests per unit of time (see for example Twitter API Rate Limit).
This helper can help you efficiently handle these restrictions.
Helper code:
/**
* Helper for work with some API.
*/
class ApiHelper
{
/**
* Wraps specified callback function and retries of execution in the case when callback throws specified exception.
* Used Exponential BackOff algorithm for evaluating of timeouts between retries of callback execution.
*
* Example:
* <code>
* $sendHttpRequest = function ($pageNum) use ($httpClient){
* $httpClient->sendGet('http://example.com?pageNum=' . $pageNum);
* };
*
* $wrappedFn = ApiHelper::wrapUsingExponentialBackOff($sendHttpRequest, 5000, '\TooManyRequestsHttpException');
*
* // We perform 100 requests. But remote server has RateLimit.
* for ($i = 1; $i <= 100; $i++) {
* // $sendHttpRequest($i); // request without wrapper
* $wrappedFn($i); // request with wrapper
* // this wrapper retry callback execution in the case of \TooManyRequestsHttpException exception.
* }
* </code>
*
* @param \Closure $callbackFn Callback function.
* @param int $maxSleepMs Max value of timeout between callback execution.
* @param string $rateLimitExceptionClassName Exception that should throw callback on rate limit.
* @param int $maxRetries Max number of retries.
* @param \Closure $onTooManyRequest Callback function that performed on each RateLimit exception.
*
* @return \Closure
*/
public static function wrapUsingExponentialBackOff(\Closure $callbackFn, $maxSleepMs, $rateLimitExceptionClassName, $maxRetries = 10, \Closure $onTooManyRequest = null)
{
$wrapFn = function () use ($callbackFn, $maxSleepMs, $rateLimitExceptionClassName, $maxRetries, $onTooManyRequest) {
$currentTry = 0;
$retriesRemain = $maxRetries;
while ($retriesRemain-- > 0) {
try {
$currentTry++;
return call_user_func_array($callbackFn, func_get_args());
} catch (\Exception $exception) {
// retries only throttling errors
if ($exception instanceof $rateLimitExceptionClassName) {
$backOffDuration = static::_getSleepDuration($currentTry, 10, $maxSleepMs);
if ($onTooManyRequest) {
$onTooManyRequest($exception, $backOffDuration, $retriesRemain, $currentTry);
}
usleep($backOffDuration * 1000);
} else {
throw $exception;
}
}
}
throw new \Exception('ExponentialBackOff: Too many retries of function call [maxRetries=' . $maxRetries . ']');
};
return $wrapFn;
}
protected static function _getSleepDuration($currentTry, $minSleepMs, $maxSleepMs)
{
$currentSleepMs = (int)($minSleepMs * pow(2, $currentTry));
return min($currentSleepMs, $maxSleepMs);
}
}
How it use
ApiHelper::wrapUsingExponentialBackOff()
method$sendHttpRequest = function ($pageNum) use ($httpClient) {
$httpClient->sendGet('http://example.com?pageNum=' . $pageNum);
};
$wrappedFn = ApiHelper::wrapUsingExponentialBackOff($sendHttpRequest, 5000, '\TooManyRequestsHttpException');
// We perform 100 requests. But remote server has RateLimit.
for ($i = 1; $i <= 100; $i++) {
// $sendHttpRequest($i); // request without wrapper
$wrappedFn($i); // request with wrapper
// this wrapper retry callback execution in the case of \TooManyRequestsHttpException exception.
}
How it works
This helper uses "Exponential backoff" algorithm.
The main idea of this algorithm is to reduce the rate at which you are executing an operation by introducing delays. Therefore when you are "backing off" you are waiting for a period of time before attempting to execute the operation again.
Wrapped callback function $wrappedFn
will retry to execute specified original callback function $sendHttpRequest
if it throws exception.