diff --git a/features/access.feature b/features/access.feature index d5b6add..7229208 100644 --- a/features/access.feature +++ b/features/access.feature @@ -33,3 +33,11 @@ Feature: Session access Scenario: Iterate over non-populated session When data does not exist Then data is not iterated + Scenario: Overload existing array + When empty array for overload exists + Then overloading using property access succeeds + And overloading using property access succeeds + Scenario: Overload non existing array + When data does not exist + Then overloading using array access fails + And overloading using property access fails diff --git a/src/Session.php b/src/Session.php index 6e14c02..d04051d 100644 --- a/src/Session.php +++ b/src/Session.php @@ -8,6 +8,7 @@ use Countable; use ArrayAccess; use RuntimeException; +use Stringable; class Session implements ArrayAccess, Iterator, Countable { @@ -46,13 +47,17 @@ public function __construct(string $name, ?string $id = null, array $contents = * * @throws RuntimeException if not initialized */ - public function __get(string $name) + public function &__get(string $name) { if (!$this->isInitialized()) { throw new RuntimeException('Session not initialized'); } - // @phpstan-ignore-next-line + if(!isset($this->contents[$name])){ + \trigger_error("Array key not found: '$name'", \E_USER_NOTICE); + return null; + } + return $this->contents[$name]; } @@ -133,13 +138,20 @@ public function offsetUnset($name): void unset($this->contents[$name]); } - public function offsetGet($name): mixed + public function &offsetGet($name): mixed { if (!$this->isInitialized()) { throw new RuntimeException('Session not initialized'); } - // @phpstan-ignore-next-line + + if(!isset($this->contents[$name])){ + if($name === null || \is_scalar($name) || $name instanceof Stringable){ + \trigger_error("Array key not found: '$name'", \E_USER_NOTICE); + } + return null; + } + return $this->contents[$name]; } diff --git a/tests/behavior/AccessContext.php b/tests/behavior/AccessContext.php index 3bdc711..199d107 100644 --- a/tests/behavior/AccessContext.php +++ b/tests/behavior/AccessContext.php @@ -34,6 +34,16 @@ public function dataExists(): void Assert::assertCount(1, $this->session); } + /** + * @When empty array for overload exists + */ + public function emptyArrayForOverloadExists(): void + { + $this->session = new Session('foo', 'bar', ['foo' => []]); + Assert::assertTrue($this->session->isWriteable()); + Assert::assertCount(1, $this->session); + } + /** * @Then property check returns false */ @@ -199,4 +209,58 @@ public function iteratorFails(): void Assert::assertSame(0, $counter); } + + /** + * @Then overloading using array access succeeds + */ + public function arrayOverloadSucceeds(): void + { + // @phpstan-ignore-next-line + $this->session['foo'][] = 'baz'; + // @phpstan-ignore-next-line + Assert::assertSame('baz', $this->session['foo'][0]); + } + + /** + * @Then overloading using property access succeeds + */ + public function objectOverloadSucceeds(): void + { + // @phpstan-ignore-next-line + $this->session->foo[] = 'baz'; + // @phpstan-ignore-next-line + Assert::assertSame('baz', $this->session->foo[0]); + } + + /** + * @Then overloading using array access fails + */ + public function arrayOverloadFails(): void + { + try { + $errorThrown = false; + // @phpstan-ignore-next-line + $this->session['foo'][] = 'baz'; + } catch (Throwable $e) { + $errorThrown = true; + } finally { + Assert::assertTrue($errorThrown); + } + } + + /** + * @Then overloading using property access fails + */ + public function objectOverloadFails(): void + { + try { + $errorThrown = false; + // @phpstan-ignore-next-line + $this->session->foo[] = 'baz'; + } catch (Throwable $e) { + $errorThrown = true; + } finally { + Assert::assertTrue($errorThrown); + } + } }