Problem/Motivation

I was looking at the code in Display Suite today (they already have an 8.x alpha, good job!). They have extended the NodeSearch plugin so that they can use their own display method for the search results.

Great idea, but it turns out the way they had to do it was to duplicate the whole execute() method, just to change the loop where it goes through and formats the results.

This is undesirable.

Proposed resolution

We could easily make this easier for them by separating out the NodeSearch::execute() method into two parts:

a) The part up to $query...->execute() where it finds the results

b) The part after that where it does

foreach ($find as $item)

and processes the results into an array to return, by rendering etc.

This could be done by either making a protected method called something like runTheSearchQuery() and calling that in execute(), or conversely by making a protected method called something like buildTheSearchResultsArrayToReturn() and calling that in execute(). In the first case, DS would call the new run method in their execute() and then proceed to format in a different way, and in the second case, DS would override the build method to format the results differently.

We could also do both -- break up the execute() into those two methods, making it easy for a contrib module to override either piece.

Note: I am not advocating those exact names for the methods. :) Nor do I have a strong opinion on which way is better. I'm going to ask the DS maintainers to comment here. But this would be an easy change that would help DS (and maybe other contrib modules), and also break a long function up into two more understandable pieces.

And it would have no backwards-incompatible API change, so no real downside.

Remaining tasks

Patch in #5 does substantially what is described here: NodeSearch::execute() is separated out into NodeSearch::findResults() and NodeSearch::prepareResults(). Presumably, a plugin like Display Suite's can now override just the prepareResults() step, and leave findResults() alone. Alternatively, a hypothetical plugin that has a different method for searching, but wants the default display of results, could just override the findResults() method.

Beta phase evaluation

-->

Reference: https://www.drupal.org/core/beta-changes
Issue category Task because it is just a small code refactor.
Issue priority Not critical or major.
Disruption This change is not disruptive. No contrib/custom modules will need to change. But it should reduce fragility -- it allows Display Suite and potentially other search-related modules that want to extend the NodeSearch plugin to have less code, because they can either override the "find the results" part or the "format the results" part, without having to copy/paste a lot of code from NodeSearch to handle the other part that they are not overriding.

User interface changes

None.

API changes

Not really. It's all internal to NodeSearch::execute(), which is calling the two new protected methods.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

jhodgdon’s picture

jhodgdon’s picture

Status: Active » Needs review
FileSize
2.33 KB

Here's a simple patch, which I haven't tested but it should work, I hope.

jhodgdon’s picture

I filed a related issue in the Display Suite module asking them to please review this patch and see if it serves their purposes.

Status: Needs review » Needs work

The last submitted patch, 2: 2212335-separate-search-execute.patch, failed testing.

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
2.36 KB

Whoops, perhaps I should have tested it. :) Missed a local variable $keys needed in the 2nd method, so added line

    $keys = $this->keywords;

to prepareResults(). No other changes. Didnt' make an interdiff file for it. Let's see if this one works.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

I guess I never assigned this to myself. It still needs a review.

penyaskito’s picture

Looks like a cool idea.

+++ b/core/modules/node/src/Plugin/Search/NodeSearch.php
@@ -258,10 +268,29 @@ public function execute() {
+    if (!$found) {

Should we strictly compare $found to null?

jhodgdon’s picture

I don't know why that would be necessary. The context is:

+    $results = array();
+    if (!$found) {
+      return $results;
+    }

Any value that evaluates to FALSE would still mean "no results, so return an empty array".

penyaskito’s picture

Status: Needs review » Reviewed & tested by the community

I don't have any other remarks on this. If core maintainers are happy with #9 and #10, this is RTBC for me.

If I understood #2350615: [policy, no patch] What changes can be accepted during the Drupal 8 beta phase? correctly, this is acceptable in the beta period and even could be raised as critical as contrib needs ugly workarounds.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Issue summary: View changes

Thanks! Yes, I think this can be committed. I'll update the issue summary just so it is clear what exactly this patch does. And unassigning myself to avoid "jhodgdon will commit this" confusion.

swentel’s picture

Note: +1 on this commit - I've been working on DS search during DrupalCon and this patch makes our code ten times more cleaner :)

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

How about changing the patch like this (a diff on top of the current patch)?

diff --git a/core/modules/node/src/Plugin/Search/NodeSearch.php b/core/modules/node/src/Plugin/Search/NodeSearch.php
index 0cf8b80..3fdd1fb 100644
--- a/core/modules/node/src/Plugin/Search/NodeSearch.php
+++ b/core/modules/node/src/Plugin/Search/NodeSearch.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Query\SelectExtender;
+use Drupal\Core\Database\StatementInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormStateInterface;
@@ -166,20 +167,20 @@ public function isSearchExecutable() {
    * {@inheritdoc}
    */
   public function execute() {
-    $found = $this->findResults();
-    return $this->prepareResults($found);
+    $results = array();
+    if ($this->isSearchExecutable()) {
+      $results = $this->prepareResults($this->findResults());
+    }
+    return $results;
   }
 
   /**
    * Queries to find search results, and sets status messages.
    *
-   * @return \Drupal\Core\Database\StatementInterface|null
-   *   A search query result, or NULL if the query could not be run.
+   * @return \Drupal\Core\Database\StatementInterface
+   *   A search query result.
    */
   protected function findResults() {
-    if (!$this->isSearchExecutable()) {
-      return NULL;
-    }
     $keys = $this->keywords;
 
     // Build matching conditions.
@@ -265,18 +266,14 @@ protected function findResults() {
   /**
    * Prepares search results for rendering.
    *
-   * @param \Drupal\Core\Database\StatementInterface|null $found
-   *   Results found from the search query, or NULL if query failed.
+   * @param \Drupal\Core\Database\StatementInterface $found
+   *   Results found from the search query.
    *
    * @return array
    *   Array of search result item render arrays (empty array if no results).
    */
-  protected function prepareResults($found) {
+  protected function prepareResults(StatementInterface $found) {
     $results = array();
-    if (!$found) {
-      return $results;
-    }
-
     $node_storage = $this->entityManager->getStorage('node');
     $node_render = $this->entityManager->getViewBuilder('node');
     $keys = $this->keywords;

This way we have less conditions in the protected methods and stronger type hinting - making it easier to override and do the right thing.

jhodgdon’s picture

I kind of like this idea, but Select::execute() method can return NULL (at least, it's documented to return NULL sometimes), so I don't think we'll get as much simplification as you think we will.

jhodgdon’s picture

FileSize
2.46 KB
1.45 KB

OK, trying to move this forward again.

So since Select::execute() can return NULL, I think this is a better way to do something along the lines of #15. Thoughts? Reviews please? It seems pretty simplified to me...

Status: Needs review » Needs work

The last submitted patch, 18: 2212335-separate-search-execute-18.patch, failed testing.

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
2.46 KB

Whoops. findResults vs. findResults().

swentel’s picture

Status: Needs review » Reviewed & tested by the community

I'm fine with this.

alexpott’s picture

re #16 I don't understand what you mean. The point was to not random arrays of stuff between methods but objects with interfaces. The docs on the method say it all:
Results found from the search query, or NULL if query failed.
Well why call the method then? The point was not for simplification or performance it was for

This way we have less conditions in the protected methods and stronger type hinting - making it easier to override and do the right thing.

With the patch in #20 all implementations have to begin with

protected function prepareResults($found) {
  $results = array();
  if (!$found) {
    return $results;
  }

Why?

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
jhodgdon’s picture

RE #22, the Select class itself is documented to sometimes return NULL from its own execute() method. I don't see how to get around checking for it being NULL, assuming that this documentation is correct?

https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!Query!Se...

So I believe I have to check for NULL somewhere... Either it needs to be checked in the findResults() method or the prepareResults() method, and in either case, any plugin extending NodeSearch that overrides the method in question will have to check this.

I personally think it's better to have this at the very top of a method that might be overridden so it's obvious, not buried at the bottom of the other one. Thoughts?

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
2.23 KB
2.9 KB

Sigh. Doh. Here's one more go at this, sorry for misunderstanding again, hope I have it right this time!

jhodgdon’s picture

@swentel, any chance of another review? Thanks!

pwolanin’s picture

Status: Needs review » Reviewed & tested by the community

Looks good

jhodgdon’s picture

Issue summary: View changes

Adding "beta phase" evaluation to summary.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed bd02a26 and pushed to 8.0.x. Thanks!

Thanks for adding the beta evaluation for to the issue summary.

  • alexpott committed bd02a26 on 8.0.x
    Issue #2212335 by jhodgdon: Separate out NodeSearch::execute() into...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.