diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index 3756249..2b035e1 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php @@ -29,11 +29,18 @@ class Field extends TypedData implements IteratorAggregate, FieldInterface { /** - * The entity field name. + * The typed data namespace. * * @var string */ - protected $name; + protected $namespace; + + /** + * The property path. + * + * @var string + */ + protected $propertyPath; /** * The parent entity. @@ -160,8 +167,14 @@ public function offsetGet($offset) { * @return \Drupal\Core\TypedData\TypedDataInterface */ protected function createItem($value = NULL) { - $context = array('parent' => $this); - return typed_data()->create(array('list' => FALSE) + $this->definition, $value, $context); + return typed_data()->getPropertyInstance($this, 0, $value); + } + + /** + * Implements ListInterface::getItemDefinition(). + */ + public function getItemDefinition() { + return array('list' => FALSE) + $this->definition; } /** @@ -202,14 +215,35 @@ public function count() { * Implements ContextAwareInterface::getName(). */ public function getName() { - return $this->name; + return substr($this->propertyPath, strrpos('.', $this->propertyPath)); } /** - * Implements ContextAwareInterface::setName(). + * Implements ContextAwareInterface::getName(). + */ + public function getNamespace() { + return $this->namespace; + } + + /** + * Implements ContextAwareInterface::getName(). + */ + public function setNamespace($namespace) { + $this->namespace = $namespace; + } + + /** + * Implements ContextAwareInterface::getName(). + */ + public function getPropertyPath() { + return $this->propertyPath; + } + + /** + * Implements ContextAwareInterface::getName(). */ - public function setName($name) { - $this->name = $name; + public function setPropertyPath($property_path) { + $this->propertyPath = $property_path; } /** diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php index 71364ce..d6bffc6 100644 --- a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php +++ b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php @@ -12,6 +12,9 @@ /** * Interface for complex data; i.e. data containing named and typed properties. * + * The name of a property has to be a valid PHP variable name, starting with + * an alphabetic character. + * * This is implemented by entities as well as by field item classes of * entities. * diff --git a/core/lib/Drupal/Core/TypedData/ContextAwareInterface.php b/core/lib/Drupal/Core/TypedData/ContextAwareInterface.php index 41c3031..12876e6 100644 --- a/core/lib/Drupal/Core/TypedData/ContextAwareInterface.php +++ b/core/lib/Drupal/Core/TypedData/ContextAwareInterface.php @@ -23,17 +23,49 @@ public function getName(); /** - * Sets the name of a property or item. + * Returns the typed data namespace of the typed data tree. * - * This method is supposed to be used by the parental data structure in order - * to provide appropriate context only. + * A namespace to identify the current typed data tree, e.g. for the the tree + * of typed data objects of an entity it could be + * Drupal.core.entity.entity_type. + * + * @return string + * The namespace of the typed data tree, or NULL if it is not specified. + */ + public function getNamespace(); + + /** + * Sets the typed data namespace of the typed data tree. + * + * @param string $namespace + * The namespace to set. E.g. Drupal.core.entity.entity_type or NULL. + * + * @see ContextAwareInterface::getNamespace() + */ + public function setNamespace($namespace); + + /** + * Returns the property path of the data. + * + * The trail of property names relative to the root of the typed data tree, + * separated by dots; e.g. 'field_text.0.format'. + * + * @return string + * If the data is a property of some complex data, the name of the property. + * If the data is an item of a list, the name is the numeric position of the + * item in the list, starting with 0. Otherwise, NULL is returned. + */ + public function getPropertyPath(); + + /** + * Sets the property path of the data. * - * @param string $name - * The name to set for a property or item. + * @param string $property_path + * The property path to set. * - * @see ContextAwareInterface::getName() + * @see ContextAwareInterface::getPropertyPath() */ - public function setName($name); + public function setPropertyPath($property_path); /** * Returns the parent data structure; i.e. either complex data or a list. diff --git a/core/lib/Drupal/Core/TypedData/ListInterface.php b/core/lib/Drupal/Core/TypedData/ListInterface.php index 4ce6af0..cdd4487 100644 --- a/core/lib/Drupal/Core/TypedData/ListInterface.php +++ b/core/lib/Drupal/Core/TypedData/ListInterface.php @@ -29,4 +29,12 @@ * TRUE if the list is empty, FALSE otherwise. */ public function isEmpty(); + + /** + * Gets the definition of a contained item. + * + * @return array + * The data definition of contained items. + */ + public function getItemDefinition(); } diff --git a/core/lib/Drupal/Core/TypedData/Type/Language.php b/core/lib/Drupal/Core/TypedData/Type/Language.php index 11cd1d1..a4a90c3 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Language.php +++ b/core/lib/Drupal/Core/TypedData/Type/Language.php @@ -28,11 +28,18 @@ class Language extends TypedData implements TypedDataInterface, ContextAwareInterface { /** - * The name. + * The typed data namespace. * * @var string */ - protected $name; + protected $namespace; + + /** + * The property path. + * + * @var string + */ + protected $propertyPath; /** * The parent data structure. @@ -52,14 +59,35 @@ class Language extends TypedData implements TypedDataInterface, ContextAwareInte * Implements ContextAwareInterface::getName(). */ public function getName() { - return $this->name; + return substr($this->propertyPath, strrpos('.', $this->propertyPath)); } /** - * Implements ContextAwareInterface::setName(). + * Implements ContextAwareInterface::getName(). + */ + public function getNamespace() { + return $this->namespace; + } + + /** + * Implements ContextAwareInterface::getName(). + */ + public function setNamespace($namespace) { + $this->namespace = $namespace; + } + + /** + * Implements ContextAwareInterface::getName(). + */ + public function getPropertyPath() { + return $this->propertyPath; + } + + /** + * Implements ContextAwareInterface::getName(). */ - public function setName($name) { - $this->name = $name; + public function setPropertyPath($property_path) { + $this->propertyPath = $property_path; } /** diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index d7f4ee1..0e03bea 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -7,6 +7,7 @@ namespace Drupal\Core\TypedData; +use InvalidArgumentException; use Drupal\Component\Plugin\PluginManagerBase; use Drupal\Core\Plugin\Discovery\CacheDecorator; use Drupal\Core\Plugin\Discovery\HookDiscovery; @@ -16,6 +17,13 @@ */ class TypedDataManager extends PluginManagerBase { + /** + * An array of typed data property prototypes. + * + * @var array + */ + protected $prototypes = array(); + public function __construct() { $this->discovery = new CacheDecorator(new HookDiscovery('data_type_info'), 'typed_data:types'); $this->factory = new TypedDataFactory($this->discovery); @@ -70,15 +78,6 @@ public function createInstance($plugin_id, array $configuration) { * @param mixed $value * (optional) The data value. If set, it has to match one of the supported * data type format as documented for the data type classes. - * @param array $context - * (optional) An array describing the context of the data object, e.g. its - * name or parent data structure. The context should be passed if a typed - * data object is created as part of a data structure. The following keys - * are supported: - * - name: The name associated with the data. - * - parent: The parent object containing the data. Must be an instance of - * Drupal\Core\TypedData\ComplexDataInterface or - * Drupal\Core\TypedData\ListInterface. * * @return Drupal\Core\TypedData\TypedDataInterface * @@ -93,19 +92,75 @@ public function createInstance($plugin_id, array $configuration) { * @see Drupal\Core\TypedData\Type\Binary * @see Drupal\Core\Entity\Field\EntityWrapper */ - function create(array $definition, $value = NULL, array $context = array()) { + public function create(array $definition, $value = NULL) { $wrapper = $this->factory->createInstance($definition['type'], $definition); if (isset($value)) { $wrapper->setValue($value); } - if ($wrapper instanceof ContextAwareInterface) { - if (isset($context['name'])) { - $wrapper->setName($context['name']); + return $wrapper; + } + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::getInstance(). + */ + public function getInstance(array $options) { + return $this->getPropertyInstance($options['object'], $options['property'], $options['value'], $options['context']); + } + + /** + * Get a typed data instance for a property of a given typed data object. + * + * If the object has a namespace and property path specified, this method + * will use prototyping for fast and efficient instantiation of many property + * objects with the same namespace and property path; e.g., if + * comment.comment_body.0.value is instantiated very often when multiple + * comments are used. + * + * @param ContextAware $object + * The parent typed data object, implementing the ContextAwareInterface and + * either the ListInterface or the ComplexDataInterface. + * @param string $property_name + * The name of the property to instantiate, or '0' to get an item of a list. + * @param mixed $value + * (optional) The data value. If set, it has to match one of the supported + * data type format as documented for the data type classes. + * + * @throws \InvalidArgumentException + * If the passed object does not implement either the ListInterface or the + * ComplexDataInterface. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + * The new property instance. + * + * @see \Drupal\Core\TypedData\Type\create() + */ + public function getPropertyInstance(ContextAwareInterface $object, $property_name, $value = NULL) { + $namespace = $object->getNamespace(); + $property_path = $object->getPropertyPath() . '.' . $property_name; + $key = $namespace . ':' . $property_path; + + if (!$namespace || !isset($this->prototypes[$key])) { + if ($object instanceof ComplexDataInterface) { + $definition = $object->getPropertyDefinition($property_name); + } + elseif ($object instanceof ListInterface) { + $definition = $object->getItemDefinition(); } - if (isset($context['parent'])) { - $wrapper->setParent($context['parent']); + else { + throw new \InvalidArgumentException("The passed object has to either implement the ComplexDatainterface or the Listinterface."); } + $this->prototypes[$key] = $this->create($definition); } - return $wrapper; + $property = clone $this->prototypes[$key]; + + if (isset($value)) { + $property->setValue($value); + } + if ($property instanceof ContextAwareInterface) { + $property->setNamespace($namespace); + $property->setPropertyPath($property_path); + $property->setParent($object); + } + return $property; } }