[wp-trac] [WordPress Trac] #51525: Add new functions apply_filters_typesafe() and apply_filters_ref_array_typesafe()
WordPress Trac
noreply at wordpress.org
Mon Oct 19 12:34:13 UTC 2020
#51525: Add new functions apply_filters_typesafe() and
apply_filters_ref_array_typesafe()
------------------------------------------+---------------------
Reporter: jrf | Owner: (none)
Type: feature request | Status: new
Priority: normal | Milestone: 5.6
Component: General | Version: trunk
Severity: normal | Resolution:
Keywords: 2nd-opinion needs-patch php8 | Focuses:
------------------------------------------+---------------------
Comment (by giuseppe.mazzapica):
Thanks a lot for this @jrf
A few notes:
- `plugin.php` can't have external dependencies, so before doing any
`_doing_it_wrong` it is necessary to check that function is defined.
- Automatically determine the expected type might not always possible. For
example, I can pass a `string` but what I actually want is a `callable`.
Same with all the other pseudo-types. E.g. an array is passed but any
`iterable` could do.
- Things become even more complex with objects. If I pass an object to
`apply_filters_typesafe` and then I call a method on the result, I want
the object I retrieve to have that method. So checking the result to be an
`object` will not be enough, and checking all parent classes can be wrong
either. Let's imagine an object that extends/implements `A` ''and'' `B`,
but on the result of the filter, I call a method of `B`. If the filter
returns an `instance of A` that is no good.
- Regarding `null` there are two cases: the first is that `null` is the
value to be filtered, the second is that null is the value returned by the
filters. In the first case, there's basically nothing to do, passing null
to `apply_filters_typesafe` would equal to `apply_filters`. In the second
case, null could be acceptable by the caller.
- Regarding strictness about int and float, I think the default behavior
should be fine with it. Even PHP core does that and there's no need to be
stricter than PHP.
To take into consideration all the points above, I think that
`apply_filters_typesafe` should accept a set of args, as in the best
WordPress tradition, to fine-tune its behavior.
I can think of `accepted_types` and `nullable` options.
That said, because I'm better at PHP than words, I'll paste here what my
idea would be.
Note this is meant to only be a discussion point, and I have not tested
this at all.
{{{#!php
<?php
function apply_filters_typesafe( $tag, $arguments = array(), $value =
null, ...$values ) {
if ( null === $value ) {
return apply_filters( $tag, $value, ...$values );
}
$type = gettype( $value );
$is_object = false;
switch ( $type ) {
case 'boolean':
$accepted_types = array( 'boolean' );
break;
case 'integer':
case 'double':
$accepted_types = array( 'numeric' );
break;
case 'string':
$accepted_types = array( 'string' );
break;
case 'array':
$accepted_types = array( 'array' );
break;
case 'resource':
case 'resource (closed)':
$accepted_types = array( 'resource' );
break;
case 'object':
$is_object = true;
default:
$accepted_types = array();
}
// Skip calculation of accepted types if they are are explicitly
passed.
if ( $is_object && empty ( $arguments['accepted_types'] ) ) {
$class = get_class( $value );
$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 ) );
}
$arguments = wp_parse_args(
$arguments,
array(
'nullable' => false,
'accepted_types' => $accepted_types
)
);
$original = $value;
// Objects are passed by ref, clone to return original unchanged in
case of errors.
$to_filter = $is_object ? clone $value : $value;
$filtered = apply_filters( $tag, $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 $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 && !$arguments['nullable'] ) ) {
if ( $can_do_it_wrong ) {
_doing_it_wrong(
__FUNCTION__,
"Filters for $tag where not expected to return null.",
'5.6'
);
}
return $original;
}
static $functions;
if ( ! $functions ) {
$functions = array(
'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 ( $arguments['accepted_types'] as $type ) {
if ( isset( $functions[ $type ] ) && call_user_func( $functions[
$type ], $filtered ) ) {
return $filtered;
}
if ( $is_object && is_string ( $type ) && is_a( $filtered, $type )
) {
return $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 $original;
}
}}}
I know it's complex, but there are complex issues to tackle.
Just to leave here a few examples, you could use the above function like
this:
{{{#!php
<?php
$callable = apply_filters_typesafe('foo', ['accepted_types' =>
['callable']], '__return_true');
$callable();
}}}
Or:
{{{#!php
<?php
$fs = apply_filters_typesafe('foo', ['accepted_types' =>
['WP_Filesystem_Base']], $fs);
$fs->find_folder('bar');
}}}
Or even:
{{{#!php
<?php
/** @var numeric|null $num */
$num = apply_filters_typesafe('foo', ['nullable' => true], 0);
$int = (int)($num ?? 42);
}}}
--
Ticket URL: <https://core.trac.wordpress.org/ticket/51525#comment:4>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list