Ver código fonte

Initial release of PHP SessionStorage library

michelphp 1 dia atrás
commit
0e05973078

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/vendor/
+/.idea/
+.phpunit.result.cache

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 F. Michel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 160 - 0
README.md

@@ -0,0 +1,160 @@
+# Michel PHP SessionStorage Library
+
+## Introduction
+
+The `SessionStorage` is a PHP library that provides a simple implementation of the `SessionStorageInterface`. It allows developers to work with PHP sessions in a convenient way, providing methods to get, set, check, and remove session data. This library requires PHP version 7.4 or higher.
+
+## Installation
+
+Use [Composer](https://getcomposer.org/)
+
+### Composer Require
+```
+composer require michel/session
+```
+
+## Usage
+
+To start using the `SessionStorage`, you need to create an instance of the `NativeSessionStorage` class. Here's an example of how to do it:
+
+```php
+use Michel\Session\Storage\NativeSessionStorage;
+
+// Create a new session storage instance
+
+/**
+ * Constructor for NativeSessionStorage class.
+ *
+ * @param array $options Options for session start.
+ *                      Possible options:
+ *                      - 'name': Session name
+ *                      - 'lifetime': Session lifetime
+ *                      - 'path': Session save path
+ *                      - 'domain': Session domain
+ *                      - 'secure': Set to true for secure session
+ *                      - 'httponly': Set to true to only allow HTTP access
+ * @throws \RuntimeException If session start fails.
+ */
+$sessionStorage = new NativeSessionStorage();
+```
+
+## Use Cases
+
+The `SessionStorage` library offers the following methods to interact with PHP sessions:
+
+### 1. Check if a key exists in the session:
+
+```php
+if ($sessionStorage->has('user_id')) {
+    // Do something with the user_id
+}
+```
+
+### 2. Get the value of a session key:
+
+```php
+$userName = $sessionStorage->get('username', 'Guest');
+// If the 'username' key exists in the session, $userName will be set to its value,
+// otherwise, it will be set to 'Guest'.
+```
+
+### 3. Set a value in the session:
+
+```php
+$userId = 123;
+$sessionStorage->put('user_id', $userId);
+```
+
+### 4. Remove a key from the session:
+
+```php
+$sessionStorage->remove('user_id');
+```
+
+### 5. Get all session data as an array:
+
+```php
+$allData = $sessionStorage->all();
+```
+
+## Interface Implementation
+
+The NativeSessionStorage class implements the SessionStorageInterface, which extends the ArrayAccess interface. As a result, any class implementing the SessionStorageInterface should also implement the methods defined in the ArrayAccess interface. Here's how you can implement the SessionStorageInterface in a custom class:
+```php
+use Michel\Session\Storage\SessionStorageInterface;
+
+class MyCustomSessionStorage implements SessionStorageInterface
+{
+    private array $storage;
+
+    public function __construct()
+    {
+        // Initialize your custom storage mechanism here
+        // For example, you could use a database, Redis, or any other storage solution.
+        // In this example, we will use an array as a simple custom storage mechanism.
+        $this->storage = [];
+    }
+
+    public function get(string $key, $default = null)
+    {
+        return $this->storage[$key] ?? $default;
+    }
+
+    public function put(string $key, $value = null): void
+    {
+        $this->storage[$key] = $value;
+    }
+
+    public function all(): array
+    {
+        return $this->storage;
+    }
+
+    public function has(string $key): bool
+    {
+        return isset($this->storage[$key]);
+    }
+
+    public function remove(string $key): void
+    {
+        unset($this->storage[$key]);
+    }
+
+    // Implementing ArrayAccess methods
+
+    public function offsetExists($offset): bool
+    {
+        return isset($this->storage[$offset]);
+    }
+
+    public function offsetGet($offset)
+    {
+        return $this->get($offset);
+    }
+
+    public function offsetSet($offset, $value): void
+    {
+        $this->put($offset, $value);
+    }
+
+    public function offsetUnset($offset): void
+    {
+        $this->remove($offset);
+    }
+}
+```
+
+In this example, we create a custom session storage class `MyCustomSessionStorage`, which implements the `SessionStorageInterface`. It uses a simple array to store session data, but you can replace this with any custom storage mechanism like a database, Redis, etc., depending on your specific use case.
+
+## Conclusion
+
+The `SessionStorage` library simplifies working with PHP sessions by providing a clean and easy-to-use interface. It is well-suited for applications that need to manage session data efficiently. 
+
+
+## Contributing
+
+Contributions are welcome! Feel free to open issues or submit pull requests to help improve the library.
+
+## License
+
+This library is open-source software licensed under the [MIT license](https://opensource.org/licenses/MIT).

+ 24 - 0
composer.json

@@ -0,0 +1,24 @@
+{
+  "name": "michel/session",
+  "description": "PHP Session is a PHP library that optimizes session management, utilizing PHP's native session handling mechanisms for enhanced security and efficiency.",
+  "type": "library",
+  "require": {
+    "php": ">=7.4",
+    "michel/michel-package-starter": "^1.0"
+  },
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "Michel.F"
+    }
+  ],
+  "autoload": {
+    "psr-4": {
+      "Michel\\Session\\": "src",
+      "Test\\Michel\\Session\\": "tests"
+    }
+  },
+  "require-dev": {
+    "michel/unitester": "^1.0.0"
+  }
+}

+ 143 - 0
composer.lock

@@ -0,0 +1,143 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "d949da71b44112749c696a3d81e23565",
+    "packages": [
+        {
+            "name": "michel/michel-package-starter",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://git.depohub.org/michel/michel-package-starter",
+                "reference": "e2d8af0e3dd15b8c040fde42b5c4ab407e3aa2cd"
+            },
+            "require": {
+                "php": ">=7.4",
+                "psr/container": "^1.0|^2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Michel\\Package\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "F. Michel"
+                }
+            ],
+            "description": "The core interface required for creating and integrating packages into the Michel framework.",
+            "time": "2025-12-15T10:50:52+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/container/issues",
+                "source": "https://github.com/php-fig/container/tree/2.0.2"
+            },
+            "time": "2021-11-05T16:47:00+00:00"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "michel/unitester",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://git.depohub.org/michel/unitester",
+                "reference": "2877e96750aad48983ef65ad7592e1ee9cff5d56"
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": ">=7.4",
+                "psr/container": "^2.0"
+            },
+            "bin": [
+                "bin/unitester"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/assert.php"
+                ],
+                "psr-4": {
+                    "Michel\\UniTester\\": "src",
+                    "Test\\Michel\\UniTester\\": "tests"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "F. Michel"
+                }
+            ],
+            "description": "PHP UniTester is a unit testing library for PHP that provides a straightforward interface for writing and executing tests. It focuses on a minimalist approach, allowing developers to create assertions and organize tests at the lowest level of PHP, without reliance on complex external libraries.",
+            "time": "2025-12-14T15:53:05+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {},
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=7.4"
+    },
+    "platform-dev": {},
+    "plugin-api-version": "2.6.0"
+}

+ 78 - 0
src/Michel/Package/MichelSessionPackage.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Michel\Session\Michel\Package;
+
+use Michel\Package\PackageInterface;
+use Michel\Session\Storage\NativeSessionStorage;
+use Michel\Session\Storage\SessionStorageInterface;
+use Psr\Container\ContainerInterface;
+
+final class MichelSessionPackage implements PackageInterface
+{
+
+    public function getDefinitions(): array
+    {
+        return [
+            SessionStorageInterface::class => static function (ContainerInterface $container) {
+                $pathSession = $container->get('session.save_path');
+                if (($pathSession[0] ?? '') !== '/') {
+                    $pathSession = $container->get('michel.project_dir') . DIRECTORY_SEPARATOR . $pathSession;
+                }
+
+                return new NativeSessionStorage([
+                    'save_path' => $pathSession,
+                    'cookie_lifetime' => $container->get('session.cookie_lifetime'),
+                    'gc_maxlifetime' => $container->get('session.gc_maxlifetime'),
+                    'cookie_secure' => $container->get('session.cookie_secure'),
+                    'cookie_httponly' => $container->get('session.cookie_httponly'),
+                    'use_strict_mode' => $container->get('session.use_strict_mode'),
+                    'use_only_cookies' => $container->get('session.use_only_cookies'),
+                    'sid_length' => $container->get('session.sid_length'),
+                    'sid_bits_per_character' => $container->get('session.sid_bits_per_character'),
+                    'cookie_samesite' => $container->get('session.cookie_samesite'),
+                ]);
+            }
+        ];
+    }
+
+    public function getParameters(): array
+    {
+        return [
+            'session.save_path' => getenv('SESSION_SAVE_PATH') ?: 'var/session', // Default path for session storage
+            'session.cookie_lifetime' => self::getEnv('SESSION_COOKIE_LIFETIME') ?? 86400, // Cookie lifetime (24 hours)
+            'session.gc_maxlifetime' => self::getEnv('SESSION_GC_MAXLIFETIME') ?? 604800, // Server-side session lifetime (7 days)
+            'session.cookie_secure' => self::getEnv('SESSION_COOKIE_SECURE') === true, // Cookie is only transmitted via HTTPS
+            'session.cookie_httponly' => self::getEnv('SESSION_COOKIE_HTTPONLY') === true, // Prevents JavaScript access to the cookie
+            'session.use_strict_mode' => self::getEnv('SESSION_USE_STRICT_MODE') === true, // Rejects invalid SIDs
+            'session.use_only_cookies' => self::getEnv('SESSION_USE_ONLY_COOKIES') === true, // Prevents using SIDs in the URL
+            'session.sid_length' => self::getEnv('SESSION_SID_LENGTH') ?? 64, // Secure SID length
+            'session.sid_bits_per_character' => self::getEnv('SESSION_SID_BITS_PER_CHARACTER') ?? 6, // Bits per character (6 for maximum security)
+            'session.cookie_samesite' => self::getEnv('SESSION_COOKIE_SAMESITE') ?? 'Strict', // Protection against CSRF attacks
+        ];
+    }
+
+    private static function getEnv(string $name)
+    {
+        return $_ENV[$name] ?? null;
+    }
+
+    public function getRoutes(): array
+    {
+        return [];
+    }
+
+    public function getControllerSources(): array
+    {
+        return [];
+    }
+
+    public function getListeners(): array
+    {
+        return [];
+    }
+
+    public function getCommandSources(): array
+    {
+        return [];
+    }
+}

+ 103 - 0
src/Storage/NativeSessionStorage.php

@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Michel\Session\Storage;
+
+use function session_start;
+use function session_status;
+use const PHP_SESSION_NONE;
+
+class NativeSessionStorage implements SessionStorageInterface
+{
+    private array $storage;
+
+    /**
+     * Constructor for NativeSessionStorage class.
+     *
+     * @param array $options Options for session start.
+     *                      Possible options:
+     *                      - 'name': Session name
+     *                      - 'lifetime': Session lifetime
+     *                      - 'path': Session save path
+     *                      - 'domain': Session domain
+     *                      - 'secure': Set to true for secure session
+     *                      - 'httponly': Set to true to only allow HTTP access
+     * @throws \RuntimeException If session start fails.
+     */
+    public function __construct(array $options = [])
+    {
+        if (session_status() === PHP_SESSION_NONE) {
+            if (isset($options['save_path']) && !is_dir($options['save_path'])) {
+//                var_dump($options['save_path']);
+                throw new \RuntimeException(sprintf('Session save path "%s" does not exist.', $options['save_path']));
+            }
+            if (!session_start($options)) {
+                throw new \RuntimeException('Failed to start the session.');
+            }
+        }
+
+        $this->storage = &$_SESSION;
+    }
+
+    public function offsetExists($offset): bool
+    {
+        return isset($this->storage[$offset]);
+    }
+
+    public function offsetGet($offset)
+    {
+        if ($this->offsetExists($offset)) {
+            return $this->storage[$offset];
+        }
+        return null;
+    }
+
+    public function offsetSet($offset, $value): self
+    {
+        $this->storage[$offset] = $value;
+        return $this;
+    }
+
+    public function offsetUnset($offset): void
+    {
+        if ($this->offsetExists($offset)) {
+            unset($this->storage[$offset]);
+        }
+    }
+
+    /**
+     * @param string $key
+     * @param mixed $default
+     * @return mixed
+     */
+    public function get(string $key, $default = null)
+    {
+        return $this->offsetGet($key) ?: $default;
+    }
+
+    /**
+     * @param string $key
+     * @param mixed $value
+     * @return void
+     */
+    public function put(string $key, $value = null): void
+    {
+        $this->offsetSet($key, $value);
+    }
+
+    public function all(): array
+    {
+        return $this->storage;
+    }
+
+    public function has(string $key): bool
+    {
+        return $this->offsetExists($key);
+    }
+
+    public function remove(string $key): void
+    {
+        $this->offsetUnset($key);
+    }
+}

+ 16 - 0
src/Storage/SessionStorageInterface.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Michel\Session\Storage;
+
+use ArrayAccess;
+
+interface SessionStorageInterface extends ArrayAccess
+{
+    public function get(string $key,  $default = null);
+    public function put(string $key, $value = null): void;
+    public function all(): array;
+    public function has(string $key): bool;
+    public function remove(string $key): void;
+}

+ 78 - 0
tests/MichelSessionPackageTest.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Test\Michel\Session;
+
+use Michel\Session\Michel\Package\MichelSessionPackage;
+use Michel\Session\Storage\NativeSessionStorage;
+use Michel\Session\Storage\SessionStorageInterface;
+use Michel\UniTester\TestCase;
+use Psr\Container\ContainerInterface;
+use RuntimeException;
+
+final class MichelSessionPackageTest extends TestCase
+{
+    protected function setUp(): void
+    {
+        $_SESSION = [];
+    }
+
+    protected function tearDown(): void
+    {
+        $_SESSION = [];
+        session_destroy();
+        foreach (glob( __DIR__ . '/var/session/*') as $file) {
+            unlink($file);
+        }
+    }
+
+    protected function execute(): void
+    {
+        $container = new class implements ContainerInterface {
+            private array $definitions = [];
+            private array $values = [];
+
+            public function __construct()
+            {
+                $package = new MichelSessionPackage();
+                $definitions = $package->getDefinitions();
+                $parameters = $package->getParameters();
+                $this->definitions = $definitions + $parameters + [
+                        ContainerInterface::class => $this,
+                        'michel.project_dir' => __DIR__,
+                        'session.save_path' => 'var/session',
+                    ];
+            }
+
+            public function get(string $id)
+            {
+                if (!$this->has($id)) {
+                    throw new RuntimeException('Unknown definition: ' . $id);
+                }
+
+                if (isset($this->values[$id])) {
+                    return $this->values[$id];
+                }
+
+                $value = $this->definitions[$id];
+                if (is_callable($value)) {
+                    $value = $value($this);
+                }
+
+                $this->values[$id] = $value;
+                return $value;
+            }
+
+            public function has(string $id): bool
+            {
+                return isset($this->definitions[$id]);
+            }
+        };
+
+        $sessionStorage = $container->get(SessionStorageInterface::class);
+        $this->assertInstanceOf(NativeSessionStorage::class, $sessionStorage);
+
+        $sessionStorage->put('username', 'myName');
+        $this->assertTrue($sessionStorage->has('username'));
+        $this->assertTrue(!empty(glob( __DIR__ . '/var/session/*')));
+    }
+}

+ 49 - 0
tests/NativeSessionTest.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace Test\Michel\Session;
+
+use Michel\Session\Storage\NativeSessionStorage;
+use Michel\UniTester\TestCase;
+
+final class NativeSessionTest extends TestCase
+{
+    protected function setUp(): void
+    {
+        $_SESSION = [];
+    }
+
+    protected function tearDown(): void
+    {
+        $_SESSION = [];
+        session_destroy();
+    }
+
+    protected function execute(): void
+    {
+        $this->expectException(\RuntimeException::class, function () {
+            new NativeSessionStorage([
+                'save_path' => '/tmp/testnotfound',
+            ]);
+        });
+
+        $session = new NativeSessionStorage();
+        $session['username'] = 'myName';
+        $this->assertTrue($session->has('username'));
+        $session['role'] = 'ADMIN';
+        $this->assertTrue($session->has('role'));
+        $this->assertStrictEquals('myName', $session->get('username'));
+
+        $article = [
+            'title' => 'TV',
+            'description' => 'lorem',
+            'price' => 199.80
+        ];
+        $session->put('article',$article);
+        $this->assertStrictEquals($article, $session->get('article'));
+        $this->assertTrue(is_float($session->get('article')['price']));
+        $this->assertTrue(count($session->all()) === 3);
+
+        $this->assertStrictEquals(null, $session->get('email'));
+        $this->assertStrictEquals('dev@phpdevcommunity.com', $session->get('email', 'dev@phpdevcommunity.com'));
+    }
+}

+ 2 - 0
tests/var/session/.gitignore

@@ -0,0 +1,2 @@
+.gitignore
+!.gitignore