vendor/pimcore/pimcore/models/DataObject/ClassDefinition/Data/Numeric.php line 564

Open in your IDE?
  1. <?php
  2. /**
  3. * Pimcore
  4. *
  5. * This source file is available under two different licenses:
  6. * - GNU General Public License version 3 (GPLv3)
  7. * - Pimcore Commercial License (PCL)
  8. * Full copyright and license information is available in
  9. * LICENSE.md which is distributed with this source code.
  10. *
  11. * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12. * @license http://www.pimcore.org/license GPLv3 and PCL
  13. */
  14. namespace Pimcore\Model\DataObject\ClassDefinition\Data;
  15. use Pimcore\Model;
  16. use Pimcore\Model\DataObject\ClassDefinition\Data;
  17. use Pimcore\Normalizer\NormalizerInterface;
  18. class Numeric extends Data implements ResourcePersistenceAwareInterface, QueryResourcePersistenceAwareInterface, TypeDeclarationSupportInterface, EqualComparisonInterface, VarExporterInterface, NormalizerInterface, PreSetDataInterface
  19. {
  20. use Model\DataObject\Traits\DefaultValueTrait;
  21. use Model\DataObject\Traits\SimpleNormalizerTrait;
  22. use Model\DataObject\Traits\SimpleComparisonTrait;
  23. use Extension\ColumnType {
  24. getColumnType as public genericGetColumnType;
  25. }
  26. use Extension\QueryColumnType {
  27. getQueryColumnType as public genericGetQueryColumnType;
  28. }
  29. const DECIMAL_SIZE_DEFAULT = 64;
  30. const DECIMAL_PRECISION_DEFAULT = 0;
  31. /**
  32. * Static type of this element
  33. *
  34. * @internal
  35. *
  36. * @var string
  37. */
  38. public $fieldtype = 'numeric';
  39. /**
  40. * @internal
  41. *
  42. * @var string|int
  43. */
  44. public $width = 0;
  45. /**
  46. * @internal
  47. *
  48. * @var float
  49. */
  50. public $defaultValue;
  51. /**
  52. * Type for the column to query
  53. *
  54. * @internal
  55. *
  56. * @var string
  57. */
  58. public $queryColumnType = 'double';
  59. /**
  60. * Type for the column
  61. *
  62. * @internal
  63. *
  64. * @var string
  65. */
  66. public $columnType = 'double';
  67. /**
  68. * @internal
  69. *
  70. * @var bool
  71. */
  72. public $integer = false;
  73. /**
  74. * @internal
  75. *
  76. * @var bool
  77. */
  78. public $unsigned = false;
  79. /**
  80. * @internal
  81. *
  82. * @var float|null
  83. */
  84. public $minValue;
  85. /**
  86. * @internal
  87. *
  88. * @var float|null
  89. */
  90. public $maxValue;
  91. /**
  92. * @internal
  93. *
  94. * @var bool
  95. */
  96. public $unique;
  97. /**
  98. * This is the x part in DECIMAL(x, y) and denotes the total amount of digits. In MySQL this is called precision
  99. * but as decimalPrecision already existed to denote the amount of digits after the point (as it is called on the ExtJS
  100. * number field), decimalSize was chosen instead.
  101. *
  102. * @internal
  103. *
  104. * @var int|null
  105. */
  106. public $decimalSize;
  107. /**
  108. * This is the y part in DECIMAL(x, y) and denotes amount of digits after a comma. In MySQL this is called scale. See
  109. * commend on decimalSize.
  110. *
  111. * @internal
  112. *
  113. * @var int|null
  114. */
  115. public $decimalPrecision;
  116. /**
  117. * @return string
  118. */
  119. private function getPhpdocType(): string
  120. {
  121. if ($this->getInteger()) {
  122. return 'int';
  123. }
  124. if ($this->isDecimalType()) {
  125. return 'string';
  126. }
  127. return 'float';
  128. }
  129. /**
  130. * @return string|int
  131. */
  132. public function getWidth()
  133. {
  134. return $this->width;
  135. }
  136. /**
  137. * @param string|int $width
  138. *
  139. * @return $this
  140. */
  141. public function setWidth($width)
  142. {
  143. if (is_numeric($width)) {
  144. $width = (int)$width;
  145. }
  146. $this->width = $width;
  147. return $this;
  148. }
  149. /**
  150. * @return int|null
  151. */
  152. public function getDefaultValue()
  153. {
  154. if ($this->defaultValue !== null) {
  155. return $this->toNumeric($this->defaultValue);
  156. }
  157. return null;
  158. }
  159. /**
  160. * @param int $defaultValue
  161. *
  162. * @return $this
  163. */
  164. public function setDefaultValue($defaultValue)
  165. {
  166. if ((string)$defaultValue !== '') {
  167. $this->defaultValue = $defaultValue;
  168. }
  169. return $this;
  170. }
  171. /**
  172. * @param bool $integer
  173. */
  174. public function setInteger($integer)
  175. {
  176. $this->integer = $integer;
  177. }
  178. /**
  179. * @return bool
  180. */
  181. public function getInteger()
  182. {
  183. return $this->integer;
  184. }
  185. /**
  186. * @param float|null $maxValue
  187. */
  188. public function setMaxValue($maxValue)
  189. {
  190. $this->maxValue = $maxValue;
  191. }
  192. /**
  193. * @return float|null
  194. */
  195. public function getMaxValue()
  196. {
  197. return $this->maxValue;
  198. }
  199. /**
  200. * @param float|null $minValue
  201. */
  202. public function setMinValue($minValue)
  203. {
  204. $this->minValue = $minValue;
  205. }
  206. /**
  207. * @return float|null
  208. */
  209. public function getMinValue()
  210. {
  211. return $this->minValue;
  212. }
  213. /**
  214. * @param bool $unsigned
  215. */
  216. public function setUnsigned($unsigned)
  217. {
  218. $this->unsigned = $unsigned;
  219. }
  220. /**
  221. * @return bool
  222. */
  223. public function getUnsigned()
  224. {
  225. return $this->unsigned;
  226. }
  227. /**
  228. * @return int|null
  229. */
  230. public function getDecimalSize()
  231. {
  232. return $this->decimalSize;
  233. }
  234. /**
  235. * @param int|null $decimalSize
  236. */
  237. public function setDecimalSize($decimalSize)
  238. {
  239. if (!is_numeric($decimalSize)) {
  240. $decimalSize = null;
  241. }
  242. $this->decimalSize = $decimalSize;
  243. }
  244. /**
  245. * @param int|null $decimalPrecision
  246. */
  247. public function setDecimalPrecision($decimalPrecision)
  248. {
  249. if (!is_numeric($decimalPrecision)) {
  250. $decimalPrecision = null;
  251. }
  252. $this->decimalPrecision = $decimalPrecision;
  253. }
  254. /**
  255. * @return int|null
  256. */
  257. public function getDecimalPrecision()
  258. {
  259. return $this->decimalPrecision;
  260. }
  261. /**
  262. * @return bool
  263. */
  264. public function getUnique()
  265. {
  266. return $this->unique;
  267. }
  268. /**
  269. * @param bool $unique
  270. */
  271. public function setUnique($unique)
  272. {
  273. $this->unique = $unique;
  274. }
  275. /**
  276. * {@inheritdoc}
  277. */
  278. public function getColumnType()
  279. {
  280. if ($this->getInteger()) {
  281. return 'bigint(20)';
  282. }
  283. if ($this->isDecimalType()) {
  284. return $this->buildDecimalColumnType();
  285. }
  286. return $this->genericGetColumnType();
  287. }
  288. /**
  289. * {@inheritdoc}
  290. */
  291. public function getQueryColumnType()
  292. {
  293. if ($this->getInteger()) {
  294. return 'bigint(20)';
  295. }
  296. if ($this->isDecimalType()) {
  297. return $this->buildDecimalColumnType();
  298. }
  299. return $this->genericGetQueryColumnType();
  300. }
  301. /**
  302. * @return bool
  303. */
  304. private function isDecimalType(): bool
  305. {
  306. return null !== $this->getDecimalSize() || null !== $this->getDecimalPrecision();
  307. }
  308. /**
  309. * @return string
  310. */
  311. private function buildDecimalColumnType(): string
  312. {
  313. // decimalPrecision already existed in earlier versions to denote the amount of digits after the
  314. // comma (and is used in ExtJS). To avoid migrations, decimalSize was chosen to denote the total amount
  315. // of supported digits despite the confusing naming.
  316. //
  317. // The two properties used in the class definition translate to the following MySQL naming:
  318. //
  319. // DECIMAL(precision, scale) = DECIMAL(decimalSize, decimalPrecision)
  320. // these are named after what MySQL expects - DECIMAL(precision, scale)
  321. $precision = self::DECIMAL_SIZE_DEFAULT;
  322. $scale = self::DECIMAL_PRECISION_DEFAULT;
  323. if (null !== $this->decimalSize) {
  324. $precision = (int)$this->decimalSize;
  325. }
  326. if (null !== $this->decimalPrecision) {
  327. $scale = (int)$this->decimalPrecision;
  328. }
  329. if ($precision < 1 || $precision > 65) {
  330. throw new \InvalidArgumentException('Decimal precision must be a value between 1 and 65');
  331. }
  332. if ($scale < 0 || $scale > 30 || $scale > $precision) {
  333. throw new \InvalidArgumentException('Decimal scale must be a value between 0 and 30');
  334. }
  335. if ($scale > $precision) {
  336. throw new \InvalidArgumentException(sprintf(
  337. 'Decimal scale can\'t be larger than precision (%d)',
  338. $precision
  339. ));
  340. }
  341. return sprintf('DECIMAL(%d, %d)', $precision, $scale);
  342. }
  343. /**
  344. * @see ResourcePersistenceAwareInterface::getDataForResource
  345. *
  346. * @param float|int|string $data
  347. * @param null|Model\DataObject\Concrete $object
  348. * @param mixed $params
  349. *
  350. * @return float|int|string|null
  351. */
  352. public function getDataForResource($data, $object = null, $params = [])
  353. {
  354. $data = $this->handleDefaultValue($data, $object, $params);
  355. if (is_numeric($data)) {
  356. return $data;
  357. }
  358. return null;
  359. }
  360. /**
  361. * @see ResourcePersistenceAwareInterface::getDataFromResource
  362. *
  363. * @param float|int|string $data
  364. * @param null|Model\DataObject\Concrete $object
  365. * @param mixed $params
  366. *
  367. * @return float|int|string
  368. */
  369. public function getDataFromResource($data, $object = null, $params = [])
  370. {
  371. if (is_numeric($data)) {
  372. return $this->toNumeric($data);
  373. }
  374. return $data;
  375. }
  376. /**
  377. * @see QueryResourcePersistenceAwareInterface::getDataForQueryResource
  378. *
  379. * @param float|int|string $data
  380. * @param null|Model\DataObject\Concrete $object
  381. * @param mixed $params
  382. *
  383. * @return float|int|string
  384. */
  385. public function getDataForQueryResource($data, $object = null, $params = [])
  386. {
  387. //TODO same fallback as above
  388. return $this->getDataForResource($data, $object, $params);
  389. }
  390. /**
  391. * @see Data::getDataForEditmode
  392. *
  393. * @param float|int|string $data
  394. * @param null|Model\DataObject\Concrete $object
  395. * @param mixed $params
  396. *
  397. * @return float|int|string
  398. */
  399. public function getDataForEditmode($data, $object = null, $params = [])
  400. {
  401. return $this->getDataForResource($data, $object, $params);
  402. }
  403. /**
  404. * @see Data::getDataFromEditmode
  405. *
  406. * @param float|int|string $data
  407. * @param null|Model\DataObject\Concrete $object
  408. * @param mixed $params
  409. *
  410. * @return float|int|string
  411. */
  412. public function getDataFromEditmode($data, $object = null, $params = [])
  413. {
  414. return $this->getDataFromResource($data, $object, $params);
  415. }
  416. /**
  417. * @see Data::getVersionPreview
  418. *
  419. * @param float|int|string $data
  420. * @param null|Model\DataObject\Concrete $object
  421. * @param mixed $params
  422. *
  423. * @return float|int|string
  424. */
  425. public function getVersionPreview($data, $object = null, $params = [])
  426. {
  427. return $data;
  428. }
  429. /**
  430. * {@inheritdoc}
  431. */
  432. public function checkValidity($data, $omitMandatoryCheck = false, $params = [])
  433. {
  434. if (!$omitMandatoryCheck && $this->getMandatory() && $this->isEmpty($data)) {
  435. throw new Model\Element\ValidationException('Empty mandatory field [ '.$this->getName().' ]');
  436. }
  437. if (!$this->isEmpty($data) && !is_numeric($data)) {
  438. throw new Model\Element\ValidationException('field ['.$this->getName().' ] - invalid numeric data [' . $data . '] ');
  439. }
  440. if (!$this->isEmpty($data) && !$omitMandatoryCheck) {
  441. $data = $this->toNumeric($data);
  442. if ($data >= PHP_INT_MAX) {
  443. throw new Model\Element\ValidationException('Value exceeds PHP_INT_MAX please use an input data type instead of numeric!');
  444. }
  445. if ($this->getInteger() && strpos((string) $data, '.') !== false) {
  446. throw new Model\Element\ValidationException('Value in field [ '.$this->getName().' ] is not an integer');
  447. }
  448. if ($this->getMinValue() !== null && $this->getMinValue() > $data) {
  449. throw new Model\Element\ValidationException('Value in field [ '.$this->getName().' ] is not at least ' . $this->getMinValue());
  450. }
  451. if ($this->getMaxValue() !== null && $data > $this->getMaxValue()) {
  452. throw new Model\Element\ValidationException('Value in field [ '.$this->getName().' ] is bigger than ' . $this->getMaxValue());
  453. }
  454. if ($this->getUnsigned() && $data < 0) {
  455. throw new Model\Element\ValidationException('Value in field [ '.$this->getName().' ] is not unsigned (bigger than 0)');
  456. }
  457. }
  458. }
  459. /**
  460. * {@inheritdoc}
  461. */
  462. public function getForCsvExport($object, $params = [])
  463. {
  464. $data = $this->getDataFromObjectParam($object, $params);
  465. return (string)$data;
  466. }
  467. /**
  468. * {@inheritdoc}
  469. */
  470. public function isDiffChangeAllowed($object, $params = [])
  471. {
  472. return true;
  473. }
  474. /**
  475. * @param string|null $data
  476. *
  477. * @return bool
  478. */
  479. public function isEmpty($data)
  480. {
  481. return strlen($data) < 1;
  482. }
  483. /**
  484. * @param string $value
  485. *
  486. * @return float|int|string
  487. */
  488. private function toNumeric($value)
  489. {
  490. $value = str_replace(',', '.', (string) $value);
  491. if ($this->isDecimalType()) {
  492. return $value;
  493. }
  494. if (strpos($value, '.') === false) {
  495. return (int) $value;
  496. }
  497. return (float) $value;
  498. }
  499. /**
  500. * { @inheritdoc }
  501. */
  502. public function preSetData(/** mixed */ $container, /** mixed */ $data, /** array */ $params = []) // : mixed
  503. {
  504. if (!is_null($data) && $this->getDecimalPrecision()) {
  505. $data = round($data, $this->getDecimalPrecision());
  506. }
  507. return $data;
  508. }
  509. /**
  510. * {@inheritdoc}
  511. */
  512. public function isFilterable(): bool
  513. {
  514. return true;
  515. }
  516. /**
  517. * {@inheritdoc}
  518. */
  519. protected function doGetDefaultValue($object, $context = [])
  520. {
  521. return $this->getDefaultValue() ?? null;
  522. }
  523. /**
  524. * @param float|int|string $oldValue
  525. * @param float|int|string $newValue
  526. *
  527. * @return bool
  528. */
  529. public function isEqual($oldValue, $newValue): bool
  530. {
  531. return $this->toNumeric($oldValue) == $this->toNumeric($newValue);
  532. }
  533. /**
  534. * {@inheritdoc}
  535. */
  536. public function getParameterTypeDeclaration(): ?string
  537. {
  538. return '?' . $this->getPhpdocType();
  539. }
  540. /**
  541. * {@inheritdoc}
  542. */
  543. public function getReturnTypeDeclaration(): ?string
  544. {
  545. return '?' . $this->getPhpdocType();
  546. }
  547. /**
  548. * {@inheritdoc}
  549. */
  550. public function getPhpdocInputType(): ?string
  551. {
  552. return $this->getPhpdocType() . '|null';
  553. }
  554. /**
  555. * {@inheritdoc}
  556. */
  557. public function getPhpdocReturnType(): ?string
  558. {
  559. return $this->getPhpdocType() . '|null';
  560. }
  561. }