3v4l.org

run code in 300+ PHP versions simultaneously
<?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);
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:
28.29 ms | 406 KiB | 5 Q