From 96616cac502b1b65e0558c2c56ce3226a2ce6dab Mon Sep 17 00:00:00 2001
From: Ere Maijala <ere.maijala@helsinki.fi>
Date: Wed, 6 Nov 2019 23:04:22 +0200
Subject: [PATCH] Optimize record collection creation. (#1488)

- Avoids checking for existing records when we know there shouldn't be any duplicates.
- Moves setting source identifier of records inside the collection class to make it faster.
---
 .../VuFindSearch/Backend/AbstractBackend.php  |  5 +---
 .../Response/RecordCollectionFactory.php      |  2 +-
 .../EDS/Response/RecordCollectionFactory.php  |  2 +-
 .../Response/XML/RecordCollectionFactory.php  |  2 +-
 .../Response/RecordCollectionFactory.php      |  2 +-
 .../Response/RecordCollectionFactory.php      |  2 +-
 .../Response/RecordCollectionFactory.php      |  2 +-
 .../Response/Json/RecordCollectionFactory.php |  2 +-
 .../Response/RecordCollectionFactory.php      |  2 +-
 .../Response/XML/RecordCollectionFactory.php  |  2 +-
 .../Response/AbstractRecordCollection.php     | 23 +++++++++++++++---
 .../Response/Json/RecordCollectionTest.php    | 24 +++++++++++++++++++
 12 files changed, 54 insertions(+), 16 deletions(-)

diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/AbstractBackend.php b/module/VuFindSearch/src/VuFindSearch/Backend/AbstractBackend.php
index dff9533c693..758ac6db9e8 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/AbstractBackend.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/AbstractBackend.php
@@ -110,14 +110,11 @@ abstract class AbstractBackend implements BackendInterface, LoggerAwareInterface
      *
      * @param ResponseInterface $response Response
      *
-     * @return void
+     * @return ResponseInterface
      */
     protected function injectSourceIdentifier(RecordCollectionInterface $response)
     {
         $response->setSourceIdentifier($this->identifier);
-        foreach ($response as $record) {
-            $record->setSourceIdentifier($this->identifier);
-        }
         return $response;
     }
 }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/BrowZine/Response/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/BrowZine/Response/RecordCollectionFactory.php
index d9a55f933b4..6de40da175c 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/BrowZine/Response/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/BrowZine/Response/RecordCollectionFactory.php
@@ -100,7 +100,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
         }
         $collection = new $this->collectionClass($response);
         foreach ($response['data'] as $doc) {
-            $collection->add(call_user_func($this->recordFactory, $doc));
+            $collection->add(call_user_func($this->recordFactory, $doc), false);
         }
         return $collection;
     }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/EDS/Response/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/EDS/Response/RecordCollectionFactory.php
index 577e8558e53..6fab79eebce 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/EDS/Response/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/EDS/Response/RecordCollectionFactory.php
@@ -98,7 +98,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
             ?? $response['Records'] ?? [];
 
         foreach ($records as $record) {
-            $collection->add(call_user_func($this->recordFactory, $record));
+            $collection->add(call_user_func($this->recordFactory, $record), false);
         }
         return $collection;
     }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/EIT/Response/XML/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/EIT/Response/XML/RecordCollectionFactory.php
index 9c237a60adb..170774149e2 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/EIT/Response/XML/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/EIT/Response/XML/RecordCollectionFactory.php
@@ -96,7 +96,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
         }
         $collection = new $this->collectionClass($response);
         foreach ($response['docs'] as $doc) {
-            $collection->add(call_user_func($this->recordFactory, $doc));
+            $collection->add(call_user_func($this->recordFactory, $doc), false);
         }
         return $collection;
     }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/LibGuides/Response/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/LibGuides/Response/RecordCollectionFactory.php
index ede139d3023..fada0c10634 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/LibGuides/Response/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/LibGuides/Response/RecordCollectionFactory.php
@@ -100,7 +100,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
         }
         $collection = new $this->collectionClass($response);
         foreach ($response['documents'] as $doc) {
-            $collection->add(call_user_func($this->recordFactory, $doc));
+            $collection->add(call_user_func($this->recordFactory, $doc), false);
         }
         return $collection;
     }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Pazpar2/Response/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/Pazpar2/Response/RecordCollectionFactory.php
index c8acc2dac29..e624040d54e 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/Pazpar2/Response/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/Pazpar2/Response/RecordCollectionFactory.php
@@ -93,7 +93,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
             $response['total'], $response['offset']
         );
         foreach ($response['records'] as $doc) {
-            $collection->add(call_user_func($this->recordFactory, $doc));
+            $collection->add(call_user_func($this->recordFactory, $doc), false);
         }
         return $collection;
     }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Primo/Response/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/Primo/Response/RecordCollectionFactory.php
index 210ca51b7b8..7837856a725 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/Primo/Response/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/Primo/Response/RecordCollectionFactory.php
@@ -100,7 +100,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
         }
         $collection = new $this->collectionClass($response);
         foreach ($response['documents'] as $doc) {
-            $collection->add(call_user_func($this->recordFactory, $doc));
+            $collection->add(call_user_func($this->recordFactory, $doc), false);
         }
         return $collection;
     }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Response/Json/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Response/Json/RecordCollectionFactory.php
index 6ffd4eff6c4..3618d941906 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Response/Json/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/Solr/Response/Json/RecordCollectionFactory.php
@@ -97,7 +97,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
         $collection = new $this->collectionClass($response);
         if (isset($response['response']['docs'])) {
             foreach ($response['response']['docs'] as $doc) {
-                $collection->add(call_user_func($this->recordFactory, $doc));
+                $collection->add(call_user_func($this->recordFactory, $doc), false);
             }
         }
         return $collection;
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/Summon/Response/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/Summon/Response/RecordCollectionFactory.php
index fbfb17f124b..aa319b2d92f 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/Summon/Response/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/Summon/Response/RecordCollectionFactory.php
@@ -100,7 +100,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
         }
         $collection = new $this->collectionClass($response);
         foreach ($response['documents'] as $doc) {
-            $collection->add(call_user_func($this->recordFactory, $doc));
+            $collection->add(call_user_func($this->recordFactory, $doc), false);
         }
         return $collection;
     }
diff --git a/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollectionFactory.php b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollectionFactory.php
index 38a35c4f428..29c727c73c1 100644
--- a/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollectionFactory.php
+++ b/module/VuFindSearch/src/VuFindSearch/Backend/WorldCat/Response/XML/RecordCollectionFactory.php
@@ -100,7 +100,7 @@ class RecordCollectionFactory implements RecordCollectionFactoryInterface
         }
         $collection = new $this->collectionClass($response);
         foreach ($response['docs'] as $doc) {
-            $collection->add(call_user_func($this->recordFactory, $doc));
+            $collection->add(call_user_func($this->recordFactory, $doc), false);
         }
         return $collection;
     }
diff --git a/module/VuFindSearch/src/VuFindSearch/Response/AbstractRecordCollection.php b/module/VuFindSearch/src/VuFindSearch/Response/AbstractRecordCollection.php
index 9320e7f226c..196744af486 100644
--- a/module/VuFindSearch/src/VuFindSearch/Response/AbstractRecordCollection.php
+++ b/module/VuFindSearch/src/VuFindSearch/Response/AbstractRecordCollection.php
@@ -127,6 +127,9 @@ abstract class AbstractRecordCollection implements RecordCollectionInterface
     public function setSourceIdentifier($identifier)
     {
         $this->source = $identifier;
+        foreach ($this->records as $record) {
+            $record->setSourceIdentifier($identifier);
+        }
     }
 
     /**
@@ -142,18 +145,32 @@ abstract class AbstractRecordCollection implements RecordCollectionInterface
     /**
      * Add a record to the collection.
      *
-     * @param RecordInterface $record Record to add
+     * @param RecordInterface $record        Record to add
+     * @param bool            $checkExisting Whether to check for existing record in
+     * the collection (slower, but makes sure there are no duplicates)
      *
      * @return void
      */
-    public function add(RecordInterface $record)
+    public function add(RecordInterface $record, $checkExisting = true)
     {
-        if (!in_array($record, $this->records, true)) {
+        if (!$checkExisting || !$this->has($record)) {
             $this->records[$this->pointer] = $record;
             $this->next();
         }
     }
 
+    /**
+     * Check if the collection contains the given record
+     *
+     * @param RecordInterface $record Record to check
+     *
+     * @return bool
+     */
+    public function has(RecordInterface $record)
+    {
+        return in_array($record, $this->records, true);
+    }
+
     /**
      * Replace a record in the collection.
      *
diff --git a/module/VuFindSearch/tests/unit-tests/src/VuFindTest/Backend/Solr/Response/Json/RecordCollectionTest.php b/module/VuFindSearch/tests/unit-tests/src/VuFindTest/Backend/Solr/Response/Json/RecordCollectionTest.php
index d02b6d932ca..3c4357babe1 100644
--- a/module/VuFindSearch/tests/unit-tests/src/VuFindTest/Backend/Solr/Response/Json/RecordCollectionTest.php
+++ b/module/VuFindSearch/tests/unit-tests/src/VuFindTest/Backend/Solr/Response/Json/RecordCollectionTest.php
@@ -192,4 +192,28 @@ class RecordCollectionTest extends TestCase
         $this->assertTrue(in_array($r2, $final));
         $this->assertTrue(in_array($r3, $final));
     }
+
+    /**
+     * Test that the object handles offsets properly.
+     *
+     * @return void
+     */
+    public function testAdd()
+    {
+        $coll = new RecordCollection(
+            [
+                'response' => ['numFound' => 10, 'start' => 5]
+            ]
+        );
+        $record = $this->createMock(\VuFindSearch\Response\RecordInterface::class);
+        $coll->add($record);
+        for ($i = 0; $i < 4; $i++) {
+            $coll->add($this->createMock(\VuFindSearch\Response\RecordInterface::class));
+        }
+        $this->assertEquals(5, $coll->count());
+        $coll->add($record);
+        $this->assertEquals(5, $coll->count());
+        $coll->add($record, false);
+        $this->assertEquals(6, $coll->count());
+    }
 }
-- 
GitLab