From 2cc505b89becd0f7838baa9e87e9d208ac8b7367 Mon Sep 17 00:00:00 2001 From: Anatoliy Babushka Date: Wed, 24 Jun 2026 22:14:42 +0200 Subject: [PATCH 1/4] feat(config): add get()/toJson() API and tighten Config internals - Add get() and toJson() to Config and ConfigInterface, mirroring Chart. - toJson() hardens output with JSON_THROW_ON_ERROR and HEX flags. - Guard toArray() so it only calls ->toArray() on objects that define it. - Return void from __set() and move fluent chaining into __call(). - Add generic array PHPDoc types for stricter PHPStan. --- src/Chart.php | 4 ++-- src/ChartInterface.php | 6 ++++++ src/Config/Config.php | 39 +++++++++++++++++++++++++++------- src/Config/ConfigInterface.php | 10 +++++++++ src/Config/Options.php | 3 +++ tests/ChartTest.php | 10 +++++++++ 6 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/Chart.php b/src/Chart.php index c4220d0..0efa901 100644 --- a/src/Chart.php +++ b/src/Chart.php @@ -25,7 +25,7 @@ public function __construct() /** * Get the chart as an array * - * @return array + * @return array */ public function get(): array { @@ -35,7 +35,7 @@ public function get(): array /** * Convert the chart to an array * - * @return array + * @return array */ public function toArray(): array { diff --git a/src/ChartInterface.php b/src/ChartInterface.php index f84ea52..d094f01 100644 --- a/src/ChartInterface.php +++ b/src/ChartInterface.php @@ -4,9 +4,15 @@ interface ChartInterface { + /** + * @return array + */ public function get(): array; public function toJson(): string; + /** + * @return array + */ public function toArray(): array; } diff --git a/src/Config/Config.php b/src/Config/Config.php index 2244799..5e00ed4 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -4,8 +4,14 @@ class Config implements ConfigInterface { + /** + * @var array + */ protected array $attributes; + /** + * @param array $attributes + */ public function __construct(array $attributes = []) { $this->attributes = $attributes; @@ -27,40 +33,57 @@ public function &__get($name) * * @param string $name * @param mixed $value - * @return $this + * @return void */ - public function &__set($name, $value) + public function __set($name, $value) { $this->attributes[$name] = $value; - - return $this; } /** * Dynamically set the value of a property. * * @param string $name - * @param mixed $value + * @param array $value * @return $this */ public function __call($name, $value) { - return $this->__set($name, reset($value)); + $this->__set($name, reset($value)); + + return $this; } /** * Convert the object to an array. * - * @return array + * @return array */ public function toArray(): array { array_walk_recursive($this->attributes, function (&$item) { - if (is_object($item)) { + if (is_object($item) && method_exists($item, 'toArray')) { $item = $item->toArray(); } }); return $this->attributes; } + + /** + * @return array + */ + public function get(): array + { + return $this->toArray(); + } + + /** + * @return string + * @throws \JsonException + */ + public function toJson(): string + { + return json_encode($this->toArray(), JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); + } } diff --git a/src/Config/ConfigInterface.php b/src/Config/ConfigInterface.php index 041e6a4..e877c2d 100644 --- a/src/Config/ConfigInterface.php +++ b/src/Config/ConfigInterface.php @@ -4,5 +4,15 @@ interface ConfigInterface { + /** + * @return array + */ + public function get(): array; + + public function toJson(): string; + + /** + * @return array + */ public function toArray(): array; } diff --git a/src/Config/Options.php b/src/Config/Options.php index 019e326..4797bb5 100644 --- a/src/Config/Options.php +++ b/src/Config/Options.php @@ -2,6 +2,9 @@ namespace Bbsnly\ChartJs\Config; +/** + * @method self scales(array $value) + */ class Options extends Config { diff --git a/tests/ChartTest.php b/tests/ChartTest.php index cd38ff6..956c543 100644 --- a/tests/ChartTest.php +++ b/tests/ChartTest.php @@ -526,4 +526,14 @@ private function assertStringNotContains(string $needle, string $haystack): void "Failed asserting that '$haystack' does not contain '$needle'" ); } + /** + * Test if Config class get() and toJson() methods work correctly + */ + public function test_config_get_and_tojson_methods() + { + $config = new \Bbsnly\ChartJs\Config\Config(['key' => 'value']); + + $this->assertEquals(['key' => 'value'], $config->get()); + $this->assertEquals('{"key":"value"}', $config->toJson()); + } } From c6c7daaba20e91392471622345214f6bc8f8a70d Mon Sep 17 00:00:00 2001 From: Anatoliy Babushka Date: Wed, 24 Jun 2026 22:14:58 +0200 Subject: [PATCH 2/4] fix(chart): use explicit nullable type for toHtml() $chart param Implicitly nullable parameters ($chart = null without a ?-prefixed type) are deprecated as of PHP 8.4. Declare the parameter as ?Chart to silence the deprecation and keep the package forward-compatible. --- src/Chart.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chart.php b/src/Chart.php index 0efa901..9d10c7b 100644 --- a/src/Chart.php +++ b/src/Chart.php @@ -107,10 +107,10 @@ public function beginAtZero(): self * Generate the HTML representation of the chart * * @param string $element - * @param Chart $chart + * @param Chart|null $chart * @return string */ - public function toHtml(string $element, Chart $chart = null): string + public function toHtml(string $element, ?Chart $chart = null): string { if ($chart === null) { $chart = $this; From cf4e0a046b5a8fa06dec1611f8da4c7a58173c97 Mon Sep 17 00:00:00 2001 From: Anatoliy Babushka Date: Wed, 24 Jun 2026 22:15:13 +0200 Subject: [PATCH 3/4] test(config): add regression test for issue #34 array append Guards $data->datasets[] = ...; against regressions. Removing the by-reference return from Config::__get() makes this test fail with the exact #34 symptom, so the magic-array-append fix can no longer be lost silently. No existing test covered this path. --- tests/ChartTest.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/ChartTest.php b/tests/ChartTest.php index 956c543..8e5c85c 100644 --- a/tests/ChartTest.php +++ b/tests/ChartTest.php @@ -536,4 +536,33 @@ public function test_config_get_and_tojson_methods() $this->assertEquals(['key' => 'value'], $config->get()); $this->assertEquals('{"key":"value"}', $config->toJson()); } + + /** + * Regression test for issue #34: `$data->datasets[] = ...; PHP error`. + * + * Appending to a not-yet-existing magic property must mutate the real + * underlying array. This relies on Config::&__get() returning the + * attribute by reference. Without it PHP raises + * "Indirect modification of overloaded property ... has no effect" + * (which PHPUnit turns into a failure) and the append is silently lost. + */ + public function test_it_can_append_to_a_magic_array_property() + { + $data = new Data(); + + // Property does not exist yet — append must create and grow the array. + $data->datasets[] = (new Dataset)->data([10, 20, 30]); + $data->datasets[] = (new Dataset)->data([30, 20, 10]); + + $this->assertCount(2, $data->datasets); + $this->assertEquals( + [ + 'datasets' => [ + ['data' => [10, 20, 30]], + ['data' => [30, 20, 10]], + ], + ], + $data->toArray() + ); + } } From a249f56f56f55a7f6e7c0cde09fe492f889e69bb Mon Sep 17 00:00:00 2001 From: Anatoliy Babushka Date: Wed, 24 Jun 2026 22:15:13 +0200 Subject: [PATCH 4/4] ci: add PHP 8.5 to the test matrix PHP 8.5 is stable and the suite passes on it with no deprecations. Add it to the matrix and move continue-on-error from 8.4 to 8.5, so 8.4 is now an enforced target while the newest release stays non-blocking. --- .github/workflows/php.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 3455d6c..b32b4ec 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['8.1', '8.2', '8.3', '8.4'] + php-versions: ['8.1', '8.2', '8.3', '8.4', '8.5'] fail-fast: false name: PHP ${{ matrix.php-versions }} Test @@ -29,7 +29,7 @@ jobs: php-version: ${{ matrix.php-versions }} tools: composer:v2 coverage: xdebug - continue-on-error: ${{ matrix.php-versions == '8.4' }} + continue-on-error: ${{ matrix.php-versions == '8.5' }} - name: Validate composer.json and composer.lock run: composer validate --strict