<?php
namespace
{
!defined('DS') and define('DS', DIRECTORY_SEPARATOR);
!defined('VOID') and define('VOID', "\0");
/**
* Return Unmodified Argument
*
* usage:
*
* __(new Classes())->callMethods();
*
* @param mixed $var
* @return mixed
*/
function __($var)
{
return $var;
}
}
namespace Poirot\Std\Invokable
{
use Poirot\Std\Interfaces\Pact\ipInvokableCallback;
/**
* Resolve Arguments Matched With Callable Arguments
*
* @param callable $callable
* @param array|\Traversable $parameters Params to match with function arguments
*
* @return \Closure
*/
function resolveCallableWithArgs(/*callable*/$callable, $parameters)
{
if ($parameters instanceof \Traversable)
$parameters = \Poirot\Std\cast($parameters)->toArray();
$reflection = reflectCallable($callable);
$matchedArguments = resolveArgsForReflection($reflection, $parameters);
if (!empty($parameters) && empty($matchedArguments))
// In Case That Fun Has No Any Argument.
// exp. func() { $args = func_get_args() ..
$matchedArguments = $parameters;
$callbackResolved = function() use ($callable, $matchedArguments) {
return call_user_func_array($callable, $matchedArguments);
};
return $callbackResolved;
}
/**
* Resolve Arguments Matched With Reflection Method/Function
*
* @param \ReflectionMethod|\ReflectionFunction $reflectFunc
* @param array|\Traversable $givenArgs Params to match with function arguments
* @param bool $inDepth Resolve argument in depth from parameters; if false just
* resolve arguments by name
*
* @return array Of Matching Arguments
*/
function resolveArgsForReflection(/*callable*/$reflectFunc, $givenArgs, $inDepth = true)
{
if ( !($reflectFunc instanceof \ReflectionFunction || $reflectFunc instanceof \ReflectionMethod) )
throw new \InvalidArgumentException(sprintf(
'(%s) is not reflection.'
, \Poirot\Std\flatten($reflectFunc)
));
$mappedArgs = array(); $positionalArgs = array();
foreach ($givenArgs as $key => $val) {
if (is_string($key)) {
// Create Map Of "field_name" to "fieldName" and "FieldName" to Resolve To Callable
$res = (string) \Poirot\Std\cast($key)->camelCase();
if (! isset($givenArgs[$res]) )
$mappedArgs[strtolower($res)] = $val;
$mappedArgs[strtolower($key)] = $val;
}
$mappedArgs[$key] = $val;
if (is_int($key))
$positionalArgs[] = $val;
}
$matchedArguments = array();
foreach ($reflectFunc->getParameters() as $reflectArgument)
{
/** @var \ReflectionParameter $reflectArgument */
$argValue = $notSet = uniqid(); // maybe null value is default
if ( $reflectArgument->isDefaultValueAvailable() )
$argValue = $reflectArgument->getDefaultValue();
$argName = strtolower($reflectArgument->getName());
if ( array_key_exists($argName, $mappedArgs) ) {
## resolve argument value match with name given in parameters list
$argValue = $mappedArgs[$argName];
unset($mappedArgs[$argName]);
} elseif ($inDepth) {
## in depth argument resolver
$av = $notSet;
foreach ( $givenArgs as $k => $v ) {
if ( ( $class = $reflectArgument->getClass() ) && is_object($v) && $class->isInstance($v) )
$av = $v;
if ( $reflectArgument->isArray() && is_array($v) )
$av = $v;
if ( $reflectArgument->isCallable() && is_callable($v) )
$av = $v;
if ( is_int($k) )
// Placement Argument
$av = $v;
if ($av !== $notSet) {
unset($givenArgs[$k]);
break;
}
}
($av === $notSet) ?: $argValue = $av;
}
if ($argValue === $notSet)
throw new \InvalidArgumentException(sprintf(
'Callable (%s) has no match found on parameter (%s) from (%s) list.'
, ($reflectFunc instanceof \ReflectionMethod)
? $reflectFunc->getDeclaringClass()->getName().'::'.$reflectFunc->getName()
: $reflectFunc->getName()
, $reflectArgument->getName()
, \Poirot\Std\flatten($givenArgs)
));
$matchedArguments[$reflectArgument->getName()] = $argValue;
}
return $matchedArguments;
}
/**
* Factory Reflection From Given Callable
*
* $function
* 'function_name' | \closure
* 'classname::method'
* [className_orObject, 'method_name']
*
* @param $callable
*
* @throws \ReflectionException
* @return \ReflectionFunction|\ReflectionMethod
*/
function reflectCallable($callable)
{
if (!is_callable($callable))
throw new \InvalidArgumentException(sprintf(
'Argument provided is not callable; given: (%s).'
, \Poirot\Std\flatten($callable)
));
if ($callable instanceof ipInvokableCallback)
$callable = $callable->getCallable();
if (is_array($callable))
## [className_orObject, 'method_name']
$reflection = new \ReflectionMethod($callable[0], $callable[1]);
if (is_string($callable)) {
if (strpos($callable, '::'))
## 'classname::method'
$reflection = new \ReflectionMethod($callable);
else
## 'function_name'
$reflection = new \ReflectionFunction($callable);
}
if (method_exists($callable, '__invoke')) {
## Closure and Invokable
if ($callable instanceof \Closure)
$reflection = new \ReflectionFunction($callable);
else
$reflection = new \ReflectionMethod($callable, '__invoke');
}
if (!isset($reflection))
throw new \ReflectionException;
return $reflection;
}
}
namespace Poirot\Std\Lexer
{
/**
* Tokenize Parse String Definition Into Parts
*
* String in form of:
* '[:subdomain{{static}}.]localhost.:tld{{\s+}}'
* : variable {delimiter}
* delimiter .. localhost\.(?P<tld>\s+)
* [optional]
*
* TODO: optional in optional;
*
* @param string $criteria
* @param string $expectedLiteral
*
* @return array
*/
function parseCriteria($criteria, $expectedLiteral = null)
{
$TOKENS = preg_quote('\:~<>');
($expectedLiteral !== null) ?: $expectedLiteral = '/@+-.';
$LITERAL = preg_quote($expectedLiteral).'A-Za-z0-9_';
$currentPos = 0;
$length = strlen($criteria);
$parts = array();
$levelParts = array(&$parts);
$level = 0;
while ($currentPos < $length)
{
## the tokens are .:~<>
preg_match("(\G(?P<_literal_>[$LITERAL]*)(?P<_token_>[$TOKENS]|$))"
, $criteria
, $matches
, 0
, $currentPos
);
if (empty($matches)) break;
$currentPos += strlen($matches[0]);
if (!empty($matches['_literal_']))
$levelParts[$level][] = array('_literal_' => $matches['_literal_']);
# Deal With Token:
if (!isset($matches['_token_']) || $matches['_token_'] == '')
continue;
$Token = $matches['_token_'];
if ($Token === '~') {
// ^ match any character that is not in list
$pmatch = preg_match("/~(?P<_delimiter_>[^~]+)~/"
, $criteria
, $matches
, 0
, 0
);
if (! $pmatch )
throw new \RuntimeException('Miss usage of ~ ~ token.');
$val['_regex_'] = $matches['_delimiter_'];
$levelParts[$level][] = $val;
$currentPos += strlen($matches[0])+2/*{}*/;
}
elseif ($Token === ':') {
// TODO better expression
// currently match everything between {{ \w+}}/:identifier_type{{\w+ }}
// /validate/resend/:validation_code{{\w+}}/:identifier_type{{\w+}}
// ^ match any character that is not in list
$pmatch = preg_match("(\G(?P<_name_>[^$TOKENS]+)(?:~(?P<_delimiter_>[^~]+)~)?:?)"
, $criteria
, $matches
, 0
, $currentPos
);
if (! $pmatch && !(isset($matches['_name_']) && isset($matches['_delimiter_'])) )
throw new \RuntimeException(
'Found empty parameter name or delimiter on (%s).'
, $criteria
);
$parameter = $matches['_name_'];
$val = array('_parameter_' => $parameter);
if (isset($matches['_delimiter_']))
$val[$parameter] = $matches['_delimiter_'];
$levelParts[$level][] = $val;
$currentPos += strlen($matches[0]);
}
elseif ($Token === '\\') {
// Consider next character as Literal
// localhost\::port
$nextChar = $currentPos += 1;
$levelParts[$level][] = array('_literal_' => $criteria[$nextChar]);
}
elseif ($Token === '<') {
if (!isset($va)) {
$va = array();
$n = 0;
}
$n++;
$va[$n] = array();
$levelParts[$level][] = array('_optional_' => &$va[$n]);
$level++;
$levelParts[$level] = &$va[$n];
}
elseif ($Token === '>') {
unset($levelParts[$level]);
$level--;
if ($level < 0)
throw new \RuntimeException('Found closing bracket without matching opening bracket');
} else
// Recognized unused token return immanently
$levelParts[$level][] = array('_token_' => $Token);
} // end while
if ($level > 0)
throw new \RuntimeException('Found unbalanced brackets');
return $parts;
}
/**
* Build the matching regex from parsed parts.
*
* @param array $parts
* @param bool $matchExact
*
* @return string
*/
function buildRegexFromParsed(array $parts, $matchExact = null)
{
$regex = '';
// [0 => ['_literal_' => 'localhost'], 1=>['_optional' => ..] ..]
foreach ($parts as $parsed) {
$definition_name = key($parsed);
$definition_value = $parsed[$definition_name];
// $parsed can also have extra parsed data options
// _parameter_ String(3) => tld \
// tld String(4) => .com
switch ($definition_name) {
case '_regex_':
$regex .= $definition_value;
break;
case '_token_':
case '_literal_':
$regex .= preg_quote($definition_value);
break;
case '_parameter_':
$groupName = '?P<' . $definition_value . '>';
if (isset($parsed[$definition_value])) {
// Delimiter: localhost:port{{\d+}}
$regex .= '(' . $groupName . $parsed[$definition_value] . ')';
} else{
$regex .= '(' . $groupName . '[^.]+)';
}
break;
case '_optional_':
$regex .= '(?:' . buildRegexFromParsed($definition_value, null) . ')?';
break;
}
}
if ($matchExact !== null) {
$regex = ( (bool) $matchExact )
? "(^{$regex}$)" ## exact match
// TODO /me match on /members request with matchWhole Option false
// so i add trailing slash but not tested completely
: "(^{$regex})"; ## only start with criteria "/pages[/other/paths]"
}
return $regex;
}
/**
* Build String Representation From Given Parts and Params
*
* @param array $parts
* @param array|\Traversable $params
*
* @return string
*/
function buildStringFromParsed(array $parts, $params = array())
{
if ($params instanceof \Traversable)
$params = \Poirot\Std\cast($params)->toArray();
if (!is_array($params))
throw new \InvalidArgumentException(sprintf(
'Parameters must be an array or Traversable; given: (%s).'
, \Poirot\Std\flatten($params)
));
// regard to recursive function call
$isOptional = false;
$args = func_get_args();
if ($args && isset($args[2]))
$isOptional = $args[2];
# Check For Presented Values in Optional Segment
// consider this "/[@:username{{\w+}}][-:userid{{\w+}}]"
// if username not presented as params injected then first part include @ as literal
// not rendered.
// optional part only render when all parameter is present
if ($isOptional) {
foreach ($parts as $parsed) {
if (!isset($parsed['_parameter_']))
continue;
## need parameter
$neededParameterName = $parsed['_parameter_'];
if (!(isset($params[$neededParameterName]) && $params[$neededParameterName] !== '') )
return '';
}
}
$return = '';
// [0 => ['_literal_' => 'localhost'], 1=>['_optional' => ..] ..]
foreach ($parts as $parsed) {
$definition_name = key($parsed);
$definition_value = $parsed[$definition_name];
// $parsed can also have extra parsed data options
// _parameter_ String(3) => tld \
// tld String(4) => .com
switch ($definition_name)
{
case '_literal_':
$return .= $definition_value;
break;
case '_parameter_':
if (!isset($params[$definition_value])) {
if ($isOptional) return '';
throw new \InvalidArgumentException(sprintf(
'Missing parameter (%s).'
, $definition_value
));
}
$return .= $params[$definition_value];
break;
case '_optional_':
$optionalPart = buildStringFromParsed($definition_value, $params, true);
if ($optionalPart !== '')
$return .= $optionalPart;
break;
}
}
return $return;
}
}
namespace Poirot\Std
{
use Closure;
use ReflectionFunction;
use Poirot\Std\Type\StdArray;
use Poirot\Std\Type\StdString;
use Poirot\Std\Type\StdTravers;
const CODE_NUMBERS = 2;
const CODE_STRINGS_LOWER = 4;
const CODE_STRINGS_UPPER = 8;
const CODE_STRINGS = CODE_STRINGS_LOWER | CODE_STRINGS_UPPER;
/**
* Cast Given Value Into SplTypes
* SplTypes Contains Some Utility For That Specific Type
*
* ! when you want to force cast to string is necessary to
* use type casting cast((string) 10)
*
* @param mixed $type
*
* @throws \UnexpectedValueException
* @return StdString|StdArray|StdTravers|\SplType
*/
function cast($type)
{
switch(1) {
case is_array($type):
$return = new StdArray($type);
break;
case ($type instanceof \Traversable):
$return = new StdTravers($type);
break;
case isStringify($type):
// Stringify has low priority than \Traversable
// TODO and object may has both \Traversable or Stringify ...
$return = new StdString($type);
break;
default: throw new \UnexpectedValueException(sprintf(
'Type (%s) is unexpected.', gettype($type)
));
}
return $return;
}
/**
* Insert Into Array Pos
*
* @param $array
* @param $element
* @param $offset
*
* @return int
*/
function insertIntoPosArray(&$array, $element, $offset)
{
if ($offset == 0)
return array_unshift($array, $element);
if ( $offset + 1 >= count($array) )
return array_push($array, $element);
// [1, 2, x, 4, 5, 6] ---> before [1, 2], after [4, 5, 6]
$beforeOffsetPart = array_slice($array, 0, $offset);
$afterOffsetPart = array_slice($array, $offset);
# insert element in offset
$beforeOffsetPart = $beforeOffsetPart + [$offset => $element];
# glue them back
$array = array_merge($beforeOffsetPart , $afterOffsetPart);
arsort($array);
}
/**
* Catch Callable Exception
*
* @param callable $fun
* @param callable $catch mixed function(\Exception $exception)
*
* @return mixed value returned by $fun or $catch
* @throws \Exception
*/
function catchIt(/*callable*/ $fun, /*callable*/ $catch = null)
{
try {
$return = call_user_func($fun);
} catch (\Exception $e) {
if ($catch)
return call_user_func($catch, $e);
throw $e;
}
return $return;
}
/**
* Retry Callable
*
* @param Closure $f
* @param int $maxTries
* @param int $sleep
*
* @return mixed
* @throws \Exception
*/
function reTry(\Closure $f, $maxTries = 5, $sleep = 0)
{
$attempt = 0;
do {
try {
// Pop Payload form Queue
return $f();
} catch (\Exception $e) {
if ($sleep) sleep($sleep);
$attempt++;
continue;
}
} while ($attempt >= $maxTries);
throw $e;
}
/**
* Is Given Mime Match With Mime-Type Lists
*
* @param array $mimesList
* @param string $against
*
* @return bool|null
*/
function isMimeMatchInList(array $mimesList, $against)
{
$exMimeType = explode('/', $against);
$flag = null; // mean we have no list
foreach ($mimesList as $mimeDef)
{
foreach (explode('/', $mimeDef) as $i => $v) {
if ($v == '*')
// Skip This mimeType Definition Part, try next ...
continue;
$flag = false; // mean we have a list
if (isset($exMimeType[$i]) && strtolower($v) === strtolower($exMimeType[$i]))
return $against; // mean we have given mime in list
}
}
return $flag;
};
/**
* Timer Diff
*
* @param $timeStart
*
* @return string
*/
function timerDiff($timeStart)
{
return number_format(microtime(true) - $timeStart, 3);
}
/**
* // TODO $contains is string contains chars. to use by shuffler
* Generate Random Strings
*
* @param int $length
* @param int $contains
*
* @return string
*/
function generateShuffleCode($length = 8, $contains = CODE_NUMBERS | CODE_STRINGS)
{
$characters = null;
if (($contains & CODE_NUMBERS) == CODE_NUMBERS)
$characters .= '0123456789';
if (($contains & CODE_STRINGS_LOWER) == CODE_STRINGS_LOWER)
$characters .= 'abcdefghijklmnopqrstuvwxyz';
if (($contains & CODE_STRINGS_UPPER) == CODE_STRINGS_UPPER)
$characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if ($characters === null)
throw new \InvalidArgumentException('Invalid Contains Argument Provided; Does Not Match Any Condition.');
$randomString = '';
for ($i = 0; $i < $length; $i++)
$randomString .= $characters[rand(0, strlen($characters) - 1)];
return $randomString;
}
/**
* Generate a unique identifier
*
* @param int $length
*
* @return string
* @throws \Exception
*/
function generateUniqueIdentifier($length = 40)
{
try {
// Converting bytes to hex will always double length. Hence, we can reduce
// the amount of bytes by half to produce the correct length.
return bin2hex(random_bytes($length / 2));
} catch (\TypeError $e) {
throw new \Exception('Server Error While Creating Unique Identifier.');
} catch (\Error $e) {
throw new \Exception('Server Error While Creating Unique Identifier.');
} catch (\Exception $e) {
// If you get this message, the CSPRNG failed hard.
throw new \Exception('Server Error While Creating Unique Identifier.');
}
}
function makeReadableFileSize($size, $precision = 2)
{
static $units = array('B','KB','MB','GB','TB','PB','EB','ZB','YB');
$step = 1024;
$i = 0;
while (($size / $step) > 0.9) {
$size = $size / $step;
$i++;
}
return round($size, $precision).' '.$units[$i];
}
/**
* With null|false Data Return Default Value
* Elsewhere Return Data
*
* @param null|false|mixed $data
* @param mixed $default
*
* @return mixed
*/
function emptyCoalesce($data, $default = null)
{
return ($data === null || $data === false) ? $default : $data;
}
/**
* Swap Value Of Two Variable
*
* @param mixed $a
* @param mixed $b
*
* @return void
*/
function swap(&$a, &$b)
{
list($a, $b) = array($b, $a);
}
/**
* Check Variable/Object Is String
*
* @param mixed $var
*
* @return bool
*/
function isStringify($var)
{
return ( (!is_array($var)) && (
(!is_object($var) && @settype($var, 'string') !== false) ||
(is_object($var) && method_exists($var, '__toString' ))
));
}
/**
* Convert Object To Array
*
* @param \stdClass|object $data
*
* @return array
*/
function toArrayObject($data)
{
if (is_object($data))
$data = get_object_vars($data);
if (is_array($data))
return array_map(__FUNCTION__, $data);
else
return $data;
}
/**
* Represent Stringify From Variable
*
* @param mixed $var
*
* @return string
* @throws \Exception
*/
function toStrVar($var)
{
$r = null;
if (is_bool($var))
$r = ($var) ? 'True' : 'False';
elseif (is_null($var))
$r = 'null';
elseif (isStringify($var))
$r = (string) $var;
else
throw new \Exception(sprintf('Variable (%s) cant convert to string.'));
return $r;
}
/**
* Flatten Value
*
* @param mixed $value
*
* @return string
*/
function flatten($value)
{
if ($value instanceof Closure) {
$closureReflection = new ReflectionFunction($value);
$value = sprintf(
'(Closure at %s:%s)',
$closureReflection->getFileName(),
$closureReflection->getStartLine()
);
} elseif (is_object($value)) {
$value = sprintf('%s:object(%s)', spl_object_hash($value), get_class($value));
} elseif (is_resource($value)) {
$value = sprintf('resource(%s-%s)', get_resource_type($value), $value);
} elseif (is_array($value)) {
foreach($value as $k => &$v)
$v = flatten($v);
$value = 'Array: ['.implode(', ', $value).']';
} elseif (is_scalar($value)) {
$value = sprintf('%s(%s)',gettype($value), $value);
} elseif ($value === null)
$value = 'NULL';
return $value;
}
}
|