diff --git a/src/SessionManager.php b/src/SessionManager.php index 3bb41b3c..2f969a3d 100644 --- a/src/SessionManager.php +++ b/src/SessionManager.php @@ -9,6 +9,8 @@ use function array_key_exists; use function array_merge; +use function constant; +use function defined; use function headers_sent; use function is_array; use function iterator_to_array; @@ -91,12 +93,19 @@ public function sessionExists() return true; } - if ($this->getId()) { + /** + * @var string|false $sid + */ + $sid = defined('SID') ? constant('SID') : false; + + if ($sid !== false && $this->getId()) { return true; } + if (headers_sent()) { return true; } + return false; } diff --git a/test/SessionManagerTest.php b/test/SessionManagerTest.php index 301f7034..ceb684cf 100644 --- a/test/SessionManagerTest.php +++ b/test/SessionManagerTest.php @@ -17,6 +17,8 @@ use Laminas\Session\Validator\Id; use Laminas\Session\Validator\RemoteAddr; use LaminasTest\Session\TestAsset\Php81CompatibleStorageInterface; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; use PHPUnit\Framework\TestCase; use Traversable; @@ -35,6 +37,7 @@ use function session_write_close; use function set_error_handler; use function stristr; +use function uniqid; use function var_export; use function xdebug_get_headers; @@ -157,20 +160,14 @@ public function testCanDisableAttachDefaultValidators(): void $this->assertAttributeEquals([], 'validators', $manager); } - // Session-related functionality - - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSessionExistsReturnsFalseWhenNoSessionStarted(): void { $this->manager = new SessionManager(); self::assertFalse($this->manager->sessionExists()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSessionExistsReturnsTrueWhenSessionStarted(): void { $this->manager = new SessionManager(); @@ -178,9 +175,8 @@ public function testSessionExistsReturnsTrueWhenSessionStarted(): void self::assertTrue($this->manager->sessionExists()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] + #[IgnoreDeprecations] public function testSessionExistsReturnsTrueWhenSessionStartedThenWritten(): void { $this->manager = new SessionManager(); @@ -189,9 +185,8 @@ public function testSessionExistsReturnsTrueWhenSessionStartedThenWritten(): voi self::assertTrue($this->manager->sessionExists()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] + #[IgnoreDeprecations] public function testSessionExistsReturnsFalseWhenSessionStartedThenDestroyed(): void { $this->manager = new SessionManager(); @@ -200,9 +195,7 @@ public function testSessionExistsReturnsFalseWhenSessionStartedThenDestroyed(): self::assertFalse($this->manager->sessionExists()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSessionIsStartedAfterCallingStart(): void { $this->manager = new SessionManager(); @@ -211,9 +204,8 @@ public function testSessionIsStartedAfterCallingStart(): void self::assertTrue($this->manager->sessionExists()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] + #[IgnoreDeprecations] public function testStartDoesNothingWhenCalledAfterWriteCloseOperation(): void { $this->manager = new SessionManager(); @@ -226,9 +218,7 @@ public function testStartDoesNothingWhenCalledAfterWriteCloseOperation(): void self::assertEquals($id1, $id2); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testStartWithOldTraversableSessionData(): void { // pre-populate session with data @@ -245,9 +235,7 @@ public function testStartWithOldTraversableSessionData(): void self::assertEquals('value2', $_SESSION->key2); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testStorageContentIsPreservedByWriteCloseOperation(): void { $this->manager = new SessionManager(); @@ -259,9 +247,8 @@ public function testStorageContentIsPreservedByWriteCloseOperation(): void self::assertEquals('bar', $storage['foo']); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] + #[IgnoreDeprecations] public function testStartCreatesNewSessionIfPreviousSessionHasBeenDestroyed(): void { $this->manager = new SessionManager(); @@ -274,9 +261,7 @@ public function testStartCreatesNewSessionIfPreviousSessionHasBeenDestroyed(): v self::assertNotEquals($id1, $id2); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testStartConvertsSessionDataFromStorageInterfaceToArrayBeforeMerging(): void { $this->manager = new SessionManager(); @@ -298,9 +283,7 @@ public function testStartConvertsSessionDataFromStorageInterfaceToArrayBeforeMer self::assertSame($data[$key], $_SESSION[$key]); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testStartConvertsSessionDataFromTraversableToArrayBeforeMerging(): void { $this->manager = new SessionManager(); @@ -334,9 +317,7 @@ public function testStartWillNotBlockHeaderSentNotices(): void self::assertContains('already sent', $this->error); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testGetNameReturnsSessionName(): void { $this->manager = new SessionManager(); @@ -344,9 +325,7 @@ public function testGetNameReturnsSessionName(): void self::assertEquals($ini, $this->manager->getName()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSetNameRaisesExceptionOnInvalidName(): void { $this->manager = new SessionManager(); @@ -355,9 +334,7 @@ public function testSetNameRaisesExceptionOnInvalidName(): void $this->manager->setName('foo bar!'); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSetNameSetsSessionNameOnSuccess(): void { $this->manager = new SessionManager(); @@ -366,9 +343,8 @@ public function testSetNameSetsSessionNameOnSuccess(): void self::assertEquals('foobar', session_name()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] + #[IgnoreDeprecations] public function testCanSetNewSessionNameAfterSessionDestroyed(): void { $this->manager = new SessionManager(); @@ -379,9 +355,7 @@ public function testCanSetNewSessionNameAfterSessionDestroyed(): void self::assertEquals('foobar', session_name()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSettingNameWhenAnActiveSessionExistsRaisesException(): void { $this->manager = new SessionManager(); @@ -391,9 +365,7 @@ public function testSettingNameWhenAnActiveSessionExistsRaisesException(): void $this->manager->setName('foobar'); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testDestroyByDefaultSendsAnExpireCookie(): void { if (! extension_loaded('xdebug')) { @@ -424,9 +396,7 @@ public function testDestroyByDefaultSendsAnExpireCookie(): void self::assertTrue($found, 'No session cookie found: ' . var_export($headers, true)); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSendingFalseToSendExpireCookieWhenCallingDestroyShouldNotSendCookie(): void { if (! extension_loaded('xdebug')) { @@ -461,9 +431,7 @@ public function testSendingFalseToSendExpireCookieWhenCallingDestroyShouldNotSen } } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testDestroyDoesNotClearSessionStorageByDefault(): void { $this->manager = new SessionManager(); @@ -475,9 +443,7 @@ public function testDestroyDoesNotClearSessionStorageByDefault(): void self::assertEquals('bar', $storage['foo']); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testPassingClearStorageOptionWhenCallingDestroyClearsStorage(): void { $this->manager = new SessionManager(); @@ -488,9 +454,7 @@ public function testPassingClearStorageOptionWhenCallingDestroyClearsStorage(): self::assertFalse(isset($storage['foo'])); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testDestroySessionWhenHeadersHaveBeenSent(): void { $this->manager = new SessionManager(); @@ -503,9 +467,7 @@ public function testDestroySessionWhenHeadersHaveBeenSent(): void self::assertFalse(isset($storage['foo'])); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testCallingWriteCloseMarksStorageAsImmutable(): void { $this->manager = new SessionManager(); @@ -516,9 +478,8 @@ public function testCallingWriteCloseMarksStorageAsImmutable(): void self::assertTrue($storage->isImmutable()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] + #[IgnoreDeprecations] public function testCallingWriteCloseShouldNotAlterSessionExistsStatus(): void { $this->manager = new SessionManager(); @@ -527,18 +488,14 @@ public function testCallingWriteCloseShouldNotAlterSessionExistsStatus(): void self::assertTrue($this->manager->sessionExists()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testIdShouldBeEmptyPriorToCallingStart(): void { $this->manager = new SessionManager(); self::assertSame('', $this->manager->getId()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testIdShouldBeMutablePriorToCallingStart(): void { $this->manager = new SessionManager(); @@ -547,9 +504,7 @@ public function testIdShouldBeMutablePriorToCallingStart(): void self::assertSame(self::class, session_id()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testIdShouldNotBeMutableAfterSessionStarted(): void { $this->manager = new SessionManager(); @@ -561,9 +516,7 @@ public function testIdShouldNotBeMutableAfterSessionStarted(): void $this->manager->setId(__METHOD__); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testRegenerateIdShouldWorkAfterSessionStarted(): void { $this->manager = new SessionManager(); @@ -573,9 +526,7 @@ public function testRegenerateIdShouldWorkAfterSessionStarted(): void self::assertNotSame($origId, $this->manager->getId()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testRegenerateIdDoesNothingWhenSessioIsNotStarted(): void { $this->manager = new SessionManager(); @@ -585,9 +536,7 @@ public function testRegenerateIdDoesNothingWhenSessioIsNotStarted(): void self::assertEquals('', $this->manager->getId()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testRegeneratingIdAfterSessionStartedShouldSendExpireCookie(): void { if (! extension_loaded('xdebug')) { @@ -617,9 +566,7 @@ public function testRegeneratingIdAfterSessionStartedShouldSendExpireCookie(): v self::assertTrue($found, 'No session cookie found: ' . var_export($headers, true)); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testRememberMeShouldSendNewSessionCookieWithUpdatedTimestamp(): void { if (! extension_loaded('xdebug')) { @@ -663,9 +610,7 @@ public function testRememberMeShouldSendNewSessionCookieWithUpdatedTimestamp(): ); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testRememberMeShouldSetTimestampBasedOnConfigurationByDefault(): void { if (! extension_loaded('xdebug')) { @@ -715,9 +660,7 @@ public function testRememberMeShouldSetTimestampBasedOnConfigurationByDefault(): ); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testForgetMeShouldSendCookieWithZeroTimestamp(): void { if (! extension_loaded('xdebug')) { @@ -748,9 +691,7 @@ public function testForgetMeShouldSendCookieWithZeroTimestamp(): void self::assertStringNotContainsString('expires=', $header); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testStartingSessionThatFailsAValidatorShouldRaiseException(): void { $this->manager = new SessionManager(); @@ -761,9 +702,7 @@ public function testStartingSessionThatFailsAValidatorShouldRaiseException(): vo $this->manager->start(); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testResumeSessionThatFailsAValidatorShouldRaiseException(): void { $this->manager = new SessionManager(); @@ -773,9 +712,7 @@ public function testResumeSessionThatFailsAValidatorShouldRaiseException(): void $this->manager->start(); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSessionWriteCloseStoresMetadata(): void { $this->manager = new SessionManager(); @@ -787,9 +724,7 @@ public function testSessionWriteCloseStoresMetadata(): void self::assertSame($_SESSION['__Laminas'], $metaData); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testSessionValidationDoesNotHaltOnNoopListener(): void { $this->manager = new SessionManager(); @@ -804,9 +739,7 @@ public function testSessionValidationDoesNotHaltOnNoopListener(): void self::assertTrue($validatorCalled); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testProducedSessionManagerWillNotReplaceSessionSuperGlobalValues(): void { $this->manager = new SessionManager(); @@ -818,9 +751,7 @@ public function testProducedSessionManagerWillNotReplaceSessionSuperGlobalValues self::assertSame('bar', $_SESSION['foo']); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testValidatorChainSessionMetadataIsPreserved(): void { $this->manager = new SessionManager(); @@ -836,9 +767,7 @@ public function testValidatorChainSessionMetadataIsPreserved(): void self::assertEquals('', $_SESSION['__Laminas']['_VALID'][RemoteAddr::class]); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testRemoteAddressValidationWillFailOnInvalidAddress(): void { $this->manager = new SessionManager(); @@ -850,9 +779,7 @@ public function testRemoteAddressValidationWillFailOnInvalidAddress(): void $this->manager->start(); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testRemoteAddressValidationWillSucceedWithValidPreSetData(): void { $this->manager = new SessionManager(); @@ -869,9 +796,7 @@ public function testRemoteAddressValidationWillSucceedWithValidPreSetData(): voi self::assertTrue($this->manager->isValid()); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testRemoteAddressValidationWillFailWithInvalidPreSetData(): void { $this->manager = new SessionManager(); @@ -888,9 +813,7 @@ public function testRemoteAddressValidationWillFailWithInvalidPreSetData(): void $this->manager->start(); } - /** - * @runInSeparateProcess - */ + #[RunInSeparateProcess] public function testIdValidationWillFailOnInvalidData(): void { $this->manager = new SessionManager(); @@ -902,6 +825,30 @@ public function testIdValidationWillFailOnInvalidData(): void $this->manager->start(); } + #[RunInSeparateProcess] + #[IgnoreDeprecations] + public function testSettingTheIdentifierBeforeStartingTheSessionYieldsTheExpectedId(): void + { + $manager = new SessionManager(); + + $id = uniqid(); + + $manager->setId($id); + + // setting a session id does not mark a session as started + self::assertFalse($manager->sessionExists()); + + $manager->start(); + + self::assertTrue($manager->sessionExists()); + + $manager->writeClose(); + + // calling writeClose() does not mark the session as closed + self::assertTrue($manager->sessionExists()); + self::assertSame($id, $manager->getId()); + } + /** @param non-empty-string $property */ private function assertAttributeEquals(mixed $expected, string $property, object $object): void {