WP_Interactivity_API::data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode )

In this article

This function’s access is marked private. This means it is not intended for use by plugin or theme developers, only by core. It is listed here for completeness.

Processes the data-wp-bind directive.

Description

It updates or removes the bound attributes based on the evaluation of its associated reference.

Parameters

$pWP_Interactivity_API_Directives_Processorrequired
The directives processor instance.
$modestringrequired
Whether the processing is entering or exiting the tag.

Source

private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ): void {
	if ( 'enter' === $mode ) {
		$entries = $this->get_directive_entries( $p, 'bind' );
		foreach ( $entries as $entry ) {
			if ( empty( $entry['suffix'] ) || null !== $entry['unique_id'] ) {
				continue;
			}

			// Skip if the suffix is an event handler.
			if ( str_starts_with( $entry['suffix'], 'on' ) ) {
				_doing_it_wrong(
					__METHOD__,
					sprintf(
						/* translators: %s: The directive, e.g. data-wp-on--click. */
						__( 'Binding event handler attributes is not supported. Please use "%s" instead.' ),
						esc_attr( 'data-wp-on--' . substr( $entry['suffix'], 2 ) )
					),
					'6.9.2'
				);
				continue;
			}

			$result = $this->evaluate( $entry );

			/*
			 * An object is resolved to whatever it serializes to. When the reference points to a value stored
			 * in state or context, that is the value the client receives for it when the store is hydrated.
			 * A derived state closure is never serialized, so there the client value comes from the derived
			 * state's client-side implementation instead; the resolution is still applied so that both origins
			 * behave the same. Round-tripping through the JSON encoder rather than calling
			 * JsonSerializable::jsonSerialize() directly keeps this resolution identical to the client's,
			 * including for an object which serializes to another serializable object. When the encoding fails
			 * the object is left in place, to be reported as a usage error below. Note that it rarely does
			 * fail: wp_json_encode() retries through _wp_json_sanity_check(), which rebuilds the object from
			 * its public properties and so ignores jsonSerialize() altogether. An object whose serialized form
			 * JSON cannot represent therefore resolves to whatever that rebuild encodes to, which is what the
			 * client is sent for it as well.
			 *
			 * A throwing JsonSerializable::jsonSerialize() is caught for the same reason the value is checked
			 * at all: a binding must not be able to abort the render. An exception escaping here would leave
			 * `$context_stack` and `$namespace_stack` unrestored for every later `process_directives()` call
			 * on this instance, so the object is treated as one which failed to encode.
			 */
			if ( is_object( $result ) ) {
				try {
					$encoded = wp_json_encode( $result );
				} catch ( Throwable $e ) {
					$encoded = false;
				}
				if ( false !== $encoded ) {
					$result = json_decode( $encoded );
				}
			}

			/*
			 * Only a value which can be sent to the client may be stored in an attribute value. Strings and
			 * booleans are passed in as-is, numbers are formatted, and everything else is rejected as a usage
			 * error.
			 *
			 * An object which does not serialize to a scalar is rejected even when it defines `__toString()`,
			 * which PHP would otherwise coerce for the string parameters of the escaping functions. Its string
			 * representation is not what the client evaluates this reference to, whether that is the form
			 * serialized into the store or the return value of a derived state's client-side implementation,
			 * so the two could disagree once the directive is evaluated during hydration.
			 */
			if ( null !== $result ) {
				if ( ! is_scalar( $result ) ) {
					_doing_it_wrong(
						__METHOD__,
						sprintf(
							/* translators: %s: The attribute name. */
							__( 'Attempted to bind a non-scalar value to the "%s" attribute. Ensure the state/context property or the derived state closure resolves to a string, number, or boolean.' ),
							esc_html( $entry['suffix'] )
						),
						'7.1.0'
					);
					$result = null;
				} elseif ( is_int( $result ) || is_float( $result ) ) {
					/*
					 * A number is formatted by the JSON encoder rather than cast to string, so that the
					 * attribute value matches the number the client receives for this same reference. Casting
					 * a float is locale-dependent before PHP 8.0, and rounds to `precision` rather than to the
					 * encoder's `serialize_precision`.
					 *
					 * This closes the cases which differ in practice, not every one. A float written in
					 * exponent notation still disagrees, since PHP encodes 1e25 as `1.0e+25` where JavaScript
					 * renders it as `1e+25`, as does negative zero, and an integer above the range JavaScript
					 * can represent exactly is rounded once it reaches the client. Casting diverged on all
					 * three as well, so none is a regression.
					 */
					$encoded = wp_json_encode( $result );
					if ( JSON_ERROR_INF_OR_NAN === json_last_error() ) {
						/*
						 * The encoder only rejects INF and NAN, of which JSON can represent neither. When such
						 * a value is stored in state, the store itself also fails to encode in its entirety,
						 * and the client is sent an empty script tag in place of all of its state; only
						 * removing the value from the state resolves that. A derived state closure returning
						 * one never reaches the store, so there only the binding itself is affected.
						 */
						_doing_it_wrong(
							__METHOD__,
							sprintf(
								/* translators: %s: The attribute name. */
								__( 'Attempted to bind a non-finite number to the "%s" attribute. Ensure the state/context property or the derived state closure resolves to a finite number or a string.' ),
								esc_html( $entry['suffix'] )
							),
							'7.1.0'
						);
						$result = null;
					} else {
						$result = $encoded;
					}
				}
			}

			if (
				null !== $result &&
				(
					false !== $result ||
					( strlen( $entry['suffix'] ) > 5 && '-' === $entry['suffix'][4] )
				)
			) {
				/*
				 * If the result of the evaluation is a boolean and the attribute is
				 * `aria-` or `data-, convert it to a string "true" or "false". It
				 * follows the exact same logic as Preact because it needs to
				 * replicate what Preact will later do in the client:
				 * https://github.com/preactjs/preact/blob/ea49f7a0f9d1ff2c98c0bdd66aa0cbc583055246/src/diff/props.js#L131C24-L136
				 */
				if (
					is_bool( $result ) &&
					( strlen( $entry['suffix'] ) > 5 && '-' === $entry['suffix'][4] )
				) {
					$result = $result ? 'true' : 'false';
				}
				$p->set_attribute( $entry['suffix'], $result );
			} else {
				$p->remove_attribute( $entry['suffix'] );
			}
		}
	}
}

Changelog

VersionDescription
7.1.0An object is resolved to whatever it serializes to for the client, a number is formatted by the JSON encoder, and a value which cannot be sent to the client is rejected rather than passed to WP_HTML_Tag_Processor::set_attribute().
6.5.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.

Sponsor
SponsoredKunjungi sekarang
Promo