Commit f7b43d31c9d for woocommerce

commit f7b43d31c9d71681eeb0481d27abbd154866d60a
Author: Wesley Rosa <wesleyjrosa@gmail.com>
Date:   Wed May 13 10:15:48 2026 -0300

    Add get_entry_count() to product feed FeedInterface (#64394)

    * Add get_entry_count() to FeedInterface

    Expose the number of rows actually written to the feed so consumers can
    report an accurate count. ProductWalker reports the number of products
    iterated, which overcounts when FeedValidatorInterface::validate_entry()
    silently drops entries before they reach FeedInterface::add_entry().
    Implemented on JsonFileFeed by tracking the count in add_entry().

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    * Reset JsonFileFeed state on start() and correct @since version

    - Reset $entry_count, $file_completed, and $file_url at the top of start()
      so a feed instance can be regenerated without producing invalid JSON
      (carried-over entry_count would emit a leading "," after "[") or
      leaking stale completion state into get_entry_count() / get_file_url().
    - Add a regression test (test_start_resets_state_from_previous_run)
      covering the start -> end -> start -> end flow.
    - Bump @since 10.8.0 -> 10.9.0 on FeedInterface::get_entry_count() to
      match the current dev version (10.9.0-dev), per review feedback.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---------

    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/add-feed-interface-get-entry-count b/plugins/woocommerce/changelog/add-feed-interface-get-entry-count
new file mode 100644
index 00000000000..4fe3c309c44
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-feed-interface-get-entry-count
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add `get_entry_count()` to `FeedInterface` so product feed consumers can report the number of rows actually written to the feed (distinct from the number of products iterated by `ProductWalker`).
diff --git a/plugins/woocommerce/src/Internal/ProductFeed/Feed/FeedInterface.php b/plugins/woocommerce/src/Internal/ProductFeed/Feed/FeedInterface.php
index a363268f650..2420874df6f 100644
--- a/plugins/woocommerce/src/Internal/ProductFeed/Feed/FeedInterface.php
+++ b/plugins/woocommerce/src/Internal/ProductFeed/Feed/FeedInterface.php
@@ -51,4 +51,16 @@ interface FeedInterface {
 	 * @return string|null The URL of the feed file, null if not ready.
 	 */
 	public function get_file_url(): ?string;
+
+	/**
+	 * Get the number of entries that have been added to the feed.
+	 *
+	 * This reflects the rows actually written to the feed, which may be fewer
+	 * than the number of products iterated by `ProductWalker` because the
+	 * validator can silently drop entries before they reach `add_entry()`.
+	 *
+	 * @since 10.9.0
+	 * @return int Number of entries added to the feed.
+	 */
+	public function get_entry_count(): int;
 }
diff --git a/plugins/woocommerce/src/Internal/ProductFeed/Storage/JsonFileFeed.php b/plugins/woocommerce/src/Internal/ProductFeed/Storage/JsonFileFeed.php
index 0b418d09b2f..83a63ef680b 100644
--- a/plugins/woocommerce/src/Internal/ProductFeed/Storage/JsonFileFeed.php
+++ b/plugins/woocommerce/src/Internal/ProductFeed/Storage/JsonFileFeed.php
@@ -27,11 +27,11 @@ class JsonFileFeed implements FeedInterface {
 	public const UPLOAD_DIR = 'product-feeds';

 	/**
-	 * Indicates if there are previous entries in the feed.
+	 * The number of entries added to the feed.
 	 *
-	 * @var bool
+	 * @var int
 	 */
-	private $has_entries = false;
+	private $entry_count = 0;

 	/**
 	 * The base name of the feed file.
@@ -98,6 +98,10 @@ class JsonFileFeed implements FeedInterface {
 	 * @throws Exception If the feed directory cannot be created.
 	 */
 	public function start(): void {
+		$this->entry_count    = 0;
+		$this->file_completed = false;
+		$this->file_url       = null;
+
 		/**
 		 * Allows the current time to be overridden before a feed is stored.
 		 *
@@ -154,16 +158,17 @@ class JsonFileFeed implements FeedInterface {
 			return;
 		}

-		if ( ! $this->has_entries ) {
-			$this->has_entries = true;
-		} else {
-			fwrite( $this->file_handle, ',' );
+		$json = wp_json_encode( $entry );
+		if ( false === $json ) {
+			return;
 		}

-		$json = wp_json_encode( $entry );
-		if ( false !== $json ) {
-			fwrite( $this->file_handle, $json );
+		if ( $this->entry_count > 0 ) {
+			fwrite( $this->file_handle, ',' );
 		}
+
+		fwrite( $this->file_handle, $json );
+		++$this->entry_count;
 	}

 	/**
@@ -184,6 +189,13 @@ class JsonFileFeed implements FeedInterface {
 		$this->file_completed = true;
 	}

+	/**
+	 * {@inheritDoc}
+	 */
+	public function get_entry_count(): int {
+		return $this->entry_count;
+	}
+
 	/**
 	 * {@inheritDoc}
 	 */
diff --git a/plugins/woocommerce/tests/php/src/Internal/ProductFeed/Storage/JsonFileFeedTest.php b/plugins/woocommerce/tests/php/src/Internal/ProductFeed/Storage/JsonFileFeedTest.php
index 21947be02c0..2f13234fd7d 100644
--- a/plugins/woocommerce/tests/php/src/Internal/ProductFeed/Storage/JsonFileFeedTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/ProductFeed/Storage/JsonFileFeedTest.php
@@ -83,6 +83,58 @@ class JsonFileFeedTest extends \WC_Unit_Test_Case {
 		);
 	}

+	/**
+	 * Test that get_entry_count reflects the number of rows written to the feed.
+	 */
+	public function test_get_entry_count_reflects_added_entries() {
+		$feed = new JsonFileFeed( 'test-feed' );
+		$this->assertSame( 0, $feed->get_entry_count() );
+
+		$feed->start();
+		$this->assertSame( 0, $feed->get_entry_count() );
+
+		$feed->add_entry( array( 'name' => 'First' ) );
+		$feed->add_entry( array( 'name' => 'Second' ) );
+		$this->assertSame( 2, $feed->get_entry_count() );
+
+		$feed->end();
+		$this->assertSame( 2, $feed->get_entry_count() );
+	}
+
+	/**
+	 * Test that add_entry does not count entries added before start().
+	 */
+	public function test_get_entry_count_ignores_entries_added_before_start() {
+		$feed = new JsonFileFeed( 'test-feed' );
+		$feed->add_entry( array( 'name' => 'dropped' ) );
+		$this->assertSame( 0, $feed->get_entry_count() );
+	}
+
+	/**
+	 * Test that start() resets state from a previous run so the feed can be regenerated.
+	 */
+	public function test_start_resets_state_from_previous_run() {
+		$feed = new JsonFileFeed( 'test-feed' );
+		$feed->start();
+		$feed->add_entry( array( 'name' => 'First' ) );
+		$feed->add_entry( array( 'name' => 'Second' ) );
+		$feed->end();
+		$this->assertSame( 2, $feed->get_entry_count() );
+
+		$feed->start();
+		$this->assertSame( 0, $feed->get_entry_count() );
+		$this->assertNull( $feed->get_file_path() );
+
+		$feed->add_entry( array( 'name' => 'Only' ) );
+		$feed->end();
+
+		$this->assertSame( 1, $feed->get_entry_count() );
+		$this->assertSame(
+			wp_json_encode( array( array( 'name' => 'Only' ) ) ),
+			file_get_contents( $feed->get_file_path() )
+		);
+	}
+
 	/**
 	 * Test that get_file_url returns null if feed is not completed.
 	 */