DecimalItem.php 4.93 KB
Newer Older
1 2
<?php

3
namespace Drupal\Core\Field\Plugin\Field\FieldType;
4

5
use Drupal\Core\Field\FieldDefinitionInterface;
6
use Drupal\Core\Field\FieldStorageDefinitionInterface;
7
use Drupal\Core\Form\FormStateInterface;
8
use Drupal\Core\TypedData\DataDefinition;
9 10

/**
11
 * Defines the 'decimal' field type.
12 13
 *
 * @FieldType(
14
 *   id = "decimal",
15
 *   label = @Translation("Number (decimal)"),
16
 *   description = @Translation("This field stores a number in the database in a fixed decimal format."),
17
 *   category = @Translation("Number"),
18 19 20 21
 *   default_widget = "number",
 *   default_formatter = "number_decimal"
 * )
 */
22
class DecimalItem extends NumericItemBase {
23

24 25 26
  /**
   * {@inheritdoc}
   */
27
  public static function defaultStorageSettings() {
28
    return [
29 30
      'precision' => 10,
      'scale' => 2,
31
    ] + parent::defaultStorageSettings();
32 33
  }

34 35 36
  /**
   * {@inheritdoc}
   */
37
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
38
    $properties['value'] = DataDefinition::create('string')
39 40
      ->setLabel(t('Decimal value'))
      ->setRequired(TRUE);
41 42

    return $properties;
43 44 45 46 47
  }

  /**
   * {@inheritdoc}
   */
48
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
49 50 51
    return [
      'columns' => [
        'value' => [
52
          'type' => 'numeric',
53 54
          'precision' => $field_definition->getSetting('precision'),
          'scale' => $field_definition->getSetting('scale'),
55
        ],
56 57
      ],
    ];
58 59 60 61 62
  }

  /**
   * {@inheritdoc}
   */
63
  public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
64
    $element = [];
65
    $settings = $this->getSettings();
66

67
    $element['precision'] = [
68
      '#type' => 'number',
69
      '#title' => t('Precision'),
70 71
      '#min' => 10,
      '#max' => 32,
72 73 74
      '#default_value' => $settings['precision'],
      '#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'),
      '#disabled' => $has_data,
75
    ];
76

77
    $element['scale'] = [
78
      '#type' => 'number',
79
      '#title' => t('Scale', [], ['context' => 'decimal places']),
80 81
      '#min' => 0,
      '#max' => 10,
82 83 84
      '#default_value' => $settings['scale'],
      '#description' => t('The number of digits to the right of the decimal.'),
      '#disabled' => $has_data,
85
    ];
86 87 88 89

    return $element;
  }

90 91 92 93 94 95 96
  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager();
    $constraints = parent::getConstraints();

97 98 99
    $constraints[] = $constraint_manager->create('ComplexData', [
      'value' => [
        'Regex' => [
100
          'pattern' => '/^[+-]?((\d+(\.\d*)?)|(\.\d+))$/i',
101
        ],
102 103
      ],
    ]);
104 105 106 107

    return $constraints;
  }

108 109 110 111 112 113 114 115 116 117 118 119 120
  /**
   * {@inheritdoc}
   */
  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
    $element = parent::fieldSettingsForm($form, $form_state);
    $settings = $this->getSettings();

    $element['min']['#step'] = pow(0.1, $settings['scale']);
    $element['max']['#step'] = pow(0.1, $settings['scale']);

    return $element;
  }

121 122 123 124
  /**
   * {@inheritdoc}
   */
  public function preSave() {
125
    $this->value = round($this->value, $this->getSetting('scale'));
126 127
  }

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
  /**
   * {@inheritdoc}
   */
  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
    $settings = $field_definition->getSettings();
    $precision = $settings['precision'] ?: 10;
    $scale = $settings['scale'] ?: 2;
    // $precision - $scale is the number of digits on the left of the decimal
    // point.
    // The maximum number you can get with 3 digits is 10^3 - 1 --> 999.
    // The minimum number you can get with 3 digits is -1 * (10^3 - 1).
    $max = is_numeric($settings['max']) ?: pow(10, ($precision - $scale)) - 1;
    $min = is_numeric($settings['min']) ?: -pow(10, ($precision - $scale)) + 1;

    // Get the number of decimal digits for the $max
    $decimal_digits = self::getDecimalDigits($max);
    // Do the same for the min and keep the higher number of decimal digits.
    $decimal_digits = max(self::getDecimalDigits($min), $decimal_digits);
    // If $min = 1.234 and $max = 1.33 then $decimal_digits = 3
    $scale = rand($decimal_digits, $scale);

    // @see "Example #1 Calculate a random floating-point number" in
150
    // http://php.net/manual/function.mt-getrandmax.php
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
    $random_decimal = $min + mt_rand() / mt_getrandmax() * ($max - $min);
    $values['value'] = self::truncateDecimal($random_decimal, $scale);
    return $values;
  }

  /**
   * Helper method to get the number of decimal digits out of a decimal number.
   *
   * @param int $decimal
   *   The number to calculate the number of decimals digits from.
   *
   * @return int
   *   The number of decimal digits.
   */
  protected static function getDecimalDigits($decimal) {
    $digits = 0;
    while ($decimal - round($decimal)) {
      $decimal *= 10;
      $digits++;
    }
    return $digits;
  }

174
}