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 8.4.1 - 8.4.2
Deprecated: {closure:/in/pbMST:183}(): Implicitly marking parameter $previous as nullable is deprecated, the explicit nullable type must be used instead in /in/pbMST on line 183 Deprecated: {closure:/in/pbMST:187}(): Implicitly marking parameter $previous as nullable is deprecated, the explicit nullable type must be used instead in /in/pbMST on line 187 Deprecated: {closure:/in/pbMST:191}(): Implicitly marking parameter $previous as nullable is deprecated, the explicit nullable type must be used instead in /in/pbMST on line 191 -------------- 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.
Output for 7.0.0 - 7.0.33, 7.1.0 - 7.1.33, 7.2.0 - 7.2.34, 7.3.0 - 7.3.33, 7.4.0 - 7.4.33, 8.0.0 - 8.0.30, 8.1.0 - 8.1.31, 8.2.0 - 8.2.27, 8.3.0 - 8.3.15
-------------- 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.
Output for 5.6.0 - 5.6.40
Parse error: syntax error, unexpected '?' in /in/pbMST on line 184
Process exited with code 255.
Output for 5.4.0 - 5.4.45, 5.5.0 - 5.5.38
Parse error: syntax error, unexpected '.', expecting '&' or variable (T_VARIABLE) in /in/pbMST on line 14
Process exited with code 255.
Output for 5.1.0 - 5.1.6, 5.2.0 - 5.2.17, 5.3.0 - 5.3.29
Parse error: syntax error, unexpected '.', expecting '&' or T_VARIABLE in /in/pbMST on line 14
Process exited with code 255.
Output for 5.0.0 - 5.0.5
Parse error: parse error, unexpected '.', expecting '&' or T_VARIABLE in /in/pbMST on line 14
Process exited with code 255.
Output for 4.4.2 - 4.4.9
Parse error: syntax error, unexpected T_STRING, expecting ')' in /in/pbMST on line 4
Process exited with code 255.
Output for 4.3.0 - 4.3.1, 4.3.5 - 4.3.11, 4.4.0 - 4.4.1
Parse error: parse error, unexpected T_STRING, expecting ')' in /in/pbMST on line 4
Process exited with code 255.
Output for 4.3.2 - 4.3.4
Parse error: parse error, expecting `')'' in /in/pbMST on line 4
Process exited with code 255.

preferences:
99.14 ms | 417 KiB | 5 Q