@ 2020-10-19T14:21:39Z <?php
// Mocking WordPress
function add_filter(string $tag, callable $value) {
$GLOBALS[$tag][] = $value;
}
function _doing_it_wrong($fn, string $message, string $ver) {
echo "\n--------------\nDOING IT WRONG: $message\n--------------\n";
}
// ------------------------------------------------------------------
function apply_filters_typesafe( $tag, $arguments = array(), $value = null, ...$values ) {
if ( empty( $GLOBALS[$tag] ) ) {
return $value;
}
static $types_map;
if (!$types_map) {
$types_map = [
'boolean' => 'boolean',
'integer' => 'numeric',
'double' => 'numeric',
'string' => 'string',
'array' => 'array',
'resource' => 'resource',
'resource (closed)' => 'resource',
'NULL' => 'mixed',
];
}
$type = gettype( $value );
$is_object = is_object( $value );
$accepted_types = isset( $types_map[ $type ] ) ? array( $types_map[ $type ] ) : array();
// Do not calculate multiple times for same class.
static $classes_types = [];
// Skip calculation of accepted types if they are are explicitly passed.
if ( $is_object && empty ( $arguments['accepted_types'] ) ) {
$class = get_class( $value );
if ( isset( $classes_types[ $class ] ) ) {
$accepted_types = $classes_types[ $class ];
} else {
$accepted_types = array( $class );
$parent = get_parent_class( $class );
while ( $parent ) {
$accepted_types[] = $parent;
$parent = get_parent_class( $parent );
}
$accepted_types = array_merge( $accepted_types, class_implements( $class ) );
}
$classes_types[ $class ] = $accepted_types;
}
$arguments = array_replace(
array(
'nullable' => false,
'accepted_types' => $accepted_types
),
$arguments
);
$original = $value;
// Objects are passed by ref, clone to return original unchanged in case of errors.
$to_filter = $is_object ? clone $value : $value;
$filter = array_shift($GLOBALS[$tag]);
$filtered = $filter( $to_filter, ...$values );
// 'mixed' is a valid PHP 8 pseudo-type so we support for consistency.
// That said, if mixed is fine then just use apply_filters.
if ( in_array( 'mixed', (array)$arguments['accepted_types'] ) ) {
return $GLOBALS[$tag]
? apply_filters_typesafe( $tag, $arguments, $filtered, ...$values )
: $filtered;
}
static $can_do_it_wrong = false;
if ( ! $can_do_it_wrong && function_exists( '_doing_it_wrong' ) ) {
$can_do_it_wrong = true;
}
if ( null === $filtered ) {
if ( !$arguments['nullable'] ) {
$filtered = $original;
if ( $can_do_it_wrong ) {
_doing_it_wrong(
__FUNCTION__,
"Filters for '$tag' where not expected to return null.",
'5.6'
);
}
}
return $GLOBALS[$tag]
? apply_filters_typesafe( $tag, $arguments, $filtered, ...$values )
: $filtered;
}
static $functions;
if ( ! $functions ) {
$functions = array(
'int' => 'is_int',
'integer' => 'is_int',
'double' => 'is_float',
'float' => 'is_float',
'numeric' => 'is_numeric',
'number' => 'is_numeric',
'bool' => 'is_bool',
'boolean' => 'is_boolean',
'string' => 'is_string',
'array' => 'is_array',
'callable' => 'is_callable',
'function' => 'is_callable',
'resource' => 'is_resource',
'iterable' => 'is_iterable',
'countable' => 'is_countable',
);
}
foreach ( (array)$arguments['accepted_types'] as $type ) {
if ( isset( $functions[ $type ] ) && call_user_func( $functions[ $type ], $filtered ) ) {
return $GLOBALS[$tag]
? apply_filters_typesafe( $tag, $arguments, $filtered, ...$values )
: $filtered;
}
if ( $is_object && is_string ( $type ) && is_a( $filtered, $type ) ) {
return $GLOBALS[$tag]
? apply_filters_typesafe( $tag, $arguments, $filtered, ...$values )
: $filtered;
}
}
if ( $can_do_it_wrong ) {
$expected = implode( "', '", $arguments['accepted_types'] );
$actual = is_object( $filtered ) ? 'instance of ' . get_class($filtered) : gettype( $filtered );
_doing_it_wrong(
__FUNCTION__,
"Filters for '$tag' where expected to return a value of one of types: '$expected'. Got '$actual' instead.",
'5.6'
);
}
return $GLOBALS[$tag]
? apply_filters_typesafe( $tag, $arguments, $original, ...$values )
: $original;
}
// Let's try now to add some hooks, the first is bad, the second is good.
add_filter('hello', function () {
return 'This is not a callable' ;
});
add_filter('hello', function () {
return function () {
return 'It works!';
};
});
// And lets' see if it works:
$callable = apply_filters_typesafe('hello', ['accepted_types' => ['callable']], '__return_empty_string');
echo $callable();
echo "\n----------------------\n";
// Now, let's try with more filters and nullable.
add_filter('answer', function () {
return null;
});
add_filter('answer', function (int $previous = null) {
return $previous ?? 21;
});
add_filter('answer', function (int $previous = null) {
return ':troll:';
});
add_filter('answer', function (int $previous = null) {
return $previous === null ? null : $previous * 2;
});
$answer = apply_filters_typesafe('answer', ['accepted_types' => ['int'], 'nullable' => true], 0);
printf('The answer is: %d.', $answer);
Enable javascript to submit You have javascript disabled. You will not be able to edit any code.
Output for git.master , git.master_jit , rfc.property-hooks --------------
DOING IT WRONG: Filters for 'hello' where expected to return a value of one of types: 'callable'. Got 'string' instead.
--------------
It works!
----------------------
--------------
DOING IT WRONG: Filters for 'answer' where expected to return a value of one of types: 'int'. Got 'string' instead.
--------------
The answer is: 42. This tab shows result from various feature-branches currently under review by the php developers. Contact me to have additional branches featured.
Active branches Archived branches Once feature-branches are merged or declined, they are no longer available. Their functionality (when merged) can be viewed from the main output page
preferences:dark mode live preview
28.29 ms | 406 KiB | 5 Q