From dccae5b6c078546aa261635d409b444fcb4195d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Jure=C4=8Dko?= Date: Mon, 11 Jan 2021 12:13:26 +0100 Subject: [PATCH] Update packages and coding standards --- composer.json | 19 +- src/Analyzer.php | 79 ++------- src/Cache.php | 25 +-- src/CsvRow.php | 22 +-- src/Exception/InconsistentValueException.php | 2 + src/Exception/JsonParserException.php | 5 +- src/Exception/NoDataException.php | 3 + src/NodePath.php | 42 ++--- src/Parser.php | 163 +++++++----------- src/Structure.php | 172 ++++++++----------- tests/phpunit/ParserTest.php | 1 + tests/phpunit/RealDataTest.php | 5 +- 12 files changed, 217 insertions(+), 321 deletions(-) diff --git a/composer.json b/composer.json index 013197e..4efb66a 100755 --- a/composer.json +++ b/composer.json @@ -11,15 +11,17 @@ ], "require": { "php": ">=7.4", - "keboola/php-csvtable": "^0.1", - "keboola/php-temp": "^1.0", - "keboola/php-utils": "^2.2", - "monolog/monolog": "^1.23" + "ext-json": "*", + "keboola/php-csvtable": "^1.1.1", + "keboola/php-temp": "^2.0", + "keboola/php-utils": "^4.1", + "monolog/monolog": "^2.2" }, "require-dev": { - "phpunit/phpunit": "^6.3", - "codeclimate/php-test-reporter": "^0.4", - "squizlabs/php_codesniffer": "^3.0" + "phpunit/phpunit": "^9.0", + "phpstan/phpstan": "^0.12.14", + "php-parallel-lint/php-parallel-lint": "^1.2", + "keboola/coding-standard": ">=9.0" }, "autoload": { "psr-4": { @@ -42,6 +44,9 @@ "phpcbf": "phpcbf -n --ignore=vendor --extensions=php .", "phplint": "parallel-lint -j 10 --exclude vendor .", "build": [ + "@phplint", + "@phpcs", + "@phpstan", "@tests" ], "ci": [ diff --git a/src/Analyzer.php b/src/Analyzer.php index 36912c4..b9a33f9 100644 --- a/src/Analyzer.php +++ b/src/Analyzer.php @@ -1,5 +1,7 @@ structure = $structure; } - /** - * @return Structure - */ - public function getStructure() + public function getStructure(): Structure { return $this->structure; } - /** - * @return LoggerInterface - */ - public function getLogger() + public function getLogger(): LoggerInterface { return $this->log; } - /** - * @param array $data - * @param string $rootType - */ - public function analyzeData(array $data, string $rootType) + public function analyzeData(array $data, string $rootType): void { if (empty($data)) { return; @@ -77,12 +50,9 @@ public function analyzeData(array $data, string $rootType) } /** - * @param $item - * @param NodePath $nodePath - * @return string - * @throws JsonParserException + * @param mixed $item */ - private function analyzeItem($item, NodePath $nodePath) : string + private function analyzeItem($item, NodePath $nodePath): string { if (is_scalar($item)) { if ($this->strict) { @@ -115,11 +85,7 @@ private function analyzeItem($item, NodePath $nodePath) : string return $nodeType; } - /** - * @param array $array - * @param NodePath $nodePath - */ - private function analyzeArray(array $array, NodePath $nodePath) + private function analyzeArray(array $array, NodePath $nodePath): void { $oldType = null; $nodePath = $nodePath->addChild(Structure::ARRAY_NAME); @@ -133,26 +99,18 @@ private function analyzeArray(array $array, NodePath $nodePath) } } - /** - * @param $object - * @param NodePath $nodePath - */ - private function analyzeObject($object, NodePath $nodePath) + private function analyzeObject(object $object, NodePath $nodePath): void { - foreach ($object as $key => $field) { - $this->analyzeItem($field, $nodePath->addChild($key)); + foreach (get_object_vars($object) as $key => $field) { + $this->analyzeItem($field, $nodePath->addChild((string) $key)); } } /** * Check that two types same or compatible. - * @param $oldType - * @param $newType - * @param NodePath $nodePath - * @return string * @throws JsonParserException */ - private function checkType($oldType, $newType, NodePath $nodePath) : string + private function checkType(?string $oldType, string $newType, NodePath $nodePath): string { if (!is_null($oldType) && ($newType !== $oldType) && ($newType !== 'null') && ($oldType !== 'null')) { throw new JsonParserException( @@ -162,10 +120,7 @@ private function checkType($oldType, $newType, NodePath $nodePath) : string return $newType; } - /** - * @return bool - */ - public function getNestedArrayAsJson() : bool + public function getNestedArrayAsJson(): bool { return $this->nestedArrayAsJson; } diff --git a/src/Cache.php b/src/Cache.php index 904cf9a..9f6d335 100755 --- a/src/Cache.php +++ b/src/Cache.php @@ -1,10 +1,12 @@ Parser::getCache()->setMemLimit(X)) // either to stop using memory once X mem is used or once X is left from PHP limit - if (ini_get('memory_limit') != "-1" + if (ini_get('memory_limit') !== '-1' && memory_get_usage() > (\Keboola\Utils\returnBytes(ini_get('memory_limit')) * 0.25) || ($this->memoryLimit !== null && memory_get_usage() > $this->memoryLimit) ) { // cache if (empty($this->temp)) { // TODO use /maxmemory ? - $this->temp = fopen("php://temp/", 'w+'); + /** @var resource $temp */ + $temp = fopen('php://temp/', 'w+'); + $this->temp = $temp; } fseek($this->temp, 0, SEEK_END); @@ -37,14 +41,14 @@ public function store($data) } } - public function getNext() + public function getNext(): ?array { if (!empty($this->temp) && !feof($this->temp)) { // keep the file position in case the file's been written to fseek($this->temp, $this->readPosition); $data = fgets($this->temp); + /** @var string $data */ $this->readPosition += strlen($data); - return unserialize(base64_decode($data)); } elseif (!empty($this->temp) && feof($this->temp)) { fclose($this->temp); @@ -54,10 +58,7 @@ public function getNext() return array_shift($this->data); } - /** - * @param int $limit - */ - public function setMemoryLimit(int $limit) + public function setMemoryLimit(int $limit): void { $this->memoryLimit = $limit; } diff --git a/src/CsvRow.php b/src/CsvRow.php index 89a2195..80e2c04 100644 --- a/src/CsvRow.php +++ b/src/CsvRow.php @@ -1,28 +1,31 @@ data = array_fill_keys($columns, null); } - public function setValue($column, $value) + /** + * @param mixed $value + * @throws JsonParserException + */ + public function setValue(string $column, $value): void { if (!array_key_exists($column, $this->data)) { throw new JsonParserException( "Error assigning '{$value}' to a non-existing column '{$column}'!", [ - 'columns' => array_keys($this->data) + 'columns' => array_keys($this->data), ] ); } @@ -32,7 +35,7 @@ public function setValue($column, $value) "Error assigning value to '{$column}': The value's not scalar!", [ 'type' => gettype($value), - 'value' => json_encode($value) + 'value' => json_encode($value), ] ); } @@ -40,10 +43,7 @@ public function setValue($column, $value) $this->data[$column] = $value; } - /** - * @return array - */ - public function getRow() + public function getRow(): array { return $this->data; } diff --git a/src/Exception/InconsistentValueException.php b/src/Exception/InconsistentValueException.php index 1ad6910..bfb4298 100644 --- a/src/Exception/InconsistentValueException.php +++ b/src/Exception/InconsistentValueException.php @@ -1,5 +1,7 @@ setData($data); diff --git a/src/Exception/NoDataException.php b/src/Exception/NoDataException.php index eb678f4..e5d5254 100755 --- a/src/Exception/NoDataException.php +++ b/src/Exception/NoDataException.php @@ -1,4 +1,7 @@ path = $path; } - /** - * @return string - */ - public function __toString() : string + public function __toString(): string { return implode('.', $this->path); } /** * Convert path to user-display string. - * @return string */ - public function toCleanString() : string + public function toCleanString(): string { $path = array_filter($this->path, function ($val) { - return $val != Structure::ARRAY_NAME; + return $val !== Structure::ARRAY_NAME; }); return implode('.', $path); } /** * Return new path with an added child. - * @param string $key - * @return NodePath */ - public function addChild(string $key) : NodePath + public function addChild(string $key): NodePath { $path = $this->path; $path[] = $key; @@ -52,47 +44,39 @@ public function addChild(string $key) : NodePath /** * Return true if the path points to array. - * @return bool */ - public function isArray() : bool + public function isArray(): bool { - return end($this->path) == Structure::ARRAY_NAME; + return end($this->path) === Structure::ARRAY_NAME; } /** * Remove the first item from path and return new path - * @param string $first - * @return NodePath */ - public function popFirst(&$first) : NodePath + public function popFirst(string &$first): NodePath { $path = $this->path; $first = array_shift($path); return new NodePath($path); } - /** - * @return bool - */ - public function isEmpty() : bool + public function isEmpty(): bool { return count($this->path) === 0; } /** * Return last item of the path - * @return string */ - public function getLast() : string + public function getLast(): string { return end($this->path); } /** * Remove last item of the path and return new path. - * @return NodePath */ - public function popLast() : NodePath + public function popLast(): NodePath { $path = $this->path; array_pop($path); diff --git a/src/Parser.php b/src/Parser.php index 89b47eb..84b15cf 100755 --- a/src/Parser.php +++ b/src/Parser.php @@ -1,5 +1,7 @@ analyzer = $analyzer; $analyzer->getStructure()->load($definitions); $this->structure = $analyzer->getStructure(); - $this->temp = new Temp("json-parser"); + $this->temp = new Temp('json-parser'); $this->cache = new Cache(); } + public function __destruct() + { + $this->temp->remove(); + } + /** * Analyze and store an array of data for parsing. * The analysis is done immediately, based on the analyzer settings, * then the data is stored using \Keboola\Json\Cache and parsed * upon retrieval using getCsvFiles(). * - * @param array $data * @param string $type is used for naming the resulting table(s) - * @param string|array $parentId may be either a string, + * @param string|array|null $parentId may be either a string, * which will be saved in a JSON_parentId column, * or an array with "column_name" => "value", * which will name the column(s) by array key provided * @throws NoDataException */ - public function process(array $data, $type = "root", $parentId = null) + public function process(array $data, string $type = 'root', $parentId = null): void { - if (empty($data) || $data == [null]) { + if (empty($data) || $data === [null]) { throw new NoDataException("Empty data set received for '{$type}'", [ - "data" => $data, - "type" => $type, - "parentId" => $parentId + 'data' => $data, + 'type' => $type, + 'parentId' => $parentId, ]); } $this->analyzer->analyzeData($data, $type); $this->structure->generateHeaderNames(); - $this->cache->store(["data" => $data, "type" => $type, "parentId" => $parentId]); + $this->cache->store(['data' => $data, 'type' => $type, 'parentId' => $parentId]); } /** * Parse data of known type - * - * @param array $data - * @param NodePath $nodePath - * @param string|array $parentId + * @param string|array|null $parentId */ - private function parse(array $data, NodePath $nodePath, $parentId = null) + private function parse(array $data, NodePath $nodePath, $parentId = null): void { $parentId = $this->validateParentId($parentId); $csvFile = $this->createCsvFile($this->structure->getTypeFromNodePath($nodePath), $nodePath, $parentId); - $parentCols = array_fill_keys(array_keys($parentId), "string"); + $parentCols = array_fill_keys(array_keys($parentId), 'string'); foreach ($data as $row) { // in case of non-associative array of strings @@ -150,20 +136,19 @@ private function parse(array $data, NodePath $nodePath, $parentId = null) * @param NodePath $nodePath * @param array $parentCols to inject parent columns, which aren't part of $this->struct * @param string $outerObjectHash Outer object hash to distinguish different parents in deep nested arrays - * @return CsvRow */ private function parseRow( \stdClass $dataRow, NodePath $nodePath, array $parentCols = [], - $outerObjectHash = null - ) { + ?string $outerObjectHash = null + ): CsvRow { $csvRow = new CsvRow($this->getHeaders($nodePath, $parentCols)); // Generate parent ID for arrays $arrayParentId = $this->getPrimaryKeyValue($dataRow, $nodePath, $outerObjectHash); $columns = $this->structure->getColumnTypes($nodePath); foreach (array_replace($columns, $parentCols) as $column => $dataType) { - $this->parseField($dataRow, $csvRow, $arrayParentId, $column, $dataType, $nodePath); + $this->parseField($dataRow, $csvRow, $arrayParentId, (string) $column, $dataType, $nodePath); } return $csvRow; @@ -171,21 +156,15 @@ private function parseRow( /** * Handle the actual write to CsvRow - * @param \stdClass $dataRow - * @param CsvRow $csvRow - * @param string $arrayParentId - * @param string $column - * @param string $dataType - * @param NodePath $nodePath */ private function parseField( \stdClass $dataRow, CsvRow $csvRow, - $arrayParentId, - $column, - $dataType, + string $arrayParentId, + string $column, + string $dataType, NodePath $nodePath - ) { + ): void { // A hack allowing access to numeric keys in object if (!isset($dataRow->{$column}) && isset(json_decode(json_encode($dataRow), true)[$column]) @@ -199,7 +178,7 @@ private function parseField( || (empty($dataRow->{$column}) && !is_scalar($dataRow->{$column})) ) { // do not save empty objects to prevent creation of ["obj_name" => null] - if ($dataType != 'object') { + if ($dataType !== 'object') { $safeColumn = $this->structure->getNodeProperty($nodePath->addChild($column), 'headerNames'); if ($safeColumn === null) { $safeColumn = $this->structure->getNodeProperty($nodePath, 'headerNames'); @@ -211,7 +190,7 @@ private function parseField( } switch ($dataType) { - case "array": + case 'array': if (!is_array($dataRow->{$column})) { $dataRow->{$column} = [$dataRow->{$column}]; } @@ -223,7 +202,7 @@ private function parseField( $arrayParentId ); break; - case "object": + case 'object': $childRow = $this->parseRow($dataRow->{$column}, $nodePath->addChild($column), [], $arrayParentId); foreach ($childRow->getRow() as $key => $value) { @@ -244,7 +223,7 @@ private function parseField( $this->analyzer->getLogger()->error( "Data parse error in '{$column}' - unexpected '" . gettype($dataRow->{$column}) . "' where '{$dataType}' was expected!", - [ "data" => $jsonColumn, "row" => json_encode($dataRow) ] + [ 'data' => $jsonColumn, 'row' => json_encode($dataRow) ] ); $sf = $this->structure->getNodeProperty($nodePath->addChild($column), 'headerNames'); $csvRow->setValue($sf, $jsonColumn); @@ -255,17 +234,18 @@ private function parseField( /** * Get column names for a particular node path - * @param NodePath $nodePath - * @param bool $parent Parent column, may be renamed in case a conflict occurs - * @return array + * @param array|null $parent Parent column, may be renamed in case a conflict occurs */ - private function getHeaders(NodePath $nodePath, &$parent = false) + private function getHeaders(NodePath $nodePath, ?array &$parent = null): array { $headers = []; - $nodeData = $this->structure->getNode($nodePath); - if ($nodeData['nodeType'] == 'scalar') { + $nodeData = $this->structure->getNode($nodePath) ?? []; + $nodeType = $nodeData['nodeType'] ?? []; + + if ($nodeType === 'scalar') { $headers[] = $nodeData['headerNames']; } + if (is_array($parent) && !empty($parent)) { foreach ($parent as $key => $value) { // check all parent columns @@ -300,13 +280,14 @@ private function getHeaders(NodePath $nodePath, &$parent = false) ['nodeType' => 'scalar', 'type' => 'parent']; $this->structure->saveNode($previousPath, $previousNode); $this->structure->generateHeaderNames(); - $nodeData = $this->structure->getNode($nodePath); + $nodeData = $this->structure->getNode($nodePath) ?? []; } } } + foreach ($nodeData as $nodeName => $data) { - if (is_array($data) && ($data['nodeType'] == 'object')) { - $pparent = false; + if (is_array($data) && ($data['nodeType'] === 'object')) { + $pparent = null; $nodeName = $this->structure->decodeNodeName($nodeName); $ch = $this->getHeaders($nodePath->addChild($nodeName), $pparent); $headers = array_merge($headers, $ch); @@ -319,21 +300,17 @@ private function getHeaders(NodePath $nodePath, &$parent = false) /** * to allow saving a single type to different files - * - * @param string $type - * @param NodePath $nodePath - * @param $parentId - * @return Table */ - private function createCsvFile($type, NodePath $nodePath, &$parentId) + private function createCsvFile(string $type, NodePath $nodePath, array &$parentId): Table { if (empty($this->csvFiles[$type])) { - $this->csvFiles[$type] = Table::create( + $this->csvFiles[$type] = new Table( $type, $this->getHeaders($nodePath, $parentId), + true, $this->temp ); - $this->csvFiles[$type]->addAttributes(["fullDisplayName" => $type]); + $this->csvFiles[$type]->addAttributes(['fullDisplayName' => $type]); if (!empty($this->primaryKeys[$type])) { $this->csvFiles[$type]->setPrimaryKey($this->primaryKeys[$type]); } @@ -342,13 +319,7 @@ private function createCsvFile($type, NodePath $nodePath, &$parentId) return $this->csvFiles[$type]; } - /** - * @param \stdClass $dataRow - * @param NodePath $nodePath - * @param string $outerObjectHash - * @return string - */ - private function getPrimaryKeyValue(\stdClass $dataRow, NodePath $nodePath, $outerObjectHash = null) + private function getPrimaryKeyValue(\stdClass $dataRow, NodePath $nodePath, ?string $outerObjectHash = null): string { $column = $this->structure->getTypeFromNodePath($nodePath); if (!empty($this->primaryKeys[$column])) { @@ -368,7 +339,7 @@ private function getPrimaryKeyValue(\stdClass $dataRow, NodePath $nodePath, $out } } // this awkward format is because of backward compatibility - return $nodePath->toCleanString() . "_" . join(";", $values); + return $nodePath->toCleanString() . '_' . join(';', $values); } else { // this awkward format is because of backward compatibility return $nodePath->toCleanString() . '_' . md5(serialize($dataRow) . $outerObjectHash); @@ -378,19 +349,18 @@ private function getPrimaryKeyValue(\stdClass $dataRow, NodePath $nodePath, $out /** * Ensure the parentId array is not multidimensional * - * @param string|array $parentId - * @return array + * @param string|array|null $parentId * @throws JsonParserException */ - private function validateParentId($parentId) : array + private function validateParentId($parentId): array { if (!empty($parentId)) { if (is_array($parentId)) { - if (count($parentId) != count($parentId, COUNT_RECURSIVE)) { + if (count($parentId) !== count($parentId, COUNT_RECURSIVE)) { throw new JsonParserException( 'Error assigning parentId to a CSV file! $parentId array cannot be multidimensional.', [ - 'parentId' => $parentId + 'parentId' => $parentId, ] ); } @@ -411,29 +381,22 @@ private function validateParentId($parentId) : array * Returns an array of CSV files containing results * @return Table[] */ - public function getCsvFiles() + public function getCsvFiles(): array { // parse what's in cache before returning results while ($batch = $this->cache->getNext()) { // root node is always array - $this->parse($batch["data"], new NodePath([$batch['type'], Structure::ARRAY_NAME]), $batch["parentId"]); + $this->parse($batch['data'], new NodePath([$batch['type'], Structure::ARRAY_NAME]), $batch['parentId']); } return $this->csvFiles; } - /** - * @return Analyzer - */ - public function getAnalyzer() + public function getAnalyzer(): Analyzer { return $this->analyzer; } - /** - * @param array $pks - * @throws JsonParserException - */ - public function addPrimaryKeys(array $pks) + public function addPrimaryKeys(array $pks): void { if (!empty($this->csvFiles)) { throw new JsonParserException('"addPrimaryKeys" must be used before any data is parsed'); @@ -446,8 +409,8 @@ public function addPrimaryKeys(array $pks) * Set maximum memory used before Cache starts using php://temp * @param string|int $limit */ - public function setCacheMemoryLimit(int $limit) + public function setCacheMemoryLimit($limit): void { - $this->cache->setMemoryLimit($limit); + $this->cache->setMemoryLimit((int) $limit); } } diff --git a/src/Structure.php b/src/Structure.php index 5bd0c30..347b6d1 100644 --- a/src/Structure.php +++ b/src/Structure.php @@ -1,5 +1,7 @@ data = $this->storeValue($nodePath, $this->data, $property, $value); } catch (InconsistentValueException $e) { - if ($property == 'nodeType') { - $node = $this->getNode($nodePath); + if ($property === 'nodeType') { + $node = $this->getNode($nodePath) ?? []; $this->handleUpgrade($node, $nodePath, $value); } else { throw $e; @@ -97,12 +90,10 @@ public function saveNodeValue(NodePath $nodePath, string $property, $value) /** * Encode real JSON node name into the one stored in structure - * @param $nodeName - * @return string */ - public function encodeNodeName($nodeName) + public function encodeNodeName(string $nodeName): string { - if ($nodeName == self::ARRAY_NAME) { + if ($nodeName === self::ARRAY_NAME) { return $nodeName; } else { return '_' . $nodeName; @@ -111,12 +102,10 @@ public function encodeNodeName($nodeName) /** * Decode node name into real node name found in JSON - * @param $nodeName - * @return string */ - public function decodeNodeName($nodeName) + public function decodeNodeName(string $nodeName): string { - if ($nodeName == self::ARRAY_NAME) { + if ($nodeName === self::ARRAY_NAME) { return $nodeName; } else { return substr($nodeName, 1); @@ -132,8 +121,9 @@ public function decodeNodeName($nodeName) * @return array Structure data * @throws InconsistentValueException In case the values is already set and not same. */ - private function storeValue(NodePath $nodePath, array $data, string $property, $value) : array + private function storeValue(NodePath $nodePath, array $data, string $property, $value): array { + $nodeName = ''; $nodePath = $nodePath->popFirst($nodeName); $nodeName = $this->encodeNodeName($nodeName); if (!isset($data[$nodeName])) { @@ -141,7 +131,7 @@ private function storeValue(NodePath $nodePath, array $data, string $property, $ } if ($nodePath->isEmpty()) { // we arrived at the target, check if the value is not set already - if (!empty($data[$nodeName][$property]) && ($data[$nodeName][$property] != $value)) { + if (!empty($data[$nodeName][$property]) && ($data[$nodeName][$property] !== $value)) { throw new InconsistentValueException("Attempting to overwrite '$property' value '" . $data[$nodeName][$property] . "' with '$value'."); } @@ -159,9 +149,9 @@ private function storeValue(NodePath $nodePath, array $data, string $property, $ * @param string $newType * @throws JsonParserException */ - private function handleUpgrade(array $node, NodePath $nodePath, string $newType) + private function handleUpgrade(array $node, NodePath $nodePath, string $newType): void { - if ((($node['nodeType'] == 'array') || ($newType == 'array')) && $this->autoUpgradeToArray) { + if ((($node['nodeType'] === 'array') || ($newType === 'array')) && $this->autoUpgradeToArray) { $this->checkArrayUpgrade($node, $nodePath, $newType); // copy all properties to the array if (!empty($node[self::ARRAY_NAME])) { @@ -170,11 +160,11 @@ private function handleUpgrade(array $node, NodePath $nodePath, string $newType) } } foreach ($node as $key => $value) { - if (is_array($value) && ($key != self::ARRAY_NAME)) { + if (is_array($value) && ($key !== self::ARRAY_NAME)) { $newNode[self::ARRAY_NAME][$key] = $value; } } - if ($newType != 'array') { + if ($newType !== 'array') { $newNode[self::ARRAY_NAME]['nodeType'] = $newType; } else { $newNode[self::ARRAY_NAME]['nodeType'] = $node['nodeType']; @@ -185,9 +175,9 @@ private function handleUpgrade(array $node, NodePath $nodePath, string $newType) $newNode['headerNames'] = $node['headerNames']; } $this->data = $this->storeNode($nodePath, $this->data, $newNode); - } elseif (($node['nodeType'] != 'null') && ($newType == 'null')) { + } elseif (($node['nodeType'] !== 'null') && ($newType === 'null')) { // do nothing, old type is fine - } elseif (($node['nodeType'] == 'null') && ($newType != 'null')) { + } elseif (($node['nodeType'] === 'null') && ($newType !== 'null')) { $newNode = $this->getNode($nodePath); $newNode['nodeType'] = $newType; $this->data = $this->storeNode($nodePath, $this->data, $newNode); @@ -199,23 +189,23 @@ private function handleUpgrade(array $node, NodePath $nodePath, string $newType) } } - private function checkArrayUpgrade(array $node, NodePath $nodePath, string $newType) + private function checkArrayUpgrade(array $node, NodePath $nodePath, string $newType): void { - if ((($node['nodeType'] == 'array') || ($newType == 'array')) && $this->autoUpgradeToArray) { + if ((($node['nodeType'] === 'array') || ($newType === 'array')) && $this->autoUpgradeToArray) { // if one of the two different types is array, we may consider upgrade // at this moment, the array items should already be set if (empty($node[self::ARRAY_NAME]['nodeType'])) { - throw new JsonParserException("Array contents are unknown"); + throw new JsonParserException('Array contents are unknown'); } // now get the non array type - if ($node['nodeType'] == 'array') { + if ($node['nodeType'] === 'array') { $nonArray = $newType; } else { $nonArray = $node['nodeType']; } // now verify if array contents match the non-array type - if (($node[self::ARRAY_NAME]['nodeType'] != $nonArray) && ($nonArray != 'null') && - $node[self::ARRAY_NAME]['nodeType'] != 'null') { + if (($node[self::ARRAY_NAME]['nodeType'] !== $nonArray) && ($nonArray !== 'null') && + $node[self::ARRAY_NAME]['nodeType'] !== 'null') { throw new JsonParserException("Data array in '" . $nodePath->__toString() . "' contains incompatible types '" . $node[self::ARRAY_NAME]['nodeType'] . "' and '" . $nonArray . "'"); @@ -232,7 +222,7 @@ private function checkArrayUpgrade(array $node, NodePath $nodePath, string $newT * @param NodePath $nodePath Node path. * @param array $node Node data. */ - public function saveNode(NodePath $nodePath, array $node) + public function saveNode(NodePath $nodePath, array $node): void { $this->data = $this->storeNode($nodePath, $this->data, $node); } @@ -245,8 +235,9 @@ public function saveNode(NodePath $nodePath, array $node) * @return array Structure data. * @throws JsonParserException In case the node path is not valid. */ - private function storeNode(NodePath $nodePath, array $data, array $node) : array + private function storeNode(NodePath $nodePath, array $data, array $node): array { + $nodeName = ''; $nodePath = $nodePath->popFirst($nodeName); $nodeName = $this->encodeNodeName($nodeName); if ($nodePath->isEmpty()) { @@ -255,7 +246,7 @@ private function storeNode(NodePath $nodePath, array $data, array $node) : array if (isset($data[$nodeName])) { $data[$nodeName] = $this->storeNode($nodePath, $data[$nodeName], $node); } else { - throw new JsonParserException("Node path " . $nodePath->__toString() . " does not exist."); + throw new JsonParserException('Node path ' . $nodePath->__toString() . ' does not exist.'); } } return $data; @@ -263,9 +254,8 @@ private function storeNode(NodePath $nodePath, array $data, array $node) : array /** * Return complete structure. - * @return array */ - public function getData() + public function getData(): array { foreach ($this->data as $value) { $this->validateDefinitions($value); @@ -279,11 +269,12 @@ public function getData() * @param array $data Optional structure data (for recursive call). * @return array|null Null in case the node path does not exist. */ - public function getNode(NodePath $nodePath, array $data = null) + public function getNode(NodePath $nodePath, ?array $data = null): ?array { if (empty($data)) { $data = $this->data; } + $nodeName = ''; $nodePath = $nodePath->popFirst($nodeName); $nodeName = $this->encodeNodeName($nodeName); if (!isset($data[$nodeName])) { @@ -316,14 +307,13 @@ public function getNodeProperty(NodePath $nodePath, string $property) * Return a particular property of a the children of the node. * @param NodePath $nodePath Node path * @param string $property Property name (e.g. 'nodeType'). - * @return array */ - private function getNodeChildrenProperties(NodePath $nodePath, string $property) : array + private function getNodeChildrenProperties(NodePath $nodePath, string $property): array { $nodeData = $this->getNode($nodePath); $result = []; if (!empty($nodeData)) { - if ($nodeData['nodeType'] == 'object') { + if ($nodeData['nodeType'] === 'object') { foreach ($nodeData as $itemName => $value) { if (is_array($value)) { $itemName = $this->decodeNodeName($itemName); @@ -335,9 +325,9 @@ private function getNodeChildrenProperties(NodePath $nodePath, string $property) } } } - } elseif ($nodeData['nodeType'] == 'scalar') { + } elseif ($nodeData['nodeType'] === 'scalar') { foreach ($nodeData as $itemName => $value) { - if ($itemName == $property) { + if ($itemName === $property) { $result[$nodePath->getLast()] = $value; } } @@ -351,7 +341,7 @@ private function getNodeChildrenProperties(NodePath $nodePath, string $property) * @param NodePath $nodePath * @return array Index is column name, value is data type. */ - public function getColumnTypes(NodePath $nodePath) + public function getColumnTypes(NodePath $nodePath): array { $values = $this->getNodeChildrenProperties($nodePath, 'nodeType'); $result = []; @@ -368,7 +358,7 @@ public function getColumnTypes(NodePath $nodePath) /** * Generate header names for the whole structure. */ - public function generateHeaderNames() + public function generateHeaderNames(): void { foreach ($this->data as $baseType => &$baseArray) { foreach ($baseArray as $nodeName => &$nodeData) { @@ -387,10 +377,8 @@ public function generateHeaderNames() /** * Get new name of a parent column - * @param $name - * @return mixed */ - public function getParentTargetName(string $name) + public function getParentTargetName(string $name): string { if (empty($this->parentAliases[$name])) { return $name; @@ -403,38 +391,33 @@ public function getParentTargetName(string $name) * @param string $name Original name. * @param string $target New Name. */ - public function setParentTargetName(string $name, string $target) + public function setParentTargetName(string $name, string $target): void { $this->parentAliases[$name] = $target; } /** * Get structure version for compatibility. - * @return int */ - public function getVersion() + public function getVersion(): int { return 3; } /** * Return a legacy type for the given node. - * @param NodePath $nodePath - * @return string */ - public function getTypeFromNodePath(NodePath $nodePath) + public function getTypeFromNodePath(NodePath $nodePath): string { return $this->getSafeName($nodePath->toCleanString()); } /** * If necessary, change the name to a safe to store in database. - * @param string $name - * @return string */ - private function getSafeName(string $name) : string + private function getSafeName(string $name): string { - $name = preg_replace('/[^A-Za-z0-9-]/', '_', $name); + $name = (string) preg_replace('/[^A-Za-z0-9-]/', '_', $name); if (strlen($name) > 64) { if (str_word_count($name) > 1 && preg_match_all('/\b(\w)/', $name, $m)) { // Create an "acronym" from first letters @@ -442,10 +425,10 @@ private function getSafeName(string $name) : string } else { $short = md5($name); } - $short .= "_"; + $short .= '_'; $remaining = 64 - strlen($short); - $nextSpace = strpos($name, " ", (strlen($name)-$remaining)) - ? : strpos($name, "_", (strlen($name)-$remaining)); + $nextSpace = strpos($name, ' ', (strlen($name)-$remaining)) + ? : strpos($name, '_', (strlen($name)-$remaining)); $newName = $nextSpace === false ? $short @@ -454,18 +437,15 @@ private function getSafeName(string $name) : string $newName = $name; } - $newName = preg_replace('/[^A-Za-z0-9-]+/', '_', $newName); - $newName = trim($newName, "_"); + $newName = (string) preg_replace('/[^A-Za-z0-9-]+/', '_', $newName); + $newName = trim($newName, '_'); return $newName; } /** * If necessary change the name to a unique one. - * @param string $baseType - * @param string $headerName - * @return string */ - private function getUniqueName(string $baseType, string $headerName) : string + private function getUniqueName(string $baseType, string $headerName): string { if (isset($this->headerIndex[$baseType][$headerName])) { $newName = $headerName; @@ -487,11 +467,11 @@ private function getUniqueName(string $baseType, string $headerName) : string * @param string $parentName * @param string $baseType */ - private function generateHeaderName(array &$data, NodePath $nodePath, string $parentName, string $baseType) + private function generateHeaderName(array &$data, NodePath $nodePath, string $parentName, string $baseType): void { if (empty($data['headerNames'])) { // write only once, because generateHeaderName may be called repeatedly - if ($parentName != self::ARRAY_NAME) { + if ($parentName !== self::ARRAY_NAME) { // do not generate headers for arrays $headerName = $this->getSafeName($parentName); $headerName = $this->getUniqueName($baseType, $headerName); @@ -500,7 +480,7 @@ private function generateHeaderName(array &$data, NodePath $nodePath, string $pa $data['headerNames'] = self::DATA_COLUMN; } } - if ($data['nodeType'] == 'array') { + if ($data['nodeType'] === 'array') { // array node creates a new type and does not nest deeper $baseType = $baseType . '.' . $parentName; $parentName = ''; @@ -508,7 +488,7 @@ private function generateHeaderName(array &$data, NodePath $nodePath, string $pa foreach ($data as $key => &$value) { if (is_array($value)) { $key = $this->decodeNodeName($key); - if (!$parentName || (!empty($data[$key]['type']) && $data[$key]['type'] == 'parent')) { + if (!$parentName || (!empty($data[$key]['type']) && $data[$key]['type'] === 'parent')) { // skip nesting if there is nowhere to nest (array or parent-type child) $childName = $key; } else { @@ -523,7 +503,7 @@ private function generateHeaderName(array &$data, NodePath $nodePath, string $pa * Load structure data. * @param array $definitions */ - public function load(array $definitions) + public function load(array $definitions): void { $this->data = $definitions['data'] ?? []; $this->parentAliases = $definitions['parent_aliases'] ?? []; @@ -532,14 +512,14 @@ public function load(array $definitions) } } - private function validateDefinitions(array $definitions) + private function validateDefinitions(array $definitions): void { $knownProps = [self::PROP_HEADER, self::PROP_NODE_DATA_TYPE, self::PROP_NODE_TYPE]; if (!isset($definitions[self::PROP_NODE_DATA_TYPE])) { - throw new JsonParserException("Node data type is not set.", $definitions); + throw new JsonParserException('Node data type is not set.', $definitions); } - if (($definitions[self::PROP_NODE_DATA_TYPE] == 'array') && empty($definitions[self::ARRAY_NAME])) { - throw new JsonParserException("Array node does not have array.", $definitions); + if (($definitions[self::PROP_NODE_DATA_TYPE] === 'array') && empty($definitions[self::ARRAY_NAME])) { + throw new JsonParserException('Array node does not have array.', $definitions); } foreach ($definitions as $key => $value) { if (is_array($value)) { @@ -548,8 +528,8 @@ private function validateDefinitions(array $definitions) throw new JsonParserException("Conflict property $key", $definitions); } $this->validateDefinitions($value); - if ($key == self::ARRAY_NAME) { - if ($definitions[self::PROP_NODE_DATA_TYPE] != 'array') { + if ($key === self::ARRAY_NAME) { + if ($definitions[self::PROP_NODE_DATA_TYPE] !== 'array') { throw new JsonParserException("Array $key is not an array.", $definitions); } } @@ -558,11 +538,11 @@ private function validateDefinitions(array $definitions) if (!in_array($key, $knownProps)) { throw new JsonParserException("Undefined property $key", $definitions); } - if ($key == self::PROP_NODE_DATA_TYPE) { + if ($key === self::PROP_NODE_DATA_TYPE) { if (!in_array($value, self::$nodeDataTypes)) { throw new JsonParserException("Undefined data type $value", $definitions); } - } elseif ($key == self::PROP_NODE_TYPE) { + } elseif ($key === self::PROP_NODE_TYPE) { if (!in_array($value, self::$nodeTypes)) { throw new JsonParserException("Undefined node type $value", $definitions); } diff --git a/tests/phpunit/ParserTest.php b/tests/phpunit/ParserTest.php index 8c67114..73aa0fd 100755 --- a/tests/phpunit/ParserTest.php +++ b/tests/phpunit/ParserTest.php @@ -902,6 +902,7 @@ public function testParseInvalidParentId(): void $parser->process($testFile->data, 'someType', ['someColumn' => ['this' => 'is wrong']]); $this->expectException(JsonParserException::class); + $this->expectExceptionMessage( 'Error assigning parentId to a CSV file! $parentId array cannot be multidimensional' ); diff --git a/tests/phpunit/RealDataTest.php b/tests/phpunit/RealDataTest.php index 9647395..f34d5e6 100644 --- a/tests/phpunit/RealDataTest.php +++ b/tests/phpunit/RealDataTest.php @@ -4,7 +4,6 @@ namespace Keboola\Json\Tests; -use Keboola\Csv\CsvFile; use Keboola\Csv\CsvReader; use Keboola\Json\Analyzer; use Keboola\Json\Parser; @@ -29,7 +28,7 @@ public function testProcess(): void // compare column counts $headerCount = null; - $parsedFile = new CsvFile($table->getPathname()); + $parsedFile = new CsvReader($table->getPathname()); foreach ($parsedFile as $row) { if (empty($headerCount)) { $headerCount = count($row); @@ -209,7 +208,7 @@ public function testProcessWithAutoUpgradeToArray(): void // compare column counts $headerCount = null; - $parsedFile = new CsvFile($table->getPathname()); + $parsedFile = new CsvReader($table->getPathname()); foreach ($parsedFile as $row) { if (empty($headerCount)) { $headerCount = count($row);