Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PHPUnit tests for Image Loading Optimization #898

Merged
merged 52 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5183678
Add tests for detection.php and fix prefix on filter
westonruter Dec 5, 2023
b1e6f9d
Rename load tests to hooks tests
westonruter Dec 5, 2023
365bd43
Remove needless reliance on ImagesTestCase
westonruter Dec 5, 2023
2190c28
Add tests for storage/lock.php
westonruter Dec 5, 2023
3b5a049
Add tests for post type; improve validation of parsed URL metric
westonruter Dec 5, 2023
c0ef937
Add stubs for data tests
westonruter Dec 5, 2023
e1a13d6
Add tests for REST API
westonruter Dec 6, 2023
5c83545
Discontinue excluding all of WordPress-Extra sniffs from tests and fi…
westonruter Dec 6, 2023
7d8afd8
Add half of tests for storage/data.php functions
westonruter Dec 6, 2023
00b9ccb
Make ilo_unshift_url_metrics() easier to test
westonruter Dec 7, 2023
c9d41c3
Add test for ilo_unshift_url_metrics() and improve logic
westonruter Dec 7, 2023
ea08eda
Add tests for functions to get sample size and breakpoint max widths
westonruter Dec 7, 2023
0f922f3
Add test for ilo_group_url_metrics_by_breakpoint
westonruter Dec 7, 2023
a6d6997
Add tests for ilo_get_lcp_elements_by_minimum_viewport_widths()
westonruter Dec 7, 2023
1d92756
Add remainint test stubs
westonruter Dec 8, 2023
63136ab
Add tests for 2 optimization functions and improve comments
westonruter Dec 8, 2023
746f463
Fix XPath for foreign elements and add tests for ILO_HTML_Tag_Processor
westonruter Dec 8, 2023
59fb175
Add remaining tests for ILO_HTML_Tag_Processor
westonruter Dec 12, 2023
3e77988
Add test for ilo_needs_url_metric_for_breakpoint and fix stub
westonruter Dec 12, 2023
46080ef
Add two test cases for ilo_construct_preload_links and remove integri…
westonruter Dec 12, 2023
e12b798
Add array shape typing for ilo_construct_preload_links() arg
westonruter Dec 13, 2023
0410678
Add remaining tests for ilo_construct_preload_links()
westonruter Dec 13, 2023
1b16857
Add tests for ilo_optimize_template_output_buffer()
westonruter Dec 13, 2023
9265b9d
Fix REST API endpoint to reject metrics for specific unneeded viewpor…
westonruter Dec 14, 2023
1b219f5
Remove debug code
westonruter Dec 14, 2023
e1ac28e
Remove todo
westonruter Dec 14, 2023
7324ce2
Use ILO prefix for test classes
westonruter Dec 14, 2023
966c5b3
Use ILO prefix for test classes
westonruter Dec 21, 2023
7455a2e
Merge branch 'feature/image-loading-optimization' into add/ilo-tests
westonruter Dec 21, 2023
28db827
Run composer update
westonruter Dec 21, 2023
3709688
Fix Generic.CodeAnalysis.UnusedFunctionParameter
westonruter Dec 21, 2023
1b3b859
Fix filter return type for PHPStan static analysis
westonruter Dec 21, 2023
fd43aaa
Fix sending header() in test_perflab_aea_clean_aea_audit_action
westonruter Dec 21, 2023
df0d09e
Add since tags to ILO_HTML_Tag_Processor methods
westonruter Jan 9, 2024
1fa877c
Use test method prefix consistenty instead of redundant tag
westonruter Jan 9, 2024
3da1363
Remove unnecessary ilo_needs_url_metric_for_breakpoint()
westonruter Jan 9, 2024
2181d88
Revert test changes moved to #924
westonruter Jan 9, 2024
930d4ed
Unrevert 2181d88 for server-timing-tests.php
westonruter Jan 9, 2024
8dd3283
Unrevert 2181d88 for audit-enqueued-assets-test.php
westonruter Jan 10, 2024
bf1bd17
Reset SERVER global after each test
westonruter Jan 10, 2024
54396ec
Unset REQUEST_URI in tests that call go_to
westonruter Jan 10, 2024
492fde9
Merge branch 'feature/image-loading-optimization' of https://github.c…
westonruter Jan 11, 2024
461ca6b
Fix PHPStan errors in tests
westonruter Jan 11, 2024
5c009ba
Run composer update
westonruter Jan 11, 2024
c5053f1
Re-run composer update with PHP 8.1
westonruter Jan 12, 2024
d865bac
Update ilo_verify_url_metrics_storage_nonce() to return bool
westonruter Jan 12, 2024
ed305a9
Move ilo_can_optimize_response() to optimization.php
westonruter Jan 12, 2024
e1c05c7
Merge pull request #932 from WordPress/fix/phpstan-errors
mukeshpanchal27 Jan 12, 2024
8874d07
Merge branch 'trunk' of https://github.com/WordPress/performance into…
westonruter Jan 12, 2024
06ac5fe
Add missing since tag
westonruter Jan 12, 2024
4bb2ca8
Add line break before dataProvider tag
westonruter Jan 12, 2024
d62d710
Add additional line breaks in phpdoc
westonruter Jan 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"require": {
"composer/installers": "~1.0",
"php": ">=7|^8",
"ext-dom": "*",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added because DOMDocument is used in tests now.

"ext-json": "*"
},
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ public function open_tags(): Generator {
yield $tag_name;

// Immediately pop off self-closing tags.
if ( in_array( $tag_name, self::VOID_TAGS, true ) ) {
if (
in_array( $tag_name, self::VOID_TAGS, true )
||
( $p->has_self_closing_flag() && $this->is_foreign_element() )
) {
array_pop( $this->open_stack_tags );
}
Comment on lines -194 to 202
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmsnell: FYI, I'm pretty happy with how has_self_closing_flag and the foreign element aspect of the HTML spec simplified things! Now it can properly get breadcrumbs/XPaths for each SVG/MathML element as well.

} else {
Expand All @@ -192,32 +196,21 @@ public function open_tags(): Generator {
continue;
}

// Since SVG and MathML can have a lot more self-closing/empty tags, potentially pop off the stack until getting to the open tag.
$did_splice = false;
if ( 'SVG' === $tag_name || 'MATH' === $tag_name ) {
$i = array_search( $tag_name, $this->open_stack_tags, true );
if ( false !== $i ) {
array_splice( $this->open_stack_tags, $i );
$did_splice = true;
}
}

if ( ! $did_splice ) {
$popped_tag_name = array_pop( $this->open_stack_tags );
if ( $popped_tag_name !== $tag_name && function_exists( 'wp_trigger_error' ) ) {
wp_trigger_error(
__METHOD__,
esc_html(
sprintf(
/* translators: 1: Popped tag name, 2: Closing tag name */
__( 'Expected popped tag stack element %1$s to match the currently visited closing tag %2$s.', 'performance-lab' ),
$popped_tag_name,
$tag_name
)
$popped_tag_name = array_pop( $this->open_stack_tags );
if ( $popped_tag_name !== $tag_name && function_exists( 'wp_trigger_error' ) ) {
wp_trigger_error(
__METHOD__,
esc_html(
sprintf(
/* translators: 1: Popped tag name, 2: Closing tag name */
__( 'Expected popped tag stack element %1$s to match the currently visited closing tag %2$s.', 'performance-lab' ),
$popped_tag_name,
$tag_name
)
);
}
)
);
}

array_splice( $this->open_stack_indices, count( $this->open_stack_tags ) + 1 );
}
}
Expand All @@ -236,6 +229,20 @@ private function get_breadcrumbs(): Generator {
}
}

/**
* Determines whether currently inside a foreign element (MATH or SVG).
*
westonruter marked this conversation as resolved.
Show resolved Hide resolved
westonruter marked this conversation as resolved.
Show resolved Hide resolved
* @return bool In foreign element.
*/
private function is_foreign_element(): bool {
foreach ( $this->open_stack_tags as $open_stack_tag ) {
mukeshpanchal27 marked this conversation as resolved.
Show resolved Hide resolved
if ( 'MATH' === $open_stack_tag || 'SVG' === $open_stack_tag ) {
return true;
}
}
return false;
}

/**
* Gets XPath for the current open tag.
*
Expand Down
2 changes: 1 addition & 1 deletion modules/images/image-loading-optimization/detection.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function ilo_get_detection_script( string $slug, array $needed_minimum_viewport_
*
* @param int $detection_time_window Detection time window in milliseconds.
*/
$detection_time_window = apply_filters( 'perflab_image_loading_detection_time_window', 5000 );
$detection_time_window = apply_filters( 'ilo_detection_time_window', 5000 );

$detect_args = array(
'serveTime' => microtime( true ) * 1000, // In milliseconds for comparison with `Date.now()` in JavaScript.
Expand Down
28 changes: 16 additions & 12 deletions modules/images/image-loading-optimization/optimization.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,27 @@ function ilo_maybe_add_template_output_buffer_filter() {
* @since n.e.x.t
* @access private
*
* @param array $lcp_images_by_minimum_viewport_widths LCP images keyed by minimum viewport width, amended with attributes key for the IMG attributes.
* @param array<int, array{attributes: array{src?: string, srcset?: string, sizes?: string, crossorigin?: string}}|false> $lcp_elements_by_minimum_viewport_widths LCP images keyed by minimum viewport width, amended with attributes key for the IMG attributes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a personal preference, but I find this notation overkill. This isn't used anywhere in WordPress. If we want to describe more complex shapes like this, we should probably introduce a class for them. This would be safer in any case than using an associative array which by definition takes any arbitrary data.

I think a simple ILO_LCP_Element class or similar would be helpful here. Then this would become ILO_LCP_Element[].

Separate minor question: Why was this renamed to "LCP elements" instead of "LCP images"? Would it ever not be an LCP image? If so, the choice of fields would be a bit odd, since almost all of them are specific to images.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a personal preference, but I find this notation overkill. This isn't used anywhere in WordPress. If we want to describe more complex shapes like this, we should probably introduce a class for them. This would be safer in any case than using an associative array which by definition takes any arbitrary data.

Actually, it is used in WordPress, specifically in the sodium_compat code. Granted, this is not really organic WordPress code. In any case, with precise phpdoc typing here, PHPStan will detect any anomalies with static analysis. So it's trying to gain the same benefits as TypeScript gives.

I think a simple ILO_LCP_Element class or similar would be helpful here. Then this would become ILO_LCP_Element[].

Yeah, I like that idea. However, believe it or not, I was wanting to limit the refactor of the existing code in this PR once the test coverage was completed. With the coverage in place, refactoring will be safer as I'll be able to detect regressions better.

Separate minor question: Why was this renamed to "LCP elements" instead of "LCP images"? Would it ever not be an LCP image? If so, the choice of fields would be a bit odd, since almost all of them are specific to images.

Yes, for example in #914 this is extended to support elements with CSS background images.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I like that idea. However, believe it or not, I was wanting to limit the refactor of the existing code in this PR once the test coverage was completed. With the coverage in place, refactoring will be safer as I'll be able to detect regressions better.

+1, that makes total sense to me. Maybe open an issue already?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just now added it as a todo item on the overview issue.

* @return string Markup for zero or more preload link tags.
*/
function ilo_construct_preload_links( array $lcp_images_by_minimum_viewport_widths ): string {
function ilo_construct_preload_links( array $lcp_elements_by_minimum_viewport_widths ): string {
$preload_links = array();

// This uses a for loop to be able to access the following element within the iteration, using a numeric index.
$minimum_viewport_widths = array_keys( $lcp_images_by_minimum_viewport_widths );
$minimum_viewport_widths = array_keys( $lcp_elements_by_minimum_viewport_widths );
for ( $i = 0, $len = count( $minimum_viewport_widths ); $i < $len; $i++ ) {
$lcp_element = $lcp_images_by_minimum_viewport_widths[ $minimum_viewport_widths[ $i ] ];
if ( false === $lcp_element || empty( $lcp_element['attributes'] ) ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty( $lcp_element['attributes'] ) check is removed here because it is always set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another benefit of using a class to represent LCP elements would be that that would be truly enforced :)

// No LCP element at this breakpoint, so nothing to preload.
$lcp_element = $lcp_elements_by_minimum_viewport_widths[ $minimum_viewport_widths[ $i ] ];
if ( false === $lcp_element ) {
// No supported LCP element at this breakpoint, so nothing to preload.
continue;
}

$img_attributes = $lcp_element['attributes'];
// TODO: Add support for background images.
$attributes = $lcp_element['attributes'];

// Prevent preloading src for browsers that don't support imagesrcset on the link element.
if ( isset( $img_attributes['src'], $img_attributes['srcset'] ) ) {
unset( $img_attributes['src'] );
if ( isset( $attributes['src'], $attributes['srcset'] ) ) {
unset( $attributes['src'] );
}

// Add media query if it's going to be something other than just `min-width: 0px`.
Expand All @@ -60,12 +61,12 @@ function ilo_construct_preload_links( array $lcp_images_by_minimum_viewport_widt
if ( null !== $maximum_viewport_width ) {
$media_query .= sprintf( ' and ( max-width: %dpx )', $maximum_viewport_width );
}
$img_attributes['media'] = $media_query;
$attributes['media'] = $media_query;
}

// Construct preload link.
$link_tag = '<link data-ilo-added-tag rel="preload" fetchpriority="high" as="image"';
foreach ( array_filter( $img_attributes ) as $name => $value ) {
foreach ( array_filter( $attributes ) as $name => $value ) {
// Map img attribute name to link attribute name.
if ( 'srcset' === $name || 'sizes' === $name ) {
$name = 'image' . $name;
Expand Down Expand Up @@ -169,10 +170,13 @@ function ilo_optimize_template_output_buffer( string $buffer ): string {
$processor->remove_attribute( 'fetchpriority' );
}

// TODO: If the image is visible (intersectionRatio!=0) in any of the URL metrics, remove loading=lazy.
// TODO: Conversely, if an image is the LCP element for one breakpoint but not another, add loading=lazy. This won't hurt performance since the image is being preloaded.

// Capture the attributes from the LCP elements to use in preload links.
if ( isset( $lcp_element_minimum_viewport_width_by_xpath[ $xpath ] ) ) {
$attributes = array();
foreach ( array( 'src', 'srcset', 'sizes', 'crossorigin', 'integrity' ) as $attr_name ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The integrity attribute doesn't exist on IMG so it shouldn't be copied.

foreach ( array( 'src', 'srcset', 'sizes', 'crossorigin' ) as $attr_name ) {
$attributes[ $attr_name ] = $processor->get_attribute( $attr_name );
}
foreach ( $lcp_element_minimum_viewport_width_by_xpath[ $xpath ] as $minimum_viewport_width ) {
Expand Down
69 changes: 38 additions & 31 deletions modules/images/image-loading-optimization/storage/data.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,33 +150,40 @@ function ilo_verify_url_metrics_storage_nonce( string $nonce, string $slug ): in
* @since n.e.x.t
* @access private
*
* @param array $url_metrics URL metrics.
* @param array $url_metrics URL metrics. Each URL metric is expected to have a timestamp key.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker for this PR, but I think it would be useful to follow up once this is merged with another PR to introduce a class to represent a URL metric (e.g. ILO_URL_Metric). It would make the code much more intuitive and easier to follow as the fields would be enforced by the class definition.

* @param array $validated_url_metric Validated URL metric. See JSON Schema defined in ilo_register_endpoint().
* @return array Updated URL metrics.
* @param int[] $breakpoints Breakpoint max widths.
* @param int $sample_size Sample size for URL metrics at a given breakpoint.
* @return array Updated URL metrics, with timestamp key added.
*/
function ilo_unshift_url_metrics( array $url_metrics, array $validated_url_metric ): array {
function ilo_unshift_url_metrics( array $url_metrics, array $validated_url_metric, array $breakpoints, int $sample_size ): array {
$validated_url_metric['timestamp'] = microtime( true );
array_unshift( $url_metrics, $validated_url_metric );
$breakpoints = ilo_get_breakpoint_max_widths();
$sample_size = ilo_get_url_metrics_breakpoint_sample_size();
$grouped_url_metrics = ilo_group_url_metrics_by_breakpoint( $url_metrics, $breakpoints );

foreach ( $grouped_url_metrics as &$breakpoint_url_metrics ) {
if ( count( $breakpoint_url_metrics ) > $sample_size ) {

// Sort URL metrics in descending order by timestamp.
usort(
$breakpoint_url_metrics,
static function ( $a, $b ) {
if ( ! isset( $a['timestamp'] ) || ! isset( $b['timestamp'] ) ) {
return 0;
// Make sure there is at most $sample_size number of URL metrics for each breakpoint.
$grouped_url_metrics = array_map(
static function ( $breakpoint_url_metrics ) use ( $sample_size ) {
if ( count( $breakpoint_url_metrics ) > $sample_size ) {

// Sort URL metrics in descending order by timestamp.
usort(
$breakpoint_url_metrics,
static function ( $a, $b ) {
if ( ! isset( $a['timestamp'] ) || ! isset( $b['timestamp'] ) ) {
return 0;
}
return $b['timestamp'] <=> $a['timestamp'];
}
return $b['timestamp'] <=> $a['timestamp'];
}
);
);

$breakpoint_url_metrics = array_slice( $breakpoint_url_metrics, 0, $sample_size );
}
}
// Only keep the sample size of the newest URL metrics.
$breakpoint_url_metrics = array_slice( $breakpoint_url_metrics, 0, $sample_size );
}
return $breakpoint_url_metrics;
},
$grouped_url_metrics
);

return array_merge( ...$grouped_url_metrics );
}
Expand Down Expand Up @@ -266,24 +273,23 @@ function ilo_get_url_metrics_breakpoint_sample_size(): int {
function ilo_group_url_metrics_by_breakpoint( array $url_metrics, array $breakpoints ): array {

// Convert breakpoint max widths into viewport minimum widths.
$viewport_minimum_widths = array_map(
$minimum_viewport_widths = array_map(
static function ( $breakpoint ) {
return $breakpoint + 1;
},
$breakpoints
);

$grouped = array_fill_keys( array_merge( array( 0 ), $viewport_minimum_widths ), array() );
$grouped = array_fill_keys( array_merge( array( 0 ), $minimum_viewport_widths ), array() );

foreach ( $url_metrics as $url_metric ) {
if ( ! isset( $url_metric['viewport']['width'] ) ) {
continue;
}
$viewport_width = $url_metric['viewport']['width'];

$current_minimum_viewport = 0;
foreach ( $viewport_minimum_widths as $viewport_minimum_width ) {
if ( $viewport_width > $viewport_minimum_width ) {
foreach ( $minimum_viewport_widths as $viewport_minimum_width ) {
if ( $url_metric['viewport']['width'] > $viewport_minimum_width ) {
$current_minimum_viewport = $viewport_minimum_width;
} else {
break;
Expand All @@ -299,15 +305,16 @@ static function ( $breakpoint ) {
* Gets the LCP element for each breakpoint.
*
* The array keys are the minimum viewport width required for the element to be LCP. If there are URL metrics for a
* given breakpoint and yet there is no LCP element, then the array value is `false`. If there is an LCP element at the
* breakpoint, then the array value is an array representing that element, including its breadcrumbs. If two adjoining
* breakpoints have the same value, then the latter is dropped.
* given breakpoint and yet there is no supported LCP element, then the array value is `false`. (Currently only IMG is
* a supported LCP element.) If there is a supported LCP element at the breakpoint, then the array value is an array
* representing that element, including its breadcrumbs. If two adjoining breakpoints have the same value, then the
* latter is dropped.
*
* @since n.e.x.t
* @access private
*
* @param array $grouped_url_metrics URL metrics grouped by breakpoint. See `ilo_group_url_metrics_by_breakpoint()`.
* @return array LCP elements keyed by its minimum viewport width. If there is no LCP element at a breakpoint, then `false` is used.
* @return array LCP elements keyed by its minimum viewport width. If there is no supported LCP element at a breakpoint, then `false` is used.
*/
function ilo_get_lcp_elements_by_minimum_viewport_widths( array $grouped_url_metrics ): array {

Expand Down Expand Up @@ -382,8 +389,8 @@ static function ( $lcp_element ) use ( &$prev_lcp_element ) {
* @since n.e.x.t
* @access private
*
* @param array $url_metrics URL metrics.
* @param float $current_time Current time as returned by microtime(true).
* @param array $url_metrics URL metrics.
* @param float $current_time Current time as returned by `microtime(true)`.
* @param int[] $breakpoint_max_widths Breakpoint max widths.
* @param int $sample_size Sample size for viewports in a breakpoint.
* @param int $freshness_ttl Freshness TTL for a URL metric.
Expand Down
46 changes: 41 additions & 5 deletions modules/images/image-loading-optimization/storage/post-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ function ilo_register_url_metrics_post_type() {
* @since n.e.x.t
* @access private
*
* @todo Consider returning post ID instead of WP_Post object.
*
* @param string $slug URL metrics slug.
* @return WP_Post|null Post object if exists.
*/
Expand Down Expand Up @@ -110,7 +112,40 @@ function ilo_parse_stored_url_metrics( WP_Post $post ): array {
);
$url_metrics = array();
}
return $url_metrics;

return array_values(
array_filter(
$url_metrics,
static function ( $url_metric ) use ( $trigger_error ) {
// TODO: If we wanted, we could use the JSON Schema to validate the stored metrics.
$is_valid = (
is_array( $url_metric )
&&
isset(
$url_metric['viewport']['width'],
$url_metric['viewport']['height'],
$url_metric['elements']
)
&&
is_int( $url_metric['viewport']['width'] )
&&
is_array( $url_metric['elements'] )
);

if ( ! $is_valid ) {
$trigger_error(
sprintf(
/* translators: %s is post type slug */
__( 'Unexpected shape to JSON array in post_content of %s post type.', 'performance-lab' ),
ILO_URL_METRICS_POST_TYPE
)
);
}

return $is_valid;
}
)
);
Comment on lines +114 to +146
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes another good case why there should be a class to represent these objects. Validation would belong there, and probably be fewer lines of code as some of those checks would become irrelevant when enforcing proper fields via class properties.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevertheless, the data would be stored as JSON in the database and not as a serialized class. So this validation would still have to be done somewhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I think it could then be done in the class. I would expect a class to e.g. receive this associative array as constructor parameter and then validate it.

}

/**
Expand All @@ -119,13 +154,12 @@ function ilo_parse_stored_url_metrics( WP_Post $post ): array {
* @since n.e.x.t
* @access private
*
* @param string $url URL for the URL metrics. This is used purely as metadata.
* @param string $slug URL metrics slug (computed from query vars).
* @param string $url URL for the URL metrics. This is used purely as metadata.
* @param string $slug URL metrics slug (computed from query vars).
* @param array $validated_url_metric Validated URL metric. See JSON Schema defined in ilo_register_endpoint().
* @return int|WP_Error Post ID or WP_Error otherwise.
*/
function ilo_store_url_metric( string $url, string $slug, array $validated_url_metric ) {
$validated_url_metric['timestamp'] = microtime( true );

// TODO: What about storing a version identifier?
$post_data = array(
Expand All @@ -143,7 +177,9 @@ function ilo_store_url_metric( string $url, string $slug, array $validated_url_m
$url_metrics = array();
}

$url_metrics = ilo_unshift_url_metrics( $url_metrics, $validated_url_metric );
$breakpoints = ilo_get_breakpoint_max_widths();
$sample_size = ilo_get_url_metrics_breakpoint_sample_size();
$url_metrics = ilo_unshift_url_metrics( $url_metrics, $validated_url_metric, $breakpoints, $sample_size );

$post_data['post_content'] = wp_json_encode( $url_metrics, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); // TODO: No need for pretty-printing.

Expand Down
Loading