diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1318904..96deb7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: name: 'Coding style' steps: - name: 'Checkout' - uses: 'actions/checkout@v3' + uses: 'actions/checkout@v4' - name: 'Setup PHP' uses: 'shivammathur/setup-php@v2' @@ -51,7 +51,7 @@ jobs: - '8.3' steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Setup PHP' uses: 'shivammathur/setup-php@v2' @@ -73,7 +73,7 @@ jobs: name: 'Code Coverage' steps: - name: 'Checkout' - uses: 'actions/checkout@v3' + uses: 'actions/checkout@v4' - name: 'Setup PHP' uses: 'shivammathur/setup-php@v2' diff --git a/.gitignore b/.gitignore index 014d167..247ad6b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ phpcbf-fixed.difff php-session.code-workspace .repository.md .phpunit.result.cache -coverage.xml \ No newline at end of file +coverage.xml +/nbproject/ diff --git a/README.md b/README.md index fbc47cd..b906f94 100644 --- a/README.md +++ b/README.md @@ -344,14 +344,293 @@ Destroys the session: public static function destroy(): bool; ``` -## Exceptions Used + +### Flashable Session Segment Class + +`Josantonius\Session\FlashableSessionSegment` + +This is a special type of Session class whose data is always stored in a sub-array in **$_SESSION**. This sub-array is automatically created & managed by each instance of this class and its key in **$_SESSION** is the string value passed as the first argument to this class' constructor. The session is always either auto-started or auto-resumed when an instance of this class is created, so you never need to explicitly call the **start** method after creating an instance of this class. This class also has flash functionality that allows you to store values in session via an instance of this class that are meant to be read once from session and removed after that one-time read (for example, you can set a login success notification message in session in your application using this flash mechanism and once the success message is read once from the session, it will no longer be available in session). You can also store non-flash values in session via each instance of this class that will stay in session until you explicitly remove them or the session expires or is destroyed. Both flash and non-flash values are stored in the earlier mentioned sub-array in **$_SESSION** and as a result, they are shielded from being overwritten by other parts of your application that write to **$_SESSION** directly as long as the key / segment name you specified for each instance of this class is unique & not being already used to store some other stuff in **$_SESSION**. + +The first non-optional argument to its constructor is a key (also referred to as a segment name) to the sub-array in **$_SESSION** where all the data for the instance of `Josantonius\Session\FlashableSessionSegment` you are creating will be stored. Make sure the key you specify is a unique key that isn't being used by other packages in your application that write to **$_SESSION** so you don't have your data stored via an instance of this class overwritten by some other packages in your application. + +An instance of **Josantonius\Session\SessionInterface** can optionally be passed as the second argument to the constructor. +This instance will be used to interact with the session (**$_SESSION**). If the second argument is **null** +then an instance of **Josantonius\Session\Session** will be created & used to interact with the session (**$_SESSION**). + +The optional third argument to its constructor is an array of options for configuring the session. It must contain the same valid options acceptable by [session_start](https://www.php.net/manual/en/function.session-start.php). + + +Create an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +/** + * @throws EmptySegmentNameException if an empty string (i.e. '' or "") was passed as the first argument + * @throws HeadersSentException if headers already sent. + * @throws SessionNotStartedException if session could not be started. + * @throws WrongSessionOptionException if setting options failed. + * + * @param string $segmentName key to be used in $_SESSION to store all the data for an instance of this class + + * @param null|\Josantonius\Session\SessionInterface $storage used for performing some session operations for each instance of this class. If null, an instance of \Josantonius\Session\Session will be created & used. + * + * @param array $options session start configuration options that will be used to automatically start a new session or resume an existing session. It must contain the same valid options acceptable by https://www.php.net/manual/en/function.session-start.php. + * + * @see https://php.net/session.configuration for List of available $options. + */ +public function __construct(string $segmentName, ?SessionInterface $storage = null, array $options = []); +``` + +Get the segment name (this is the key to the sub-array inside **$_SESSION** where all the data for an instance of `Josantonius\Session\FlashableSessionSegment` will be stored. It is the first argument passed to the constructor above): + +```php +public function getSegmentName(): string; +``` + +Get the instance of `\Josantonius\Session\SessionInterface` being used to perform session operations on behalf of an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +public function getStorage(): SessionInterface; +``` + +Start the session: + +```php +public function start(array $options = []): bool +``` + +Check if the session is started: + +```php +public function isStarted(): bool; +``` + +Get all values stored via an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +public function all(): array +``` + +Check if an attribute exists via an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +public function has(string $name): bool +``` + +Get the corresponding value associated with an attribute that was stored via an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +/** + * Optionally defines a default value when the attribute does not exist. + */ +public function get(string $name, mixed $default = null): mixed +``` + +Set an attribute and its corresponding value via an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +public function set(string $name, mixed $value): void +``` + +Set several attribute & value pairs at once via an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +/** + * If attributes exist they are replaced, if they do not exist they are created. + */ +public function replace(array $data): void +``` + +Delete an attribute associated with an instance of `Josantonius\Session\FlashableSessionSegment` by name and returns its value: + +```php +/** + * Optionally defines a default value when the attribute does not exist. + */ +public function pull(string $name, mixed $default = null): mixed +``` + +Delete an attribute associated with an instance of `Josantonius\Session\FlashableSessionSegment` by name: + +```php +public function remove(string $name): void +``` + +Clear / free all attributes and corresponding values stored via an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +public function clear(): void +``` + +Get the session ID: ```php -use Josantonius\Session\Exceptions\HeadersSentException; -use Josantonius\Session\Exceptions\SessionException; +public function getId(): string +``` + +Set the session ID: + +```php +/** + * This method will be of no effect if called outside the constructor + * of this class since the session will already be started after the + * constructor method finishes execution + */ +public function setId(string $sessionId): void +``` + +Update the current session ID with a newly generated one: + +```php +public function regenerateId(bool $deleteOldSession = false): bool +``` + +Get the session name: + +```php +public function getName(): string; +``` + +Set the session name: + +```php +/** + * This method will be of no effect if called outside the constructor + * of this class since the session will already be started after the + * constructor method finishes execution. + */ +public function setName(string $name): void; +``` + +Clear / free all attributes and corresponding values stored via an instance of `Josantonius\Session\FlashableSessionSegment`, but does not destroy the session **$_SESSION**. + +**$_SESSION** data written outside this class will remain intact: + +```php +/** + * Always returns true + */ +public function destroy(): bool; +``` + + +> **Flash Mechanism:** every time an instance of `Josantonius\Session\FlashableSessionSegment` is created, an array to store flash data (meant to be read only once by the next instance of `Josantonius\Session\FlashableSessionSegment` with the same key / segment name) is either created inside the sub-array inside **$_SESSION** where all the data for the created instance of `Josantonius\Session\FlashableSessionSegment` will be stored or if that sub-sub-array for the flash data already exists in **$_SESSION**, it is copied into the **objectsFlashData** property of the created instance of `Josantonius\Session\FlashableSessionSegment` and reset to an empty array in **$_SESSION**. + +> Flash data contained in the **objectsFlashData** property of an instance of `Josantonius\Session\FlashableSessionSegment` is referred to as the **current / object instance's flash data** (which is the copy of flash data read once from the session). Flash data set inside the sub-array in **$_SESSION** is referred to as the **next / session segment's flash data** (i.e. flash data to be copied from **$_SESSION** into the **objectsFlashData** property of the next instance of `Josantonius\Session\FlashableSessionSegment` with the same key / segment name as the current instance used to store the flash data in **$_SESSION**). + +> Below are methods related to storing and retrieving flash data. More concrete examples of how to store and retrieve flash data will be shown in the usage section later below. + +Set an attribute & value in the object instance's flash for an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +/** + * Sets an item with the specified $key in the flash storage for an instance + * of this class (i.e. $this->objectsFlashData). + * + * The item will only be retrievable from the instance of this class it was set in. + */ +public function setInObjectsFlash(string $key, mixed $value): void +``` + +Set an attribute & value in the session segment's flash for an instance of `Josantonius\Session\FlashableSessionSegment`: + +```php +/** + * Sets an item with the specified $key in the flash storage located in + * $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + * + * The item will only be retrievable by calling the getFromObjectsFlash + * on the next instance of this class created with the same segment name. + */ +public function setInSessionFlash(string $key, mixed $value): void +``` + +Check if an attribute exists in the object instance's flash: + +```php +/** + * Check if item with specified $key exists in the flash storage for + * an instance of this class (i.e. in $this->objectsFlashData). + */ +public function hasInObjectsFlash(string $key): bool +``` + + +Check if an attribute exists in the session segment's flash: + +```php +/** + * Check if item with specified $key exists in + * $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST]. + */ +public function hasInSessionFlash(string $key): bool +``` + +Get the value associated with a specified attribute from the object instance's flash: + +```php +/** + * Get an item with the specified $key from the flash storage for an instance + * of this class (i.e. $this->objectsFlashData) if it exists or return $default. + */ +public function getFromObjectsFlash(string $key, mixed $default = null): mixed +``` + + +Get the value associated with a specified attribute from the session segment's flash: + +```php +/** + * Get an item with the specified $key from + * + * $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + * if it exists or return $default. + */ +public function getFromSessionFlash(string $key, mixed $default = null): mixed +``` + +Remove an attribute & its corresponding value from the object instance's and / or session segment's flash: + +```php +/** + * Remove an item with the specified $key (if it exists) from: + * + * - the flash storage for an instance of this class (i.e. in $this->objectsFlashData), + * if $fromObjectsFlash === true + * + * - $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST], + * if $fromSessionFlash === true + */ +public function removeFromFlash( + string $key, + bool $fromObjectsFlash = true, + bool $fromSessionFlash = false +): void +``` + +Get all attributes & their corresponding values from the object instance's flash: + +```php +/** + * Get all items in the flash storage for an instance of this class + * (i.e. in $this->objectsFlashData) + */ +public function getAllFromObjectsFlash(): array +``` + +Get all attributes & their corresponding values from the session segment's flash: + +```php +/** + * Get all items in $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + */ +public function getAllFromSessionFlash(): array +``` + +## Exceptions Used in \Josantonius\Session\FlashableSessionSegment + +```php +use Josantonius\Session\Exceptions\EmptySegmentNameException; use Josantonius\Session\Exceptions\SessionNotStartedException; -use Josantonius\Session\Exceptions\SessionStartedException; -use Josantonius\Session\Exceptions\WrongSessionOptionException; ``` ## Usage @@ -374,6 +653,26 @@ use Josantonius\Session\Facades\Session; Session::start(); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); +``` + + ### Starts the session setting options ```php @@ -419,6 +718,29 @@ Session::start([ ]); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment( + 'demo-segment', + null, + ['cookie_httponly' => true] +); +``` + ### Check if the session is started ```php @@ -435,6 +757,27 @@ use Josantonius\Session\Facades\Session; Session::isStarted(); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->isStarted(); +``` + ### Sets an attribute by name ```php @@ -451,6 +794,27 @@ use Josantonius\Session\Facades\Session; Session::set('foo', 'bar'); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->set('foo', 'bar'); +``` + ### Gets an attribute by name without setting a default value ```php @@ -467,6 +831,27 @@ use Josantonius\Session\Facades\Session; Session::get('foo'); // null if attribute does not exist ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->get('foo'); // null if attribute does not exist +``` + ### Gets an attribute by name setting a default value ```php @@ -483,6 +868,27 @@ use Josantonius\Session\Facades\Session; Session::get('foo', false); // false if attribute does not exist ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->get('foo', false); // false if attribute does not exist +``` + ### Gets all attributes ```php @@ -499,6 +905,27 @@ use Josantonius\Session\Facades\Session; Session::all(); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->all(); +``` + ### Check if an attribute exists in the session ```php @@ -515,6 +942,27 @@ use Josantonius\Session\Facades\Session; Session::has('foo'); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->has('foo'); +``` + ### Sets several attributes at once ```php @@ -531,6 +979,28 @@ use Josantonius\Session\Facades\Session; Session::replace(['foo' => 'bar', 'bar' => 'foo']); ``` + +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->replace(['foo' => 'bar', 'bar' => 'foo']); +``` + ### Deletes an attribute and returns its value or the default value if not exist ```php @@ -547,6 +1017,28 @@ use Josantonius\Session\Facades\Session; Session::pull('foo'); // null if attribute does not exist ``` + +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->pull('foo'); // null if attribute does not exist +``` + ### Deletes an attribute and returns its value or the custom value if not exist ```php @@ -563,6 +1055,27 @@ use Josantonius\Session\Facades\Session; Session::pull('foo', false); // false if attribute does not exist ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->pull('foo', false); // false if attribute does not exist +``` + ### Deletes an attribute by name ```php @@ -579,6 +1092,27 @@ use Josantonius\Session\Facades\Session; Session::remove('foo'); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->remove('foo'); +``` + ### Free all session variables ```php @@ -595,6 +1129,31 @@ use Josantonius\Session\Facades\Session; Session::clear(); ``` +### Free all session segment variables + +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +// Only removes flash and non-flash data from the segment's +// sub-array in $_SESSION +$sessionSegment->clear(); +``` + ### Gets the session ID ```php @@ -611,6 +1170,27 @@ use Josantonius\Session\Facades\Session; Session::getId(); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->getId(); +``` + ### Sets the session ID ```php @@ -627,6 +1207,31 @@ use Josantonius\Session\Facades\Session; Session::setId('foo'); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +// Does nothing because sessions are auto-started for each +// instance of \Josantonius\Session\FlashableSessionSegment. +// Setting the ID after the session has already been started +// is of no effect. +$sessionSegment->setId('foo'); +``` + ### Update the current session ID with a newly generated one ```php @@ -643,6 +1248,27 @@ use Josantonius\Session\Facades\Session; Session::regenerateId(); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->regenerateId(); +``` + ### Update the current session ID with a newly generated one deleting the old session ```php @@ -659,6 +1285,27 @@ use Josantonius\Session\Facades\Session; Session::regenerateId(true); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->regenerateId(true); +``` + ### Gets the session name ```php @@ -675,6 +1322,27 @@ use Josantonius\Session\Facades\Session; Session::getName(); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +$sessionSegment->getName(); +``` + ### Sets the session name ```php @@ -691,6 +1359,31 @@ use Josantonius\Session\Facades\Session; Session::setName('foo'); ``` +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +// Does nothing because sessions are auto-started for each +// instance of \Josantonius\Session\FlashableSessionSegment. +// Setting the session name after the session has already +// been started is of no effect. +$sessionSegment->setName('foo'); +``` + ### Destroys the session ```php @@ -707,6 +1400,214 @@ use Josantonius\Session\Facades\Session; Session::destroy(); ``` +### Destroy the session segment but not the session + +```php +use Josantonius\Session\FlashableSessionSegment; + +///////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array +// +// OR +// +// auto-resume existing session & read flash +// data in $_SESSION (if any) into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +///////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +// Only removes flash and non-flash data from the segment's +// sub-array in $_SESSION +$sessionSegment->destroy(); +``` + +## Usage of Flash Functionality + +The major use case of flashes is to store data that is only meant to be read from the session segment's flash storage once and only once and immediately it is read once, it is automatically removed from the session segment's flash storage. + +For example you may want to store a message indicating the status of an operation (like whether or not you were able to successfully delete a record from the database) in the the flash storage, which you intend to be read only once when another script runs that tries to read that data. Let's show how you can accomplish that between two scripts: **script-a.php** and **script-b.php** + +> Both scripts must create an instance of `\Josantonius\Session\FlashableSessionSegment` with the same segment name passed as the first argument to the constructor, in order to both be able to access flash data from the same session segment. + +### script-a.php + +```php +include './vendor/autoload.php'; + +use Josantonius\Session\FlashableSessionSegment; + +// Message to store in session segment's flash +$statusMsg = "Operation Successful"; + +////////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array. +// +// This instance's objectsFlashData property has +// a value of an empty array, since at this point +// there is no data in the session segment's flash +// array to be removed and copied into this instance's +// objectsFlashData property at this point. +////////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +////////////////////////////////////////////////// +// Store your message in the session segment's flash. +// It will be loaded into the objectsFlashData property +// of the next instance of FlashableSessionSegment with +// the same segment name `demo-segment` and removed from +// the session segment's flash when the constructor of +// that next instance gets executed. +////////////////////////////////////////////////// +$sessionSegment->setInSessionFlash('operation-status', $statusMsg); +``` + +### script-b.php + +```php +include './vendor/autoload.php'; + +use Josantonius\Session\FlashableSessionSegment; + +////////////////////////////////////////////////// +// Auto-resume existing session & read flash data +// inside $_SESSION into this object's +// objectsFlashData property & reset the flash +// data in $_SESSION to an empty array +// +// This instance's objectsFlashData property now +// points to an array of all the flash data read +// and removed from the session segment's flash +// at this point. +////////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +////////////////////////////////////////////////// +// Retrieve the message from script-a.php stored +// in the session segment's flash with a key named +// `operation-status` which is now contained +// in this object's flash +// (i.e. its objectsFlashData property) +////////////////////////////////////////////////// +$statusMsg = + $sessionSegment->getFromObjectsFlash('operation-status'); +// $statusMsg should contain the string "Operation Successful" +var_dump($statusMsg); + +$nonExistentItem = + $sessionSegment->getFromObjectsFlash('non-existent', false); +// $nonExistentItem will contain false because no item +// with the key value of `non-existent` was ever stored +// in the session segment's flash +var_dump($nonExistentItem); +``` + +To test the 2 scripts above you should use a web-server or start php's built-in web-server. + +> Running the scripts on the command line will not work because php cli always creates a new session for each script you execute that tries to start a session. + +To test both scripts, save both scripts in the same directory and start the built-in web-server by running the command below: + +`php -S 0.0.0.0:8888 -t .` + +Open your browser & browse to http://localhost:8888/script-a.php & then to http://localhost:8888/script-b.php + +You should see the output below: + +``` +string(20) "Operation Successful" bool(false) +``` + +### Other methods for interacting with flash data in both the session segment and instances of `\Josantonius\Session\FlashableSessionSegment` + +```php +use Josantonius\Session\FlashableSessionSegment; + +// Message to store in session segment's flash +$statusMsg = "Operation Successful"; + +////////////////////////////////////////////////// +// Auto-start new session & initialize sub-array +// inside $_SESSION to store this objects data +// in $_SESSION & initialize session segment's +// flash data to an empty array. +// +// This instance's objectsFlashData property has +// a value of an empty array, since at this point +// there is no data in the session segment's flash +// array to be removed and copied into this +// instance's objectsFlashData property at this point. +////////////////////////////////////////////////// +$sessionSegment = new FlashableSessionSegment('demo-segment'); + +////////////////////////////////////////////////// +// Store your message in the session segment's flash. +// It will be loaded into the objectsFlashData property +// of the next instance of FlashableSessionSegment with +// the same segment name `demo-segment` and removed from +// the session segment's flash when the constructor of +// that next instance gets executed. +////////////////////////////////////////////////// +$sessionSegment->setInSessionFlash('operation-status', $statusMsg); + +// To get a value stored in the current object's +// session segment's flash in the same script or +// page request where the value was stored +$sessionFlashVal = + $sessionSegment->getFromSessionFlash('operation-status'); +// $sessionFlashVal will contain "Operation Successful" + +// To get an array of all values stored in the current object's +// session segment's flash in the same script or +// page request where the value was stored +$allSessionFlashValues = + $sessionSegment->getAllFromSessionFlash(); + +// To remove a value from the current object's +// session segment's flash in the same script or +// page request where the value was stored +$sessionSegment->removeFromFlash('operation-status', false, true); + +// To store a value in the current object's flash storage +// (not the session segment's flash storage inside $_SESSION) +// This value will only always be available in the script +// where it was set and will disappear once the object is +// unset, garbage collected or the script finishes executing. +$sessionSegment->setInObjectsFlash('value-in-object-flash', 'foo'); + +// To get a value that was stored in the current object's +// flash storage (not the session segment's flash storage +// inside $_SESSION) +$val = $sessionSegment->getFromObjectsFlash('value-in-object-flash'); +// $val will have a value of 'foo' + +// To get an array of all values stored in the current object's +// flash storage (not the session segment's flash storage +// inside $_SESSION) +$allValues = $sessionSegment->getAllFromObjectsFlash(); + +// To remove a value from the current object's flash storage +// (not the session segment's flash storage inside $_SESSION) +$sessionSegment->removeFromFlash('value-in-object-flash', true, false); + +// To check if an attribute exists in the current object's +// flash storage (not the session segment's flash storage +// inside $_SESSION) +$sessionSegment->hasInObjectsFlash('value-in-object-flash'); // returns true or false + +// To check if an attribute exists in the current object's +// session segment's flash +$sessionSegment->hasInSessionFlash('operation-status'); // returns true or false +``` + + + ## Tests To run [tests](tests) you just need [composer](http://getcomposer.org/download/) diff --git a/phpcs.xml b/phpcs.xml index 97e64cc..b81d01d 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -38,8 +38,8 @@ - - + + diff --git a/src/ExceptionThrowingMethodsTrait.php b/src/ExceptionThrowingMethodsTrait.php new file mode 100644 index 0000000..6cec745 --- /dev/null +++ b/src/ExceptionThrowingMethodsTrait.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Josantonius\Session; + +use Josantonius\Session\Exceptions\HeadersSentException; +use Josantonius\Session\Exceptions\SessionStartedException; +use Josantonius\Session\Exceptions\SessionNotStartedException; +use Josantonius\Session\Exceptions\WrongSessionOptionException; + +/** + * @author https://github.com/rotexdegba + */ +trait ExceptionThrowingMethodsTrait +{ + /** + * Throw exception if the session have wrong options. + * + * @throws WrongSessionOptionException If setting options failed. + */ + private function throwExceptionIfHasWrongOptions(array $options): void + { + $validOptions = array_flip([ + 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_samesite', 'cookie_secure', + 'gc_divisor', 'gc_maxlifetime', 'gc_probability', 'lazy_write', + 'name', 'read_and_close', 'referer_check', 'save_handler', + 'save_path', 'serialize_handler', 'sid_bits_per_character', 'sid_length', + 'trans_sid_hosts', 'trans_sid_tags', 'use_cookies', 'use_only_cookies', + 'use_strict_mode', 'use_trans_sid', + ]); + + foreach (array_keys($options) as $key) { + if (!isset($validOptions[$key])) { + throw new WrongSessionOptionException($key); + } + } + } + + /** + * Throw exception if headers have already been sent. + * + * @throws HeadersSentException if headers already sent. + */ + private function throwExceptionIfHeadersWereSent(): void + { + $headersWereSent = (bool) ini_get('session.use_cookies') && headers_sent($file, $line); + + $headersWereSent && throw new HeadersSentException($file, $line); + } + + /** + * Throw exception if the session has already been started. + * + * @throws SessionStartedException if session already started. + */ + private function throwExceptionIfSessionWasStarted(): void + { + $methodName = debug_backtrace()[1]['function'] ?? 'unknown'; + + $this->isStarted() && throw new SessionStartedException($methodName); + } + + /** + * Throw exception if the session was not started. + * + * @throws SessionNotStartedException if session was not started. + */ + private function throwExceptionIfSessionWasNotStarted(): void + { + $methodName = debug_backtrace()[1]['function'] ?? 'unknown'; + + !$this->isStarted() && throw new SessionNotStartedException($methodName); + } +} diff --git a/src/Exceptions/EmptySegmentNameException.php b/src/Exceptions/EmptySegmentNameException.php new file mode 100644 index 0000000..030a83d --- /dev/null +++ b/src/Exceptions/EmptySegmentNameException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Josantonius\Session\Exceptions; + +/** + * @author https://github.com/rotexdegba + */ +class EmptySegmentNameException extends SessionException +{ + public function __construct(string $methodName) + { + parent::__construct( + $methodName . '(): Session segment name cannot be an empty string.' + ); + } +} diff --git a/src/FlashableSessionSegment.php b/src/FlashableSessionSegment.php new file mode 100644 index 0000000..f5e0da8 --- /dev/null +++ b/src/FlashableSessionSegment.php @@ -0,0 +1,574 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Josantonius\Session; + +use Josantonius\Session\Exceptions\SessionNotStartedException; +use Josantonius\Session\Exceptions\EmptySegmentNameException; + +/** + * @author https://github.com/rotexdegba + * + * Each valid instance of this class will always have a valid session started or resumed + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + */ +class FlashableSessionSegment implements SessionInterface +{ + use ExceptionThrowingMethodsTrait; + + /** + * Used for performing some session operations for each instance of this class + */ + protected SessionInterface $storage; + + /** + * Flash data previously stored in session is copied here. + * + * It must be copied from + * $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + * every time an instance of this class is created. + * + * If $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + * isn't set, this $this->objectsFlashData should maintain + * the default value of empty array. + */ + protected array $objectsFlashData = []; + + /** + * When different libraries and projects try to modify data with the same keys + * in $_SESSION , the resulting conflicts can result in unexpected behavior. + * + * This value will be used as a key in $_SESSION to avoid session + * data for an instance of this class from colliding with or being + * overwritten by other libraries or projects within an application. + * + * Be sure to set it to a unique value for each instance of this class. + * For example, you could use a value such as the fully qualified + * class name concatenated with the method name of the method where + * an instance of this class is being created. + */ + protected string $segmentName; + + /** + * Key for storing flash data in $_SESSION[$this->segmentName] + */ + public const FLASH_DATA_FOR_NEXT_REQUEST = self::class . '__flash'; + + /** + * List of available $options with their default values: + * + * * cache_expire: "180" + * * cache_limiter: "nocache" + * * cookie_domain: "" + * * cookie_httponly: "0" + * * cookie_lifetime: "0" + * * cookie_path: "/" + * * cookie_samesite: "" + * * cookie_secure: "0" + * * gc_divisor: "100" + * * gc_maxlifetime: "1440" + * * gc_probability: "1" + * * lazy_write: "1" + * * name: "PHPSESSID" + * * read_and_close: "0" + * * referer_check: "" + * * save_handler: "files" + * * save_path: "" + * * serialize_handler: "php" + * * sid_bits_per_character: "4" + * * sid_length: "32" + * * trans_sid_hosts: $_SERVER['HTTP_HOST'] + * * trans_sid_tags: "a=href,area=href,frame=src,form=" + * * use_cookies: "1" + * * use_only_cookies: "1" + * * use_strict_mode: "0" + * * use_trans_sid: "0" + * + * @see https://php.net/session.configuration + * + * + * @param string $segmentName Name of key to be used in $_SESSION to store all + * session data for an instance of this class. This + * helps avoid writing session data directly to + * $_SESSION, which runs the risk of being overwritten + * by other libraries that write to $_SESSION with the + * same key(s). The value specified for this key should + * be something unique, like the domain name of the web + * application, or some other unique value that other + * libraries writing to $_SESSION wouldn't use. + * + * @param null|\Josantonius\Session\SessionInterface $storage used for performing some session operations for each instance of this class. + * If null, an instance of \Josantonius\Session\Session will be created + * + * @param array $options session start configuration options that will be used to + * automatically start a new session or resume existing session + * when an instance of this class is created. It must contain + * the same valid options acceptable by + * https://www.php.net/manual/en/function.session-start.php + * + * @throws \Josantonius\Session\Exceptions\EmptySegmentNameException + * @throws \Josantonius\Session\Exceptions\HeadersSentException + * @throws \Josantonius\Session\Exceptions\SessionNotStartedException + * @throws \Josantonius\Session\Exceptions\WrongSessionOptionException + */ + public function __construct(string $segmentName, ?SessionInterface $storage = null, array $options = []) + { + if ($segmentName === '') { + throw new EmptySegmentNameException(__METHOD__); + } + + $this->storage = $storage ?? new Session(); + $this->segmentName = $segmentName; + + if (!$this->isStarted()) { + // start or resume session + $this->start($options); + } + + if (!$this->isStarted()) { + $msg = 'Error: Could not start session in ' . __METHOD__; + throw new SessionNotStartedException($msg); + } + + $this->initializeOrReinitialize(); + $this->moveSessionFlashToObjectsFlash(); + } + + protected function initializeOrReinitialize(): void + { + if ($this->isStarted()) { + if (!isset($_SESSION[$this->segmentName])) { + // We want session data to be in a segment to protect it from + // being overwritten by other packages + $_SESSION[$this->segmentName] = []; + + if (!isset($_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST])) { + // Initialize the flash storage in the session's segment + $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] = []; + } + } + } + } + + protected function moveSessionFlashToObjectsFlash(): void + { + if ($this->isStarted()) { + /////////////////////////////////////////////////////////////////////////// + // Flash update logic: We update the flash data by copying the existing + // flash data stored in + // $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + // to $this->objectsFlashData & then reset + // $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + // to an empty array. + /////////////////////////////////////////////////////////////////////////// + $this->objectsFlashData = $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] ?? []; + + // Reset the flash data in session to an empty array + // but we still have the existing flash data in $this->objectsFlashData + $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] = []; + } + } + + /** + * Starts the session. + * + * List of available $options with their default values: + * + * * cache_expire: "180" + * * cache_limiter: "nocache" + * * cookie_domain: "" + * * cookie_httponly: "0" + * * cookie_lifetime: "0" + * * cookie_path: "/" + * * cookie_samesite: "" + * * cookie_secure: "0" + * * gc_divisor: "100" + * * gc_maxlifetime: "1440" + * * gc_probability: "1" + * * lazy_write: "1" + * * name: "PHPSESSID" + * * read_and_close: "0" + * * referer_check: "" + * * save_handler: "files" + * * save_path: "" + * * serialize_handler: "php" + * * sid_bits_per_character: "4" + * * sid_length: "32" + * * trans_sid_hosts: $_SERVER['HTTP_HOST'] + * * trans_sid_tags: "a=href,area=href,frame=src,form=" + * * use_cookies: "1" + * * use_only_cookies: "1" + * * use_strict_mode: "0" + * * use_trans_sid: "0" + * + * @see https://php.net/session.configuration + */ + public function start(array $options = []): bool + { + $isStarted = true; + + if (!$this->isStarted()) { + $isStarted = $this->storage->start($options); + + if ($isStarted) { + $this->initializeOrReinitialize(); + } // if($isStarted) + } // if(!$this->isStarted()) + + return $isStarted; + } + + /** + * Gets all attributes in $_SESSION[$this->segmentName]. + */ + public function all(): array + { + return $this->isStarted() ? ($_SESSION[$this->segmentName] ?? []) : []; + } + + /** + * Checks if an attribute exists in $_SESSION[$this->segmentName]. + */ + public function has(string $name): bool + { + return $this->isStarted() && isset($_SESSION[$this->segmentName][$name]); + } + + /** + * Gets an attribute by name from $_SESSION[$this->segmentName]. + */ + public function get(string $name, mixed $default = null): mixed + { + return $this->isStarted() ? ($_SESSION[$this->segmentName][$name] ?? $default) : $default; + } + + /** + * Sets an attribute by name in $_SESSION[$this->segmentName]. + */ + public function set(string $name, mixed $value): void + { + if ($this->isStarted()) { + // Set it in this session object's segmented session data + $_SESSION[$this->segmentName][$name] = $value; + } + } + + /** + * Sets several attributes at once inside $_SESSION[$this->segmentName]. + * + * If attributes exist they are replaced, if they do not exist they are created. + */ + public function replace(array $data): void + { + if ($this->isStarted()) { + // Replace it in this session object's segmented session data + $_SESSION[$this->segmentName] = + array_merge($_SESSION[$this->segmentName], $data); + } + } + + /** + * Deletes an attribute by name from $_SESSION[$this->segmentName] and returns its value. + * + * Optionally defines a default value when the attribute does not exist. + */ + public function pull(string $name, mixed $default = null): mixed + { + $value = $default; + + if ($this->isStarted()) { + // Try to get it from this session object's segmented session data + // or return the default value + $value = $_SESSION[$this->segmentName][$name] ?? $default; + + if (isset($_SESSION[$this->segmentName][$name])) { + // Remove it from this session object's segmented session data + unset($_SESSION[$this->segmentName][$name]); + } + } + + return $value; + } + + /** + * Deletes an attribute by name from $_SESSION[$this->segmentName]. + */ + public function remove(string $name): void + { + if ($this->isStarted() && isset($_SESSION[$this->segmentName][$name])) { + // Remove it from this session object's segmented session data + unset($_SESSION[$this->segmentName][$name]); + } + } + + /** + * Clears the session segment ($_SESSION[$this->segmentName]) and restores + * this object to its default original state after creation. + */ + public function clear(): void + { + if ($this->isStarted() && isset($_SESSION[$this->segmentName])) { + // Get rid of this session object's segmented session data, but we + // don't destroy $_SESSION since other data may be stored in it by + // other instances of this class or other code in the application + // this class is being used in. + unset($_SESSION[$this->segmentName]); + + // Reset segment to empty array + $_SESSION[$this->segmentName] = []; + + // Reset the flash data in session segment to an empty arrays + $this->objectsFlashData = []; + $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] = []; + } + } + + /** + * Gets the session ID. + */ + public function getId(): string + { + return $this->storage->getId(); + } + + /** + * Sets the session ID. + * + * This method will be of no effect if called outside the constructor + * of this class since the session will already be started after the + * constructor method finishes execution + */ + public function setId(string $sessionId): void + { + (!$this->storage->isStarted()) && $this->storage->setId($sessionId); + } + + /** + * Updates the current session id with a newly generated one. + */ + public function regenerateId(bool $deleteOldSession = false): bool + { + return $this->isStarted() && $this->storage->regenerateId($deleteOldSession); + } + + /** + * Gets the session name. + */ + public function getName(): string + { + return $this->storage->getName(); + } + + /** + * Sets the session name. + * + * This method will be of no effect if called outside the constructor + * of this class since the session will already be started after the + * constructor method finishes execution + */ + public function setName(string $name): void + { + (!$this->storage->isStarted()) && $this->storage->setName($name); + } + + /** + * Clears the session segment ($_SESSION[$this->segmentName]) and restores + * this object to its original state after creation. + * + * The session ($_SESSION) is not destroyed. + */ + public function destroy(): bool + { + $this->clear(); + + return true; + } + + /** + * Checks if the session is started. + */ + public function isStarted(): bool + { + return $this->storage->isStarted(); + } + + /** + * Sets an item with the specified $key in the flash storage for an instance + * of this class (i.e. $this->objectsFlashData). + * + * The item will only be retrievable from the instance of this class it was set in. + */ + public function setInObjectsFlash(string $key, mixed $value): void + { + $this->objectsFlashData[$key] = $value; + } + + /** + * Sets an item with the specified $key in the flash storage located in + * $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + * + * The item will only be retrievable by calling getFromObjectsFlash + * on the next instance of this class created with the same segment name. + */ + public function setInSessionFlash(string $key, mixed $value): void + { + $canSet = $this->isStarted() + && isset($_SESSION[$this->segmentName]) + && isset($_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST]); + + if ($canSet) { + // Set it in this session object's segmented session data + $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST][$key] = $value; + } + } + + /** + * Check if item with specified $key exists in the flash storage for + * an instance of this class (i.e. in $this->objectsFlashData). + */ + public function hasInObjectsFlash(string $key): bool + { + //////////////////////////////////////////////////////////////////////// + // Flash data previously stored in $_SESSION is always loaded into + // $this->objectsFlashData + // while + // $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + // contains flash data that will be loaded into $this->objectsFlashData + // for the next instance of this class with the same segmentName as this + // instance + //////////////////////////////////////////////////////////////////////// + return \array_key_exists($key, $this->objectsFlashData); + } + + /** + * Check if item with specified $key exists in + * $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST]. + */ + public function hasInSessionFlash(string $key): bool + { + //////////////////////////////////////////////////////////////////////// + // Flash data previously stored in $_SESSION is always loaded into + // $this->objectsFlashData + // while + // $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + // contains flash data that will be loaded into $this->objectsFlashData + // for the next instance of this class with the same segmentName as this + // instance + //////////////////////////////////////////////////////////////////////// + return + ( // Check in this session object's segmented session data + $this->isStarted() + && isset($_SESSION[$this->segmentName]) + && isset($_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST]) + && is_array($_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST]) + && isset($_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST][$key]) + ); + } + + /** + * Get an item with the specified $key from the flash storage for an instance + * of this class (i.e. $this->objectsFlashData) if it exists or return $default. + */ + public function getFromObjectsFlash(string $key, mixed $default = null): mixed + { + //////////////////////////////////////////////////////////////////////// + // Flash data previously stored in $_SESSION is always loaded into + // $this->objectsFlashData + // while + // $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + // contains flash data that will be loaded into $this->objectsFlashData + // for the next instance of this class with the same segmentName as this + // instance + //////////////////////////////////////////////////////////////////////// + return ($this->isStarted() && $this->hasInObjectsFlash($key)) ? $this->objectsFlashData[$key] : $default; + } + + /** + * Get an item with the specified $key from + * + * $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + * if it exists or return $default. + */ + public function getFromSessionFlash(string $key, mixed $default = null): mixed + { + return ($this->isStarted() && $this->hasInSessionFlash($key)) + ? $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST][$key] + : $default; + } + + /** + * Remove an item with the specified $key (if it exists) from: + * - the flash storage for an instance of this class (i.e. in $this->objectsFlashData), if $fromObjectsFlash === true + * - $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST], if $fromSessionFlash === true + */ + public function removeFromFlash( + string $key, + bool $fromObjectsFlash = true, + bool $fromSessionFlash = false + ): void { + //////////////////////////////////////////////////////////////////////// + // Flash data previously stored in $_SESSION is always loaded into + // $this->objectsFlashData + // while + // $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + // contains flash data that will be loaded into $this->objectsFlashData + // for the next instance of this class with the same segmentName as this + // instance + //////////////////////////////////////////////////////////////////////// + if ($fromObjectsFlash && $this->hasInObjectsFlash($key)) { + unset($this->objectsFlashData[$key]); + } + + if ($fromSessionFlash && $this->isStarted() && $this->hasInSessionFlash($key)) { + // Remove it from this session object's segmented session data + unset($_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST][$key]); + } + } + + /** + * Get all items in the flash storage for an instance of this class + * (i.e. in $this->objectsFlashData) + */ + public function getAllFromObjectsFlash(): array + { + return $this->objectsFlashData; + } + + /** + * Get all items in $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST] + */ + public function getAllFromSessionFlash(): array + { + $canGet = $this->isStarted() + && isset($_SESSION[$this->segmentName]) + && isset($_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST]) + && is_array($_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST]); + + if ($canGet) { + // Get it from this session object's segmented session data + return $_SESSION[$this->segmentName][static::FLASH_DATA_FOR_NEXT_REQUEST]; + } + + return []; + } + + public function getSegmentName(): string + { + return $this->segmentName; + } + + public function getStorage(): SessionInterface + { + return $this->storage; + } +} diff --git a/src/Session.php b/src/Session.php index 465a0f9..72ff932 100644 --- a/src/Session.php +++ b/src/Session.php @@ -21,8 +21,10 @@ /** * Session handler. */ -class Session +class Session implements SessionInterface { + use ExceptionThrowingMethodsTrait; + /** * Starts the session. * @@ -67,6 +69,10 @@ public function start(array $options = []): bool $this->throwExceptionIfSessionWasStarted(); $this->throwExceptionIfHasWrongOptions($options); + if (isset($options['name'])) { + session_name($options['name']); + } + return session_start($options); } @@ -169,7 +175,10 @@ public function clear(): void */ public function getId(): string { - return session_id(); + $sessionId = session_id(); + + // session_id returns false on failure + return ($sessionId !== false) ? $sessionId : ''; } /** @@ -203,7 +212,7 @@ public function getName(): string { $name = session_name(); - return $name ? $name : ''; + return ($name !== false) ? $name : ''; } /** @@ -237,64 +246,4 @@ public function isStarted(): bool { return session_status() === PHP_SESSION_ACTIVE; } - - /** - * Throw exception if the session have wrong options. - * - * @throws WrongSessionOptionException If setting options failed. - */ - private function throwExceptionIfHasWrongOptions(array $options): void - { - $validOptions = array_flip([ - 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', - 'cookie_lifetime', 'cookie_path', 'cookie_samesite', 'cookie_secure', - 'gc_divisor', 'gc_maxlifetime', 'gc_probability', 'lazy_write', - 'name', 'read_and_close', 'referer_check', 'save_handler', - 'save_path', 'serialize_handler', 'sid_bits_per_character', 'sid_length', - 'trans_sid_hosts', 'trans_sid_tags', 'use_cookies', 'use_only_cookies', - 'use_strict_mode', 'use_trans_sid', - ]); - - foreach (array_keys($options) as $key) { - if (!isset($validOptions[$key])) { - throw new WrongSessionOptionException($key); - } - } - } - - /** - * Throw exception if headers have already been sent. - * - * @throws HeadersSentException if headers already sent. - */ - private function throwExceptionIfHeadersWereSent(): void - { - $headersWereSent = (bool) ini_get('session.use_cookies') && headers_sent($file, $line); - - $headersWereSent && throw new HeadersSentException($file, $line); - } - - /** - * Throw exception if the session has already been started. - * - * @throws SessionStartedException if session already started. - */ - private function throwExceptionIfSessionWasStarted(): void - { - $methodName = debug_backtrace()[1]['function'] ?? 'unknown'; - - $this->isStarted() && throw new SessionStartedException($methodName); - } - - /** - * Throw exception if the session was not started. - * - * @throws SessionNotStartedException if session was not started. - */ - private function throwExceptionIfSessionWasNotStarted(): void - { - $methodName = debug_backtrace()[1]['function'] ?? 'unknown'; - - !$this->isStarted() && throw new SessionNotStartedException($methodName); - } } diff --git a/src/SessionInterface.php b/src/SessionInterface.php new file mode 100644 index 0000000..d320877 --- /dev/null +++ b/src/SessionInterface.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Josantonius\Session; + +/** + * Description of SessionInterface + * + * @author https://github.com/rotexdegba + */ +interface SessionInterface +{ + /** + * Starts the session. + * + * List of available $options with their default values: + * + * * cache_expire: "180" + * * cache_limiter: "nocache" + * * cookie_domain: "" + * * cookie_httponly: "0" + * * cookie_lifetime: "0" + * * cookie_path: "/" + * * cookie_samesite: "" + * * cookie_secure: "0" + * * gc_divisor: "100" + * * gc_maxlifetime: "1440" + * * gc_probability: "1" + * * lazy_write: "1" + * * name: "PHPSESSID" + * * read_and_close: "0" + * * referer_check: "" + * * save_handler: "files" + * * save_path: "" + * * serialize_handler: "php" + * * sid_bits_per_character: "4" + * * sid_length: "32" + * * trans_sid_hosts: $_SERVER['HTTP_HOST'] + * * trans_sid_tags: "a=href,area=href,frame=src,form=" + * * use_cookies: "1" + * * use_only_cookies: "1" + * * use_strict_mode: "0" + * * use_trans_sid: "0" + * + * @see https://php.net/session.configuration + */ + public function start(array $options = []): bool; + + /** + * Gets all attributes. + */ + public function all(): array; + + /** + * Checks if an attribute exists in the session. + */ + public function has(string $name): bool; + + /** + * Gets an attribute by name. + * + * Optionally defines a default value when the attribute does not exist. + */ + public function get(string $name, mixed $default = null): mixed; + + /** + * Sets an attribute by name. + * + * @throws SessionNotStartedException if session was not started. + */ + public function set(string $name, mixed $value): void; + + /** + * Sets several attributes at once. + * + * If attributes exist they are replaced, if they do not exist they are created. + * + * @throws SessionNotStartedException if session was not started. + */ + public function replace(array $data): void; + + /** + * Deletes an attribute by name and returns its value. + * + * Optionally defines a default value when the attribute does not exist. + * + * @throws SessionNotStartedException if session was not started. + */ + public function pull(string $name, mixed $default = null): mixed; + + /** + * Deletes an attribute by name. + * + * @throws SessionNotStartedException if session was not started. + */ + public function remove(string $name): void; + + /** + * Free all session variables or all session segment variables. + * + * @throws SessionNotStartedException if session was not started. + */ + public function clear(): void; + + /** + * Gets the session ID. + */ + public function getId(): string; + + /** + * Sets the session ID. + * + * @throws SessionStartedException if session already started. + */ + public function setId(string $sessionId): void; + + /** + * Updates the current session id with a newly generated one. + * + * @throws SessionNotStartedException if session was not started. + */ + public function regenerateId(bool $deleteOldSession = false): bool; + + /** + * Gets the session name. + */ + public function getName(): string; + + /** + * Sets the session name. + * + * @throws SessionStartedException if session already started. + */ + public function setName(string $name): void; + + /** + * Destroys the session or session segment. + * + * @throws SessionNotStartedException if session was not started. + */ + public function destroy(): bool; + + /** + * Checks if the session is started. + */ + public function isStarted(): bool; +} diff --git a/tests/AllMethodSegmentedSessionTest.php b/tests/AllMethodSegmentedSessionTest.php new file mode 100644 index 0000000..1634d15 --- /dev/null +++ b/tests/AllMethodSegmentedSessionTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class AllMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_all_attributes_in_segment(): void + { + $session = new SegSession('da-segment'); + + $this->assertIsArray($_SESSION); + + $session->set('foo', 'bar'); + + $this->assertEquals(['foo' => 'bar', SegSession::FLASH_DATA_FOR_NEXT_REQUEST => []], $session->all()); + + // foo should only be inside $_SESSION[$session->getSegmentName()] + $this->assertArrayNotHasKey('foo', $_SESSION); + + // SegSession::FLASH_DATA_FOR_NEXT_REQUEST should only be inside $_SESSION[$session->getSegmentName()] + $this->assertArrayNotHasKey(SegSession::FLASH_DATA_FOR_NEXT_REQUEST, $_SESSION); + + $this->assertEquals( + [ + $session->getSegmentName() => + [ + 'foo' => 'bar', SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [] + ] + ], + $_SESSION + ); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_all_attributes_in_segment_when_session_started_outside_library(): void + { + session_start(); + + $this->assertIsArray($_SESSION); + + $session = new SegSession('da-segment'); + + $_SESSION['top-key'] = 'top-value'; + + $session->set('foo', 'bar'); + + $this->assertEquals(['foo' => 'bar', SegSession::FLASH_DATA_FOR_NEXT_REQUEST => []], $session->all()); + + // foo should only be inside $_SESSION[$session->getSegmentName()] + $this->assertArrayNotHasKey('foo', $_SESSION); + + // SegSession::FLASH_DATA_FOR_NEXT_REQUEST should only be inside $_SESSION[$session->getSegmentName()] + $this->assertArrayNotHasKey(SegSession::FLASH_DATA_FOR_NEXT_REQUEST, $_SESSION); + + // Make sure both data set in $_SESSION from outside & within this + // library are where they should be + $this->assertEquals( + [ + 'top-key' => 'top-value', + $session->getSegmentName() => + [ + 'foo' => 'bar', SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [] + ] + ], + $_SESSION + ); + } +} diff --git a/tests/ClearMethodSegmentedSessionTest.php b/tests/ClearMethodSegmentedSessionTest.php new file mode 100644 index 0000000..9b8f7db --- /dev/null +++ b/tests/ClearMethodSegmentedSessionTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +/** + * @SuppressWarnings(PHPMD.LongVariable) + */ +class ClearMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_clear_only_session_segment(): void + { + $session = new SegSession('da-segment'); + + $_SESSION['bar'] = 'foo'; + + $session->set('foo', 'bar'); + + $segmentBeforeClearing = [ + 'foo' => 'bar', + SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [], + ]; + + // Verify segment data before clearing + $this->assertEquals($segmentBeforeClearing, $_SESSION[$session->getSegmentName()]); + + $session->clear(); + + // Cleared segment should only contain an empty array for flash data + $this->assertEquals( + [SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [],], + $_SESSION[$session->getSegmentName()] + ); + + // Items set directly in $_SESSION should still be present & not cleared + $this->assertArrayHasKey('bar', $_SESSION); + $this->assertEquals('foo', $_SESSION['bar']); + } + + /** + * @runInSeparateProcess + */ + public function test_should_clear_only_session_segment_when_session_started_outside_library(): void + { + session_start(); + + $_SESSION['bar'] = 'foo'; + + $session = new SegSession('da-segment'); + + $_SESSION['bar1'] = 'foo1'; + + $session->set('foo', 'bar'); + + $segmentBeforeClearing = [ + 'foo' => 'bar', + SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [], + ]; + + // Verify segment data before clearing + $this->assertEquals($segmentBeforeClearing, $_SESSION[$session->getSegmentName()]); + + $session->clear(); + + // Cleared segment should only contain an empty array for flash data + $this->assertEquals( + [SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [],], + $_SESSION[$session->getSegmentName()] + ); + + // Items set directly in $_SESSION should still be present & not cleared + $this->assertArrayHasKey('bar', $_SESSION); + $this->assertArrayHasKey('bar1', $_SESSION); + $this->assertEquals('foo', $_SESSION['bar']); + $this->assertEquals('foo1', $_SESSION['bar1']); + } +} diff --git a/tests/ConstructorMethodSegmentedSessionTest.php b/tests/ConstructorMethodSegmentedSessionTest.php new file mode 100644 index 0000000..13cbafa --- /dev/null +++ b/tests/ConstructorMethodSegmentedSessionTest.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\Session; +use Josantonius\Session\FlashableSessionSegment as SegSession; +use Josantonius\Session\Exceptions\EmptySegmentNameException; +use Josantonius\Session\Exceptions\HeadersSentException; +use Josantonius\Session\Exceptions\SessionStartedException; +use Josantonius\Session\Exceptions\SessionNotStartedException; +use Josantonius\Session\Exceptions\WrongSessionOptionException; + +class ConstructorMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_verify_segment_exists_in_session_and_session_options_were_properly_set(): void + { + $options = [ + 'name' => 'boooooooo', + 'cookie_lifetime' => 8000, + ]; + $segmentName = 'da-segment'; + $session = new SegSession($segmentName, null, $options); + + // Session was started outside the library. + $this->assertTrue($session->isStarted()); + + // Check that the segment name was properly set in the session object + $this->assertEquals($segmentName, $session->getSegmentName()); + + // Check that session options were not set in the constructor + // since in this scenario the session was started outside the library. + $this->assertEquals($options['name'], $session->getName()); + $this->assertEquals($options['cookie_lifetime'], ini_get('session.cookie_lifetime')); + + // check that flash was initialized + $this->assertArrayHasKey($segmentName, $_SESSION); + $this->assertEquals([SegSession::FLASH_DATA_FOR_NEXT_REQUEST => []], $_SESSION[$segmentName]); + $this->assertArrayHasKey(SegSession::FLASH_DATA_FOR_NEXT_REQUEST, $_SESSION[$segmentName]); + $this->assertEquals([], $_SESSION[$segmentName][SegSession::FLASH_DATA_FOR_NEXT_REQUEST]); + } + + /** + * @runInSeparateProcess + */ + public function test_should_verify_segment_exists_in_session_and_session_options_were_properly_set_when_session_started_outside_library(): void + { + session_start(); + $options = [ + 'name' => 'boooooooo', + 'cookie_lifetime' => 8000, + ]; + $segmentName = 'da-segment'; + $session = new SegSession($segmentName, null, $options); + + // Session was started outside the library. + $this->assertTrue($session->isStarted()); + + // Check that the segment name was properly set in the session object + $this->assertEquals($segmentName, $session->getSegmentName()); + + // Check that session options were not set in the constructor + // since in this scenario the session was started outside the library. + $this->assertEquals('PHPSESSID', $session->getName()); + $this->assertNotEquals($options['name'], $session->getName()); + $this->assertNotEquals($options['cookie_lifetime'], ini_get('session.cookie_lifetime')); + + // check that flash was initialized + $this->assertArrayHasKey($segmentName, $_SESSION); + $this->assertEquals([SegSession::FLASH_DATA_FOR_NEXT_REQUEST => []], $_SESSION[$segmentName]); + $this->assertArrayHasKey(SegSession::FLASH_DATA_FOR_NEXT_REQUEST, $_SESSION[$segmentName]); + $this->assertEquals([], $_SESSION[$segmentName][SegSession::FLASH_DATA_FOR_NEXT_REQUEST]); + } + + /** + * @runInSeparateProcess + */ + public function test_should_verify_injected_session_object_was_properly_set(): void + { + $segmentName = 'da-segment'; + $storage = new Session(); + $session = new SegSession($segmentName, $storage); + + // Injected $storage should be returned by $session->getStorage() + $this->assertSame($storage, $session->getStorage()); + } + + /** + * @runInSeparateProcess + */ + public function test_should_verify_injected_session_object_was_properly_set_when_session_started_outside_library(): void + { + session_start(); + $segmentName = 'da-segment'; + $storage = new Session(); + $session = new SegSession($segmentName, $storage); + + // Injected $storage should be returned by $session->getStorage() + $this->assertSame($storage, $session->getStorage()); + } + + /** + * @runInSeparateProcess + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function test_should_fail_with_empty_segment_name(): void + { + $this->expectException(EmptySegmentNameException::class); + + $session = new SegSession(''); + } + + /** + * @runInSeparateProcess + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function test_should_fail_with_wrong_options(): void + { + $this->expectException(WrongSessionOptionException::class); + + $session = new SegSession('a', null, ['foo' => 'bar']); + } + + /** + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function test_should_fail_when_headers_sent(): void + { + // Because we are not running this in a separate process it will trigger the exception + $this->expectException(HeadersSentException::class); + + $session = new SegSession('a'); + } + + /** + * @runInSeparateProcess + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function test_should_fail_when_session_not_startable(): void + { + $this->expectException(SessionNotStartedException::class); + + $unstartableSsStorage = new class ('vlah') extends SegSession { + public function start(array $options = []): bool + { + return false; + } + + public function isStarted(): bool + { + return false; + } + }; + + $session = new SegSession('a', $unstartableSsStorage); + } +} diff --git a/tests/DestroyMethodSegmentedSessionTest.php b/tests/DestroyMethodSegmentedSessionTest.php new file mode 100644 index 0000000..f4b2ed2 --- /dev/null +++ b/tests/DestroyMethodSegmentedSessionTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class DestroyMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_clear_only_session_segment(): void + { + $session = new SegSession('da-segment'); + + $_SESSION['bar'] = 'foo'; + + $session->set('foo', 'bar'); + + $segmBeforeClearing = [ + 'foo' => 'bar', + SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [], + ]; + + // Verify segment data before clearing + $this->assertEquals($segmBeforeClearing, $_SESSION[$session->getSegmentName()]); + + // Verify that it always returns true + $this->assertTrue($session->destroy()); + + // Cleared segment should only contain an empty array for flash data + $this->assertEquals( + [SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [],], + $_SESSION[$session->getSegmentName()] + ); + + // Items set directly in $_SESSION should still be present & not cleared + $this->assertArrayHasKey('bar', $_SESSION); + $this->assertEquals('foo', $_SESSION['bar']); + } + + /** + * @runInSeparateProcess + */ + public function test_should_clear_only_session_segment_when_session_started_outside_library(): void + { + session_start(); + + $_SESSION['bar'] = 'foo'; + + $session = new SegSession('da-segment'); + + $_SESSION['bar1'] = 'foo1'; + + $session->set('foo', 'bar'); + + $segBeforeClearing = [ + 'foo' => 'bar', + SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [], + ]; + + // Verify segment data before clearing + $this->assertEquals($segBeforeClearing, $_SESSION[$session->getSegmentName()]); + + // Verify that it always returns true + $this->assertTrue($session->destroy()); + + // Cleared segment should only contain an empty array for flash data + $this->assertEquals( + [SegSession::FLASH_DATA_FOR_NEXT_REQUEST => [],], + $_SESSION[$session->getSegmentName()] + ); + + // Items set directly in $_SESSION should still be present & not cleared + $this->assertArrayHasKey('bar', $_SESSION); + $this->assertArrayHasKey('bar1', $_SESSION); + $this->assertEquals('foo', $_SESSION['bar']); + $this->assertEquals('foo1', $_SESSION['bar1']); + } +} diff --git a/tests/GetAllFromObjectsFlashMethodTest.php b/tests/GetAllFromObjectsFlashMethodTest.php new file mode 100644 index 0000000..955a25c --- /dev/null +++ b/tests/GetAllFromObjectsFlashMethodTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class GetAllFromObjectsFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_empty_array_on_newly_created_segment(): void + { + $session = new SegSession('da-segment'); + + $this->assertEquals([], $session->getAllFromObjectsFlash()); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_right_values_before_and_after_setting_items_in_current_flash(): void + { + $session = new SegSession('da-segment'); + + $session->setInObjectsFlash('foo', 'bar'); + $session->setInObjectsFlash('bar', 'foo'); + + $this->assertEquals( + ['foo' => 'bar', 'bar' => 'foo'], + $session->getAllFromObjectsFlash() + ); + + // these values should be available in the current flash of the next instance below + $session->setInSessionFlash('foo2', 'bar2'); + $session->setInSessionFlash('foo3', 'bar3'); + + $session2 = new SegSession('da-segment'); + + // previous next flash items should now be in the current flash for this instance + $this->assertEquals( + ['foo2' => 'bar2', 'foo3' => 'bar3'], + $session2->getAllFromObjectsFlash() + ); + } +} diff --git a/tests/GetAllFromSessionFlashMethodTest.php b/tests/GetAllFromSessionFlashMethodTest.php new file mode 100644 index 0000000..ca94e06 --- /dev/null +++ b/tests/GetAllFromSessionFlashMethodTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class GetAllFromSessionFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_empty_array_on_newly_created_segment(): void + { + $session = new SegSession('da-segment'); + + $this->assertEquals([], $session->getAllFromSessionFlash()); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_right_values_before_and_after_setting_items_in_next_flash(): void + { + $session = new SegSession('da-segment'); + + $session->setInSessionFlash('foo', 'bar'); + $session->setInSessionFlash('bar', 'foo'); + + $this->assertEquals( + ['foo' => 'bar', 'bar' => 'foo'], + $session->getAllFromSessionFlash() + ); + + // these values should not be available in the next flash of the next instance below + $session->setInSessionFlash('foo2', 'bar2'); + $session->setInSessionFlash('foo3', 'bar3'); + + $session2 = new SegSession('da-segment'); + + // previous next flash items should not be in the next flash for this instance + $this->assertEquals([], $session2->getAllFromSessionFlash()); + + // test the return [] scenario in getAllFromSessionFlash + $session2->getStorage()->clear(); // we clear the actual session, not just the segment + $session2->getStorage()->destroy(); // we destroy the actual session, not just the segment + + $this->assertEquals([], $session2->getAllFromSessionFlash()); + } +} diff --git a/tests/GetFromObjectsFlashMethodTest.php b/tests/GetFromObjectsFlashMethodTest.php new file mode 100644 index 0000000..4c9cf09 --- /dev/null +++ b/tests/GetFromObjectsFlashMethodTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class GetFromObjectsFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_null_default_for_non_existent_attribute(): void + { + $session = new SegSession('da-segment'); + + $this->assertNull($session->getFromObjectsFlash('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_custom_default_for_non_existent_attribute(): void + { + $session = new SegSession('da-segment'); + + $this->assertEquals('bar', $session->getFromObjectsFlash('foo', 'bar')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_right_values_after_setting_items_in_current_flash(): void + { + $session = new SegSession('da-segment'); + + $session->setInObjectsFlash('foo', 'bar'); + $session->setInObjectsFlash('bar', 'foo'); + + $this->assertEquals('bar', $session->getFromObjectsFlash('foo')); + $this->assertEquals('foo', $session->getFromObjectsFlash('bar')); + + // these values should be available in the current flash of the next instance below + $session->setInSessionFlash('foo2', 'bar2'); + $session->setInSessionFlash('foo3', 'bar3'); + + $session2 = new SegSession('da-segment'); + + // previous next flash items should now be in the current flash for this instance + $this->assertEquals('bar2', $session2->getFromObjectsFlash('foo2')); + $this->assertEquals('bar3', $session2->getFromObjectsFlash('foo3')); + } +} diff --git a/tests/GetFromSessionFlashMethodTest.php b/tests/GetFromSessionFlashMethodTest.php new file mode 100644 index 0000000..03ca176 --- /dev/null +++ b/tests/GetFromSessionFlashMethodTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class GetFromSessionFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_null_default_for_non_existent_attribute(): void + { + $session = new SegSession('da-segment'); + + $this->assertNull($session->getFromSessionFlash('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_custom_default_for_non_existent_attribute(): void + { + $session = new SegSession('da-segment'); + + $this->assertEquals('bar', $session->getFromSessionFlash('foo', 'bar')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_right_values_after_setting_items_in_current_flash(): void + { + $session = new SegSession('da-segment'); + + $session->setInSessionFlash('foo', 'bar'); + $session->setInSessionFlash('bar', 'foo'); + + $this->assertEquals('bar', $session->getFromSessionFlash('foo')); + $this->assertEquals('foo', $session->getFromSessionFlash('bar')); + + // these values should NOT be available in the next flash of the next instance below + $session->setInSessionFlash('foo2', 'bar2'); + $session->setInSessionFlash('foo3', 'bar3'); + + $session2 = new SegSession('da-segment'); + + // previous next flash items should NOT be in the next flash for this instance + $this->assertNotEquals('bar2', $session2->getFromSessionFlash('foo2')); + $this->assertNotEquals('bar3', $session2->getFromSessionFlash('foo3')); + } +} diff --git a/tests/GetIdMethodSegmentedSessionTest.php b/tests/GetIdMethodSegmentedSessionTest.php new file mode 100644 index 0000000..8b8d6ed --- /dev/null +++ b/tests/GetIdMethodSegmentedSessionTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class GetIdMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_session_id(): void + { + $session = new SegSession('da-segment'); + + // Should have the same id as it's internal storage object + $this->assertEquals($session->getId(), $session->getStorage()->getId()); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_session_id_when_session_started_outside_library(): void + { + session_start(); + + $session = new SegSession('da-segment'); + + // Should have the same id as it's internal storage object + $this->assertEquals($session->getId(), $session->getStorage()->getId()); + } +} diff --git a/tests/GetMethodSegmentedSessionTest.php b/tests/GetMethodSegmentedSessionTest.php new file mode 100644 index 0000000..f215fb3 --- /dev/null +++ b/tests/GetMethodSegmentedSessionTest.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class GetMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_attribute_if_exists(): void + { + $session = new SegSession('da-segment'); + + $session->set('foo', 'bar'); + + $this->assertEquals('bar', $session->get('foo')); + + // Verify that set item was inside the segment in $_SESSION and not directly in $_SESSION + $this->assertArrayHasKey('foo', $_SESSION[$session->getSegmentName()]); + $this->assertEquals('bar', $_SESSION[$session->getSegmentName()]['foo']); + $this->assertArrayNotHasKey('foo', $_SESSION); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_attribute_if_exists_when_session_started_outside_library(): void + { + session_start(); + + $session = new SegSession('da-segment'); + + $session->set('foo', 'bar'); + + $this->assertEquals('bar', $session->get('foo')); + + // Verify that set item was inside the segment in $_SESSION and not directly in $_SESSION + $this->assertArrayHasKey('foo', $_SESSION[$session->getSegmentName()]); + $this->assertEquals('bar', $_SESSION[$session->getSegmentName()]['foo']); + $this->assertArrayNotHasKey('foo', $_SESSION); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_default_value_if_not_exists(): void + { + $session = new SegSession('da-segment'); + + $this->assertNull($session->get('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_default_value_if_not_exists_when_session_started_outside_library(): void + { + session_start(); + + $session = new SegSession('da-segment'); + + $this->assertNull($session->get('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_custom_default_value_if_not_exists(): void + { + $session = new SegSession('da-segment'); + + $this->assertEquals('bar', $session->get('foo', 'bar')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_custom_default_value_if_not_exists_when_session_started_outside_library(): void + { + session_start(); + + $session = new SegSession('da-segment'); + + $this->assertEquals('bar', $session->get('foo', 'bar')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_not_get_attribute_defined_outside_library(): void + { + session_start(); + + $_SESSION['foo'] = 'bar'; + + $session = new SegSession('da-segment'); + + $this->assertNull($session->get('foo')); + } +} diff --git a/tests/GetSegmentNameMethodTest.php b/tests/GetSegmentNameMethodTest.php new file mode 100644 index 0000000..218550b --- /dev/null +++ b/tests/GetSegmentNameMethodTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class GetSegmentNameMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_attribute_if_exists_when_session_started_outside_library(): void + { + $session = new SegSession('da-segment'); + + $this->assertEquals('da-segment', $session->getSegmentName()); + } +} diff --git a/tests/GetStorageMethodTest.php b/tests/GetStorageMethodTest.php new file mode 100644 index 0000000..9557b48 --- /dev/null +++ b/tests/GetStorageMethodTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\Session; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class GetStorageMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_get_attribute_if_exists_when_session_started_outside_library(): void + { + $storage = new Session(); + $session = new SegSession('da-segment', $storage); + + $this->assertSame($storage, $session->getStorage()); + } +} diff --git a/tests/HasInObjectsFlashMethodTest.php b/tests/HasInObjectsFlashMethodTest.php new file mode 100644 index 0000000..4ec4aec --- /dev/null +++ b/tests/HasInObjectsFlashMethodTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class HasInObjectsFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_false_on_newly_created_segment(): void + { + $session = new SegSession('da-segment'); + + $this->assertFalse($session->hasInObjectsFlash('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_right_values_before_and_after_setting_items_in_current_flash(): void + { + $session = new SegSession('da-segment'); + + $this->assertFalse($session->hasInObjectsFlash('foo')); + + $session->setInObjectsFlash('foo', 'bar'); + + $this->assertTrue($session->hasInObjectsFlash('foo')); + + // these values should be available in the current flash of the next instance below + $session->setInSessionFlash('foo2', 'bar2'); + $session->setInSessionFlash('foo3', 'bar3'); + + $session2 = new SegSession('da-segment'); + + // previous next flash items should now be in the current flash for this instance + $this->assertTrue($session2->hasInObjectsFlash('foo2')); + $this->assertTrue($session2->hasInObjectsFlash('foo3')); + } +} diff --git a/tests/HasInSessionFlashMethodTest.php b/tests/HasInSessionFlashMethodTest.php new file mode 100644 index 0000000..d65cd5f --- /dev/null +++ b/tests/HasInSessionFlashMethodTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class HasInSessionFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_false_on_newly_created_segment(): void + { + $session = new SegSession('da-segment'); + + $this->assertFalse($session->hasInSessionFlash('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_right_values_before_and_after_setting_items_in_next_flash(): void + { + $session = new SegSession('da-segment'); + + $this->assertFalse($session->hasInSessionFlash('foo')); + + $session->setInSessionFlash('foo', 'bar'); + + $this->assertTrue($session->hasInSessionFlash('foo')); + + // these values should not be available in the next flash of the next instance below + $session->setInSessionFlash('foo2', 'bar2'); + $session->setInSessionFlash('foo3', 'bar3'); + + $session2 = new SegSession('da-segment'); + + // previous next flash items should not be in the next flash for this instance + $this->assertFalse($session2->hasInSessionFlash('foo2')); + $this->assertFalse($session2->hasInSessionFlash('foo3')); + } +} diff --git a/tests/HasMethodSegmentedSessionTest.php b/tests/HasMethodSegmentedSessionTest.php new file mode 100644 index 0000000..6bade0c --- /dev/null +++ b/tests/HasMethodSegmentedSessionTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class HasMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_check_if_attribute_exists(): void + { + $session = new SegSession('da-segment'); + $session->set('foo', 'bar'); + + $this->assertTrue($session->has('foo')); + $this->assertFalse($session->has('bar')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_check_attribute_defined_outside_library(): void + { + session_start(); + $_SESSION['foo'] = 'bar'; + + $session = new SegSession('da-segment'); + $this->assertFalse($session->has('foo')); + } +} diff --git a/tests/IsStartedMethodSegmentedSessionTest.php b/tests/IsStartedMethodSegmentedSessionTest.php new file mode 100644 index 0000000..0cde5ca --- /dev/null +++ b/tests/IsStartedMethodSegmentedSessionTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class IsStartedMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_check_if_session_is_active(): void + { + $session = new SegSession('da-segment'); + + $this->assertTrue($session->isStarted()); // always auto-started + + $session->start(); + + $this->assertTrue($session->isStarted()); // should still be started + } + + /** + * @runInSeparateProcess + */ + public function test_should_check_if_session_is_active_when_session_started_outside_library(): void + { + session_start(); + $session = new SegSession('da-segment'); + + $this->assertTrue($session->isStarted()); // always auto-started + + $session->start(); + + $this->assertTrue($session->isStarted()); // should still be started + } +} diff --git a/tests/PullMethodSegmentedSessionTest.php b/tests/PullMethodSegmentedSessionTest.php new file mode 100644 index 0000000..1e0df02 --- /dev/null +++ b/tests/PullMethodSegmentedSessionTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class PullMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_pull_attribute_and_return_the_value_if_exists(): void + { + $session = new SegSession('da-segment'); + + $_SESSION['foo'] = 'bar'; + + // foo has not yet been set in the segment + // default value of null should be returned + $this->assertNull($session->pull('foo')); + + // pull above should not affect $_SESSION['foo'] + $this->assertArrayHasKey('foo', $_SESSION); + + $session->set('foo', 'bar2'); // this will be stored in the segment + + // we are pulling the foo in the segment & not $_SESSION['foo'] + $this->assertEquals('bar2', $session->pull('foo')); + + // foo set directly in $_SESSION remains unchanged + $this->assertArrayHasKey('foo', $_SESSION); + $this->assertEquals('bar', $_SESSION['foo']); + + // foo in segment has been removed + $this->assertIsArray($_SESSION[$session->getSegmentName()]); + $this->assertArrayNotHasKey('foo', $_SESSION[$session->getSegmentName()]); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_default_value_if_attribute_not_exists(): void + { + $session = new SegSession('da-segment'); + + $this->assertNull($session->pull('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_return_custom_default_value_if_attribute_not_exists(): void + { + $session = new SegSession('da-segment'); + + $this->assertEquals('bar', $session->pull('foo', 'bar')); + } +} diff --git a/tests/RegenerateIdMethodSegmentedSessionTest.php b/tests/RegenerateIdMethodSegmentedSessionTest.php new file mode 100644 index 0000000..27d1f39 --- /dev/null +++ b/tests/RegenerateIdMethodSegmentedSessionTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class RegenerateIdMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_regenerate_session_id_without_deleting_old_session(): void + { + $session = new SegSession('da-segment'); + $sessionId = session_id(); + + $this->assertTrue($session->regenerateId()); + + $this->assertNotEquals($sessionId, session_id()); + + $sessionId2 = session_id(); + + $this->assertTrue($session->regenerateId()); + + $this->assertNotEquals($sessionId2, session_id()); + } +} diff --git a/tests/RemoveFromFlashMethodTest.php b/tests/RemoveFromFlashMethodTest.php new file mode 100644 index 0000000..51190a8 --- /dev/null +++ b/tests/RemoveFromFlashMethodTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\Session; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class RemoveFromFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_remove_set_attributes(): void + { + $storage = new Session(); + $session = new SegSession('da-segment', $storage); + + $session->setInObjectsFlash('foo-current-1', 'val-in-current-1'); + $session->setInObjectsFlash('foo-current-2', 'val-in-current-2'); + + $session->setInSessionFlash('foo-next-1', 'val-in-next-1'); + $session->setInSessionFlash('foo-next-2', 'val-in-next-2'); + + $this->assertTrue($session->hasInObjectsFlash('foo-current-1')); + $this->assertTrue($session->hasInObjectsFlash('foo-current-2')); + $this->assertTrue($session->hasInSessionFlash('foo-next-1')); + $this->assertTrue($session->hasInSessionFlash('foo-next-2')); + + $session->removeFromFlash('foo-current-1', true); + + $this->assertFalse($session->hasInObjectsFlash('foo-current-1')); + $this->assertTrue($session->hasInObjectsFlash('foo-current-2')); + $this->assertTrue($session->hasInSessionFlash('foo-next-1')); + $this->assertTrue($session->hasInSessionFlash('foo-next-2')); + + $session->removeFromFlash('foo-current-2', true); + + $this->assertFalse($session->hasInObjectsFlash('foo-current-1')); + $this->assertFalse($session->hasInObjectsFlash('foo-current-2')); + $this->assertTrue($session->hasInSessionFlash('foo-next-1')); + $this->assertTrue($session->hasInSessionFlash('foo-next-2')); + + $session->removeFromFlash('foo-next-1', true, true); + + $this->assertFalse($session->hasInObjectsFlash('foo-current-1')); + $this->assertFalse($session->hasInObjectsFlash('foo-current-2')); + $this->assertFalse($session->hasInSessionFlash('foo-next-1')); + $this->assertTrue($session->hasInSessionFlash('foo-next-2')); + + $session->removeFromFlash('foo-next-2', true, true); + + $this->assertFalse($session->hasInObjectsFlash('foo-current-1')); + $this->assertFalse($session->hasInObjectsFlash('foo-current-2')); + $this->assertFalse($session->hasInSessionFlash('foo-next-1')); + $this->assertFalse($session->hasInSessionFlash('foo-next-2')); + } +} diff --git a/tests/RemoveMethodSegmentedSessionTest.php b/tests/RemoveMethodSegmentedSessionTest.php new file mode 100644 index 0000000..63ec7af --- /dev/null +++ b/tests/RemoveMethodSegmentedSessionTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class RemoveMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_remove_attribute_even_if_not_exist(): void + { + $session = new SegSession('da-segment'); + + $originalSegContent = $session->all(); + + $session->remove('foo'); + + $this->assertEquals($originalSegContent, $session->all()); + } + + /** + * @runInSeparateProcess + */ + public function test_should_remove_attribute_if_exist(): void + { + $session = new SegSession('da-segment'); + + $_SESSION['foo'] = 'bar'; + + $session->remove('foo'); + + // Remove above should not affect $_SESSION['foo'] + $this->assertArrayHasKey('foo', $_SESSION); + $this->assertEquals('bar', $_SESSION['foo']); + + // Set foo in the segment + $session->set('foo', 'bar2'); + + // foo should be in segment + $this->assertTrue($session->has('foo')); + $this->assertEquals('bar2', $session->get('foo')); + + $session->remove('foo'); // foo in the segment will be removed + $this->assertFalse($session->has('foo')); + + // $_SESSION['foo'] should still remain unaffected + $this->assertArrayHasKey('foo', $_SESSION); + $this->assertEquals('bar', $_SESSION['foo']); + } +} diff --git a/tests/ReplaceMethodSegmentedSessionTest.php b/tests/ReplaceMethodSegmentedSessionTest.php new file mode 100644 index 0000000..947a027 --- /dev/null +++ b/tests/ReplaceMethodSegmentedSessionTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class ReplaceMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_add_attributes_if_not_exist(): void + { + $session = new SegSession('da-segment'); + + $_SESSION['bar'] = 'foo'; + + // foo has not yet been set in the segment + $this->assertFalse($session->has('foo')); + + // bar should not be in the segment + $this->assertFalse($session->has('bar')); + + $session->replace(['foo' => 'bar']); + + // foo was just set via the call to replace above + $this->assertTrue($session->has('foo')); + + // $_SESSION['bar'] should be untouched + $this->assertEquals('foo', $_SESSION['bar']); + + // assert that replaced value is present + $this->assertEquals('bar', $session->get('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_replace_attributes_if_exist(): void + { + $session = new SegSession('da-segment'); + + $session->set('foo', 'bar'); + $session->set('bar', 'foo'); + + $session->replace(['foo' => 'val']); + + $this->assertEquals('val', $session->get('foo')); + $this->assertEquals('foo', $session->get('bar')); + + $this->assertArrayNotHasKey('foo', $_SESSION); + $this->assertArrayNotHasKey('bar', $_SESSION); + } +} diff --git a/tests/SetIdMethodSegmentedSessionTest.php b/tests/SetIdMethodSegmentedSessionTest.php new file mode 100644 index 0000000..50ceac0 --- /dev/null +++ b/tests/SetIdMethodSegmentedSessionTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class SetIdMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_not_set_session_id_since_session_is_auto_started(): void + { + $session = new SegSession('da-segment'); + $originalId = $session->getId(); + + $session->setId('foo'); + + $this->assertEquals($originalId, session_id()); + $this->assertNotEquals('foo', session_id()); + } +} diff --git a/tests/SetInObjectsFlashMethodTest.php b/tests/SetInObjectsFlashMethodTest.php new file mode 100644 index 0000000..eabc886 --- /dev/null +++ b/tests/SetInObjectsFlashMethodTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\Session; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class SetInObjectsFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_correctly_set_attributes(): void + { + $storage = new Session(); + $session = new SegSession('da-segment', $storage); + + $session->setInObjectsFlash('foo-current-1', 'val-in-current-1'); + $session->setInObjectsFlash('foo-current-2', 'val-in-current-2'); + + $this->assertTrue($session->hasInObjectsFlash('foo-current-1')); + $this->assertTrue($session->hasInObjectsFlash('foo-current-2')); + + $this->assertEquals('val-in-current-1', $session->getFromObjectsFlash('foo-current-1')); + $this->assertEquals('val-in-current-2', $session->getFromObjectsFlash('foo-current-2')); + } +} diff --git a/tests/SetInSessionFlashMethodTest.php b/tests/SetInSessionFlashMethodTest.php new file mode 100644 index 0000000..108ad11 --- /dev/null +++ b/tests/SetInSessionFlashMethodTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\Session; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class SetInSessionFlashMethodTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_correctly_set_attributes(): void + { + $storage = new Session(); + $session = new SegSession('da-segment', $storage); + + $session->setInSessionFlash('foo-next-1', 'val-in-next-1'); + $session->setInSessionFlash('foo-next-2', 'val-in-next-2'); + + $this->assertTrue($session->hasInSessionFlash('foo-next-1')); + $this->assertTrue($session->hasInSessionFlash('foo-next-2')); + + $this->assertEquals('val-in-next-1', $session->getFromSessionFlash('foo-next-1')); + $this->assertEquals('val-in-next-2', $session->getFromSessionFlash('foo-next-2')); + } +} diff --git a/tests/SetMethodSegmentedSessionTest.php b/tests/SetMethodSegmentedSessionTest.php new file mode 100644 index 0000000..675f8ff --- /dev/null +++ b/tests/SetMethodSegmentedSessionTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class SetMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_set_attribute_if_session_was_started(): void + { + $session = new SegSession('da-segment'); + + $session->set('foo', 'bar'); + + $this->assertEquals('bar', $session->get('foo')); + } + + /** + * @runInSeparateProcess + */ + public function test_should_set_attribute_if_native_session_was_started(): void + { + session_start(); + + $session = new SegSession('da-segment'); + + $session->set('foo', 'bar'); + + $this->assertEquals('bar', $session->get('foo')); + } +} diff --git a/tests/SetNameMethodSegmentedSessionTest.php b/tests/SetNameMethodSegmentedSessionTest.php new file mode 100644 index 0000000..01b6747 --- /dev/null +++ b/tests/SetNameMethodSegmentedSessionTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class SetNameMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_should_not_set_session_name_since_session_is_auto_started(): void + { + $session = new SegSession('da-segment'); // auto-starts session + $originalSessionName = $session->getName(); + + $session->setName('foo'); + + $this->assertEquals($originalSessionName, session_name()); + $this->assertNotEquals('foo', session_name()); + } +} diff --git a/tests/StartMethodSegmentedSessionTest.php b/tests/StartMethodSegmentedSessionTest.php new file mode 100644 index 0000000..1d1965a --- /dev/null +++ b/tests/StartMethodSegmentedSessionTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps + */ + +namespace Josantonius\Session\Tests; + +use PHPUnit\Framework\TestCase; +use Josantonius\Session\FlashableSessionSegment as SegSession; + +class StartMethodSegmentedSessionTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + } + + /** + * @runInSeparateProcess + */ + public function test_session_should_always_be_started_since_it_gets_auto_started_in_constructor(): void + { + $session = new SegSession('da-segment'); + + $this->assertTrue($session->start()); + + $this->assertEquals(PHP_SESSION_ACTIVE, session_status()); + + $this->assertTrue(isset($_SESSION)); + + // Call again + $this->assertTrue($session->start()); + + $this->assertEquals(PHP_SESSION_ACTIVE, session_status()); + + $this->assertTrue(isset($_SESSION)); + } +} diff --git a/tests/StartMethodTest.php b/tests/StartMethodTest.php index e7e53d9..b061a3c 100644 --- a/tests/StartMethodTest.php +++ b/tests/StartMethodTest.php @@ -48,9 +48,10 @@ public function test_should_start_session(): void */ public function test_should_accept_options(): void { - $this->session->start(['cookie_lifetime' => 8000]); + $this->session->start(['cookie_lifetime' => 8000, 'name' => 'boooo']); $this->assertEquals(8000, ini_get('session.cookie_lifetime')); + $this->assertEquals('boooo', $this->session->getName()); } /**