diff --git a/.github/workflows/on-pr-merged.yml b/.github/workflows/on-pr-merged.yml new file mode 100644 index 0000000..36529bb --- /dev/null +++ b/.github/workflows/on-pr-merged.yml @@ -0,0 +1,33 @@ +name: After-Merge Chores +on: + pull_request: + types: + - closed + # branches: + # - FRAMEWORK_6_0 + workflow_dispatch: + +jobs: + PostMerge: + if: github.event.pull_request.merged == true + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + extensions: bcmath, ctype, curl, dom, gd, gettext, iconv, imagick, json, ldap, mbstring, mysql, opcache, openssl, pcntl, pdo, posix, redis, soap, sockets, sqlite, tokenizer, xmlwriter, xdebug + ini-values: post_max_size=512M, max_execution_time=360 + coverage: xdebug + tools: php-cs-fixer, phpunit:${{ matrix.phpunit-versions }}, composer:v2 + - name: Setup Github Token as composer credential + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + - name: Install dependencies and local tools + run: | + COMPOSER_ROOT_VERSION=dev-FRAMEWORK_6_0 composer config minimum-stability dev + COMPOSER_ROOT_VERSION=dev-FRAMEWORK_6_0 composer config prefer-stable true + COMPOSER_ROOT_VERSION=dev-FRAMEWORK_6_0 composer install --no-interaction --no-progress + diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml new file mode 100644 index 0000000..9e936ba --- /dev/null +++ b/.github/workflows/on-pr.yml @@ -0,0 +1,43 @@ +name: Pull Request Chores +on: + pull_request: + branches: + - FRAMEWORK_6_0 + workflow_dispatch: + +jobs: + CI: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + extensions: bcmath, ctype, curl, dom, gd, gettext, iconv, imagick, json, ldap, mbstring, mysql, opcache, openssl, pcntl, pdo, posix, redis, soap, sockets, sqlite, tokenizer, xmlwriter, xdebug + ini-values: post_max_size=512M, max_execution_time=360 + coverage: xdebug + tools: php-cs-fixer, phpunit:${{ matrix.phpunit-versions }}, composer:v2 + - name: Setup Github Token as composer credential + run: composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} + - name: Install dependencies and local tools + run: | + COMPOSER_ROOT_VERSION=dev-FRAMEWORK_6_0 composer config minimum-stability dev + COMPOSER_ROOT_VERSION=dev-FRAMEWORK_6_0 composer config prefer-stable true + COMPOSER_ROOT_VERSION=dev-FRAMEWORK_6_0 composer install --no-interaction --no-progress + + - name: Run PHPUnit + run: vendor/bin/phpunit --testdox + + - name: Run php-cs-fixer + run: vendor/bin/php-cs-fixer check -vvv + + - name: Run phpstan (mandatory level) + run: vendor/bin/phpstan --no-progress + + - name: Run phpstan (level 9, allowed to fail) + run: vendor/bin/phpstan --no-progress --level=9 + continue-on-error: true + diff --git a/.gitignore b/.gitignore index da47a4a..5440b8e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ tools/ .php-cs-fixer.cache .phpunit.cache/ composer.lock +.phpunit.result.cache \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 030ee56..37a104e 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,8 +1,7 @@ exclude(['fixtures']); return (new PhpCsFixer\Config()) ->setRules([ diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index 3390630..0000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":{"Horde\\PhpConfigFile\\Test\\Unit\\PhpConfigFileTest::testReadConfigFile":4,"Horde\\PhpConfigFile\\Test\\Unit\\PhpConfigFileTest::testWriteConfigFile":4},"times":{"Horde\\PhpConfigFile\\Test\\Unit\\PhpConfigFileTest::testReadConfigFile":0,"Horde\\PhpConfigFile\\Test\\Unit\\PhpConfigFileTest::testWriteConfigFile":0}} \ No newline at end of file diff --git a/src/PhpConfigFile.php b/src/PhpConfigFile.php index 21d4aa1..66ccdc3 100644 --- a/src/PhpConfigFile.php +++ b/src/PhpConfigFile.php @@ -7,6 +7,7 @@ use Stringable; use RuntimeException; use InvalidArgumentException; + use function file_exists; use function file_get_contents; use function file_put_contents; @@ -17,7 +18,6 @@ use function trim; use function ltrim; use function get_defined_vars; -use function eval; use function var_export; use function in_array; @@ -56,15 +56,15 @@ public function getContentBetweenHeaderAndFooter(): string return $this->contentBetweenHeaderAndFooter; } - public function readConfigFile() + public function readConfigFile(): self { // Read the config file and parse it into an array - if (!file_exists($this->configFilePath)) { - throw new \RuntimeException("Config file does not exist: {$this->configFilePath}"); + if (!file_exists((string) $this->configFilePath)) { + throw new RuntimeException("Config file does not exist: {$this->configFilePath}"); } - $configContent = file_get_contents($this->configFilePath); + $configContent = file_get_contents((string) $this->configFilePath); if ($configContent === false) { - throw new \RuntimeException("Failed to read config file: {$this->configFilePath}"); + throw new RuntimeException("Failed to read config file: {$this->configFilePath}"); } // Strip leading and trailing php tags $configContent = trim($configContent); @@ -77,31 +77,37 @@ public function readConfigFile() } $this->content = $configContent; // Get everything before $header - $headerStartPos = strpos($configContent, $this->header); + $headerStartPos = strpos($configContent, (string) $this->header); $headerEndPos = 0; $this->contentBeforeHeader = ''; if ($headerStartPos === false) { $headerStartPos = 0; } else { - $headerEndPos = $headerStartPos + strlen($this->header); + $headerEndPos = $headerStartPos + strlen((string) $this->header); $this->contentBeforeHeader = substr($configContent, 0, $headerStartPos); } // Get everything after $footer - $footerStartPos = strpos($configContent, $this->footer); + $footerStartPos = strpos($configContent, (string) $this->footer); if ($footerStartPos === false) { $this->contentAfterFooter = ''; $footerStartPos = strlen($configContent); $footerEndPos = $footerStartPos; } else { - $footerEndPos = $footerStartPos + strlen($this->footer); + $footerEndPos = $footerStartPos + strlen((string) $this->footer); $this->contentAfterFooter = substr($configContent, $footerEndPos); } $this->contentBetweenHeaderAndFooter = substr($configContent, $headerEndPos, $footerStartPos - $headerEndPos); // Parse the content into an array (assuming it's a PHP array) + return $this; } - + /** + * Parse the content between the header and footer into an array + * @param string $area The area to parse from. Can be 'contentBetweenHeaderAndFooter', 'contentBeforeHeader', 'contentAfterFooter', or 'content'. + * @return array The parsed content as an array + * @throws InvalidArgumentException If the area is invalid + */ public function parseContent(string $area = 'content'): array { // TODO: Ensure to prevent any output from eval @@ -117,7 +123,12 @@ public function parseContent(string $area = 'content'): array throw new \InvalidArgumentException("Invalid area to parse from: $area"); } - public function writeConfigFile(array $config): void + /** + * Write the config file with the given array + * @param array $config The config array to write + * @return self + */ + public function writeConfigFile(array $config): self { // Convert the array back to a string $configContent = "footer . "\n" . $this->contentAfterFooter; // Write the content back to the file - file_put_contents($this->configFilePath, $configContent); + file_put_contents((string) $this->configFilePath, $configContent); + return $this; } } diff --git a/test/unit/PhpConfigFileTest.php b/test/unit/PhpConfigFileTest.php index e5c0ba8..c4d05d4 100644 --- a/test/unit/PhpConfigFileTest.php +++ b/test/unit/PhpConfigFileTest.php @@ -13,7 +13,7 @@ */ class PhpConfigFileTest extends TestCase { - public function testReadEmptyConfigFile() + public function testReadEmptyConfigFile(): void { $file = new PhpConfigFile( configFilePath: __DIR__ . '/../fixtures/EmptyConfigFile.php', @@ -23,7 +23,7 @@ public function testReadEmptyConfigFile() $this->assertEquals('', $file->getContent()); } - public function testReadEmptyConfigFileWithComment() + public function testReadEmptyConfigFileWithComment(): void { $file = new PhpConfigFile( configFilePath: __DIR__ . '/../fixtures/EmptyConfigFileWithComment.php', @@ -32,7 +32,7 @@ public function testReadEmptyConfigFileWithComment() $this->assertEquals('// To be done', $file->getContent()); } - public function testReadConfigFileDefaultsAndOverridesWorkAsExpected() + public function testReadConfigFileDefaultsAndOverridesWorkAsExpected(): void { $file = new PhpConfigFile( configFilePath: __DIR__ . '/../fixtures/WithPreHeaderAndPostFooterContent.php', @@ -49,7 +49,7 @@ public function testReadConfigFileDefaultsAndOverridesWorkAsExpected() } - public function testReadConfigFileIgnoringBeforeHeaderAndAfterFooter() + public function testReadConfigFileIgnoringBeforeHeaderAndAfterFooter(): void { $file = new PhpConfigFile( configFilePath: __DIR__ . '/../fixtures/WithPreHeaderAndPostFooterContent.php', @@ -64,10 +64,10 @@ public function testReadConfigFileIgnoringBeforeHeaderAndAfterFooter() $this->assertEquals('not you the other one', $betweenContent['you'], 'Variable you not found in content'); $this->assertEquals('set', $betweenContent['something'], 'Variable something overwritten by footer in content'); $this->assertArrayNotHasKey('me', $betweenContent, 'Variable me found in content but only exists before header and after footer'); - + } - public function testNestedModernArrayFormat() + public function testNestedModernArrayFormat(): void { $file = new PhpConfigFile( configFilePath: __DIR__ . '/../fixtures/NestedModernArrayFormat.php', @@ -78,7 +78,7 @@ public function testNestedModernArrayFormat() $this->assertEquals('subsubvalue1', $allContent['config']['key3']['subkey2']['subsubkey1']); } - public function testClassicHordeFormat() + public function testClassicHordeFormat(): void { $file = new PhpConfigFile( configFilePath: __DIR__ . '/../fixtures/ClassicHordeFormat.php', @@ -89,7 +89,7 @@ public function testClassicHordeFormat() $this->assertTrue($allContent['conf']['readwritesplit']); } - public function testWriteEmptyFileWithHeaderAndFooter() + public function testWriteEmptyFileWithHeaderAndFooter(): void { $file = new PhpConfigFile( configFilePath: 'deleteme', @@ -98,7 +98,7 @@ public function testWriteEmptyFileWithHeaderAndFooter() ); $file->writeConfigFile([]); $this->assertFileExists('deleteme'); - $contentString = file_get_contents('deleteme'); + $contentString = (string) file_get_contents('deleteme'); $this->assertStringContainsString('/* Begin */', $contentString, 'Header not found'); $this->assertStringContainsString('/* End */', $contentString, 'Footer not found'); $contentValues = $file->parseContent(); @@ -106,7 +106,7 @@ public function testWriteEmptyFileWithHeaderAndFooter() unlink('deleteme'); } - public function testWriteFailure() + public function testReadFailure(): void { $this->expectException(\RuntimeException::class); $file = new PhpConfigFile(