Skip to content
33 changes: 28 additions & 5 deletions src/BaseComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
use Keboola\Component\Manifest\ManifestManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use function error_reporting;
use function file_get_contents;

/**
* This is the core class that does all the heavy lifting. By default you don't need to setup anything. There are some
Expand All @@ -32,6 +31,9 @@ class BaseComponent
/** @var LoggerInterface */
private $logger;

/** @var array */
private $inputState;

public function __construct(LoggerInterface $logger)
{
static::setEnvironment();
Expand All @@ -41,6 +43,7 @@ public function __construct(LoggerInterface $logger)
$this->setDataDir($dataDir);

$this->loadConfig();
$this->loadInputState();

$this->loadManifestManager();

Expand Down Expand Up @@ -85,11 +88,26 @@ protected function loadConfig(): void
$this->logger->debug('Config loaded');
}

protected function loadInputState(): void
{
try {
$this->inputState = JsonFileHelper::read($this->getDataDir() . '/in/state.json');
} catch (FileNotFoundException $exception) {
$this->inputState = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tady je jeden potencionalni problem, kdyz bude ve state {} nebo [] a prozene se to pres assoc decoder, takze z toho vzdycky vznikne []. Vzhledem k tomu, ze state jsou interni data jedne aplikace a ta aplikace je vzdycky v PHP, tak si myslim, ze to vubec nevadi. Presto jsem to sem napsal, abyste se nad tim s @tomasfejfar zamysleli jestli vas nenapadne sitauce ve ktere by to mohl byt problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@odinuv Kdyz bude ve state {}/[], tak je to uplne jedno, oboji je validni prazdny json. To by nemelo vadit nikde.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ja vim, ze je oboji validni, jde o to, ze se prazdny objekty konvertuji na pole a nelze rozlisit mezi tim jestli to byl prazdny objekt nebo prazdne pole
python by se na tom treba vysypal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To uz je pak ale na pythonovou komponentu. Jak si psal toto je vstup pouze pro php, takze je to jedno. Na vystupu nikdy prazdny state zapisovat nebudeme, coz bych videl jeko potencialni nekonzistenci.

}
}

protected function writeOutputStateToFile(array $state): void
{
JsonFileHelper::write(
$this->getDataDir() . '/out/state.json',
$state
);
}

protected function getRawConfig(): array
{
$jsonContents = file_get_contents($this->dataDir . '/config.json');
$jsonEncoder = new JsonEncoder();
return $jsonEncoder->decode($jsonContents, JsonEncoder::FORMAT);
return JsonFileHelper::read($this->dataDir . '/config.json');
}

/**
Expand Down Expand Up @@ -137,6 +155,11 @@ public function getLogger() : LoggerInterface
return $this->logger;
}

public function getInputState(): array
{
return $this->inputState;
}

/**
* This is the main method for your code to run in. You have the `Config`
* and `ManifestManager` ready as well as environment set up.
Expand Down
45 changes: 45 additions & 0 deletions src/JsonFileHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Keboola\Component;

use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Serializer\Encoder\JsonEncoder;

class JsonFileHelper
{
public static function read(string $filePath): array
{
if (!file_exists($filePath)) {
throw new FileNotFoundException(null, 0, null, $filePath);
}

$jsonEncoder = new JsonEncoder();
$jsonContents = file_get_contents($filePath);
return $jsonEncoder->decode($jsonContents, JsonEncoder::FORMAT);
}

public static function write(string $filePath, array $data, bool $formatted = true): void
{
$filePathDir = pathinfo($filePath, PATHINFO_DIRNAME);
if (!is_dir($filePathDir)) {
mkdir($filePathDir, 0777, true);
}

$context = [];
if ($formatted) {
$context = ['json_encode_options' => JSON_PRETTY_PRINT];
}

$jsonEncoder = new JsonEncoder();
$result = file_put_contents(
$filePath,
$jsonEncoder->encode($data, JsonEncoder::FORMAT, $context)
);

if ($result === false) {
throw new \ErrorException('Could not write to file "%s".');
}
}
}
71 changes: 71 additions & 0 deletions tests/BaseComponentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace Keboola\Component\Tests;

use Keboola\Component\BaseComponent;
use Keboola\Component\Logger;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;

class BaseComponentTest extends TestCase
{
public function testLoadInputStateFile(): void
{
putenv(sprintf(
'KBC_DATADIR=%s',
__DIR__ . '/fixtures/base-component-data-dir/state-file'
));

$logger = new Logger();
$baseComponent = new BaseComponent($logger);

$inputStateFile = $baseComponent->getInputState();
$this->assertCount(4, $inputStateFile);

$this->assertArrayHasKey('key1', $inputStateFile);
$this->assertEquals('value1', $inputStateFile['key1']);

$this->assertArrayHasKey('key2', $inputStateFile);
$this->assertEquals(2, $inputStateFile['key2']);

$this->assertArrayHasKey('list', $inputStateFile);
$this->assertCount(3, $inputStateFile['list']);
$this->assertEquals('a', $inputStateFile['list'][0]);
$this->assertEquals('b', $inputStateFile['list'][1]);
$this->assertEquals('c', $inputStateFile['list'][2]);

$this->assertArrayHasKey('dict', $inputStateFile);
$this->assertCount(1, $inputStateFile['dict']);
$this->assertArrayHasKey('key', $inputStateFile['dict']);
$this->assertEquals('value', $inputStateFile['dict']['key']);
}

public function testLoadInputStateFileEmptyThrowsException(): void
{
putenv(sprintf(
'KBC_DATADIR=%s',
__DIR__ . '/fixtures/base-component-data-dir/empty-state-file'
));

$logger = new Logger();

$this->expectException(NotEncodableValueException::class);
$this->expectExceptionMessage('Syntax error');
new BaseComponent($logger);
}

public function testLoadInputStateFileUndefined(): void
{
putenv(sprintf(
'KBC_DATADIR=%s',
__DIR__ . '/fixtures/base-component-data-dir/undefined-state-file'
));

$logger = new Logger();
$baseComponent = new BaseComponent($logger);

$this->assertSame([], $baseComponent->getInputState());
}
}
101 changes: 101 additions & 0 deletions tests/JsonFileHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);

namespace Keboola\Component\Tests;

use Keboola\Component\JsonFileHelper;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;

class JsonFileHelperTest extends TestCase
{
public function testReadNonExistingFileThrowsException(): void
{
$this->expectException(FileNotFoundException::class);
$this->expectExceptionMessage('File "/dev/null/file.json" could not be found.');
JsonFileHelper::read('/dev/null/file.json');
}

public function testReadInvalidFileThrowsException(): void
{
$this->expectException(NotEncodableValueException::class);
$this->expectExceptionMessage('Syntax error');
JsonFileHelper::read(__DIR__ . '/fixtures/json-file-helper-test/invalidJsonFile.json');
}

public function testReadFileSuccessfully(): void
{
$array = JsonFileHelper::read(__DIR__ . '/fixtures/json-file-helper-test/file.json');
$this->assertSame(
[
'key' => 'value',
'keys' => ['a', 'b'],
],
$array
);
}

public function testWriteToFileSuccessfully(): void
{
$filePath = __DIR__ . '/fixtures/json-file-helper-test/tmp.json';
$array = [
'key' => 'val',
'keys' => [0, 1, 2],
];
JsonFileHelper::write($filePath, $array, false);

$this->assertSame(
'{"key":"val","keys":[0,1,2]}',
file_get_contents($filePath)
);
}

public function testWriteToFilePrettyPrintedSuccessfully(): void
{
$filePath = __DIR__ . '/fixtures/json-file-helper-test/tmp.json';
$array = [
'key' => 'val',
'keys' => [0, 1, 2],
];
JsonFileHelper::write($filePath, $array);

$this->assertSame(
'{
"key": "val",
"keys": [
0,
1,
2
]
}',
file_get_contents($filePath)
);
unlink($filePath);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chybí test writeToDirectoryThatDoesNotExist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doplnil jsem.


public function testWriteToNonExistingDirectorySuccessfully(): void
{
$filePath = __DIR__ . '/non-existing-folder/tmp.json';
$array = [
'key' => 'val',
];

JsonFileHelper::write($filePath, $array, false);
$this->assertSame('{"key":"val"}', file_get_contents($filePath));

unlink($filePath);
rmdir(pathinfo($filePath, PATHINFO_DIRNAME));
}

public function testWriteToProtectedDirectoryThrowsException(): void
{
$filePath = '/tmp.json';
$array = ['key'];

$this->expectException(\ErrorException::class);
$this->expectExceptionMessageRegExp('~^file_put_contents(.*): failed to open stream: Permission denied$~');
JsonFileHelper::write($filePath, $array);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
12 changes: 12 additions & 0 deletions tests/fixtures/base-component-data-dir/state-file/in/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"key1": "value1",
"key2": 2,
"list": [
"a",
"b",
"c"
],
"dict": {
"key": "value"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
7 changes: 7 additions & 0 deletions tests/fixtures/json-file-helper-test/file.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"key": "value",
"keys": [
"a",
"b"
]
}
3 changes: 3 additions & 0 deletions tests/fixtures/json-file-helper-test/invalidJsonFile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"key" => "value"
}