Skip to content

BUG: Fatal TypeError in QueryLogger::maybe_add_notice() when $notices is null #4264

@qudwill

Description

@qudwill

Describe the bug

Environment

  • WordPress: 6.9
  • PHP: 8.3.16
  • ElasticPress: 5.3.2
  • Occurs in wp-admin (Plugins page, Dashboard notices)

❌ The problem

A fatal error occurs in wp-admin due to a strict type hint in
ElasticPress\QueryLogger::maybe_add_notice() when the $notices argument is null:

PHP Fatal error: Uncaught TypeError:
ElasticPress\QueryLogger::maybe_add_notice(): Argument #1 ($notices)
must be of type array, null given

Stack trace (trimmed):

ElasticPress\QueryLogger->maybe_add_notice()
WP_Hook->apply_filters()
ElasticPress\AdminNotices->get_notices()
ElasticPress\Dashboard\maybe_notice()

🔍 Root cause

In AdminNotices::get_notices():

$notices = apply_filters( 'ep_admin_notices', $this->notices );

$this->notices can be null at runtime (not initialized yet), so:

  1. apply_filters() starts with null
  2. The filter chain passes null
  3. QueryLogger::maybe_add_notice() is strictly type-hinted as:
    public function maybe_add_notice( array $notices ): array
  4. PHP throws a TypeError before the method body executes

This causes a fatal error and makes the entire wp-admin inaccessible.

✅ Confirmed fix

Normalizing the input inside maybe_add_notice() resolves the issue completely:

public function maybe_add_notice( $notices ): array {
    $notices = is_array( $notices ) ? $notices : [];

    ...
}

This is safe, backward-compatible, and aligns with WordPress’ filter behavior
(filters may receive null).

🎯 Expected behavior

  • wp-admin should never fatal if a filter returns null
  • ElasticPress should defensively handle unexpected filter input
  • The notices system should degrade gracefully

🧪 Status

  • ✅ Issue reproduced
  • ✅ Root cause identified
  • ✅ Fix verified in production
  • ❌ Current versions crash on PHP 8+ without patch

Steps to Reproduce

  1. Install and activate ElasticPress on a WordPress site.
  2. Run the site on PHP 8.3.16.
  3. Ensure ElasticPress admin notices are enabled (default behavior).
  4. Open wp-admin (for example: Plugins → Installed Plugins or Dashboard).
  5. WordPress executes ElasticPress\AdminNotices::get_notices().
  6. $this->notices is null, so apply_filters( 'ep_admin_notices', null ) is executed.
  7. The filter chain reaches QueryLogger::maybe_add_notice() which is strictly typed as:
    public function maybe_add_notice( array $notices ): array
  8. PHP throws a fatal error:
    TypeError: Argument #1 ($notices) must be of type array, null given
  9. wp-admin becomes inaccessible due to the fatal error.

Screenshots, screen recording, code snippet

No response

Environment information

  • WordPress: 6.9
  • PHP: 8.3.16
  • ElasticPress: 5.3.2

WordPress and ElasticPress information

Failed Queries

WordPress

WordPress Environment

wp_version: 6.9
home_url: https://blog.backyardbrains.com
site_url: https://blog.backyardbrains.com
is_multisite: false
theme: Backyard Brains (1.0)
plugins: 301 Redirects (2.79), Akismet Anti-spam: Spam Protection (5.6), Better Search Replace (1.4.10), Database Manager - WP Adminer (4.1.1), ElasticPress (5.3.2), EmbedPress (4.4.8), Embeds for YouTube (5.4), Gallery Custom Links (2.2.8), Image Regenerate & Select Crop (8.1.2), Login by Auth0 (4.6.2), MC4WP: Mailchimp for WordPress (4.10.3), MediaElement.js - HTML5 Audio and Video (4.2.8), Post Gallery (2.3.12), Really Simple Security (9.3.5), Redis Object Cache (2.5.4), Simple Audio Player (0.2), Simple Lightbox (2.9.4), Simple Local Avatars (2.8.3), Site Kit by Google (1.153.0), SVG Support (2.5.14), UpdraftPlus - Backup/Restore (1.25.9), Wordfence Security (8.1.4), WP-PageNavi (2.94.5), WP Migrate Lite (2.7.3), and Yoast SEO (26.7)
revisions: all

Server Environment

php_version: 8.3.16
memory_limit: 40M
timeout: 30

Indexable Content

Backyard Brains — https://blog.backyardbrains.com

post_count: 440
page_count: 4
post_meta_keys: 0
page_meta_keys: 0
total-all-post-types: 0
distinct-meta-keys:

ElasticPress

Settings

host: https://search.backyardbrains.com/
index_prefix:
language: site-default
per_page: 350
network_active: false

Timeouts

request_timeout: 5
index_document_timeout: 15
bulk_request_timeout: 30

Elasticsearch Indices

blogbackyardbrainscom-post-1

health: yellow
status: open
index: blogbackyardbrainscom-post-1
uuid: zQV06eKlS2yl9LYLpleR_g
pri: 5
rep: 1
docs.count: 444
docs.deleted: 30
store.size: 6.4mb
pri.store.size: 6.4mb
dataset.size: 6.4mb
total_fields_limit: 5000
analyzer_language: english
stop_language: english
snowball_language: English

Last Sync

2025/03/06 11:45:21 am

method: WP Dashboard
is_full_sync: Yes
end_date_time: 2025/03/06 11:45:34 am
total_time: 0 hours, 0 minutes, 14 seconds
total: 443
synced: 443
skipped: 0
failed: 0
errors: array (
)
trigger:
final_status: success

Feature Settings

Custom Search Results

active: true
force_inactive: false

Filters

active: true
force_inactive: false
match_type: all

Post Search

active: true
decaying_enabled: 1
force_inactive: false
highlight_enabled: 1
highlight_excerpt: 1
highlight_tag: strong
synonyms_editor_mode: simple
synonyms:

# Defined synonyms.
runner, running shoe, sneaker, tennis shoe, trainer

Defined hyponyms.

blue => blue, aqua, azure, cerulean, cyan, ultramarine

Defined replacements.

supposably => supposedly
flustrated => flustered, frustrated
intensive purposes => intents and purposes


weighting: array (
)

Related Posts

active: true
force_inactive: false

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions