You are here

class StateItem in State Machine 8

Plugin implementation of the 'state' field type.

Plugin annotation


@FieldType(
  id = "state",
  label = @Translation("State"),
  description = @Translation("Stores the current workflow state."),
  default_widget = "options_select",
  default_formatter = "list_default"
)

Hierarchy

Expanded class hierarchy of StateItem

File

src/Plugin/Field/FieldType/StateItem.php, line 29

Namespace

Drupal\state_machine\Plugin\Field\FieldType
View source
class StateItem extends FieldItemBase implements StateItemInterface, OptionsProviderInterface {

  /**
   * The original value, used to validate state changes.
   *
   * @var string
   */
  protected $originalValue;

  /**
   * The transition to apply.
   *
   * @var \Drupal\state_machine\Plugin\Workflow\WorkflowTransition
   */
  protected $transitionToApply;

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return [
      'columns' => [
        'value' => [
          'type' => 'varchar_ascii',
          'length' => 255,
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['value'] = DataDefinition::create('string')
      ->setLabel(t('State'))
      ->setRequired(TRUE);
    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    $constraints = parent::getConstraints();

    // Replace the 'AllowedValuesConstraint' constraint with the 'State' one.
    foreach ($constraints as $key => $constraint) {
      if ($constraint instanceof AllowedValuesConstraint) {
        unset($constraints[$key]);
      }
    }
    $manager = \Drupal::typedDataManager()
      ->getValidationConstraintManager();
    $constraints[] = $manager
      ->create('State', []);
    return $constraints;
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultFieldSettings() {
    return [
      'workflow' => '',
      'workflow_callback' => '',
    ] + parent::defaultFieldSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
    $element = [];

    // Allow the workflow to be changed if it's not determined by a callback.
    if (!$this
      ->getSetting('workflow_callback')) {
      $workflow_manager = \Drupal::service('plugin.manager.workflow');
      $workflows = $workflow_manager
        ->getGroupedLabels($this
        ->getEntity()
        ->getEntityTypeId());
      $element['workflow'] = [
        '#type' => 'select',
        '#title' => $this
          ->t('Workflow'),
        '#options' => $workflows,
        '#default_value' => $this
          ->getSetting('workflow'),
        '#required' => TRUE,
      ];
    }
    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {

    // Note that in this field's case the value will never be empty
    // because of the default returned in applyDefaultValue().
    return $this->value === NULL || $this->value === '';
  }

  /**
   * {@inheritdoc}
   */
  public function applyDefaultValue($notify = TRUE) {
    if ($workflow = $this
      ->getWorkflow()) {
      $states = $workflow
        ->getStates();
      $initial_state = reset($states);
      $this
        ->setValue([
        'value' => $initial_state
          ->getId(),
      ], $notify);
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setValue($values, $notify = TRUE) {
    if (empty($this->originalValue)) {

      // If no array is given, then the method received just the state value.
      if (isset($values) && !is_array($values)) {
        $values = [
          'value' => $values,
        ];
      }

      // Track the original field value to allow isValid() to validate changes
      // and to react to transitions.
      $this->originalValue = $values['value'];
    }
    parent::setValue($values, $notify);
  }

  /**
   * {@inheritdoc}
   */
  public function isValid() {
    $allowed_states = $this
      ->getAllowedStates($this->originalValue);
    return isset($allowed_states[$this->value]);
  }

  /**
   * {@inheritdoc}
   */
  public function getPossibleValues(AccountInterface $account = NULL) {
    return array_keys($this
      ->getPossibleOptions($account));
  }

  /**
   * {@inheritdoc}
   */
  public function getPossibleOptions(AccountInterface $account = NULL) {
    $workflow = $this
      ->getWorkflow();
    if (!$workflow) {

      // The workflow is not known yet, the field is probably being created.
      return [];
    }
    $state_labels = array_map(function (WorkflowState $state) {
      return $state
        ->getLabel();
    }, $workflow
      ->getStates());
    return $state_labels;
  }

  /**
   * {@inheritdoc}
   */
  public function getSettableValues(AccountInterface $account = NULL) {
    return array_keys($this
      ->getSettableOptions($account));
  }

  /**
   * {@inheritdoc}
   */
  public function getSettableOptions(AccountInterface $account = NULL) {

    // $this->value is unpopulated due to https://www.drupal.org/node/2629932
    $field_name = $this
      ->getFieldDefinition()
      ->getName();
    $value = $this
      ->getEntity()
      ->get($field_name)->value;
    $allowed_states = $this
      ->getAllowedStates($value);
    $state_labels = array_map(function (WorkflowState $state) {
      return $state
        ->getLabel();
    }, $allowed_states);
    return $state_labels;
  }

  /**
   * Gets the next allowed states for the given field value.
   *
   * @param string $value
   *   The field value, representing the state ID.
   *
   * @return \Drupal\state_machine\Plugin\Workflow\WorkflowState[]
   *   The allowed states.
   */
  protected function getAllowedStates($value) {
    $workflow = $this
      ->getWorkflow();
    if (!$workflow) {

      // The workflow is not known yet, the field is probably being created.
      return [];
    }
    $allowed_states = [];
    if (!empty($value) && ($current_state = $workflow
      ->getState($value))) {
      $allowed_states[$value] = $current_state;
    }
    $transitions = $workflow
      ->getAllowedTransitions($value, $this
      ->getEntity());
    foreach ($transitions as $transition) {
      $state = $transition
        ->getToState();
      $allowed_states[$state
        ->getId()] = $state;
    }
    return $allowed_states;
  }

  /**
   * {@inheritdoc}
   */
  public function getWorkflow() {
    if ($callback = $this
      ->getSetting('workflow_callback')) {
      $workflow_id = call_user_func($callback, $this
        ->getEntity());
    }
    else {
      $workflow_id = $this
        ->getSetting('workflow');
    }
    if (empty($workflow_id)) {
      return FALSE;
    }
    $workflow_manager = \Drupal::service('plugin.manager.workflow');
    return $workflow_manager
      ->createInstance($workflow_id);
  }

  /**
   * {@inheritdoc}
   */
  public function getOriginalId() {
    return $this->originalValue;
  }

  /**
   * {@inheritdoc}
   */
  public function getId() {
    return $this->value;
  }

  /**
   * {@inheritdoc}
   */
  public function getLabel() {
    return $this
      ->getStateLabel($this->value);
  }

  /**
   * {@inheritdoc}
   */
  public function getOriginalLabel() {
    return $this
      ->getStateLabel($this->originalValue);
  }

  /**
   * Gets the state label for the given state ID.
   *
   * @param string $state_id
   *   The state ID.
   *
   * @return string
   *   The state label.
   */
  protected function getStateLabel($state_id) {
    $label = $state_id;
    if ($workflow = $this
      ->getWorkflow()) {
      $state = $workflow
        ->getState($state_id);
      if ($state) {
        $label = $state
          ->getLabel();
      }
    }
    return $label;
  }

  /**
   * {@inheritdoc}
   */
  public function getTransitions() {
    $transitions = [];
    if ($workflow = $this
      ->getWorkflow()) {
      $transitions = $workflow
        ->getAllowedTransitions($this->value, $this
        ->getEntity());
    }
    return $transitions;
  }

  /**
   * {@inheritdoc}
   */
  public function isTransitionAllowed($transition_id) {
    $allowed_transitions = $this
      ->getTransitions();
    return isset($allowed_transitions[$transition_id]);
  }

  /**
   * {@inheritdoc}
   */
  public function applyTransition(WorkflowTransition $transition) {
    if (!$this
      ->isTransitionAllowed($transition
      ->getId())) {
      throw new \InvalidArgumentException(sprintf('The transition "%s" is currently not allowed. (Current state: "%s".)', $transition
        ->getId(), $this
        ->getId()));
    }

    // Store the transition to apply, to ensure we're applying the requested
    // transition instead of guessing based on the original state.
    $this->transitionToApply = $transition;
    $this
      ->setValue([
      'value' => $transition
        ->getToState()
        ->getId(),
    ]);
  }

  /**
   * {@inheritdoc}
   */
  public function applyTransitionById($transition_id) {
    $transition = NULL;
    if ($workflow = $this
      ->getWorkflow()) {
      $transition = $workflow
        ->getTransition($transition_id);
    }
    if (!$transition) {
      throw new \InvalidArgumentException(sprintf('Unknown transition ID "%s".', $transition_id));
    }
    $this
      ->applyTransition($transition);
  }

  /**
   * {@inheritdoc}
   */
  public function preSave() {
    if ($this->value != $this->originalValue) {
      $this
        ->dispatchTransitionEvent('pre_transition');
    }
  }

  /**
   * {@inheritdoc}
   */
  public function postSave($update) {
    if ($this->value != $this->originalValue) {
      $this
        ->dispatchTransitionEvent('post_transition');
    }
    $this->originalValue = $this->value;

    // Nullify the transition to apply, to ensure the next entity save
    // doesn't trigger the same transition by mistake.
    $this->transitionToApply = NULL;
  }

  /**
   * Dispatches a transition event for the given phase.
   *
   * @param string $phase
   *   The phase: pre_transition OR post_transition.
   */
  protected function dispatchTransitionEvent($phase) {

    /** @var \Drupal\state_machine\Plugin\Workflow\WorkflowInterface $workflow */
    $workflow = $this
      ->getWorkflow();
    $transition = $this->transitionToApply ?? $workflow
      ->findTransition($this->originalValue, $this->value);
    if ($transition) {
      $field_name = $this
        ->getFieldDefinition()
        ->getName();
      $group_id = $workflow
        ->getGroup();
      $transition_id = $transition
        ->getId();
      $event_dispatcher = \Drupal::getContainer()
        ->get('event_dispatcher');
      $event = new WorkflowTransitionEvent($transition, $workflow, $this
        ->getEntity(), $field_name);
      $events = [
        // For example: 'commerce_order.place.pre_transition'.
        $group_id . '.' . $transition_id . '.' . $phase,
        // For example: 'commerce_order.pre_transition'.
        $group_id . '.' . $phase,
        // For example: 'state_machine.pre_transition'.
        'state_machine.' . $phase,
      ];
      foreach ($events as $event_id) {
        $event_dispatcher
          ->dispatch($event_id, $event);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {

    // Attempt to determine the right workflow to use.
    if ($callback = $field_definition
      ->getSetting('workflow_callback')) {
      $entity_type_id = $field_definition
        ->getTargetEntityTypeId();
      $entity_storage = \Drupal::entityTypeManager()
        ->getStorage($entity_type_id);
      if (!$entity_storage instanceof ContentEntityStorageInterface) {
        return [];
      }
      $values = [];

      // Attempt to create a sample entity with at least the bundle set.
      if ($bundle_key = $entity_storage
        ->getEntityType()
        ->getKey('bundle')) {
        if ($field_definition
          ->getTargetBundle()) {
          $bundle = $field_definition
            ->getTargetBundle();
        }
        else {
          $bundle_ids = \Drupal::service('entity_type.bundle.info')
            ->getBundleInfo($entity_type_id);
          $bundle = array_rand($bundle_ids);
        }
        $values[$bundle_key] = $bundle;
      }
      $entity = $entity_storage
        ->create($values);
      $workflow_id = call_user_func($callback, $entity);
    }
    else {
      $workflow_id = $field_definition
        ->getSetting('workflow');
    }

    // The workflow could not be determined, cannot generate a sample value.
    if (empty($workflow_id)) {
      return [];
    }

    /** @var \Drupal\state_machine\WorkflowManagerInterface $workflow_manager */
    $workflow_manager = \Drupal::service('plugin.manager.workflow');

    /** @var \Drupal\state_machine\Plugin\Workflow\WorkflowInterface $workflow */
    $workflow = $workflow_manager
      ->createInstance($workflow_id);

    // Select states that allow at least one transition.
    $candidate_states = $states = $workflow
      ->getStates();
    foreach ($candidate_states as $key => $candidate) {
      if (empty($workflow
        ->getPossibleTransitions($candidate
        ->getId()))) {
        unset($states[$key]);
      }
    }
    $random_state = array_rand($states);
    $values = [
      'value' => $states[$random_state]
        ->getId(),
    ];
    return $values;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
FieldItemBase::calculateDependencies public static function Calculates dependencies for field items. Overrides FieldItemInterface::calculateDependencies 2
FieldItemBase::calculateStorageDependencies public static function Calculates dependencies for field items on the storage level. Overrides FieldItemInterface::calculateStorageDependencies 1
FieldItemBase::defaultStorageSettings public static function Defines the storage-level settings for this plugin. Overrides FieldItemInterface::defaultStorageSettings 10
FieldItemBase::delete public function Defines custom delete behavior for field values. Overrides FieldItemInterface::delete 2
FieldItemBase::deleteRevision public function Defines custom revision delete behavior for field values. Overrides FieldItemInterface::deleteRevision
FieldItemBase::fieldSettingsFromConfigData public static function Returns a settings array in the field type's canonical representation. Overrides FieldItemInterface::fieldSettingsFromConfigData 1
FieldItemBase::fieldSettingsToConfigData public static function Returns a settings array that can be stored as a configuration value. Overrides FieldItemInterface::fieldSettingsToConfigData 1
FieldItemBase::getEntity public function Gets the entity that field belongs to. Overrides FieldItemInterface::getEntity
FieldItemBase::getFieldDefinition public function Gets the field definition. Overrides FieldItemInterface::getFieldDefinition
FieldItemBase::getLangcode public function Gets the langcode of the field values held in the object. Overrides FieldItemInterface::getLangcode
FieldItemBase::getSetting protected function Returns the value of a field setting.
FieldItemBase::getSettings protected function Returns the array of field settings.
FieldItemBase::mainPropertyName public static function Returns the name of the main property, if any. Overrides FieldItemInterface::mainPropertyName 8
FieldItemBase::onDependencyRemoval public static function Informs the plugin that a dependency of the field will be deleted. Overrides FieldItemInterface::onDependencyRemoval 1
FieldItemBase::storageSettingsForm public function Returns a form for the storage-level settings. Overrides FieldItemInterface::storageSettingsForm 8
FieldItemBase::storageSettingsFromConfigData public static function Returns a settings array in the field type's canonical representation. Overrides FieldItemInterface::storageSettingsFromConfigData 2
FieldItemBase::storageSettingsToConfigData public static function Returns a settings array that can be stored as a configuration value. Overrides FieldItemInterface::storageSettingsToConfigData 2
FieldItemBase::view public function Returns a renderable array for a single field item. Overrides FieldItemInterface::view
FieldItemBase::writePropertyValue protected function Different to the parent Map class, we avoid creating property objects as far as possible in order to optimize performance. Thus we just update $this->values if no property object has been created yet. Overrides Map::writePropertyValue
FieldItemBase::__construct public function Constructs a TypedData object given its definition and context. Overrides TypedData::__construct 1
FieldItemBase::__get public function Magic method: Gets a property value. Overrides FieldItemInterface::__get 2
FieldItemBase::__isset public function Magic method: Determines whether a property is set. Overrides FieldItemInterface::__isset
FieldItemBase::__set public function Magic method: Sets a property value. Overrides FieldItemInterface::__set 1
FieldItemBase::__unset public function Magic method: Unsets a property. Overrides FieldItemInterface::__unset
Map::$definition protected property The data definition. Overrides TypedData::$definition
Map::$properties protected property The array of properties.
Map::$values protected property An array of values for the contained properties.
Map::get public function Gets a property object. Overrides ComplexDataInterface::get
Map::getIterator public function
Map::getProperties public function Gets an array of property objects. Overrides ComplexDataInterface::getProperties
Map::getString public function Returns a string representation of the data. Overrides TypedData::getString
Map::getValue public function Gets the data value. Overrides TypedData::getValue 1
Map::onChange public function Overrides TraversableTypedDataInterface::onChange 4
Map::set public function Sets a property value. Overrides ComplexDataInterface::set
Map::toArray public function Returns an array of all property values. Overrides ComplexDataInterface::toArray 1
Map::__clone public function Magic method: Implements a deep clone.
StateItem::$originalValue protected property The original value, used to validate state changes.
StateItem::$transitionToApply protected property The transition to apply.
StateItem::applyDefaultValue public function Applies the default value. Overrides Map::applyDefaultValue
StateItem::applyTransition public function Applies the given transition, changing the current state. Overrides StateItemInterface::applyTransition
StateItem::applyTransitionById public function Applies a transition with the given ID, changing the current state. Overrides StateItemInterface::applyTransitionById
StateItem::defaultFieldSettings public static function Defines the field-level settings for this plugin. Overrides FieldItemBase::defaultFieldSettings
StateItem::dispatchTransitionEvent protected function Dispatches a transition event for the given phase.
StateItem::fieldSettingsForm public function Returns a form for the field-level settings. Overrides FieldItemBase::fieldSettingsForm
StateItem::generateSampleValue public static function Generates placeholder field values. Overrides FieldItemBase::generateSampleValue
StateItem::getAllowedStates protected function Gets the next allowed states for the given field value.
StateItem::getConstraints public function Gets a list of validation constraints. Overrides TypedData::getConstraints
StateItem::getId public function Gets the current state ID. Overrides StateItemInterface::getId
StateItem::getLabel public function Gets the label of the current state. Overrides StateItemInterface::getLabel
StateItem::getOriginalId public function Gets the original state ID. Overrides StateItemInterface::getOriginalId
StateItem::getOriginalLabel public function Gets the label of the original state. Overrides StateItemInterface::getOriginalLabel
StateItem::getPossibleOptions public function Returns an array of possible values with labels for display. Overrides OptionsProviderInterface::getPossibleOptions
StateItem::getPossibleValues public function Returns an array of possible values. Overrides OptionsProviderInterface::getPossibleValues
StateItem::getSettableOptions public function Returns an array of settable values with labels for display. Overrides OptionsProviderInterface::getSettableOptions
StateItem::getSettableValues public function Returns an array of settable values. Overrides OptionsProviderInterface::getSettableValues
StateItem::getStateLabel protected function Gets the state label for the given state ID.
StateItem::getTransitions public function Gets the allowed transitions for the current state. Overrides StateItemInterface::getTransitions
StateItem::getWorkflow public function Gets the workflow used by the field. Overrides StateItemInterface::getWorkflow
StateItem::isEmpty public function Determines whether the data structure is empty. Overrides Map::isEmpty
StateItem::isTransitionAllowed public function Gets whether the given transition is allowed. Overrides StateItemInterface::isTransitionAllowed
StateItem::isValid public function Gets whether the current state is valid. Overrides StateItemInterface::isValid
StateItem::postSave public function Defines custom post-save behavior for field values. Overrides FieldItemBase::postSave
StateItem::preSave public function Defines custom presave behavior for field values. Overrides FieldItemBase::preSave
StateItem::propertyDefinitions public static function Defines field item properties. Overrides FieldItemInterface::propertyDefinitions
StateItem::schema public static function Returns the schema for the field. Overrides FieldItemInterface::schema
StateItem::setValue public function Sets the data value. Overrides FieldItemBase::setValue
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TypedData::$name protected property The property name.
TypedData::$parent protected property The parent typed data object.
TypedData::createInstance public static function Constructs a TypedData object given its definition and context. Overrides TypedDataInterface::createInstance
TypedData::getDataDefinition public function Gets the data definition. Overrides TypedDataInterface::getDataDefinition
TypedData::getName public function Returns the name of a property or item. Overrides TypedDataInterface::getName
TypedData::getParent public function Returns the parent data structure; i.e. either complex data or a list. Overrides TypedDataInterface::getParent
TypedData::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition
TypedData::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
TypedData::getPropertyPath public function Returns the property path of the data. Overrides TypedDataInterface::getPropertyPath
TypedData::getRoot public function Returns the root of the typed data tree. Overrides TypedDataInterface::getRoot
TypedData::setContext public function Sets the context of a property or item via a context aware parent. Overrides TypedDataInterface::setContext
TypedData::validate public function Validates the currently set data value. Overrides TypedDataInterface::validate
TypedDataTrait::$typedDataManager protected property The typed data manager used for creating the data types.
TypedDataTrait::getTypedDataManager public function Gets the typed data manager. 2
TypedDataTrait::setTypedDataManager public function Sets the typed data manager. 2