class DataFetcher in Typed Data API enhancements 8
Implementation of the data fetcher service.
Hierarchy
- class \Drupal\typed_data\DataFetcher implements DataFetcherInterface
Expanded class hierarchy of DataFetcher
1 string reference to 'DataFetcher'
1 service uses DataFetcher
File
- src/
DataFetcher.php, line 25
Namespace
Drupal\typed_dataView source
class DataFetcher implements DataFetcherInterface {
/**
* {@inheritdoc}
*/
public function fetchDataByPropertyPath(TypedDataInterface $typed_data, $property_path, BubbleableMetadata $bubbleable_metadata = NULL, $langcode = NULL) {
$sub_paths = explode('.', $property_path);
return $this
->fetchDataBySubPaths($typed_data, $sub_paths, $bubbleable_metadata, $langcode);
}
/**
* {@inheritdoc}
*/
public function fetchDataBySubPaths(TypedDataInterface $typed_data, array $sub_paths, BubbleableMetadata $bubbleable_metadata = NULL, $langcode = NULL) {
$current_selector = [];
$bubbleable_metadata = $bubbleable_metadata ?: new BubbleableMetadata();
try {
foreach ($sub_paths as $name) {
$current_selector[] = $name;
// If the current data is just a reference then directly dereference the
// target.
if ($typed_data instanceof DataReferenceInterface) {
$this
->addBubbleableMetadata($typed_data, $bubbleable_metadata);
$typed_data = $typed_data
->getTarget();
if ($typed_data === NULL) {
throw new MissingDataException("The specified reference is NULL.");
}
}
// Make sure we are using the right language.
if (isset($langcode) && $typed_data instanceof TranslatableInterface) {
if ($typed_data
->hasTranslation($langcode)) {
$typed_data = $typed_data
->getTranslation($langcode);
}
// @todo What if the requested translation does not exist? Currently
// we just ignore that and continue with the current object.
}
// If this is a list but the selector is not an integer, we forward the
// selection to the first element in the list.
if ($typed_data instanceof ListInterface && !ctype_digit($name)) {
$this
->addBubbleableMetadata($typed_data, $bubbleable_metadata);
$typed_data = $typed_data
->get(0);
}
// Drill down to the next step in the data selector.
if ($typed_data instanceof ListInterface || $typed_data instanceof ComplexDataInterface) {
$this
->addBubbleableMetadata($typed_data, $bubbleable_metadata);
$typed_data = $typed_data
->get($name);
}
else {
$current_selector_string = implode('.', $current_selector);
throw new InvalidArgumentException("The parent property is not a list or a complex structure at '{$current_selector_string}'.");
}
// If an accessed list item does not exist, $typed_data will be NULL.
if (!isset($typed_data)) {
$selector_string = implode('.', $sub_paths);
$current_selector_string = implode('.', $current_selector);
throw new MissingDataException("Unable to apply data selector '{$selector_string}' at '{$current_selector_string}'");
}
}
$this
->addBubbleableMetadata($typed_data, $bubbleable_metadata);
return $typed_data;
} catch (MissingDataException $e) {
$selector = implode('.', $sub_paths);
$current_selector = implode('.', $current_selector);
throw new MissingDataException("Unable to apply data selector '{$selector}' at '{$current_selector}': " . $e
->getMessage());
} catch (\InvalidArgumentException $e) {
$selector = implode('.', $sub_paths);
$current_selector = implode('.', $current_selector);
throw new InvalidArgumentException("Unable to apply data selector '{$selector}' at '{$current_selector}': " . $e
->getMessage());
}
}
/**
* {@inheritdoc}
*/
public function fetchDefinitionByPropertyPath(DataDefinitionInterface $data_definition, $property_path, $langcode = NULL) {
$sub_paths = explode('.', $property_path);
return $this
->fetchDefinitionBySubPaths($data_definition, $sub_paths, $langcode);
}
/**
* {@inheritdoc}
*/
public function fetchDefinitionBySubPaths(DataDefinitionInterface $data_definition, array $sub_paths, $langcode = NULL) {
$current_selector = [];
foreach ($sub_paths as $name) {
$current_selector[] = $name;
// If the current data is just a reference then directly dereference the
// target.
if ($data_definition instanceof DataReferenceDefinitionInterface) {
$data_definition = $data_definition
->getTargetDefinition();
}
// If this is a list but the selector is not an integer, we forward the
// selection to the first element in the list.
if ($data_definition instanceof ListDataDefinitionInterface && !ctype_digit($name)) {
$data_definition = $data_definition
->getItemDefinition();
}
// Drill down to the next step in the data selector.
if ($data_definition instanceof ComplexDataDefinitionInterface) {
$data_definition = $data_definition
->getPropertyDefinition($name);
}
elseif ($data_definition instanceof ListDataDefinitionInterface) {
$data_definition = $data_definition
->getItemDefinition();
}
else {
$current_selector_string = implode('.', $current_selector);
if (count($current_selector) > 1) {
$parent_property = $current_selector[count($current_selector) - 2];
throw new InvalidArgumentException("The data selector '{$current_selector_string}' cannot be applied because the parent property '{$parent_property}' is not a list or a complex structure");
}
else {
$type = $data_definition
->getDataType();
throw new InvalidArgumentException("The data selector '{$current_selector_string}' cannot be applied because the definition of type '{$type}' is not a list or a complex structure");
}
}
// If an accessed property is not existing, $data_definition will be
// NULL.
if (!isset($data_definition)) {
$selector_string = implode('.', $sub_paths);
$current_selector_string = implode('.', $current_selector);
throw new InvalidArgumentException("Unable to apply data selector '{$selector_string}' at '{$current_selector_string}'");
}
}
return $data_definition;
}
/**
* {@inheritdoc}
*/
public function autocompletePropertyPath(array $data_definitions, $partial_property_path) {
// For the empty string we suggest the names of the data definitions.
if ($partial_property_path == '') {
return array_keys($data_definitions);
}
$results = [];
// Suggest top level variables where the partial path matches the start of
// the top level variable name.
foreach ($data_definitions as $variable_name => $data_definition) {
if (stripos($variable_name, $partial_property_path) === 0) {
$results = array_merge($results, $this
->getAutocompleteSuggestion($data_definition, $variable_name));
}
}
if (!empty($results)) {
return $results;
}
// Partial path now contains more than just a top level variable. In order
// to separate the variable parts we have to account for the syntax of
// global context variables versus local context variables.
// Global context variables begin with '@' and have a colon separating the
// global context from the variable.
$colon = strpos($partial_property_path, ':');
if ($colon === FALSE) {
// This is NOT a global context variable, so we only have to worry about
// the '.' separators.
$parts = explode('.', $partial_property_path);
$first_part = array_shift($parts);
}
else {
// This IS a global context variable, so the entire string up to and
// including the ':' needs to be removed before we split the remainder
// at the '.' separators.
$parts = explode('.', substr($partial_property_path, $colon + 1));
$first_part = substr($partial_property_path, 0, $colon + 1) . array_shift($parts);
}
if (!isset($data_definitions[$first_part])) {
return [];
}
$last_part = array_pop($parts);
$middle_path = implode('.', $parts);
if ($middle_path === '') {
$variable_definition = $data_definitions[$first_part];
}
else {
try {
$variable_definition = $this
->fetchDefinitionByPropertyPath($data_definitions[$first_part], $middle_path);
} catch (InvalidArgumentException $e) {
// Invalid property path, so no suggestions available.
return [];
}
}
// If the current data is just a reference then directly dereference the
// target.
if ($variable_definition instanceof DataReferenceDefinitionInterface) {
$variable_definition = $variable_definition
->getTargetDefinition();
}
if ($variable_definition instanceof ListDataDefinitionInterface) {
// Suggest a couple of example indices of a list if there is nothing
// selected on it yet. Special case for fields: only make the suggestion
// if this is a multi-valued field.
if ($last_part === '' && !($variable_definition instanceof FieldDefinitionInterface && $variable_definition
->getFieldStorageDefinition()
->getCardinality() === 1)) {
if ($middle_path === '') {
$property_path = $first_part;
}
else {
$property_path = "{$first_part}.{$middle_path}";
}
$item_definition = $variable_definition
->getItemDefinition();
for ($i = 0; $i < 3; $i++) {
$results = array_merge($results, $this
->getAutocompleteSuggestion($item_definition, "{$property_path}.{$i}"));
}
}
// If this is a list but the selector is not an integer, we forward the
// selection to the first element in the list.
if (!ctype_digit($last_part)) {
$variable_definition = $variable_definition
->getItemDefinition();
}
}
if ($variable_definition instanceof ComplexDataDefinitionInterface) {
foreach ($variable_definition
->getPropertyDefinitions() as $property_name => $property_definition) {
// If the property starts with the part then we have a suggestion. If
// the part after the dot is the empty string we include all properties.
if (stripos($property_name, $last_part) === 0 || $last_part === '') {
if ($middle_path === '') {
$property_path = "{$first_part}.{$property_name}";
}
else {
$property_path = "{$first_part}.{$middle_path}.{$property_name}";
}
$results = array_merge($results, $this
->getAutocompleteSuggestion($property_definition, $property_path));
}
}
}
usort($results, function ($a, $b) {
return strnatcasecmp($a['value'], $b['value']);
});
return $results;
}
/**
* Generates autocomplete suggestions for a matched data definition.
*
* @param \Drupal\Core\TypedData\DataDefinitionInterface $data_definition
* The data definition to inspect.
* @param string $variable_name
* The variable name or property path.
*
* @return array[]
* A list of autocomplete suggestions - valid property paths for the
* provided data definition. Each entry is an array with the following keys:
* - value: the data selector property path.
* - label: the human readable label suggestion.
*/
protected function getAutocompleteSuggestion(DataDefinitionInterface $data_definition, $variable_name) {
$label = $variable_name;
if ($data_label = $data_definition
->getLabel()) {
$label .= " ({$data_label})";
}
$results[] = [
'value' => $variable_name,
'label' => $label,
];
// If the data definition is just a reference then directly dereference the
// target.
if ($data_definition instanceof DataReferenceDefinitionInterface) {
$data_definition = $data_definition
->getTargetDefinition();
}
if ($data_definition instanceof ListDataDefinitionInterface || $data_definition instanceof ComplexDataDefinitionInterface) {
$label = "{$variable_name}...";
if ($data_label) {
$label .= " ({$data_label})";
}
$results[] = [
'value' => "{$variable_name}.",
'label' => $label,
];
}
return $results;
}
/**
* Adds the bubbleable metadata of the given data.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $data
* The data of which to add the metadata.
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
* The bubbleable metadata to which to add the data.
*/
protected function addBubbleableMetadata(TypedDataInterface $data, BubbleableMetadata $bubbleable_metadata) {
if ($data instanceof PrimitiveInterface) {
// Primitives do not have any metadata attached.
return;
}
$value = $data
->getValue();
if ($value instanceof CacheableDependencyInterface || $value instanceof AttachmentsInterface) {
$bubbleable_metadata
->addCacheableDependency($value);
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DataFetcher:: |
protected | function | Adds the bubbleable metadata of the given data. | |
DataFetcher:: |
public | function |
Provides autocomplete suggestions for an incomplete property path. Overrides DataFetcherInterface:: |
|
DataFetcher:: |
public | function |
Fetches data based upon the given property path. Overrides DataFetcherInterface:: |
|
DataFetcher:: |
public | function |
Fetches data based upon the given sub-paths. Overrides DataFetcherInterface:: |
|
DataFetcher:: |
public | function |
Fetches a data definition based upon the given property path. Overrides DataFetcherInterface:: |
|
DataFetcher:: |
public | function |
Fetches a data definition based upon the given sub-paths. Overrides DataFetcherInterface:: |
|
DataFetcher:: |
protected | function | Generates autocomplete suggestions for a matched data definition. |