diff --git a/Account/PermissionAbstract.php b/Account/PermissionAbstract.php index b7cb50c45..8771f9d47 100644 --- a/Account/PermissionAbstract.php +++ b/Account/PermissionAbstract.php @@ -62,10 +62,10 @@ class PermissionAbstract implements \JsonSerializable /** * Providing module id. * - * @var int + * @var string * @since 1.0.0 */ - protected int $from = 0; + protected ?string $from = null; /** * Type. @@ -105,7 +105,7 @@ class PermissionAbstract implements \JsonSerializable * @param null|int $unit Unit Unit to check (null if all are acceptable) * @param null|string $app App App to check (null if all are acceptable) * @param null|string $module Module Module to check (null if all are acceptable) - * @param int $from Provided by which module + * @param null|string $from Provided by which module * @param null|int $type Type (e.g. customer) (null if all are acceptable) * @param null|int $element (e.g. customer id) (null if all are acceptable) * @param null|int $component (e.g. address) (null if all are acceptable) @@ -117,7 +117,7 @@ class PermissionAbstract implements \JsonSerializable int $unit = null, string $app = null, string $module = null, - int $from = 0, + string $from = null, int $type = null, int $element = null, int $component = null, @@ -226,11 +226,11 @@ class PermissionAbstract implements \JsonSerializable /** * Get providing module id. * - * @return int Returns the module responsible for setting this permission + * @return null|string Returns the module responsible for setting this permission * * @since 1.0.0 */ - public function getFrom() : int + public function getFrom() : ?string { return $this->from; } @@ -238,13 +238,13 @@ class PermissionAbstract implements \JsonSerializable /** * Set providing module id. * - * @param int $from Providing module + * @param null|string $from Providing module * * @return void * * @since 1.0.0 */ - public function setFrom(int $from = 0) : void + public function setFrom(string $from = null) : void { $this->from = $from; } diff --git a/Algorithm/PathFinding/AStar.php b/Algorithm/PathFinding/AStar.php index 86b498768..e6c02f5d3 100644 --- a/Algorithm/PathFinding/AStar.php +++ b/Algorithm/PathFinding/AStar.php @@ -12,8 +12,8 @@ * * Extended based on: * MIT License - * © 2011-2012 Xueqiao Xu - * © PathFinding.js + * (c) 2011-2012 Xueqiao Xu + * (c) PathFinding.js */ declare(strict_types=1); diff --git a/Algorithm/PathFinding/Grid.php b/Algorithm/PathFinding/Grid.php index c2c0bcfdc..36809319e 100644 --- a/Algorithm/PathFinding/Grid.php +++ b/Algorithm/PathFinding/Grid.php @@ -12,8 +12,8 @@ * * Extended based on: * MIT License - * © 2011-2012 Xueqiao Xu - * © PathFinding.js + * (c) 2011-2012 Xueqiao Xu + * (c) PathFinding.js */ declare(strict_types=1); diff --git a/Algorithm/PathFinding/JumpPointSearch.php b/Algorithm/PathFinding/JumpPointSearch.php index 190165e33..ffc2c643a 100644 --- a/Algorithm/PathFinding/JumpPointSearch.php +++ b/Algorithm/PathFinding/JumpPointSearch.php @@ -12,8 +12,8 @@ * * Extended based on: * MIT License - * © 2011-2012 Xueqiao Xu - * © PathFinding.js + * (c) 2011-2012 Xueqiao Xu + * (c) PathFinding.js */ declare(strict_types=1); diff --git a/Algorithm/PathFinding/Path.php b/Algorithm/PathFinding/Path.php index 1c2f3bf48..c686a643e 100644 --- a/Algorithm/PathFinding/Path.php +++ b/Algorithm/PathFinding/Path.php @@ -12,8 +12,8 @@ * * Extended based on: * MIT License - * © 2011-2012 Xueqiao Xu - * © PathFinding.js + * (c) 2011-2012 Xueqiao Xu + * (c) PathFinding.js */ declare(strict_types=1); diff --git a/Business/Marketing/CustomerValue.php b/Business/Marketing/CustomerValue.php index d4e922136..c8c089862 100644 --- a/Business/Marketing/CustomerValue.php +++ b/Business/Marketing/CustomerValue.php @@ -29,7 +29,7 @@ final class CustomerValue * * Hazard Model, same as $margin * (1 + $discountRate) / (1 + $discountRate - $retentionRate) * - * @param float $margin Margin per period + * @param float $margin Margin per period (or revenue/sales) * @param float $retentionRate Rate of remaining customers per period (= average lifetime / (1 + average lifetime)) * @param float $discountRate Cost of capital to discount future revenue * @@ -42,6 +42,32 @@ final class CustomerValue return $margin * $retentionRate / (1 + $discountRate - $retentionRate); } + /** + * Basic customer lifetime value + * + * Hazard Model, same as $margin * (1 + $discountRate) / (1 + $discountRate - $retentionRate) + * + * @param array $margins Margin per period (or revenue/sales) + * @param float $retentionRate Rate of remaining customers per period (= average lifetime / (1 + average lifetime)) + * @param float $discountRate Cost of capital to discount future revenue + * + * @return float + * + * @since 1.0.0 + */ + public static function getBasicCLV(array $margins, float $retentionRate, float $discountRate) : float + { + $clv = 0.0; + $c = 1; + foreach ($margins as $margin) { + $clv += ($retentionRate ** $c) * $margin * \pow(1 / (1 + $discountRate), $c); + + ++$c; + } + + return $clv; + } + /** * Normalized measure of recurring revenue * diff --git a/Business/Marketing/Metrics.php b/Business/Marketing/Metrics.php index d6d4d9862..136417194 100644 --- a/Business/Marketing/Metrics.php +++ b/Business/Marketing/Metrics.php @@ -58,6 +58,68 @@ final class Metrics return ($ce - $cn) / $cs; } + /** + * Calcualte the coefficient of retention + * + * @param float $retentionRate Observed retention rate (optionally use the average) + * @param float $rc Retention rate ceiling + * @param int $t Period + * + * @return float + * + * @since 1.0.0 + */ + public static function getCoefficientOfRetention(float $retentionRate, float $rc, int $t) : float + { + return 1 / $t * \log($rc - $retentionRate); + } + + /** + * Predict the retention rate for period t + * + * @param float $rc Retention rate ceiling + * @param float $r Coefficient of retention + * @param int $t Period t + * + * @return float + * + * @since 1.0.0 + */ + public static function predictCustomerRetention(float $rc, float $r, int $t) : float + { + return $rc * (1 - \exp(-$r * $t)); + } + + /** + * Calculate the average lifetime duration + * + * @param array $retainedCustomers Retained customers per period + * + * @return float + * + * @since 1.0.0 + */ + public static function averageLifetimeDuration(array $retainedCustomers) : float + { + return \array_sum($retainedCustomers) / \count($retainedCustomers); + } + + /** + * Calculate the probability of a customer being active + * + * @param int $purchases Number of purchases during the periods + * @param int $periods Number of periods (e.g. number of months) + * @param int $lastPurchase In which period was the last purchase + * + * @return float + * + * @since 1.0.0 + */ + public static function customerActiveProbability(int $purchases, int $periods, int $lastPurchase) : float + { + return \pow($lastPurchase / $periods, $purchases); + } + /** * Calculate the customer profits * diff --git a/DataStorage/Database/DataMapperAbstract.php b/DataStorage/Database/DataMapperAbstract.php index 21a986484..bfdf61593 100644 --- a/DataStorage/Database/DataMapperAbstract.php +++ b/DataStorage/Database/DataMapperAbstract.php @@ -420,7 +420,7 @@ class DataMapperAbstract implements DataMapperInterface } if ($condValue['orderBy'] !== null) { - $where1->orderBy(static::$table . '_' . $searchDepth . '.' . static::$columns[$condValue['orderBy']], $condValue['sortOrder']); + $where1->orderBy(static::$table . '_' . $searchDepth . '.' . static::getColumnByMember($condValue['orderBy']), $condValue['sortOrder']); } if ($condValue['limit'] !== null) { @@ -899,14 +899,6 @@ class DataMapperAbstract implements DataMapperInterface /** @var self $mapper */ $mapper = static::$hasMany[$propertyName]['mapper']; - if (\is_object($values)) { - // conditionals - TestUtils::setMember($values, $mapper::$columns[static::$hasMany[$propertyName]['self']]['internal'], $objId); - - $mapper::createArray($values); - continue; - } - if (!\is_array($values)) { continue; } @@ -1160,14 +1152,13 @@ class DataMapperAbstract implements DataMapperInterface $values = $obj->{$propertyName}; } - if (!\is_array($values)) { - // conditionals + if (!\is_array($values) || empty($values)) { continue; } /** @var self $mapper */ $mapper = static::$hasMany[$propertyName]['mapper']; - $relReflectionClass = !empty($values) ? new \ReflectionClass(\reset($values)) : null; + $relReflectionClass = new \ReflectionClass(\reset($values)); $objsIds[$propertyName] = []; foreach ($values as $key => &$value) { @@ -1178,7 +1169,6 @@ class DataMapperAbstract implements DataMapperInterface continue; } - /** @var \ReflectionClass @relReflectionClass */ $primaryKey = $mapper::getObjectId($value, $relReflectionClass); // already in db @@ -2081,7 +2071,7 @@ class DataMapperAbstract implements DataMapperInterface */ public static function populateOwnsOne(string $member, array $result, int $depth = 3, mixed $default = null) : mixed { - /** @var self $mapper */ + /** @var self|string $mapper */ $mapper = static::$ownsOne[$member]['mapper']; if ($depth < 1) { @@ -2121,7 +2111,7 @@ class DataMapperAbstract implements DataMapperInterface */ public static function populateOwnsOneArray(string $member, array $result, int $depth = 3, mixed $default = null) : array { - /** @var self $mapper */ + /** @var self|string $mapper */ $mapper = static::$ownsOne[$member]['mapper']; if ($depth < 1) { @@ -2162,7 +2152,7 @@ class DataMapperAbstract implements DataMapperInterface */ public static function populateBelongsTo(string $member, array $result, int $depth = 3, mixed $default = null) : mixed { - /** @var self $mapper */ + /** @var self|string $mapper */ $mapper = static::$belongsTo[$member]['mapper']; if ($depth < 1) { @@ -2202,7 +2192,7 @@ class DataMapperAbstract implements DataMapperInterface */ public static function populateBelongsToArray(string $member, array $result, int $depth = 3, mixed $default = null) : array { - /** @var self $mapper */ + /** @var self|string $mapper */ $mapper = static::$belongsTo[$member]['mapper']; if ($depth < 1) { @@ -3111,6 +3101,8 @@ class DataMapperAbstract implements DataMapperInterface try { $results = false; + $t = $query->toSql(); + $sth = self::$db->con->prepare($query->toSql()); if ($sth !== false) { $sth->execute(); @@ -3323,7 +3315,7 @@ class DataMapperAbstract implements DataMapperInterface // get BelognsToQuery if ($depth > 1 && self::$relations === RelationType::ALL) { - foreach (static::$belongsTo as $rel) { + foreach (static::$belongsTo as $key => $rel) { if (isset(self::$withFields[$key]) && self::$withFields[$key]['ignore']) { continue; } @@ -3348,7 +3340,7 @@ class DataMapperAbstract implements DataMapperInterface // get HasManyQuery (but only for elements which have a 'column' defined) if ($depth > 1 && self::$relations === RelationType::ALL) { - foreach (static::$hasMany as $rel) { + foreach (static::$hasMany as $key => $rel) { // @todo: impl. conditiona/with handling, sort, limit, filter or is this not required here? if (isset($rel['external']) || !isset($rel['column']) // @todo: conflict with getHasMany()???!?!?!?! || (isset(self::$withFields[$key]) && self::$withFields[$key]['ignore']) diff --git a/DataStorage/Database/Query/Builder.php b/DataStorage/Database/Query/Builder.php index b481edf31..69393d5cd 100644 --- a/DataStorage/Database/Query/Builder.php +++ b/DataStorage/Database/Query/Builder.php @@ -28,6 +28,14 @@ use phpOMS\DataStorage\Database\Connection\ConnectionAbstract; */ class Builder extends BuilderAbstract { + /** + * Log queries. + * + * @var bool + * @since 1.0.0 + */ + public static bool $log = false; + /** * Is read only. * @@ -369,7 +377,13 @@ class Builder extends BuilderAbstract $this->resolveJoinDependencies(); } - return $this->grammar->compileQuery($this); + $query = $this->grammar->compileQuery($this); + + if (self::$log) { + \phpOMS\Log\FileLogger::getInstance()->debug($query); + } + + return $query; } /** diff --git a/Localization/Defaults/Country.php b/Localization/Defaults/Country.php index 941101ca1..4f9d99513 100644 --- a/Localization/Defaults/Country.php +++ b/Localization/Defaults/Country.php @@ -83,7 +83,7 @@ class Country /** * Country developed. * - * @var string + * @var bool * @since 1.0.0 */ protected bool $isDeveloped = false; diff --git a/Localization/Money.php b/Localization/Money.php index 20962f249..52faf645f 100644 --- a/Localization/Money.php +++ b/Localization/Money.php @@ -14,6 +14,8 @@ declare(strict_types=1); namespace phpOMS\Localization; +use phpOMS\Stdlib\Base\FloatInt; + /** * Money class. * @@ -22,32 +24,8 @@ namespace phpOMS\Localization; * @link https://orange-management.org * @since 1.0.0 */ -final class Money implements \Serializable +final class Money extends FloatInt { - /** - * Max amount of decimals. - * - * @var int - * @since 1.0.0 - */ - public const MAX_DECIMALS = 4; - - /** - * Thousands separator. - * - * @var string - * @since 1.0.0 - */ - private string $thousands = ','; - - /** - * Decimal separator. - * - * @var string - * @since 1.0.0 - */ - private string $decimal = '.'; - /** * Currency symbol position * @@ -64,14 +42,6 @@ final class Money implements \Serializable */ private string $symbol = ISO4217SymbolEnum::_USD; - /** - * Value. - * - * @var int - * @since 1.0.0 - */ - private int $value = 0; - /** * Constructor. * @@ -85,48 +55,10 @@ final class Money implements \Serializable */ public function __construct(int | float | string $value = 0, string $thousands = ',', string $decimal = '.', string $symbol = '', int $position = 0) { - $this->value = \is_int($value) ? $value : self::toInt((string) $value); - $this->thousands = $thousands; - $this->decimal = $decimal; $this->symbol = $symbol; $this->position = $position; - } - /** - * Money to int. - * - * @param string $value Money value - * @param string $thousands Thousands character - * @param string $decimal Decimal character - * - * @return int - * - * @throws \Exception this exception is thrown if an internal explode or substr error occurs - * - * @since 1.0.0 - */ - public static function toInt(string $value, string $thousands = ',', string $decimal = '.') : int - { - $split = \explode($decimal, $value); - - if ($split === false) { - throw new \Exception('Internal explode error.'); // @codeCoverageIgnore - } - - $left = $split[0]; - $left = \str_replace($thousands, '', $left); - $right = ''; - - if (\count($split) > 1) { - $right = $split[1]; - } - - $right = \substr($right, 0, self::MAX_DECIMALS); - if ($right === false) { - throw new \Exception('Internal substr error.'); // @codeCoverageIgnore - } - - return ((int) $left) * 10 ** self::MAX_DECIMALS + (int) \str_pad($right, self::MAX_DECIMALS, '0'); + parent::__construct($value, $thousands, $decimal); } /** @@ -143,30 +75,15 @@ final class Money implements \Serializable */ public function setLocalization(string $thousands = ',', string $decimal = '.', string $symbol = '', int $position = 0) : self { - $this->thousands = $thousands; - $this->decimal = $decimal; $this->symbol = $symbol; $this->position = $position; - return $this; - } - - /** - * Set value by string. - * - * @param string $value Money value - * - * @return Money - * - * @since 1.0.0 - */ - public function setString(string $value) : self - { - $this->value = self::toInt($value, $this->thousands, $this->decimal); + parent::setLocalization($thousands, $decimal); return $this; } + /** * Get money. * @@ -180,200 +97,4 @@ final class Money implements \Serializable { return ($this->position === 0 && !empty($this->symbol) ? $this->symbol . ' ' : '') . $this->getAmount($decimals) . ($this->position === 1 ? ' ' . $this->symbol : ''); } - - /** - * Get money. - * - * @param null|int $decimals Precision (null = auto decimals) - * - * @return string - * - * @throws \Exception this exception is thrown if an internal substr error occurs - * - * @since 1.0.0 - */ - public function getAmount(?int $decimals = 2) : string - { - $value = $this->value === 0 - ? \str_repeat('0', self::MAX_DECIMALS) - : (string) \round($this->value, -self::MAX_DECIMALS + $decimals); - - $left = \substr($value, 0, -self::MAX_DECIMALS); - - /** @var string $left */ - $left = $left === false ? '0' : $left; - $right = \substr($value, -self::MAX_DECIMALS); - - if ($right === false) { - throw new \Exception(); // @codeCoverageIgnore - } - - if ($decimals === null) { - $decimals = \strlen(\rtrim($right, '0')); - } - - return $decimals > 0 - ? \number_format((float) $left, 0, $this->decimal, $this->thousands) . $this->decimal . \substr($right, 0, $decimals) - : \str_pad($left, 1, '0'); - } - - /** - * Add money. - * - * @param int|float|string|Money $value Value to add - * - * @return Money - * - * @since 1.0.0 - */ - public function add(int | float | string | self $value) : self - { - if (\is_string($value) || \is_float($value)) { - $this->value += self::toInt((string) $value, $this->thousands, $this->decimal); - } elseif (\is_int($value)) { - $this->value += $value; - } else { - $this->value += $value->getInt(); - } - - return $this; - } - - /** - * Get money value. - * - * @return int - * - * @since 1.0.0 - */ - public function getInt() : int - { - return $this->value; - } - - /** - * Sub money. - * - * @param int|float|string|Money $value Value to subtract - * - * @return Money - * - * @since 1.0.0 - */ - public function sub(int | float | string | self $value) : self - { - if (\is_string($value) || \is_float($value)) { - $this->value -= self::toInt((string) $value, $this->thousands, $this->decimal); - } elseif (\is_int($value)) { - $this->value -= $value; - } else { - $this->value -= $value->getInt(); - } - - return $this; - } - - /** - * Mult. - * - * @param int|float $value Value to multiply with - * - * @return Money - * - * @since 1.0.0 - */ - public function mult(int | float $value) : self - { - $this->value = (int) ($this->value * $value); - - return $this; - } - - /** - * Div. - * - * @param int|float $value Value to divide by - * - * @return Money - * - * @since 1.0.0 - */ - public function div(int | float $value) : self - { - $this->value = (int) ($this->value / $value); - - return $this; - } - - /** - * Abs. - * - * @return Money - * - * @since 1.0.0 - */ - public function abs() : self - { - $this->value = \abs($this->value); - - return $this; - } - - /** - * Power. - * - * @param int|float $value Value to power - * - * @return Money - * - * @since 1.0.0 - */ - public function pow(int | float $value) : self - { - $this->value = (int) ($this->value ** $value); - - return $this; - } - - /** - * Searialze. - * - * @return string - * - * @since 1.0.0 - */ - public function serialize() : string - { - return (string) $this->getInt(); - } - - /** - * Unserialize. - * - * @param mixed $value Value to unserialize - * - * @return void - * - * @since 1.0.0 - */ - public function unserialize($value) : void - { - $this->setInt((int) $value); - } - - /** - * Set money value. - * - * @param int $value Value - * - * @return Money - * - * @since 1.0.0 - */ - public function setInt(int $value) : self - { - $this->value = $value; - - return $this; - } } diff --git a/Math/Stochastic/Distribution/ChiSquaredDistribution.php b/Math/Stochastic/Distribution/ChiSquaredDistribution.php index b664d5c19..2af92eb26 100644 --- a/Math/Stochastic/Distribution/ChiSquaredDistribution.php +++ b/Math/Stochastic/Distribution/ChiSquaredDistribution.php @@ -19,6 +19,8 @@ use phpOMS\Math\Functions\Gamma; /** * Chi squared distribution. * + * Test if two variables are similarly distributed (goodness fit, test dependency of variables) + * * @package phpOMS\Math\Stochastic\Distribution * @license OMS License 1.0 * @link https://orange-management.org diff --git a/Math/Stochastic/Distribution/TDistribution.php b/Math/Stochastic/Distribution/TDistribution.php index 2bfebfc42..343e9c2b0 100644 --- a/Math/Stochastic/Distribution/TDistribution.php +++ b/Math/Stochastic/Distribution/TDistribution.php @@ -15,7 +15,9 @@ declare(strict_types=1); namespace phpOMS\Math\Stochastic\Distribution; /** - * Bernulli distribution. + * T distribution. + * + * Test the similarity of two means * * @package phpOMS\Math\Stochastic\Distribution * @license OMS License 1.0 diff --git a/Math/Stochastic/Distribution/ZTesting.php b/Math/Stochastic/Distribution/ZTesting.php index 44f17c6f0..85e193d01 100644 --- a/Math/Stochastic/Distribution/ZTesting.php +++ b/Math/Stochastic/Distribution/ZTesting.php @@ -20,6 +20,8 @@ use phpOMS\Math\Statistic\MeasureOfDispersion; /** * ZTest * + * Test if the mean is the same as the population mean + * * @package phpOMS\Math\Stochastic\Distribution * @license OMS License 1.0 * @link https://orange-management.org diff --git a/Message/Mail/Email.php b/Message/Mail/Email.php index 41c16dc18..47e4365db 100644 --- a/Message/Mail/Email.php +++ b/Message/Mail/Email.php @@ -12,8 +12,8 @@ * * Extended based on: * GLGPL 2.1 License - * © 2012 - 2015 Marcus Bointon, 2010 - 2012 Jim Jagielski, 2004 - 2009 Andy Prevost - * © PHPMailer + * (c) 2012 - 2015 Marcus Bointon, 2010 - 2012 Jim Jagielski, 2004 - 2009 Andy Prevost + * (c) PHPMailer */ declare(strict_types=1); diff --git a/Message/Mail/MailHandler.php b/Message/Mail/MailHandler.php index 41d7ba382..f7d875721 100644 --- a/Message/Mail/MailHandler.php +++ b/Message/Mail/MailHandler.php @@ -11,8 +11,8 @@ * * Extended based on: * GLGPL 2.1 License - * © 2012 - 2015 Marcus Bointon, 2010 - 2012 Jim Jagielski, 2004 - 2009 Andy Prevost - * © PHPMailer + * (c) 2012 - 2015 Marcus Bointon, 2010 - 2012 Jim Jagielski, 2004 - 2009 Andy Prevost + * (c) PHPMailer */ declare(strict_types=1); diff --git a/Message/Mail/Smtp.php b/Message/Mail/Smtp.php index d304d4d05..0aad272a9 100644 --- a/Message/Mail/Smtp.php +++ b/Message/Mail/Smtp.php @@ -11,8 +11,8 @@ * * Extended based on: * GLGPL 2.1 License - * © 2012 - 2015 Marcus Bointon, 2010 - 2012 Jim Jagielski, 2004 - 2009 Andy Prevost - * © PHPMailer + * (c) 2012 - 2015 Marcus Bointon, 2010 - 2012 Jim Jagielski, 2004 - 2009 Andy Prevost + * (c) PHPMailer */ declare(strict_types=1); diff --git a/Security/PhpCode.php b/Security/PhpCode.php index 9a0ab627b..5f931915a 100644 --- a/Security/PhpCode.php +++ b/Security/PhpCode.php @@ -148,7 +148,7 @@ final class PhpCode * Validate file integrety * * @param string $source Source code path - * @param string $hash Source hash + * @param string $hash Source hash (md5) * * @return bool Returns true if filee matches expected signature otherwise false is returned * diff --git a/Stdlib/Base/AddressType.php b/Stdlib/Base/AddressType.php index d71f1804f..33373a656 100644 --- a/Stdlib/Base/AddressType.php +++ b/Stdlib/Base/AddressType.php @@ -37,4 +37,6 @@ abstract class AddressType extends Enum public const CONTRACT = 6; public const OTHER = 7; + + public const EDUCATION = 8; } diff --git a/Stdlib/Base/FloatInt.php b/Stdlib/Base/FloatInt.php new file mode 100644 index 000000000..797e0e513 --- /dev/null +++ b/Stdlib/Base/FloatInt.php @@ -0,0 +1,343 @@ +value = \is_int($value) ? $value : self::toInt((string) $value); + $this->thousands = $thousands; + $this->decimal = $decimal; + } + + /** + * FloatInt to int. + * + * @param string $value FloatInt value + * @param string $thousands Thousands character + * @param string $decimal Decimal character + * + * @return int + * + * @throws \Exception this exception is thrown if an internal explode or substr error occurs + * + * @since 1.0.0 + */ + public static function toInt(string $value, string $thousands = ',', string $decimal = '.') : int + { + $split = \explode($decimal, $value); + + if ($split === false) { + throw new \Exception('Internal explode error.'); // @codeCoverageIgnore + } + + $left = $split[0]; + $left = \str_replace($thousands, '', $left); + $right = ''; + + if (\count($split) > 1) { + $right = $split[1]; + } + + $right = \substr($right, 0, self::MAX_DECIMALS); + if ($right === false) { + throw new \Exception('Internal substr error.'); // @codeCoverageIgnore + } + + return ((int) $left) * 10 ** self::MAX_DECIMALS + (int) \str_pad($right, self::MAX_DECIMALS, '0'); + } + + /** + * Set localization. + * + * @param string $thousands Thousands separator + * @param string $decimal Decimal separator + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function setLocalization(string $thousands = ',', string $decimal = '.') : self + { + $this->thousands = $thousands; + $this->decimal = $decimal; + + return $this; + } + + /** + * Set value by string. + * + * @param string $value FloatInt value + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function setString(string $value) : self + { + $this->value = self::toInt($value, $this->thousands, $this->decimal); + + return $this; + } + + /** + * Get money. + * + * @param null|int $decimals Precision (null = auto decimals) + * + * @return string + * + * @throws \Exception this exception is thrown if an internal substr error occurs + * + * @since 1.0.0 + */ + public function getAmount(?int $decimals = 2) : string + { + $isNegative = $this->value < 0 ? 1 : 0; + + $value = $this->value === 0 + ? \str_repeat('0', self::MAX_DECIMALS) + : (string) \round($this->value, -self::MAX_DECIMALS + $decimals); + + $left = \substr($value, 0, -self::MAX_DECIMALS + $isNegative); + + /** @var string $left */ + $left = $left === false ? '0' : $left; + $right = \substr($value, -self::MAX_DECIMALS + $isNegative); + + if ($right === false) { + throw new \Exception(); // @codeCoverageIgnore + } + + if ($decimals === null) { + $decimals = \strlen(\rtrim($right, '0')); + } + + return $decimals > 0 + ? \number_format((float) $left, 0, $this->decimal, $this->thousands) . $this->decimal . \substr($right, 0, $decimals) + : \str_pad($left, 1, '0'); + } + + /** + * Add money. + * + * @param int|float|string|FloatInt $value Value to add + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function add(int | float | string | self $value) : self + { + if (\is_string($value) || \is_float($value)) { + $this->value += self::toInt((string) $value, $this->thousands, $this->decimal); + } elseif (\is_int($value)) { + $this->value += $value; + } else { + $this->value += $value->getInt(); + } + + return $this; + } + + /** + * Get money value. + * + * @return int + * + * @since 1.0.0 + */ + public function getInt() : int + { + return $this->value; + } + + /** + * Sub money. + * + * @param int|float|string|FloatInt $value Value to subtract + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function sub(int | float | string | self $value) : self + { + if (\is_string($value) || \is_float($value)) { + $this->value -= self::toInt((string) $value, $this->thousands, $this->decimal); + } elseif (\is_int($value)) { + $this->value -= $value; + } else { + $this->value -= $value->getInt(); + } + + return $this; + } + + /** + * Mult. + * + * @param int|float $value Value to multiply with + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function mult(int | float $value) : self + { + $this->value = (int) ($this->value * $value); + + return $this; + } + + /** + * Div. + * + * @param int|float $value Value to divide by + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function div(int | float $value) : self + { + $this->value = (int) ($this->value / $value); + + return $this; + } + + /** + * Abs. + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function abs() : self + { + $this->value = \abs($this->value); + + return $this; + } + + /** + * Power. + * + * @param int|float $value Value to power + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function pow(int | float $value) : self + { + $this->value = (int) ($this->value ** $value); + + return $this; + } + + /** + * Searialze. + * + * @return string + * + * @since 1.0.0 + */ + public function serialize() : string + { + return (string) $this->getInt(); + } + + /** + * Unserialize. + * + * @param mixed $value Value to unserialize + * + * @return void + * + * @since 1.0.0 + */ + public function unserialize($value) : void + { + $this->setInt((int) $value); + } + + /** + * Set money value. + * + * @param int $value Value + * + * @return FloatInt + * + * @since 1.0.0 + */ + public function setInt(int $value) : self + { + $this->value = $value; + + return $this; + } +} diff --git a/Stdlib/Base/Location.php b/Stdlib/Base/Location.php index cab2e57e1..c5707bdad 100644 --- a/Stdlib/Base/Location.php +++ b/Stdlib/Base/Location.php @@ -212,15 +212,17 @@ class Location implements \JsonSerializable, \Serializable } /** - * Constructs the object - * @link http://php.net/manual/en/serializable.unserialize.php - * @param string $serialized

- * The string representation of the object. - *

- * @return void - * @since 5.1.0 + * {@inheritdoc} */ public function unserialize($serialized) : void { + $data = \json_decode($serialized, true); + + $this->postal = $data['postal']; + $this->city = $data['city']; + $this->country = $data['country']; + $this->address = $data['address']; + $this->state = $data['state']; + $this->geo = $data['geo']; } } diff --git a/System/File/FileUtils.php b/System/File/FileUtils.php index 6c5543bac..bd197c59d 100644 --- a/System/File/FileUtils.php +++ b/System/File/FileUtils.php @@ -110,7 +110,9 @@ final class FileUtils public static function absolute(string $origPath) : string { if (\file_exists($origPath) !== false) { - return \realpath($origPath); + $path = \realpath($origPath); + + return $path === false ? '' : $path; } $startsWithSlash = \strpos($origPath, '/') === 0 || \strpos($origPath, '\\') === 0 ? '/' : ''; diff --git a/tests/Utils/MbStringUtilsTest.php b/tests/Utils/MbStringUtilsTest.php index 04907aa13..75ef4a186 100644 --- a/tests/Utils/MbStringUtilsTest.php +++ b/tests/Utils/MbStringUtilsTest.php @@ -32,7 +32,7 @@ class MbStringUtilsTest extends \PHPUnit\Framework\TestCase */ public function testEntropy() : void { - self::assertEqualsWithDelta(2.75, MbStringUtils::mb_entropy('akj@!©¥j'), 0.1); + self::assertEqualsWithDelta(2.75, MbStringUtils::mb_entropy('akj@!(c)¥j'), 0.1); } /**