Problem/Motivation

TraversableTypedDataInterface was introduced for code that needs to generically work with traversable typed data, specifically as a consolidation of commonalities between ListInterface and ComplexDataInterface.

A lot of code, however, does not utilize TraversableTypedDataInterface where appropriate, though.

Some of that is because TraversableTypedDataInterface doesn't provide the generic methods that it shoud.

Proposed resolution

Use TraversableTypedDataInterface wherever it makes sense. Specifically, this is wherever we are dealing with a parent typed data object of another object or where currently we still employ manual checks whether a certain object implements either ListInterface or ComplexDataInterface.

Move get() and isEmpty() up from ListInterface and ComplexDataInterface. These are not API changes.

There is one minor API change that is required, see below.

Remaining tasks

User interface changes

None.

API changes

Introduce a TraversableTypedDataDefinitionInterface with a getElementDefinition() method. This is the consolidation of ListDataDefinitionInterface::getItemDefinition() and ComplexDataDefinitionInterface::getPropertyDefinition(). I didn't remove the old methods to minimize API breakage. This means that only code that uses it's own data definition classes that don't subclass the core ones would break, which is very unlikely.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Task
Issue priority Normal
Unfrozen changes Not unfrozen
Prioritized changes This is not a prioritized change for the beta phase.
Disruption No disruption

Comments

tstoeckler’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new22.32 KB

Here we go.

Had to introduce one minor API change to get this done properly, see the issue summary.

tstoeckler’s picture

StatusFileSize
new23.3 KB

The previous one missed the newly introduced TraversableTypedDataDefinitionInterface. Same patch.

The last submitted patch, 1: 2420067-1-traversable-typed-data.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 2: 2420067-2-traversable-typed-data.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new1.54 KB
new24.85 KB

I wanted to leave changes to the typed config system out of this patch as much as possible (and leave that to #2298687: Sequence and Mapping implement interfaces incorrectly, make them honest about what they support) but this change is necessary apparently.

Status: Needs review » Needs work

The last submitted patch, 5: 2420067-5-traversable-typed-data.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new24.9 KB

Status: Needs review » Needs work

The last submitted patch, 7: 2420067-7-traversable-typed-data.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new1.19 KB
new24.92 KB

Dumb typo

Status: Needs review » Needs work

The last submitted patch, 9: 2420067-9-traversable-typed-data.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new1.27 KB
new26.19 KB

Let's see what I messed up this time.

jose reyero’s picture

This sounds like a good idea to me though I'm not that sure about changing the DataDefinition interfaces. We need also some clarification about what is a Property, what is an Item, what is an Element..

Also some methods that look the same are not actually the same, and I think this is a major DX issue that will be fixed by this patch:

interface ComplexDataInterface extends TraversableTypedDataInterface {
  /**
   * Gets a property object.
   *
   * @param $property_name
   *   The name of the property to get; e.g., 'title' or 'name'.
   *
   * @return \Drupal\Core\TypedData\TypedDataInterface
   *   The property object.
   *
   * @throws \InvalidArgumentException
   *   If an invalid property name is given.
   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
   *   If the complex data structure is unset and no property can be created.
   */
  public function get($property_name);
}

interface ListInterface extends TraversableTypedDataInterface, \ArrayAccess, \Countable {
  /**
   * Returns the item at the specified position in this list.
   *
   * @param int $index
   *   Index of the item to return.
   *
   * @return \Drupal\Core\TypedData\TypedDataInterface|null
   *   The item at the specified position in this list, or NULL if no item
   *   exists at that position.
   *
   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
   *   If the complex data structure is unset and no item can be created.
   */
  public function get($index);
}

Or also, this one that looks like trivial is very confusing.

interface ComplexDataInterface extends TraversableTypedDataInterface {
  /**
   * Determines whether the data structure is empty.
   *
   * @return boolean
   *   TRUE if the data structure is empty, FALSE otherwise.
   */
  public function isEmpty();
}

interface ListInterface extends TraversableTypedDataInterface, \ArrayAccess, \Countable {
  /**
   * Determines whether the list contains any non-empty items.
   *
   * @return boolean
   *   TRUE if the list is empty, FALSE otherwise.
   */
  public function isEmpty();
}

Some handy links to follow this issue:
- ComplexDataInterface,
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21TypedData...
- ListInterface,
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21TypedData...
- DataDefinitionInterface hierarchy, https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21TypedData...

tstoeckler’s picture

So while I agree that they have slightly different semantics and we might not want to use them interchangeably in theory because of that...
...as the patch proves we already are! So, I think it makes sense to be true to ourselves and accept that we need these methods generically. We can certainly improve wording/documentation/... but...

jose reyero’s picture

@tstoeckler,

Oh, no, what I mean is they must be fixed because having same method in similar interfaces with different semantics is really horrible, horrible DX. So we better consolidate all of them in TraversableTypedDataInterface.

So I'm 100% for this patch. I just mean it needs to be done carefully, looking at the different semantics and implementations and actually consolidating the behavior, not only the names of the methods.

One simple example: TraversableTypedDataInterface:get()
- The interface needs to specify exactly what happens (exception?) when we try to get a key that doesn't exist (for which there are two options, that it is not in the data and that it is not defined). Atm this is different for ComplexDataInterface and ListInterface.

TypedData is atm a major mess, and also lacking documentation everywhere about the purpose of each class and it should be fixed.

Status: Needs review » Needs work

The last submitted patch, 11: 2420067-11-traversable-typed-data.patch, failed testing.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

It is good to know that we have such an issue already!
Disclaimer: I have no idea about typed data.

Move get() and isEmpty() up from ListInterface and ComplexDataInterface. These are not API changes.

Well, according to https://secure.php.net/manual/it/class.traversable.php this is for stuff, that can be foreached. Do those things have an empty() function? Also an Iterator doesn't have the concept of random access/checking whether its empty. One question someone could ask is: What is the semantic information of traversable in typed data. For me at least, just by its naming, adding get() / empty() doesn't make sense necessarily.

fago’s picture

I don't think this is something we should do. Reasons:

  • I strongly dislike establishing TraversableTypedDataInterface more as it has no semantic meaning, but highlights a PHP implementation detail of being traversable. (I missed the issue where it was introduced...). I think we should go for #2350597: Extract a common DataContainerInterface for lists and complex data and deprecate TraversableTypedDataInterface instead.
  • I'm wary about introducing the concept of an "element". So then everything is typed data, a list item, and a traversable element. Yet another abstraction you have to grasp and understand.

That said, the need for a comon parent interface for data containers is there. That should have a semantic meaning though and try to avoid introducing new terms that we have to learn/teach.

Oh, no, what I mean is they must be fixed because having same method in similar interfaces with different semantics is really horrible, horrible DX. So we better consolidate all of them in TraversableTypedDataInterface.

Lists and maps are different, I don't see why that makes a horrible DX when a list has items and a map has properties.

#1928868: Typed config incorrectly implements Typed Data interfaces shows that we miss proper support for an ordered, keyed collection of elements, having unknown keys. Maybe it would be best to just add dedicated supported for such a data structure? E.g., would the following make sense to you?

- DataContainer (parent data structure, no metadata nor API for fetching contained elements. Can have utility methods like isEmpty() or toArray())
|--- Complex Data (map with named properties)
|---- List (ordered list of items)
|---- Collection (ordered list of key,value pairs)

Complex data does not qualify as Collection, because the values are of a different type. (Depends on how you define "Collection" exactly). Given that, config schema sequences would be collections, which can be handled as lists also. (Thus the sub-type relationship).

fago’s picture

Giving the whole topic of data structures maps, collections etc. some more thoughts (once again), I figured the following:

- Collections are mostly considered as a very generic data structure, containing things like sets or trees. So it's probably not a good choice for "ordered list of key,value pairs", that's an ordered map.
- Map vs Complex Data and ordering: Our complex data does define whether it's considered ordered or not, what means you have to be able to work with either. Imo, it would be totally fine to have an implementation that is ordered.
But one thing we miss is the definition whether it may contain additional properties or not. E.g., json schema has that "additionalProperties" indicator for objects.
- A config schema sequence would be fine as ordered implementation of complex data, that allows additional properties (and has no metadata about pre-existing properties).

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

fago’s picture

Status: Needs work » Closed (won't fix)

#2350597: Extract a common DataContainerInterface for lists and complex data is about to deprecated TraversableTypedDataInterface, so marking this as won't fix.

tstoeckler’s picture

Status: Closed (won't fix) » Closed (duplicate)

Wow, that wasn't very nice. It's also pretty silly, because #2350597: Extract a common DataContainerInterface for lists and complex data will have to touch all the places that we would have cleaned up here anyway. So basically that one just increased its scope to swallow this one. Sigh...