xero-8.x-2.x-dev/src/Form/XeroFormBuilder.php
src/Form/XeroFormBuilder.php
<?php
namespace Drupal\xero\Form;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\TypedData\ComplexDataDefinitionBase;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\ListDataDefinition;
use Drupal\Core\TypedData\ListDataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
/**
* Provides a helper class to create form elements from data typse.
*
* Example usage:
* @code
* // Get form elements required for a line item.
* $formBuilder = \Drupal::service('xero.form_builder');
* $form['lineitems'][] = $formBuilder->getElementFor('xero_line_item');
*
* // Get an autocomplete for account.
* $definition = \Drupal::service('typed_data_manager')->createDefinition('xero_account');
* $form['Account'] = $formBuilder->getElementForDefinition($definition, 'AccountID');
* @endcode
*/
class XeroFormBuilder {
/**
* The Typed Data Manager.
*
* @var \Drupal\Core\TypedData\TypedDataManagerInterface
*/
protected $typedDataManager;
/**
* Initialize.
*
* @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager
* The typed data manager service.
*/
public function __construct(TypedDataManagerInterface $typedDataManager) {
$this->typedDataManager = $typedDataManager;
}
/**
* Create a render element for the given Xero data type plugin.
*
* @param string $plugin_id
* The Xero plugin id.
* @param string $property_name
* (Optional) A property name of the data type to return.
*
* @return array<string, mixed>
* A render array.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function getElementFor($plugin_id, $property_name = '') {
$element = [];
try {
$definition = $this->typedDataManager->createDataDefinition($plugin_id);
if (is_subclass_of($definition, '\Drupal\Core\TypedData\ComplexDataDefinitionBase')) {
if (empty($property_name)) {
// Get all elements for the definition.
$element = $this->getElementsForDefinition($definition);
}
else {
// Get the element for the property of the definition.
$element = $this->getElementForDefinition($definition, $property_name);
}
}
elseif ($definition instanceof ListDataDefinitionInterface) {
$element = $this->getElementsForListDefinition($definition);
}
else {
// Get the element for the definition.
$element_type = $this->getElementTypeFromDefinition($definition);
$element = [
'#type' => $element_type,
'#title' => $definition->getLabel(),
'#description' => $definition->getDescription() ? $definition->getDescription() : '',
];
$element += $this->getAdditionalProperties($element_type, $definition);
}
}
catch (PluginNotFoundException $e) {
throw $e;
}
return $element;
}
/**
* Get a nested form element for a complex data definition with all elements.
*
* All elements being required properties.
*
* @param \Drupal\Core\TypedData\ComplexDataDefinitionBase $parent_definition
* The data definition to populate the element for.
*
* @return array<string, mixed>
* A nested form element.
*/
public function getElementsForDefinition(ComplexDataDefinitionBase $parent_definition) {
$element = [
'#type' => 'container',
'#tree' => TRUE,
'#title' => $parent_definition->getLabel(),
'#description' => $parent_definition->getDescription() ? $parent_definition->getDescription() : '',
];
// Each property definition needs to be analysed to find out the best
// element match.
foreach ($parent_definition->getPropertyDefinitions() as $name => $definition) {
// Read-only properties should not be added to the element.
if (!$definition->isReadOnly()) {
if (is_subclass_of($definition, '\Drupal\Core\TypedData\ComplexDataDefinitionBase')) {
// A complex data definition should recurse.
$element[$name] = $this->getElementsForDefinition($definition);
}
elseif ($definition instanceof ListDataDefinitionInterface) {
// Get the list of elements.
$element[$name] = $this->getElementsForListDefinition($definition);
}
else {
// Get a single element by its parent definition's property name.
$element[$name] = $this->getElementForDefinition($parent_definition, $name);
}
}
}
return $element;
}
/**
* Get the form element for a specific property of a Xero data definition.
*
* @param \Drupal\Core\TypedData\ComplexDataDefinitionBase $parent_definition
* The Xero type definition.
* @param string $name
* The property name to get the element for.
*
* @return array<string, mixed>
* The form element.
*
* @throws \InvalidArgumentException
*/
public function getElementForDefinition(ComplexDataDefinitionBase $parent_definition, $name) {
$element = [];
$definition = $parent_definition->getPropertyDefinition($name);
if (!isset($definition)) {
throw new \InvalidArgumentException('Property not found.');
}
if (is_subclass_of($definition, '\Drupal\Core\TypedData\ComplexDataDefinitionBase')) {
$element = $this->getElementsForDefinition($definition);
}
elseif ($definition instanceof ListDataDefinition) {
$element = $this->getElementsForListDefinition($definition);
}
else {
$element['#type'] = $this->getElementTypeFromDefinition($definition);
$element['#title'] = $definition->getLabel();
$element['#description'] = $definition->getDescription() ? $definition->getDescription() : '';
$element += $this->getAdditionalProperties($element['#type'], $definition, $parent_definition->getDataType());
}
return $element;
}
/**
* Get the elements for a list definition.
*
* @param \Drupal\Core\TypedData\ListDataDefinitionInterface $list_definition
* A list data definition.
*
* @return array<string, mixed>
* The nested form element.
*/
public function getElementsForListDefinition(ListDataDefinitionInterface $list_definition) {
$element = [
'#type' => 'container',
'#tree' => TRUE,
'#title' => $list_definition->getLabel(),
'#description' => $list_definition->getDescription() ? $list_definition->getDescription() : '',
];
$definition = $list_definition->getItemDefinition();
if (is_subclass_of($definition, '\Drupal\Core\TypedData\ComplexDataDefinitionBase')) {
$element[] = $this->getElementsForDefinition($definition);
}
elseif ($definition instanceof ListDataDefinitionInterface) {
$element[] = $this->getElementsForListDefinition($definition);
}
else {
$element_type = $this->getElementTypeFromDefinition($definition);
$element[] = [
'#type' => $element_type,
'#title' => $definition->getLabel(),
'#description' => $definition->getDescription() ? $definition->getDescription() : '',
];
$element += $this->getAdditionalProperties($element_type, $definition, $list_definition->getDataType());
}
return $element;
}
/**
* Get the element type to use for a definition.
*
* @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
* The typed data definition.
*
* @return string
* The element machine name to use.
*/
protected function getElementTypeFromDefinition(DataDefinitionInterface $definition) {
$type = 'textfield';
if ($definition->getDataType() === 'boolean') {
$type = 'checkbox';
}
elseif ($definition->getConstraint('XeroChoiceConstraint')) {
$type = 'select';
}
return $type;
}
/**
* Get any additional form properties to merge into the element.
*
* For a given form element type.
*
* @param string $type
* The form element type property.
* @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
* The data definition.
* @param string $parent_type
* (Optional) The parent data type.
*
* @return array<string, mixed>
* An array of properties to merge into the form element.
*/
protected function getAdditionalProperties($type, DataDefinitionInterface $definition, $parent_type = '') {
$properties = [];
if ($type === 'select') {
// Add the Constraint options to the select element.
$properties['#options'] = array_combine(
$definition->getConstraint('XeroChoiceConstraint')['choices'],
$definition->getConstraint('XeroChoiceConstraint')['choices']
);
}
elseif (array_key_exists('XeroGuidConstraint', $definition->getConstraints())) {
// Add an auto-complete path for data types with guids.
$properties['#autocomplete_route_name'] = 'xero.autocomplete';
$properties['#autocomplete_route_parameters'] = ['type' => $parent_type];
}
if ($definition->isRequired()) {
$element['#required'] = TRUE;
}
if ($definition->isReadOnly()) {
$element['#disabled'] = TRUE;
}
return $properties;
}
}
