You are here

class StockLevel in Commerce Stock 8

Plugin implementation of the 'commerce_stock_field' field type.

Plugin annotation


@FieldType(
  id = "commerce_stock_level",
  label = @Translation("Stock level"),
  module = "commerce_stock_field",
  description = @Translation("Stock level"),
  default_widget = "commerce_stock_level_simple_transaction",
  default_formatter = "commerce_stock_level_simple",
  cardinality = 1,
)

Hierarchy

Expanded class hierarchy of StockLevel

1 file declares its use of StockLevel
StockLevelTest.php in modules/field/tests/src/Kernel/StockLevelTest.php

File

modules/field/src/Plugin/Field/FieldType/StockLevel.php, line 26

Namespace

Drupal\commerce_stock_field\Plugin\Field\FieldType
View source
class StockLevel extends FieldItemBase {
  use ContextCreatorTrait;

  /**
   * {@inheritdoc}
   *
   * Originally we had to define a real db field, because cores implementation
   * of computed fields was brittle. During development of the module, we
   * found, that we can "misuse" this to provide the possibility to enter
   * initial stock values for newly created product variations.
   *
   * Currently we use the column 'value' for exactly this one purpose. Don't get
   * fooled by this. The calculation of the stock level is transaction based.
   * The transactions have their own table.
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return [
      'columns' => [
        'value' => [
          'type' => 'numeric',
          'size' => 'normal',
          'precision' => 19,
          'scale' => 4,
          'not null' => FALSE,
        ],
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['value'] = DataDefinition::create('float')
      ->setLabel(t('Available stock'));
    $properties['available_stock'] = DataDefinition::create('float')
      ->setLabel(t('Available stock'))
      ->setComputed(TRUE)
      ->setInternal(FALSE)
      ->setReadOnly(TRUE)
      ->setClass('Drupal\\commerce_stock_field\\StockLevelProcessor')
      ->setSetting('stock level', 'summary');
    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    $value = $this
      ->get('value')
      ->getValue();
    return $value === NULL;
  }

  /**
   * @inheritdoc
   *
   * This updates the stock based on parameters set by the stock widget.
   *
   * For computed fields we didn't find a chance to trigger the transaction,
   * other than in ::setValue(). ::postSave() is not called for computed fields.
   *
   * If you pass in a single value programmatically, note that we do not support
   * the setting of a absolute stock levels here. We assume a stock adjustment
   * if we get a singe value here. As usual a negative value decreases the
   * stock level and a positive value increases the stock level.
   *
   * @throws \InvalidArgumentException
   *   In case of a invalid stock level value.
   */
  public function setValue($values, $notify = TRUE) {

    // Supports absolute values being passed in directly, i.e.
    // programmatically.
    if (!is_array($values)) {
      $value = filter_var($values, FILTER_VALIDATE_FLOAT);
      if ($value !== FALSE) {
        $values = [
          'adjustment' => $value,
        ];
      }
      else {
        throw new \InvalidArgumentException('Values passed to the commerce stock level field must be floats');
      }
    }

    // Set the value so it is not recognized as empty by isEmpty() and
    // postSave() is called.
    if (isset($values['value'])) {
      $values['value'] = $values['value'];
    }
    elseif (isset($values['adjustment'])) {
      $values['value'] = $values['adjustment'];
    }
    else {
      $values['value'] = 0.0;
    }
    parent::setValue($values, $notify);
  }

  /**
   * {@inheritdoc}
   */
  public function postSave($update) {

    // Retrieve entity and saved stock.
    $entity = $this
      ->getEntity();
    $values = $entity->{$this
      ->getFieldDefinition()
      ->getName()}
      ->getValue();
    $values = reset($values);

    // Create transaction.
    $this
      ->createTransaction($entity, $values);
  }

  /**
   * Internal method to create transactions.
   */
  private function createTransaction(EntityInterface $entity, array $values) {

    // To prevent multiple stock transactions, we need to track the processing.
    static $processed = [];

    // This is essential to prevent triggering of multiple transactions.
    if (isset($processed[$entity
      ->getEntityTypeId() . $entity
      ->id()])) {
      return;
    }
    $processed[$entity
      ->getEntityTypeId() . $entity
      ->id()] = TRUE;
    $stockServiceManager = \Drupal::service('commerce_stock.service_manager');
    $transaction_qty = empty($values['adjustment']) ? 0 : $values['adjustment'];

    // Some basic validation and type coercion.
    $transaction_qty = filter_var((double) $transaction_qty, FILTER_VALIDATE_FLOAT);
    if ($transaction_qty) {
      $transaction_type = $transaction_qty > 0 ? StockTransactionsInterface::STOCK_IN : StockTransactionsInterface::STOCK_OUT;

      // @todo Add zone and location to form.

      /** @var \Drupal\commerce_stock\StockLocationInterface $location */
      $location = $stockServiceManager
        ->getTransactionLocation($this
        ->getContext($entity), $entity, $transaction_qty);
      if (empty($location)) {

        // If we have no location, something isn't properly configured.
        throw new \RuntimeException('The StockServiceManager didn\'t return a location. Make sure your store is set up correctly?');
      }
      $zone = empty($values['zone']) ? '' : $values['zone'];
      $unit_cost = NULL;
      if (isset($values['unit_cost']['amount'])) {
        $unit_cost = filter_var((double) $values['unit_cost']['amount'], FILTER_VALIDATE_FLOAT);
        $unit_cost ?: NULL;
      }
      $currency_code = empty($values['unit_cost']['currency_code']) ? NULL : $values['unit_cost']['currency_code'];
      $transaction_note = empty($values['stock_transaction_note']) ? '' : $values['stock_transaction_note'];
      $metadata = [
        'data' => [
          'message' => $transaction_note,
        ],
      ];
      if (!empty($values['user_id'])) {
        $metadata['related_uid'] = $values['user_id'];
      }
      else {
        $metadata['related_uid'] = \Drupal::currentUser()
          ->id();
      }
      $stockServiceManager
        ->createTransaction($entity, $location
        ->getId(), $zone, $transaction_qty, (double) $unit_cost, $currency_code, $transaction_type, $metadata);
    }
  }

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

    // Hint: These are our hardcoded values from the schema definitiion.
    // We could use a decimal with 15 digits, but lets keep it closer to the
    // 99% use cases. A random float between -999 and +999 should do it.
    $scale = 4;

    // (mt_rand() / $r_max) = A number between 0 and 1.
    $random_decimal = mt_rand() / mt_getrandmax() * 999 * 2 - 999;

    // @see Drupal\Core\Field\Plugin\Field\FieldTypeNumericItemBase::truncateDecimal()
    $values['value'] = floor($random_decimal * pow(10, $scale)) / pow(10, $scale);
    return $values;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ContextCreatorTrait::createContextFromOrder public static function Creates a commerce context object.
ContextCreatorTrait::getContext public function Returns the active commerce context.
ContextCreatorTrait::getContextDetails private function Get context details.
ContextCreatorTrait::isValidContext public function Checks that the context returned is valid for $entity.
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::defaultFieldSettings public static function Defines the field-level settings for this plugin. Overrides FieldItemInterface::defaultFieldSettings 7
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::fieldSettingsForm public function Returns a form for the field-level settings. Overrides FieldItemInterface::fieldSettingsForm 7
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::preSave public function Defines custom presave behavior for field values. Overrides FieldItemInterface::preSave 7
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::applyDefaultValue public function Applies the default value. Overrides TypedData::applyDefaultValue 4
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.
StockLevel::createTransaction private function Internal method to create transactions.
StockLevel::generateSampleValue public static function @inheritDoc Overrides FieldItemBase::generateSampleValue
StockLevel::isEmpty public function Determines whether the data structure is empty. Overrides Map::isEmpty
StockLevel::postSave public function Defines custom post-save behavior for field values. Overrides FieldItemBase::postSave
StockLevel::propertyDefinitions public static function Defines field item properties. Overrides FieldItemInterface::propertyDefinitions
StockLevel::schema public static function Originally we had to define a real db field, because cores implementation of computed fields was brittle. During development of the module, we found, that we can "misuse" this to provide the possibility to enter initial stock values for… Overrides FieldItemInterface::schema
StockLevel::setValue public function @inheritdoc 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::getConstraints public function Gets a list of validation constraints. Overrides TypedDataInterface::getConstraints 9
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