diff --git a/Business/Finance/Forecasting/AR.php b/Business/Finance/Forecasting/AR.php deleted file mode 100644 index 995f9adf7..000000000 --- a/Business/Finance/Forecasting/AR.php +++ /dev/null @@ -1,19 +0,0 @@ -data = $data; - $this->order = $order; - - if ($order !== 12 && $order !== 4) { - throw new \Exception('ARIMA only supports quarterly and monthly decomposition'); - } - } - - /** - * Return data decomposition. - * - * @return array - * - * @since 1.0.0 - */ - public function getDecomposition() : array - { - $iteration1 = $this->getIteration($this->data); - $iteration2 = $this->getIteration($iteration1); - $iteration3 = $this->getIteration($iteration2); - - return $iteration3; - } - - private function getIteration(array $data) : array - { - $multiplicativeDecomposition = new ClassicalDecomposition($data, $this->order, ClassicalDecomposition::MULTIPLICATIVE); - $tempDecomposition = $multiplicativeDecomposition->getDecomposition(); - - // 1 - $trendCycleComponent = ClassicalDecomposition::computeTrendCycle($data, $this->order); - $centeredRatios = ClassicalDecomposition::computeDetrendedSeries($this->data, $trendCycleComponent, ClassicalDecomposition::MULTIPLICATIVE); - $prelimSeasonalComponent = Average::totalMovingAverage(Average::totalMovingAverage($centeredRatios, 3, null, true), 3, null, true); - $prelimRemainder = $this->getPrelimRemainder($centeredRatios, $prelimSeasonalComponent); - $modifiedRemainder = $this->removeOutliers($prelimRemainder, 0.5); - $modifiedCenteredRatios = $this->getModifiedCenteredRatios($prelimSeasonalComponent, $modifiedRemainder); - $revisedSeasonalComponent = Average::totalMovingAverage(Average::totalMovingAverage($modifiedCenteredRatios, 3, null, true), 3, null, true); - $prelimSeasonalAdjustedSeries = $this->getPrelimSeasonalAdjustedSeries($revisedSeasonalComponent); - $trendCycleComponent = $this->getTrendCycleEstimation($prelimSeasonalAdjustedSeries); - - // 2 - $centeredRatios = ClassicalDecomposition::computeDetrendedSeries($this->data, $trendCycleComponent, ClassicalDecomposition::MULTIPLICATIVE); - $prelimSeasonalComponent = Average::totalMovingAverage(Average::totalMovingAverage($centeredRatios, 5, null, true), 3, null, true); - $prelimRemainder = $this->getPrelimRemainder($centeredRatios, $prelimSeasonalComponent); - $modifiedRemainder = $this->removeOutliers($prelimRemainder, 0.5); - $modifiedCenteredRatios = $this->getModifiedCenteredRatios($prelimSeasonalComponent, $modifiedRemainder); - $revisedSeasonalComponent = Average::totalMovingAverage(Average::totalMovingAverage($modifiedCenteredRatios, 5, null, true), 3, null, true); - $seasonalAdjustedSeries = $this->getSeasonalAdjustedSeries($revisedSeasonalComponent); - - $remainder = $this->getRemainder($seasonalAdjustedSeries, $trendCycleComponent); - $modifiedRemainder = $this->removeOutliers($remainder, 0.5); - $modifiedData = $this->getModifiedData($trendCycleComponent, $seasonalAdjustedSeries, $modifiedRemainder); - - return $modifiedData; - } - - private function getPrelimRemainder(array $centeredRatios, array $prelimSeasonalComponent) : array - { - $remainder = []; - $count = \count($prelimSeasonalComponent); - - for ($i = 0; $i < $count; ++$i) { - // +1 since 3x3 MA - $remainder[] = $centeredRatios[$i + 1] / $prelimSeasonalComponent[$i]; - } - - return $remainder; - } - - private function removeOutliers(array $data, float $deviation = 0.5) : array - { - $avg = Average::arithmeticMean($data); - - foreach ($data as $key => $value) { - if ($value / $avg - 1 > $deviation) { - $data[$key] = $avg; - } - } - - return $data; - } - - private function getModifiedCenteredRatios(array $seasonal, array $remainder) : array - { - $centeredRatio = []; - $count = \count($seasonal); - - for ($i = 0; $i < $count; ++$i) { - // +1 since 3x3 MA - $centeredRatio[] = $remainder[$i + 1] * $seasonal[$i]; - } - - return $centeredRatio; - } - - private function getTrendCycleEstimation(array $seasonal) : array - { - $count = \count($seasonal); - - if ($count >= 12) { - $weight = Average::MAH23; - } elseif ($count >= 6) { - $weight = Average::MAH13; - } else { - $weight = Average::MAH9; - } - - // todo: implement - - return $seasonal; - } - - private function getSeasonalAdjustedSeries(array $seasonal) : array - { - $adjusted = []; - $count = \count($seasonal); - $start = ClassicalDecomposition::getStartOfDecomposition(\count($this->data), $count); - - for ($i = 0; $i < $count; ++$i) { - $adjusted[] = $this->data[$start + $i] / $seasonal[$i]; - } - - return $adjusted; - } - - private function getRemainder(array $seasonal, array $trendCycle) : array - { - $remainder = []; - foreach ($seasonal as $key => $e) { - $remainder = $e / $trendCycle[$key]; - } - - return $remainder; - } - - private function getModifiedData(array $trendCycleComponent, array $seasonalAdjustedSeries, array $remainder) : array - { - $data = []; - $count = \count($trendCycleComponent); - - for ($i = 0; $i < $count; ++$i) { - $data[] = $trendCycleComponent[$i] * $seasonalAdjustedSeries[$i] * $remainder[$i]; - } - - return $data; - } -} diff --git a/Business/Finance/Forecasting/ARMA.php b/Business/Finance/Forecasting/ARMA.php deleted file mode 100644 index 7634df917..000000000 --- a/Business/Finance/Forecasting/ARMA.php +++ /dev/null @@ -1,19 +0,0 @@ -mode = $mode; - $this->data = $data; - $this->order = $order; - - $this->dataSize = \count($data); - } - - /** - * Get decomposition. - * - * @return array Returns an array containing the trend cycle component, detrended series, seasonal component and remainder component. - * - * @since 1.0.0 - */ - public function getDecomposition() : array - { - $trendCycleComponent = self::computeTrendCycle($this->data, $this->order); - $detrendedSeries = self::computeDetrendedSeries($this->data, $trendCycleComponent, $this->mode); - $seasonalComponent = $this->computeSeasonalComponent($detrendedSeries, $this->order); - $remainderComponent = self::computeRemainderComponent($this->data, $trendCycleComponent, $seasonalComponent, $this->mode); - - return [ - 'trendCycleComponent' => $trendCycleComponent, - 'detrendedSeries' => $detrendedSeries, - 'seasonalComponent' => $seasonalComponent, - 'remainderComponent' => $remainderComponent, - ]; - } - - /** - * Calculate trend cycle - * - * @param array $data Data to analyze - * @param int $order Seasonal period (e.g. 4 = quarterly, 12 = monthly, 7 = weekly pattern in daily data) - * - * @return array Total moving average 2 x m-MA - * - * @since 1.0.0 - */ - public static function computeTrendCycle(array $data, int $order) : array - { - $mMA = Average::totalMovingAverage($data, $order, null, true); - - return $order % 2 === 0 ? Average::totalMovingAverage($mMA, $order, null, true) : $mMA; - } - - /** - * Calculate detrended series - * - * @param array $data Data to analyze - * @param array $trendCycleComponent Trend cycle component - * @param int $mode Detrend mode - * - * @return array Detrended series / seasonal normalized data - * - * @since 1.0.0 - */ - public static function computeDetrendedSeries(array $data, array $trendCycleComponent, int $mode) : array - { - $detrended = []; - $count = \count($trendCycleComponent); - $start = self::getStartOfDecomposition(\count($data), $count); - - for ($i = 0; $i < $count; ++$i) { - $detrended[] = $mode === self::ADDITIVE ? $data[$start + $i] - $trendCycleComponent[$i] : $data[$start + $i] / $trendCycleComponent[$i]; - } - - return $detrended; - } - - /** - * Calculate the data start point for the decomposition - * - * By using averaging methods some initial data get's incorporated into the average which reduces the data points. - * - * @param int $dataSize Original data size - * @param int $trendCycleComponents Trend cycle component size - * - * @return int New data start index - * - * @since 1.0.0 - */ - public static function getStartOfDecomposition(int $dataSize, int $trendCycleComponents) : int - { - return (int) (($dataSize - $trendCycleComponents) / 2); - } - - /** - * Calculate the seasonal component - * - * Average of the detrended values for every month, quarter, day etc. - * - * @param array $detrendedSeries Detrended series - * @param int $order Seasonal period (e.g. 4 = quarterly, 12 = monthly, 7 = weekly pattern in daily data) - * - * @return array - * - * @since 1.0.0 - */ - private function computeSeasonalComponent(array $detrendedSeries, int $order) : array - { - $seasonalComponent = []; - $count = \count($detrendedSeries); - - for ($i = 0; $i < $order; ++$i) { - $temp = []; - - for ($j = $i; $j < $count; $j += $order) { - $temp[] = $detrendedSeries[$j]; - } - - $seasonalComponent[] = Average::arithmeticMean($temp); - } - - return $seasonalComponent; - } - - /** - * Calculate the remainder component or error - * - * @param array $data Raw data - * @param array $trendCycleComponent Trend cycle component - * @param array $seasonalComponent Seasonal component - * @param int $mode Detrend mode - * - * @return array All remainders or absolute errors - * - * @since 1.0.0 - */ - public static function computeRemainderComponent(array $data, array $trendCycleComponent, array $seasonalComponent, int $mode = self::ADDITIVE) : array - { - $dataSize = \count($data); - $remainderComponent = []; - $count = \count($trendCycleComponent); - $start = self::getStartOfDecomposition($dataSize, $count); - $seasons = \count($seasonalComponent); - - for ($i = 0; $i < $count; ++$i) { - $remainderComponent[] = $mode === self::ADDITIVE ? $data[$start + $i] - $trendCycleComponent[$i] - $seasonalComponent[$i % $seasons] : $data[$start + $i] / ($trendCycleComponent[$i] * $seasonalComponent[$i % $seasons]); - } - - return $remainderComponent; - } -} diff --git a/Business/Finance/Forecasting/ExponentialSmoothing/ErrorType.php b/Business/Finance/Forecasting/ExponentialSmoothing/ErrorType.php deleted file mode 100644 index 3d5b8101f..000000000 --- a/Business/Finance/Forecasting/ExponentialSmoothing/ErrorType.php +++ /dev/null @@ -1,33 +0,0 @@ -data = $data; - } - - public function getANN() - { - } - - public function getANA() - { - } - - public function getANM() - { - } - - public function getAAN() - { - } - - public function getAAA() - { - } - - public function getAAM() - { - } - - public function getAMN() - { - } - - public function getAMA() - { - } - - public function getAMM() - { - } - - public function getMNN() - { - } - - public function getMNA() - { - } - - public function getMNM() - { - } - - public function getMAN() - { - } - - public function getMAA() - { - } - - public function getMAM() - { - } - - public function getMMN() - { - } - - public function getMMA() - { - } - - public function getMMM() - { - } -} diff --git a/Business/Finance/Forecasting/ExponentialSmoothing/SeasonalType.php b/Business/Finance/Forecasting/ExponentialSmoothing/SeasonalType.php deleted file mode 100644 index cac3b3a79..000000000 --- a/Business/Finance/Forecasting/ExponentialSmoothing/SeasonalType.php +++ /dev/null @@ -1,33 +0,0 @@ - 1 ? $options : \reset($options); } catch (\PDOException $e) { + // @codeCoverageIgnoreStart $exception = DatabaseExceptionFactory::createException($e); $message = DatabaseExceptionFactory::createExceptionMessage($e); throw new $exception($message); + // @codeCoverageIgnoreEnd } } diff --git a/DataStorage/Database/Connection/MysqlConnection.php b/DataStorage/Database/Connection/MysqlConnection.php index 92676ab56..d18e6fa6f 100644 --- a/DataStorage/Database/Connection/MysqlConnection.php +++ b/DataStorage/Database/Connection/MysqlConnection.php @@ -59,6 +59,7 @@ final class MysqlConnection extends ConnectionAbstract $this->dbdata = isset($dbdata) ? $dbdata : $this->dbdata; if (!isset($this->dbdata['db'], $this->dbdata['host'], $this->dbdata['port'], $this->dbdata['database'], $this->dbdata['login'], $this->dbdata['password'])) { + $this->status = DatabaseStatus::FAILURE; throw new InvalidConnectionConfigException((string) \json_encode($this->dbdata)); } @@ -73,7 +74,7 @@ final class MysqlConnection extends ConnectionAbstract $this->status = DatabaseStatus::OK; } catch (\PDOException $e) { $this->status = DatabaseStatus::MISSING_DATABASE; - $this->con = null; + throw new InvalidConnectionConfigException((string) \json_encode($this->dbdata)); } finally { $this->dbdata['password'] = '****'; } diff --git a/DataStorage/Database/DataMapperAbstract.php b/DataStorage/Database/DataMapperAbstract.php index 7b9e3cc7b..fc6115aae 100644 --- a/DataStorage/Database/DataMapperAbstract.php +++ b/DataStorage/Database/DataMapperAbstract.php @@ -146,6 +146,14 @@ class DataMapperAbstract implements DataMapperInterface */ protected static $initObjects = []; + /** + * Initialized arrays for cross reference to reduce initialization costs + * + * @var array[] + * @since 1.0.0 + */ + protected static $initArrays = []; + /** * Highest mapper to know when to clear initialized objects * @@ -304,6 +312,7 @@ class DataMapperAbstract implements DataMapperInterface // clear parent and objects if (static::class === self::$parentMapper) { //self::$initObjects = []; // todo: now all objects are cached for the whole request + //self::$initArrays = []; // todo: now all objects are cached for the whole request self::$parentMapper = null; } } @@ -462,15 +471,13 @@ class DataMapperAbstract implements DataMapperInterface * * @since 1.0.0 */ - private static function createModelArray(array $obj) + private static function createModelArray(array &$obj) { $query = new Builder(self::$db); $query->prefix(self::$db->getPrefix())->into(static::$table); foreach (static::$columns as $key => $column) { - if (isset(static::$hasMany[$key]) - || isset(static::$hasOne[$key]) - ) { + if (isset(static::$hasMany[$key]) || isset(static::$hasOne[$key])) { continue; } @@ -478,7 +485,7 @@ class DataMapperAbstract implements DataMapperInterface if (\stripos($column['internal'], '/') !== false) { $path = \explode('/', $column['internal']); - \array_shift($path); + \array_shift($path); // todo: why am I doing this? $path = \implode('/', $path); } @@ -489,19 +496,21 @@ class DataMapperAbstract implements DataMapperInterface $value = self::parseValue($column['type'], $id); $query->insert($column['name'])->value($value, $column['type']); - break; } elseif (isset(static::$belongsTo[$path])) { $id = self::createBelongsToArray($column['internal'], $property); $value = self::parseValue($column['type'], $id); $query->insert($column['name'])->value($value, $column['type']); - break; - } elseif ($column['internal'] === $path) { + } elseif ($column['internal'] === $path && $column['name'] !== static::$primaryField) { $value = self::parseValue($column['type'], $property); $query->insert($column['name'])->value($value, $column['type']); - break; } + } + + // if a table only has a single column = primary key column. This must be done otherwise the query is empty + if ($query->getType() === QueryType::NONE) { + $query->insert(static::$primaryField)->value(0, static::$columns[static::$primaryField]['type']); } self::$db->con->prepare($query->toSql())->execute(); @@ -679,7 +688,7 @@ class DataMapperAbstract implements DataMapperInterface continue; } - $primaryKey = $obj[static::$columns[static::$primaryField]['internal']]; + $primaryKey = $value[$mapper::$columns[$mapper::$primaryField]['internal']]; // already in db if (!empty($primaryKey)) { @@ -1628,7 +1637,7 @@ class DataMapperAbstract implements DataMapperInterface foreach (static::$hasOne as $member => $one) { /** @var string $mapper */ $mapper = static::$hasOne[$member]['mapper']; - $obj[$member] = self::getInitialized($mapper, $obj['member']) ?? $mapper::getArray($obj[$member], RelationType::ALL, $depth); + $obj[$member] = self::getInitializedArray($mapper, $obj['member']) ?? $mapper::getArray($obj[$member], RelationType::ALL, $depth); } } @@ -1694,7 +1703,7 @@ class DataMapperAbstract implements DataMapperInterface foreach (static::$ownsOne as $member => $one) { /** @var string $mapper */ $mapper = static::$ownsOne[$member]['mapper']; - $obj[$member] = self::getInitialized($mapper, $obj[$member]) ?? $mapper::getArray($obj[$member], RelationType::ALL, $depth); + $obj[$member] = self::getInitializedArray($mapper, $obj[$member]) ?? $mapper::getArray($obj[$member], RelationType::ALL, $depth); } } @@ -1760,7 +1769,7 @@ class DataMapperAbstract implements DataMapperInterface foreach (static::$belongsTo as $member => $one) { /** @var string $mapper */ $mapper = static::$belongsTo[$member]['mapper']; - $obj[$member] = self::getInitialized($mapper, $obj[$member]) ?? $mapper::get($obj[$member], RelationType::ALL, null, $depth); + $obj[$member] = self::getInitializedArray($mapper, $obj[$member]) ?? $mapper::getArray($obj[$member], RelationType::ALL, $depth); } } @@ -1997,14 +2006,14 @@ class DataMapperAbstract implements DataMapperInterface $obj = []; foreach ($primaryKey as $key => $value) { - if (self::isInitialized(static::class, $value)) { - $obj[$value] = self::$initObjects[static::class][$value]; + if (self::isInitializedArray(static::class, $value)) { + $obj[$value] = self::$initArrays[static::class][$value]; continue; } $obj[$value] = self::populateAbstractArray(self::getRaw($value)); - self::addInitialized(static::class, $value, $obj[$value]); + self::addInitializedArray(static::class, $value, $obj[$value]); } self::fillRelationsArray($obj, $relations, --$depth); @@ -2070,13 +2079,13 @@ class DataMapperAbstract implements DataMapperInterface * @param mixed $refKey Key * @param string $ref The field that defines the for * @param int $relations Load relations - * @param mixed $fill Object to fill + * @param int $depth Relation depth * * @return mixed * * @since 1.0.0 */ - public static function getForArray($refKey, string $ref, int $relations = RelationType::ALL, $fill = null) + public static function getForArray($refKey, string $ref, int $relations = RelationType::ALL, int $depth = 3) { if (!isset(self::$parentMapper)) { self::$parentMapper = static::class; @@ -2096,7 +2105,7 @@ class DataMapperAbstract implements DataMapperInterface $toLoad = self::getPrimaryKeysBy($value, self::getColumnByMember($ref)); } - $obj[$value] = self::get($toLoad, $relations, $fill); + $obj[$value] = self::getArray($toLoad, $relations, $depth); } return \count($obj) === 1 ? \reset($obj) : $obj; @@ -2356,7 +2365,7 @@ class DataMapperAbstract implements DataMapperInterface return; } - if ($relations !== RelationType::NONE) { + if ($relations === RelationType::NONE) { return; } @@ -2640,6 +2649,26 @@ class DataMapperAbstract implements DataMapperInterface self::$initObjects[$mapper][$id] = $obj; } + /** + * Add initialized object to local cache + * + * @param string $mapper Mapper name + * @param mixed $id Object id + * @param array $obj Model to cache locally + * + * @return void + * + * @since 1.0.0 + */ + private static function addInitializedArray(string $mapper, $id, array $obj = null) : void + { + if (!isset(self::$initArrays[$mapper])) { + self::$initArrays[$mapper] = []; + } + + self::$initArrays[$mapper][$id] = $obj; + } + /** * Check if a object is initialized * @@ -2655,6 +2684,21 @@ class DataMapperAbstract implements DataMapperInterface return isset(self::$initObjects[$mapper]) && isset(self::$initObjects[$mapper][$id]); } + /** + * Check if a object is initialized + * + * @param string $mapper Mapper name + * @param mixed $id Object id + * + * @return bool + * + * @since 1.0.0 + */ + private static function isInitializedArray(string $mapper, $id) : bool + { + return isset(self::$initArrays[$mapper]) && isset(self::$initArrays[$mapper][$id]); + } + /** * Get initialized object * @@ -2670,6 +2714,21 @@ class DataMapperAbstract implements DataMapperInterface return self::$initObjects[$mapper][$id] ?? null; } + /** + * Get initialized object + * + * @param string $mapper Mapper name + * @param mixed $id Object id + * + * @return mixed + * + * @since 1.0.0 + */ + private static function getInitializedArray(string $mapper, $id) + { + return self::$initArrays[$mapper][$id] ?? null; + } + /** * Remove initialized object * @@ -2685,6 +2744,10 @@ class DataMapperAbstract implements DataMapperInterface if (self::isInitialized($mapper, $id)) { unset(self::$initObjects[$mapper][$id]); } + + if (self::isInitializedArray($mapper, $id)) { + unset(self::$initArrays[$mapper][$id]); + } } /** diff --git a/DataStorage/Database/Query/Builder.php b/DataStorage/Database/Query/Builder.php index 5804019b5..aa0e55ba3 100644 --- a/DataStorage/Database/Query/Builder.php +++ b/DataStorage/Database/Query/Builder.php @@ -383,14 +383,12 @@ final class Builder extends BuilderAbstract return true; } - $test = \strtolower($raw); - - if (\strpos($test, 'insert') !== false - || \strpos($test, 'update') !== false - || \strpos($test, 'drop') !== false - || \strpos($test, 'delete') !== false - || \strpos($test, 'create') !== false - || \strpos($test, 'alter') !== false + if (\stripos($raw, 'insert') !== false + || \stripos($raw, 'update') !== false + || \stripos($raw, 'drop') !== false + || \stripos($raw, 'delete') !== false + || \stripos($raw, 'create') !== false + || \stripos($raw, 'alter') !== false ) { return false; } @@ -482,10 +480,6 @@ final class Builder extends BuilderAbstract */ public function where($columns, $operator = null, $values = null, $boolean = 'and') : Builder { - if ($operator !== null && !\is_array($operator) && !\in_array(\strtolower($operator), self::OPERATORS)) { - throw new \InvalidArgumentException('Unknown operator.'); - } - if (!\is_array($columns)) { $columns = [$columns]; $operator = [$operator]; @@ -1075,10 +1069,10 @@ final class Builder extends BuilderAbstract * * @since 1.0.0 */ - public function join($column, string $type = JoinType::JOIN) : Builder + public function join($table, string $type = JoinType::JOIN) : Builder { - if (\is_string($column) || $column instanceof \Closure) { - $this->joins[] = ['type' => $type, 'column' => $column]; + if (\is_string($table) || $table instanceof \Closure) { + $this->joins[] = ['type' => $type, 'table' => $table]; } else { throw new \InvalidArgumentException(); } @@ -1250,7 +1244,7 @@ final class Builder extends BuilderAbstract $boolean = [$boolean]; } - $joinCount = \count($this->joins); + $joinCount = \count($this->joins) - 1; $i = 0; foreach ($columns as $key => $column) { @@ -1271,6 +1265,30 @@ final class Builder extends BuilderAbstract return $this; } + /** + * On. + * + * @return Builder + * + * @since 1.0.0 + */ + public function orOn($columns, $operator = null, $values = null) : Builder + { + return $this->on($columns, $operator, $values, 'or'); + } + + /** + * On. + * + * @return Builder + * + * @since 1.0.0 + */ + public function andOn($columns, $operator = null, $values = null) : Builder + { + return $this->on($columns, $operator, $values, 'and'); + } + /** * Merging query. * diff --git a/DataStorage/Database/Query/Grammar/Grammar.php b/DataStorage/Database/Query/Grammar/Grammar.php index b6d5228c3..67a10d547 100644 --- a/DataStorage/Database/Query/Grammar/Grammar.php +++ b/DataStorage/Database/Query/Grammar/Grammar.php @@ -407,7 +407,7 @@ class Grammar extends GrammarAbstract foreach ($joins as $key => $join) { $expression .= $join['type'] . ' '; - $expression .= $this->compileSystem($join, $prefix); + $expression .= $this->compileSystem($join['table'], $query->getPrefix()); $expression .= $this->compileOn($query, $query->ons[$key]); } @@ -431,7 +431,7 @@ class Grammar extends GrammarAbstract $expression = ''; foreach ($ons as $key => $on) { - $expression .= $this->compileWhereElement($on, $query, $first); + $expression .= $this->compileOnElement($on, $query, $first); $first = false; } @@ -439,7 +439,48 @@ class Grammar extends GrammarAbstract return ''; } - return 'ON ' . $expression; + return ' ON ' . $expression; + } + + /** + * Compile where element. + * + * @param array $element Element data + * @param Builder $query Query builder + * @param bool $first Is first element (usefull for nesting) + * + * @return string + * + * @since 1.0.0 + */ + protected function compileOnElement(array $element, Builder $query, bool $first = true) : string + { + $expression = ''; + + if (!$first) { + $expression = ' ' . \strtoupper($element['boolean']) . ' '; + } + + if (\is_string($element['column'])) { + // handle bug when no table is specified in the where column + if (\count($query->from) === 1 && \stripos($element['column'], '.') === false) { + $element['column'] = $query->from[0] . '.' . $element['column']; + } + + $expression .= $this->compileSystem($element['column'], $query->getPrefix()); + } elseif ($element['column'] instanceof \Closure) { + $expression .= $element['column'](); + } elseif ($element['column'] instanceof Builder) { + $expression .= '(' . $element['column']->toSql() . ')'; + } elseif ($element['column'] instanceof Where) { + $expression .= '(' . $this->compileWhere($element['column'], $query->getPrefix()) . ')'; + } + + if (isset($element['value'])) { + $expression .= ' ' . \strtoupper($element['operator']) . ' ' . $this->compileSystem($element['value'], $query->getPrefix()); + } + + return $expression; } /** diff --git a/Localization/L11nManager.php b/Localization/L11nManager.php index 0f9d6541d..04a5f5eec 100644 --- a/Localization/L11nManager.php +++ b/Localization/L11nManager.php @@ -159,11 +159,13 @@ final class L11nManager return 'ERROR'; } } catch (\Throwable $e) { + // @codeCoverageIgnoreStart FileLogger::getInstance()->warning(FileLogger::MSG_FULL, [ 'message' => 'Undefined translation for \'' . $code . '/' . $module . '/' . $translation . '\'.', ]); return 'ERROR'; + // @codeCoverageIgnoreEnd } } diff --git a/Math/Matrix/Matrix.php b/Math/Matrix/Matrix.php index d18e86a35..47bd64ce4 100644 --- a/Math/Matrix/Matrix.php +++ b/Math/Matrix/Matrix.php @@ -189,8 +189,8 @@ class Matrix implements \ArrayAccess, \Iterator $rlength = \count($rows); $clength = \count($cols); - for ($i = 0; $i <= $rlength; ++$i) { - for ($j = 0; $j <= $clength; ++$j) { + for ($i = 0; $i < $rlength; ++$i) { + for ($j = 0; $j < $clength; ++$j) { $X[$i][$j] = $this->matrix[$rows[$i]][$cols[$j]]; } } @@ -218,7 +218,7 @@ class Matrix implements \ArrayAccess, \Iterator $length = \count($cols); for ($i = $iRow; $i <= $lRow; ++$i) { - for ($j = 0; $j <= $length; ++$j) { + for ($j = 0; $j < $length; ++$j) { $X[$i - $iRow][$j] = $this->matrix[$i][$cols[$j]]; } } diff --git a/Utils/Barcode/C128Abstract.php b/Utils/Barcode/C128Abstract.php index cd9cb357d..dea55dcd5 100644 --- a/Utils/Barcode/C128Abstract.php +++ b/Utils/Barcode/C128Abstract.php @@ -264,6 +264,28 @@ abstract class C128Abstract \imagedestroy($res); } + /** + * Validate the barcode string + * + * @param string $code Barcode string + * + * @return void + * + * @since 1.0.0 + */ + public function isValidString(string $barcode) : bool + { + $length = \strlen($barcode); + + for ($i = 0; $i < $length; ++$i) { + if (!isset(static::$CODEARRAY[$barcode[$i]]) && !\in_array($barcode[$i], static::$CODEARRAY)) { + return false; + } + } + + return true; + } + /** * Generate weighted code string * diff --git a/Utils/Parser/Php/ArrayParser.php b/Utils/Parser/Php/ArrayParser.php index 6c9e2627c..2b311f7c3 100644 --- a/Utils/Parser/Php/ArrayParser.php +++ b/Utils/Parser/Php/ArrayParser.php @@ -67,14 +67,16 @@ class ArrayParser return ArrayParser::serializeArray($value, $depth); } elseif (\is_string($value)) { return '\'' . \str_replace('\'', '\\\'', $value) . '\''; - } elseif (\is_scalar($value)) { - return (string) $value; - } elseif ($value === null) { - return 'null'; } elseif (\is_bool($value)) { return $value ? 'true' : 'false'; + } elseif ($value === null) { + return 'null'; + } elseif (\is_scalar($value)) { + return (string) $value; } elseif ($value instanceOf \Serializable) { return self::parseVariable($value->serialize()); + } elseif ($value instanceOf \jsonSerializable) { + return self::parseVariable($value->jsonSerialize()); } else { throw new \UnexpectedValueException(); } diff --git a/Utils/TestUtils.php b/Utils/TestUtils.php index 52ad9ff16..4f273790a 100644 --- a/Utils/TestUtils.php +++ b/Utils/TestUtils.php @@ -78,14 +78,14 @@ final class TestUtils /** * Get private object member * - * @param object|string $obj Object to read - * @param string $name Member name to read + * @param object $obj Object to read + * @param string $name Member name to read * * @return mixed Returns the member variable value * * @since 1.0.0 */ - public static function getMember($obj, string $name) + public static function getMember(object $obj, string $name) { $reflectionClass = new \ReflectionClass(\is_string($obj) ? $obj : \get_class($obj)); @@ -99,12 +99,7 @@ final class TestUtils $reflectionProperty->setAccessible(true); } - $value = null; - if (\is_string($obj)) { - $value = $reflectionProperty->getValue(); - } elseif (\is_object($obj)) { - $value = $reflectionProperty->getValue($obj); - } + $value = $reflectionProperty->getValue($obj); if (!$accessible) { $reflectionProperty->setAccessible(false); diff --git a/Validation/Barcode/Barcode.php b/Validation/Barcode/Barcode.php deleted file mode 100644 index 74d097b2c..000000000 --- a/Validation/Barcode/Barcode.php +++ /dev/null @@ -1,28 +0,0 @@ -model = new BaseModel(); + $this->model = new BaseModel(); + $this->modelArray = [ + 'id' => 0, + 'string' => 'Base', + 'int' => 11, + 'bool' => false, + 'null' => null, + 'float' => 1.3, + 'json' => [1, 2, 3], + 'jsonSerializable' => new class implements \JsonSerializable { + public function jsonSerialize() + { + return [1, 2, 3]; + } + }, + 'datetime' => new \DateTime('2005-10-11'), + 'ownsOneSelf' => [ + 'id' => 0, + 'string' => 'OwnsOne', + ], + 'belongsToOne' => [ + 'id' => 0, + 'string' => 'BelongsTo', + ], + 'hasManyDirect' => [ + [ + 'id' => 0, + 'string' => 'ManyToManyDirect', + 'to' => 0, + ], + [ + 'id' => 0, + 'string' => 'ManyToManyDirect', + 'to' => 0, + ] + ], + 'hasManyRelations' => [ + [ + 'id' => 0, + 'string' => 'ManyToManyRel', + ], + [ + 'id' => 0, + 'string' => 'ManyToManyRel', + ] + ], + ]; $GLOBALS['dbpool']->get()->con->prepare( 'CREATE TABLE `oms_test_base` ( @@ -104,6 +152,12 @@ class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase self::assertGreaterThan(0, $this->model->id); } + public function testCreateArray() + { + self::assertGreaterThan(0, BaseModelMapper::createArray($this->modelArray)); + self::assertGreaterThan(0, $this->modelArray['id']); + } + public function testRead() { $id = BaseModelMapper::create($this->model); @@ -128,6 +182,37 @@ class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase self::assertEquals(\reset($this->model->hasManyRelations)->string, \reset($modelR->hasManyRelations)->string); self::assertEquals($this->model->ownsOneSelf->string, $modelR->ownsOneSelf->string); self::assertEquals($this->model->belongsToOne->string, $modelR->belongsToOne->string); + + $for = ManyToManyDirectModelMapper::getFor($id, 'to'); + self::assertEquals(\reset($this->model->hasManyDirect)->string, \reset($for)->string); + + self::assertEquals(1, count(BaseModelMapper::getAll())); + } + + public function testReadArray() + { + $id = BaseModelMapper::createArray($this->modelArray); + $modelR = BaseModelMapper::getArray($id); + + self::assertEquals($this->modelArray['id'], $modelR['id']); + self::assertEquals($this->modelArray['string'], $modelR['string']); + self::assertEquals($this->modelArray['int'], $modelR['int']); + self::assertEquals($this->modelArray['bool'], $modelR['bool']); + self::assertEquals($this->modelArray['float'], $modelR['float']); + self::assertEquals($this->modelArray['null'], $modelR['null']); + self::assertEquals($this->modelArray['datetime']->format('Y-m-d'), $modelR['datetime']->format('Y-m-d')); + + self::assertEquals(2, \count($modelR['hasManyDirect'])); + self::assertEquals(2, \count($modelR['hasManyRelations'])); + self::assertEquals(\reset($this->modelArray['hasManyDirect'])['string'], \reset($modelR['hasManyDirect'])['string']); + self::assertEquals(\reset($this->modelArray['hasManyRelations'])['string'], \reset($modelR['hasManyRelations'])['string']); + self::assertEquals($this->modelArray['ownsOneSelf']['string'], $modelR['ownsOneSelf']['string']); + self::assertEquals($this->modelArray['belongsToOne']['string'], $modelR['belongsToOne']['string']); + + $for = ManyToManyDirectModelMapper::getForArray($id, 'to'); + self::assertEquals(\reset($this->modelArray['hasManyDirect'])['string'], \reset($for)['string']); + + self::assertEquals(1, count(BaseModelMapper::getAllArray())); } public function testUpdate() @@ -155,6 +240,31 @@ class DataMapperAbstractTest extends \PHPUnit\Framework\TestCase // todo test update relations } + /*public function testUpdateArray() + { + $id = BaseModelMapper::createArray($this->modelArray); + $modelR = BaseModelMapper::getArray($id); + + $modelR['string'] = 'Update'; + $modelR['int'] = '321'; + $modelR['bool'] = true; + $modelR['float'] = 3.15; + $modelR['null'] = null; + $modelR['datetime'] = new \DateTime('now'); + + $id2 = BaseModelMapper::updateArray($modelR); + $modelR2 = BaseModelMapper::getArray($id2); + + self::assertEquals($modelR['string'], $modelR2['string']); + self::assertEquals($modelR['int'], $modelR2['int']); + self::assertEquals($modelR['bool'], $modelR2['bool']); + self::assertEquals($modelR['float'], $modelR2['float']); + self::assertEquals($modelR['null'], $modelR2['null']); + self::assertEquals($modelR['datetime']->format('Y-m-d'), $modelR2['datetime']->format('Y-m-d')); + + // todo test update relations + }*/ + public function testDelete() { $id = BaseModelMapper::create($this->model); diff --git a/tests/DataStorage/Database/Query/BuilderTest.php b/tests/DataStorage/Database/Query/BuilderTest.php index d8a6c4e32..c2053ed7c 100644 --- a/tests/DataStorage/Database/Query/BuilderTest.php +++ b/tests/DataStorage/Database/Query/BuilderTest.php @@ -189,6 +189,65 @@ class BuilderTest extends \PHPUnit\Framework\TestCase self::assertEquals($sql, $query->select('a.test')->from('a')->where('a.test', '=', ':testWhere')->whereIn('a.test2', ['a', ':bValue', 'c'], 'or')->toSql()); } + public function testMysqlJoins() + { + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` JOIN `b` ON `a`.`id` = `b`.`id` OR `a`.`id2` = `b`.`id2` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->orOn('a.id2', '=', 'b.id2')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` JOIN `b` ON `a`.`id` = `b`.`id` AND `a`.`id2` = `b`.`id2` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->join('b')->on('a.id', '=', 'b.id')->andOn('a.id2', '=', 'b.id2')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` LEFT JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->leftJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` LEFT OUTER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->leftOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` LEFT INNER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->leftInnerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` RIGHT JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->rightJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` RIGHT OUTER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->rightOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` RIGHT INNER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->rightInnerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` OUTER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->outerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` INNER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->innerJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` CROSS JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->crossJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` FULL JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->fullJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + + $query = new Builder($this->con); + $sql = 'SELECT `a`.`test` FROM `a` FULL OUTER JOIN `b` ON `a`.`id` = `b`.`id` WHERE `a`.`test` = 1;'; + self::assertEquals($sql, $query->select('a.test')->from('a')->fullOuterJoin('b')->on('a.id', '=', 'b.id')->where('a.test', '=', 1)->toSql()); + } + public function testMysqlInsert() { $query = new Builder($this->con); @@ -198,6 +257,7 @@ class BuilderTest extends \PHPUnit\Framework\TestCase $query = new Builder($this->con); $sql = 'INSERT INTO `a` (`test`, `test2`) VALUES (1, \'test\');'; self::assertEquals($sql, $query->insert('test', 'test2')->into('a')->values(1, 'test')->toSql()); + self::assertEquals([[1, 'test']], $query->getValues()); $query = new Builder($this->con); $sql = 'INSERT INTO `a` (`test`, `test2`) VALUES (:test, :test2);'; @@ -267,4 +327,49 @@ class BuilderTest extends \PHPUnit\Framework\TestCase $query = new Builder($this->con, true); $query->delete(); } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidWhereOperator() + { + $query = new Builder($this->con, true); + $query->where('a', 'invalid', 'b'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidJoinTable() + { + $query = new Builder($this->con, true); + $query->join(null); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidJoinOperator() + { + $query = new Builder($this->con, true); + $query->join('b')->on('a', 'invalid', 'b'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidOrOrderType() + { + $query = new Builder($this->con, true); + $query->orderBy('a', 1); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidOrColumnType() + { + $query = new Builder($this->con, true); + $query->orderBy(null, 'DESC'); + } } diff --git a/tests/Dispatcher/DispatcherTest.php b/tests/Dispatcher/DispatcherTest.php index db23f40d2..fc1058150 100644 --- a/tests/Dispatcher/DispatcherTest.php +++ b/tests/Dispatcher/DispatcherTest.php @@ -74,6 +74,22 @@ class DispatcherTest extends \PHPUnit\Framework\TestCase ); } + public function testPathMethodInArray() + { + $l11nManager = new L11nManager(); + $localization = new Localization($l11nManager); + + self::assertTrue( + !empty( + $this->app->dispatcher->dispatch( + ['dest' => 'phpOMS\tests\Dispatcher\TestController:testFunction'], + new Request(new Http(''), $localization), + new Response($localization) + ) + ) + ); + } + public function testPathStatic() { $l11nManager = new L11nManager(); @@ -126,6 +142,14 @@ class DispatcherTest extends \PHPUnit\Framework\TestCase $this->app->dispatcher->dispatch('phpOMS\tests\Dispatcher\TestControllers::testFunctionStatic'); } + /** + * @expectedException \Exception + */ + public function testInvalidControllerFunction() + { + $this->app->dispatcher->dispatch('phpOMS\tests\Dispatcher\TestController::testFunctionStaticINVALID'); + } + /** * @expectedException \UnexpectedValueException */ diff --git a/tests/Event/EventManagerTest.php b/tests/Event/EventManagerTest.php index 70c5dfd3d..a5b06949f 100644 --- a/tests/Event/EventManagerTest.php +++ b/tests/Event/EventManagerTest.php @@ -70,6 +70,8 @@ class EventManagerTest extends \PHPUnit\Framework\TestCase self::assertTrue($event->detach('group')); self::assertEquals(0, $event->count()); self::assertFalse($event->trigger('group')); + + self::assertFalse($event->detach('group')); } public function testRemove() diff --git a/tests/Localization/LocalizationTest.php b/tests/Localization/LocalizationTest.php index 60739e5a2..4f40d8b18 100644 --- a/tests/Localization/LocalizationTest.php +++ b/tests/Localization/LocalizationTest.php @@ -131,5 +131,35 @@ class LocalizationTest extends \PHPUnit\Framework\TestCase $localization->setTemperature(TemperatureType::FAHRENHEIT); self::assertEquals(TemperatureType::FAHRENHEIT, $localization->getTemperature()); + + $localization->setWeight([1]); + $localization->setLength([1]); + $localization->setArea([1]); + $localization->setVolume([1]); + $localization->setSpeed([1]); + self::assertEquals([1], $localization->getWeight()); + self::assertEquals([1], $localization->getLength()); + self::assertEquals([1], $localization->getArea()); + self::assertEquals([1], $localization->getVolume()); + self::assertEquals([1], $localization->getSpeed()); + } + + public function testLocalizationLoading() + { + $localization = new Localization(); + $localization->loadFromLanguage(ISO639x1Enum::_EN); + self::assertEquals(ISO4217CharEnum::_USD, $localization->getCurrency()); + + $localization->loadFromLanguage(ISO639x1Enum::_AA); + self::assertEquals(ISO4217CharEnum::_USD, $localization->getCurrency()); + } + + /** + * @expectedException \phpOMS\Stdlib\Base\Exception\InvalidEnumValue + */ + public function testInvalidLocalizationLoading() + { + $localization = new Localization(); + $localization->loadFromLanguage('INVALID'); } } diff --git a/tests/Math/Matrix/MatrixTest.php b/tests/Math/Matrix/MatrixTest.php index d36d85c77..9e624429b 100644 --- a/tests/Math/Matrix/MatrixTest.php +++ b/tests/Math/Matrix/MatrixTest.php @@ -14,6 +14,7 @@ namespace phpOMS\tests\Math\Matrix; use phpOMS\Math\Matrix\Matrix; +use phpOMS\Math\Matrix\Vector; class MatrixTest extends \PHPUnit\Framework\TestCase { @@ -79,6 +80,27 @@ class MatrixTest extends \PHPUnit\Framework\TestCase self::assertEquals(-306, $B->det()); } + public function testSymmetry() + { + $B = new Matrix(); + $B->setMatrix([ + [1, 7, 3], + [7, -2, -5], + [3, -5, 6], + ]); + + self::assertTrue($B->isSymmetric()); + + $C = new Matrix(); + $C->setMatrix([ + [1, 7, 4], + [7, -2, -5], + [3, -5, 6], + ]); + + self::assertFalse($C->isSymmetric()); + } + public function testTranspose() { $B = new Matrix(); @@ -90,6 +112,21 @@ class MatrixTest extends \PHPUnit\Framework\TestCase self::assertEquals([[6, 4], [1, -2], [1, 5],], $B->transpose()->toArray()); } + public function testSolve() + { + $A = new Matrix(); + $A->setMatrix([ + [25, 15, -5], + [15, 17, 0], + [-5, 0, 11], + ]); + + $vec = new Vector(); + $vec->setMatrix([[40], [49], [28]]); + + self::assertEquals([[1], [2], [3]], $A->solve($vec)->toArray(), '', 0.2); + } + public function testRank() { $B = new Matrix(); @@ -201,6 +238,37 @@ class MatrixTest extends \PHPUnit\Framework\TestCase self::assertFalse(isset($A[6])); } + public function testSubMatrix() + { + $A = new Matrix(); + $A->setMatrix([ + [0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9, 10, 11], + [12, 13, 14, 15], + ]); + + self::assertEquals( + [[1, 2], [5, 6], [9, 10]], + $A->getSubMatrix(0, 2, 1, 2)->toArray() + ); + + self::assertEquals( + [[1, 2], [5, 6], [9, 10]], + $A->getSubMatrixByColumnsRows([0, 1, 2], [1, 2])->toArray() + ); + + self::assertEquals( + [[1, 2], [5, 6], [9, 10]], + $A->getSubMatrixByColumns(0, 2, [1, 2])->toArray() + ); + + self::assertEquals( + [[1, 2], [5, 6], [9, 10]], + $A->getSubMatrixByRows([0, 1, 2], 1, 2)->toArray() + ); + } + /** * @expectedException \phpOMS\Math\Matrix\Exception\InvalidDimensionException */ diff --git a/tests/Module/ModuleAbstractTest.php b/tests/Module/ModuleAbstractTest.php index 6dacd87d4..646a60479 100644 --- a/tests/Module/ModuleAbstractTest.php +++ b/tests/Module/ModuleAbstractTest.php @@ -31,5 +31,6 @@ class ModuleAbstractTest extends \PHPUnit\Framework\TestCase self::assertEquals([1, 2], $moduleClass->getDependencies()); self::assertEquals(2, $moduleClass::MODULE_ID); self::assertEquals('1.2.3', $moduleClass::MODULE_VERSION); + self::assertEquals([], $moduleClass::getLocalization('invalid', 'invalid')); } } diff --git a/tests/Security/PhpCodeTest.php b/tests/Security/PhpCodeTest.php index 583198aeb..25a1c54c3 100644 --- a/tests/Security/PhpCodeTest.php +++ b/tests/Security/PhpCodeTest.php @@ -62,4 +62,16 @@ class RouteVerbTest extends \PHPUnit\Framework\TestCase ) ); } + + public function testFileIntegrity() + { + self::assertTrue(PhpCode::validateFileIntegrity(__DIR__ . '/Sample/hasDeprecated.php', \md5_file(__DIR__ . '/Sample/hasDeprecated.php'))); + self::assertFalse(PhpCode::validateFileIntegrity(__DIR__ . '/Sample/hasUnicode.php', \md5_file(__DIR__ . '/Sample/hasDeprecated.php'))); + } + + public function testStringIntegrity() + { + self::assertTrue(PhpCode::validateStringIntegrity('aa', 'aa')); + self::assertFalse(PhpCode::validateStringIntegrity('aa', 'aA')); + } } diff --git a/tests/Utils/Barcode/C128aTest.php b/tests/Utils/Barcode/C128aTest.php index 087308328..bedc9a391 100644 --- a/tests/Utils/Barcode/C128aTest.php +++ b/tests/Utils/Barcode/C128aTest.php @@ -29,4 +29,10 @@ class C128aTest extends \PHPUnit\Framework\TestCase self::assertTrue(\file_exists($path)); } + + public function testValidString() + { + self::assertTrue(C128a::isValidString('ABCDEFG0123+-')); + self::assertFalse(C128a::isValidString('ABCDE~FG0123+-')); + } } diff --git a/tests/Utils/Barcode/C128bTest.php b/tests/Utils/Barcode/C128bTest.php index a20772d16..ed525567e 100644 --- a/tests/Utils/Barcode/C128bTest.php +++ b/tests/Utils/Barcode/C128bTest.php @@ -29,4 +29,9 @@ class C128bTest extends \PHPUnit\Framework\TestCase self::assertTrue(\file_exists($path)); } + + public function testValidString() + { + self::assertTrue(C128b::isValidString('ABCDE~FG0123+-')); + } } diff --git a/tests/Utils/Barcode/C25Test.php b/tests/Utils/Barcode/C25Test.php index 74f9d780e..2697ae7b4 100644 --- a/tests/Utils/Barcode/C25Test.php +++ b/tests/Utils/Barcode/C25Test.php @@ -29,4 +29,10 @@ class C25Test extends \PHPUnit\Framework\TestCase self::assertTrue(\file_exists($path)); } + + public function testValidString() + { + self::assertTrue(C25::isValidString('1234567890')); + self::assertFalse(C25::isValidString('1234567A890')); + } } diff --git a/tests/Utils/Barcode/C39Test.php b/tests/Utils/Barcode/C39Test.php index fffeeacb9..c5931af01 100644 --- a/tests/Utils/Barcode/C39Test.php +++ b/tests/Utils/Barcode/C39Test.php @@ -29,4 +29,10 @@ class C39Test extends \PHPUnit\Framework\TestCase self::assertTrue(\file_exists($path)); } + + public function testValidString() + { + self::assertTrue(C39::isValidString('ABCDEFG0123+-')); + self::assertFalse(C39::isValidString('ABC(DEFG0123+-')); + } } diff --git a/tests/Utils/Barcode/CodebarTest.php b/tests/Utils/Barcode/CodebarTest.php index 57213d258..e7daeb3cd 100644 --- a/tests/Utils/Barcode/CodebarTest.php +++ b/tests/Utils/Barcode/CodebarTest.php @@ -29,4 +29,10 @@ class CodebarTest extends \PHPUnit\Framework\TestCase self::assertTrue(\file_exists($path)); } + + public function testValidString() + { + self::assertTrue(Codebar::isValidString('412163')); + self::assertFalse(Codebar::isValidString('412163F')); + } } diff --git a/tests/Utils/Converter/NumericTest.php b/tests/Utils/Converter/NumericTest.php index ef30364e2..9cd0cdb95 100644 --- a/tests/Utils/Converter/NumericTest.php +++ b/tests/Utils/Converter/NumericTest.php @@ -48,5 +48,7 @@ class NumericTest extends \PHPUnit\Framework\TestCase self::assertEquals('123', Numeric::convertBase('443', '01234', '0123456789')); self::assertEquals('123', Numeric::convertBase('7B', '0123456789ABCDEF', '0123456789')); self::assertEquals('123', Numeric::convertBase('173', '01234567', '0123456789')); + + self::assertEquals('173', Numeric::convertBase('173', '01234567', '01234567')); } } diff --git a/tests/Utils/Parser/Php/ArrayParserTest.php b/tests/Utils/Parser/Php/ArrayParserTest.php index b7a99a285..d7145d283 100644 --- a/tests/Utils/Parser/Php/ArrayParserTest.php +++ b/tests/Utils/Parser/Php/ArrayParserTest.php @@ -19,6 +19,15 @@ class ArrayParserTest extends \PHPUnit\Framework\TestCase { public function testParser() { + $serializable = new class implements \Serializable { + public function serialize() { return 2; } + public function unserialize($raw) {} + }; + + $jsonSerialize = new class implements \jsonSerializable { + public function jsonSerialize() { return [6, 7]; } + }; + $array = [ 'string' => 'test', 0 => 1, @@ -29,8 +38,32 @@ class ArrayParserTest extends \PHPUnit\Framework\TestCase 0 => 'a', 1 => 'b', ], + 5 => $serializable, + 6 => $jsonSerialize, ]; - self::assertEquals($array, eval('return '. ArrayParser::serializeArray($array) . ';')); + $expected = [ + 'string' => 'test', + 0 => 1, + 2 => true, + 'string2' => 1.3, + 3 => null, + 4 => [ + 0 => 'a', + 1 => 'b', + ], + 5 => $serializable->serialize(), + 6 => $jsonSerialize->jsonSerialize(), + ]; + + self::assertEquals($expected, eval('return '. ArrayParser::serializeArray($array) . ';')); + } + + /** + * @expectedException \UnexpectedValueException + */ + public function testInvalidValueType() + { + ArrayParser::parseVariable(new class {}); } } diff --git a/tests/Utils/TaskSchedule/TaskAbstractTest.php b/tests/Utils/TaskSchedule/TaskAbstractTest.php index 12fae0da4..27dd44d66 100644 --- a/tests/Utils/TaskSchedule/TaskAbstractTest.php +++ b/tests/Utils/TaskSchedule/TaskAbstractTest.php @@ -42,6 +42,7 @@ class TaskAbstractTest extends \PHPUnit\Framework\TestCase self::assertInstanceOf('\DateTime', $this->class->getNextRunTime()); self::assertInstanceOf('\DateTime', $this->class->getLastRuntime()); self::assertEquals('', $this->class->getComment()); + self::assertEquals('', $this->class->getInterval()); } public function testGetSet()