Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
commitizen:
name: cz_conventional_commits
tag_format: $version
version: 0.6.2
version: 1.0.0
2 changes: 1 addition & 1 deletion .github/workflows/php-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
run: composer update --no-ansi --no-interaction --prefer-dist --no-progress --ignore-platform-req=ext-ast
run: composer install --no-ansi --no-interaction --prefer-dist --no-progress --ignore-platform-req=ext-ast

- name: Run test suite
run: composer ci:test-build
4 changes: 2 additions & 2 deletions .github/workflows/pr-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.2

- name: Validate composer.json and composer.lock
run: composer validate --strict
Expand All @@ -31,7 +31,7 @@ jobs:
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
run: composer update --no-ansi --no-interaction --prefer-dist --no-progress --ignore-platform-req=ext-ast && composer dumpautoload
run: composer install --no-ansi --no-interaction --prefer-dist --no-progress --ignore-platform-req=ext-ast && composer dumpautoload

- name: Scanning for lint errors
run: composer ci:lint
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 1.0.0 (2025-01-19)

### BREAKING CHANGE

- CsvFileStorage::getByKey now returns ItemFound instead of Item.

### Feat

- use phpolar/storage 3.0

## 0.6.2 (2025-01-01)

## 0.6.1 (2023-07-03)
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<p align="center">
<img width="240" src="./phpolar.svg" />
<img width="240" src="./phpolar.svg" alt="PHPolar logo" />
</p>

# PHPolar CSV File Storage

[![Latest Stable Version](https://poser.pugx.org/phpolar/csv-file-storage/v)](https://packagist.org/packages/phpolar/csv-file-storage) [![Total Downloads](https://poser.pugx.org/phpolar/csv-file-storage/downloads)](https://packagist.org/packages/phpolar/csv-file-storage) [![PHP Version Require](https://poser.pugx.org/phpolar/csv-file-storage/require/php)](https://packagist.org/packages/phpolar/csv-file-storage) [![PHPMD](https://github.com/phpolar/csv-file-storage/actions/workflows/phpmd.yml/badge.svg)](https://github.com/phpolar/csv-file-storage/actions/workflows/phpmd.yml) [![PHP Build Latest and Nightly](https://github.com/phpolar/csv-file-storage/actions/workflows/php-latest.yml/badge.svg)](https://github.com/phpolar/csv-file-storage/actions/workflows/php-latest.yml)
[![Coverage Status](https://coveralls.io/repos/github/phpolar/csv-file-storage/badge.svg?branch=main)](https://coveralls.io/github/phpolar/csv-file-storage?branch=main) [![Latest Stable Version](https://poser.pugx.org/phpolar/csv-file-storage/v)](https://packagist.org/packages/phpolar/csv-file-storage) [![Total Downloads](https://poser.pugx.org/phpolar/csv-file-storage/downloads)](https://packagist.org/packages/phpolar/csv-file-storage) [![PHP Version Require](https://poser.pugx.org/phpolar/csv-file-storage/require/php)](https://packagist.org/packages/phpolar/csv-file-storage) [![PHPMD](https://github.com/phpolar/csv-file-storage/actions/workflows/phpmd.yml/badge.svg)](https://github.com/phpolar/csv-file-storage/actions/workflows/phpmd.yml) [![PHP Build Latest and Nightly](https://github.com/phpolar/csv-file-storage/actions/workflows/php-latest.yml/badge.svg)](https://github.com/phpolar/csv-file-storage/actions/workflows/php-latest.yml)

## Adds support for storing data on disk in CSV format

Expand Down
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "phpolar/csv-file-storage",
"description": "Adds support for storing data on disk in CSV format.",
"type": "library",
Expand Down Expand Up @@ -30,8 +29,8 @@
}
},
"require": {
"php": ">=8.1",
"phpolar/storage": "^2.0"
"php": ">= 8.2",
"phpolar/storage": "^3.0"
},
"license": "MIT",
"scripts": {
Expand Down
24 changes: 12 additions & 12 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 6 additions & 14 deletions phpunit.ci.xml
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
executionOrder="depends,defects"
beStrictAboutOutputDuringTests="false"
failOnRisky="false"
failOnWarning="false"
colors="false"
cacheDirectory=".phpunit.cache"
requireCoverageMetadata="false"
beStrictAboutCoverageMetadata="false">
cacheDirectory=".phpunit.cache">
<testsuites>
<testsuite name="default">
<directory>tests/unit</directory>
</testsuite>
<testsuite name="unit">
<directory>tests/unit</directory>
</testsuite>
</testsuites>
<coverage pathCoverage="true">
<source>
<include>
<directory suffix=".php">src</directory>
<directory>src</directory>
</include>
</coverage>
</source>
<coverage pathCoverage="true" />
</phpunit>
8 changes: 2 additions & 6 deletions phpunit.dev.xml
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
executionOrder="depends,defects"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
failOnWarning="false"
colors="true"
cacheDirectory=".phpunit.cache"
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="true">
<testsuites>
<testsuite name="default">
<directory>tests/unit</directory>
</testsuite>
<testsuite name="acceptance">
<directory>tests/acceptance</directory>
</testsuite>
Expand All @@ -24,7 +20,7 @@
<php>
<const name="SRC_GLOB" value="/src{/,/**/}*.php" />
<const name="Phpolar\Tests\PROJECT_SIZE_THRESHOLD" value="4000" />
<const name="Phpolar\Tests\PROJECT_MEMORY_USAGE_THRESHOLD" value="2000" />
<const name="Phpolar\Tests\PROJECT_MEMORY_USAGE_THRESHOLD" value="5500" />
<const name="Phpolar\Tests\PROJECT_MEMORY_USAGE_THRESHOLD_WITHOUT_PRELOADING" value="125000" />
</php>
</phpunit>
21 changes: 15 additions & 6 deletions src/CsvFileStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class CsvFileStorage extends AbstractStorage implements Countable
*/
private $writeStream;

private int|false $fileSize = 0;
private int $fileSize = 0;

/**
* The first line of the CSV file.
Expand Down Expand Up @@ -107,7 +107,14 @@ public function commit(): void
fputcsv($this->writeStream, [$record]);
break;
case is_array($record):
fputcsv($this->writeStream, $record);
/**
* @var array<int|string, bool|float|int|string|null>
*/
$arr = $record;
fputcsv(
$this->writeStream,
$arr,
);
break;
default:
throw new DomainException(self::INVALID_VALUE_MSG);
Expand Down Expand Up @@ -137,7 +144,8 @@ private function convertObjVars(array $objVars): array
*/
public function count(): int
{
return $this->getCount();
$result = $this->getCount();
return max($result, 0);
}

protected function load(): void
Expand All @@ -162,11 +170,12 @@ protected function load(): void

/**
* @param string $needle
* @param ReflectionNamedType[] $namedTypes
* @param ReflectionNamedType[]|\ReflectionIntersectionType[] $types
*/
private function containsType(string $needle, array $namedTypes): bool
private function containsType(string $needle, array $types): bool
{
$haystack = array_map(static fn (ReflectionNamedType $type) => $type->getName(), $namedTypes);
/** @phan-suppress-next-line PhanPartialTypeMismatchArgument */
$haystack = array_map(static fn (ReflectionNamedType $type) => $type->getName(), array_filter($types, static fn ($type) => $type instanceof ReflectionNamedType));
return in_array($needle, $haystack);
}

Expand Down
30 changes: 13 additions & 17 deletions tests/unit/CsvFileStorageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
use Phpolar\Phpolar\Storage\ItemKey;
use Phpolar\Phpolar\Storage\ItemNotFound;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use PHPUnit\Framework\TestCase;

#[CoversClass(CsvFileStorage::class)]
Expand Down Expand Up @@ -44,7 +44,7 @@ static function (string $filename): void {
file_exists($filename) && unlink($filename);
},
);
unset(self::$filenames);
self::$filenames = [];
}

#[TestDox("Shall save objects to file")]
Expand All @@ -62,7 +62,6 @@ public function test1a()
}

#[TestDox("Shall save objects with primary key to file")]
#[Group("me")]
public function test1b()
{
$givenPrimaryKey = uniqid();
Expand Down Expand Up @@ -124,7 +123,7 @@ public function test4()
#[TestDox("Shall not clear the internal data if data is on file, no headers exist, and load is called")]
public function test5()
{
$sut = new CsvFileStorage("tests/__fakes__/without-headers.csv");
$sut = new CsvFileStorage(__DIR__ . "/../__fakes__/without-headers.csv");
$fromFile = $sut->getAll();
$this->assertNotEmpty($fromFile);
}
Expand All @@ -133,40 +132,37 @@ public function test5()
public function test6()
{
$this->expectException(DomainException::class);
new CsvFileStorage("tests/__fakes__/empty-headers.csv");
new CsvFileStorage(__DIR__ . "/../__fakes__/empty-headers.csv");
}

#[TestDox("Shall throw an exception if storage contains object types but file has no headers")]
public function test7()
{
$this->expectException(DomainException::class);
new CsvFileStorage("tests/__fakes__/empty-headers.csv", FakeValueObject::class);
new CsvFileStorage(__DIR__ . "/../__fakes__/empty-headers.csv", FakeValueObject::class);
}

#[TestDox("Shall throw an exception if stream does not exist")]
#[WithoutErrorHandler]
public function test8()
{
$warningHandler = static fn () => true;
set_error_handler($warningHandler, E_WARNING);
$this->expectException(FileNotExistsException::class);
new CsvFileStorage("php://non-existing-stream-handle");
restore_error_handler();
@new CsvFileStorage("php://non-existing-stream-handle");
}

#[TestDox("Shall load objects from file")]
public function testaa()
{
$sut = new CsvFileStorage("tests/__fakes__/object.csv", FakeValueObject::class);
$sut = new CsvFileStorage(__DIR__ . "/../__fakes__/object.csv", FakeValueObject::class);
$itemKey = new ItemKey(0);
$item = $sut->getByKey($itemKey);
$this->assertNotInstanceOf(ItemNotFound::class, $item);
}

#[TestDox("Shall load objects with primary key from file")]
#[Group("me")]
public function testab()
{
$sut = new CsvFileStorage("tests/__fakes__/object-with-pkey.csv", FakeValueObjectWithPrimaryKey::class);
$sut = new CsvFileStorage(__DIR__ . "/../__fakes__/object-with-pkey.csv", FakeValueObjectWithPrimaryKey::class);
$primaryKey = "123";
$itemKey = new ItemKey($primaryKey);
$item = $sut->getByKey($itemKey);
Expand All @@ -176,15 +172,15 @@ public function testab()
#[TestDox("Shall load more than one object from file")]
public function testb()
{
$sut = new CsvFileStorage("tests/__fakes__/object-2.csv", FakeValueObject::class);
$sut = new CsvFileStorage(__DIR__ . "/../__fakes__/object-2.csv", FakeValueObject::class);
$this->assertCount(2, $sut);
}

#[TestDox("Shall throw an exception if attempting to load object from file with one line")]
public function testc()
{
$this->expectException(DomainException::class);
new CsvFileStorage("tests/__fakes__/object-malformed.csv", FakeValueObject::class);
new CsvFileStorage(__DIR__ . "/../__fakes__/object-malformed.csv", FakeValueObject::class);
}

#[TestDox("Shall not set first line when file is empty")]
Expand All @@ -197,15 +193,15 @@ public function testd()
#[TestDox("Shall parse into target object having union types")]
public function teste()
{
$sut = new CsvFileStorage("tests/__fakes__/object-unions.csv", FakeValueObjectWithUnions::class);
$sut = new CsvFileStorage(__DIR__ . "/../__fakes__/object-unions.csv", FakeValueObjectWithUnions::class);
$this->assertContainsOnlyInstancesOf(FakeValueObjectWithUnions::class, $sut->getAll());
}

#[TestDox("Shall throw an exception when the union type is ambiguous")]
public function testf()
{
$this->expectException(AmbiguousUnionTypeException::class);
new CsvFileStorage("tests/__fakes__/object-unions-malformed.csv", FakeValueObjectWithUnionsError::class);
new CsvFileStorage(__DIR__ . "/../__fakes__/object-unions-malformed.csv", FakeValueObjectWithUnionsError::class);
}

#[TestDox("Shall throw an exception when an invalid value is commited")]
Expand Down
Loading