/**
  * Retrieves the property path of the parent property.
  *
  * @return string|null
  *   The property path of the parent property.
  */
 protected function getParentPath()
 {
     if (!isset($this->parentPath)) {
         $combined_property_path = $this->getCombinedPropertyPath();
         list(, $property_path) = Utility::splitCombinedId($combined_property_path);
         list($this->parentPath) = Utility::splitPropertyPath($property_path);
     }
     return $this->parentPath;
 }
 /**
  * Runs before any fields are rendered.
  *
  * This gives the handlers some time to set up before any handler has
  * been rendered.
  *
  * @param \Drupal\views\ResultRow[] $values
  *   An array of all ResultRow objects returned from the query.
  *
  * @see \Drupal\views\Plugin\views\field\FieldHandlerInterface::preRender()
  */
 public function preRender(&$values)
 {
     // We deal with the properties one by one, always loading the necessary
     // values for any nested properties coming afterwards.
     // @todo This works quite well, but will load each item/entity individually.
     //   Instead, we should exploit the workflow of proceeding by each property
     //   on its own to multi-load as much as possible (maybe even entities of
     //   the same type from different properties).
     // @todo Also, this will unnecessarily load items/entities even if all
     //   required fields are provided in the results. However, to solve this,
     //   expandRequiredProperties() would have to provide more information, or
     //   provide a separate properties list for each row.
     foreach ($this->expandRequiredProperties() as $datasource_id => $properties) {
         foreach ($properties as $property_path => $combined_property_path) {
             // Determine the path of the parent property, and the property key to
             // take from it for this property. If the name is "_object", we just
             // wanted the parent object to be loaded, so we might be done – except
             // when the parent is empty, in which case we wanted to load the
             // original search result, which we haven't done yet.
             list($parent_path, $name) = Utility::splitPropertyPath($property_path);
             if ($parent_path && $name == '_object') {
                 continue;
             }
             // Now go through all rows and add the property to them, if necessary.
             foreach ($values as $i => $row) {
                 // Bail for rows with the wrong datasource for this property, or for
                 // which this field doesn't even apply (which will usually be the
                 // same, though).
                 if ($datasource_id != $row->search_api_datasource || !$this->isActiveForRow($row)) {
                     continue;
                 }
                 // Check whether there are parent objects present. If no, either load
                 // them (in case the parent is the result item itself) or bail.
                 if (empty($row->_relationship_objects[$parent_path])) {
                     if ($parent_path) {
                         continue;
                     } else {
                         $row->_relationship_objects[$parent_path] = array($row->_item->getOriginalObject());
                     }
                 }
                 // If the property key is "_object", we just needed to load the search
                 // result item, so we're now done.
                 if ($name == '_object') {
                     continue;
                 }
                 // Determine whether we want to set field values for this property on
                 // this row. This is the case if the property is one of the explicitly
                 // retrieved properties and not yet set on the result row object.
                 $set_values = isset($this->retrievedProperties[$datasource_id][$property_path]) && !isset($row->{$combined_property_path});
                 if (empty($row->_relationship_objects[$property_path])) {
                     // Iterate over all parent objects to get their typed data for this
                     // property and to extract their values.
                     $row->_relationship_objects[$property_path] = array();
                     foreach ($row->_relationship_objects[$parent_path] as $parent) {
                         // Follow references.
                         while ($parent instanceof DataReferenceInterface) {
                             $parent = $parent->getTarget();
                         }
                         // At this point we need the parent to be a complex item,
                         // otherwise it can't have any children (and thus, our property
                         // can't be present).
                         if (!$parent instanceof ComplexDataInterface) {
                             continue;
                         }
                         // Add the typed data for the property to our relationship objects
                         // for this property path. To treat list properties correctly
                         // regarding possible child properties, add all the list items
                         // individually.
                         try {
                             $typed_data = $parent->get($name);
                             // If the typed data is an entity, check whether the current
                             // user can access it.
                             $value = $typed_data->getValue();
                             if ($value instanceof EntityInterface) {
                                 if (!isset($account)) {
                                     $account = $this->getQuery()->getAccessAccount();
                                 }
                                 if (!$value->access('view', $account)) {
                                     continue;
                                 }
                             }
                             if ($typed_data instanceof ListInterface) {
                                 foreach ($typed_data as $item) {
                                     $row->_relationship_objects[$property_path][] = $item;
                                 }
                             } else {
                                 $row->_relationship_objects[$property_path][] = $typed_data;
                             }
                         } catch (\InvalidArgumentException $e) {
                             // This can easily happen, e.g., when requesting a field that
                             // only exists on a different bundle. Unfortunately, there is no
                             // ComplexDataInterface::hasProperty() method, so we can only
                             // catch and ignore the exception.
                         }
                     }
                 }
                 // Initially the array of values, if we want to set them.
                 if ($set_values) {
                     $row->{$combined_property_path} = array();
                 }
                 // Iterate over the typed data objects, extract their values and set
                 // the relationship objects for the next iteration of the outer loop
                 // over properties.
                 foreach ($row->_relationship_objects[$property_path] as $typed_data) {
                     if ($set_values) {
                         $row->{$combined_property_path}[] = Utility::extractFieldValues($typed_data);
                     }
                 }
                 // If we just set any field values on the result row, clean them up
                 // by merging them together (currently it's an array of arrays, but it
                 // should be just a flat array).
                 if ($set_values && $row->{$combined_property_path}) {
                     $row->{$combined_property_path} = call_user_func_array('array_merge', $row->{$combined_property_path});
                 }
             }
         }
     }
 }