From 368f99e002c3b6c7de1f43d85ac7f4e043845565 Mon Sep 17 00:00:00 2001 From: Rastusik Date: Mon, 25 Jul 2022 18:02:29 +0200 Subject: [PATCH] feat(platform): added integrations for PHP 8.1 + added support for Symfony 6 --- .circleci/config.yml | 62 +++++++++++- .travis.yml | 24 ++++- Dockerfile | 2 +- README.md | 2 +- composer.json | 31 +++--- composer.lock | 5 +- docker-compose.yml | 6 +- .../SessionStorageListenerPass.php | 37 ++++++++ .../DependencyInjection/Configuration.php | 4 - .../DependencyInjection/SwooleExtension.php | 9 -- .../Bundle/Resources/config/services.yaml | 5 + src/Bridge/Symfony/Bundle/SwooleBundle.php | 2 + .../Symfony/Event/SessionResetEvent.php | 24 +++++ ...ner.php => SessionCookieEventListener.php} | 95 +++++++++++++------ .../Session/SwooleSessionStorage.php | 62 ++++-------- .../Session/SwooleSessionStorageFactory.php | 60 ++++++++++++ .../StreamedResponseListener.php | 2 +- src/Client/HttpClient.php | 41 ++++---- .../Feature/SwooleCommandsRegisteredTest.php | 2 +- .../SwooleServerReloadViaApiClientTest.php | 6 +- ...fonySessionSwooleSessionIdStorageTest.php} | 6 +- tests/Fixtures/Symfony/TestAppKernel.php | 14 ++- .../Symfony/app/config/session/swoole.yaml | 9 +- .../SwooleSessionStorageFactoryTest.php | 57 +++++++++++ tests/Unit/Client/HttpClientTest.php | 2 +- 25 files changed, 414 insertions(+), 155 deletions(-) create mode 100644 src/Bridge/Symfony/Bundle/DependencyInjection/CompilerPass/SessionStorageListenerPass.php create mode 100644 src/Bridge/Symfony/Event/SessionResetEvent.php rename src/Bridge/Symfony/HttpFoundation/Session/{SetSessionCookieEventListener.php => SessionCookieEventListener.php} (61%) create mode 100644 src/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorageFactory.php rename tests/Feature/{SymfonySessionSwooleSessionStorageTest.php => SymfonySessionSwooleSessionIdStorageTest.php} (98%) create mode 100644 tests/Unit/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorageFactoryTest.php diff --git a/.circleci/config.yml b/.circleci/config.yml index a393abfc..d7d376b9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ orbs: executors: pixelfederation-docker-client: docker: - - image: pixelfederation/docker-client:20.10.5-compose + - image: pixelfederation/docker-client:20.10.17-compose working_directory: ~/workdir pixelfederation-release-version: @@ -17,7 +17,7 @@ aliases: - &docker-remote-version setup_remote_docker: # https://circleci.com/docs/2.0/building-docker-images/#docker-version - version: 20.10.2 + version: 20.10.14 - &docker-bake executor: pixelfederation-docker-client @@ -41,6 +41,9 @@ aliases: - run: name: Print docker info command: docker info + - run: + name: Docker pass initialization + command: docker-use-pass - run: name: Login to Docker Hub command: >- @@ -146,6 +149,11 @@ jobs: docker: - image: docker.io/pixelfederation/swoole-bundle-composer:8.0-latest-$CIRCLE_SHA1 + swoole-bundle-composer-81-latest-unit-tests: + <<: *job-composer-unit-tests + docker: + - image: docker.io/pixelfederation/swoole-bundle-composer:8.1-latest-$CIRCLE_SHA1 + swoole-bundle-composer-74-lowest-feature-tests: <<: *job-composer-feature-tests docker: @@ -156,6 +164,11 @@ jobs: docker: - image: docker.io/pixelfederation/swoole-bundle-composer:8.0-latest-$CIRCLE_SHA1 + swoole-bundle-composer-81-latest-feature-tests: + <<: *job-composer-feature-tests + docker: + - image: docker.io/pixelfederation/swoole-bundle-composer:8.1-latest-$CIRCLE_SHA1 + swoole-bundle-74-code-coverage: executor: pixelfederation-docker-client environment: @@ -285,6 +298,19 @@ jobs: SERVICES: composer BUILD_TYPE: latest + docker-buildx-bake-81-latest: + <<: *docker-bake + environment: + PHP_VERSION: "8.1" + PHP_API_VERSION: "20210902" + COMPOSER_ARGS: update + SWOOLE_VERSION: latest + REGISTRY: docker.io + NAMESPACE: pixelfederation + IMAGE: swoole-bundle + SERVICES: composer + BUILD_TYPE: latest + check-composer-config: docker: - image: composer:2 @@ -376,6 +402,16 @@ workflows: - swoole-bundle-composer-80-latest-feature-tests: requires: - docker-buildx-bake-80-latest + - docker-buildx-bake-81-latest: + context: swoole-bundle-dockerhub + requires: + - docker-buildx-bake-74 + - swoole-bundle-composer-81-latest-unit-tests: + requires: + - docker-buildx-bake-81-latest + - swoole-bundle-composer-81-latest-feature-tests: + requires: + - docker-buildx-bake-81-latest pull-request-checks-untrusted: when: @@ -432,6 +468,16 @@ workflows: - swoole-bundle-composer-80-latest-feature-tests: requires: - docker-buildx-bake-80-latest + - docker-buildx-bake-81-latest: + context: swoole-bundle-dockerhub + requires: + - docker-buildx-bake-74 + - swoole-bundle-composer-81-latest-unit-tests: + requires: + - docker-buildx-bake-81-latest + - swoole-bundle-composer-81-latest-feature-tests: + requires: + - docker-buildx-bake-81-latest release: when: @@ -479,6 +525,16 @@ workflows: - swoole-bundle-composer-80-latest-feature-tests: requires: - docker-buildx-bake-80-latest + - docker-buildx-bake-81-latest: + context: swoole-bundle-dockerhub + requires: + - docker-buildx-bake-74 + - swoole-bundle-composer-81-latest-unit-tests: + requires: + - docker-buildx-bake-81-latest + - swoole-bundle-composer-81-latest-feature-tests: + requires: + - docker-buildx-bake-81-latest - approve-release: type: approval requires: @@ -491,6 +547,8 @@ workflows: - swoole-bundle-composer-74-code-style - swoole-bundle-composer-80-latest-unit-tests - swoole-bundle-composer-80-latest-feature-tests + - swoole-bundle-composer-81-latest-unit-tests + - swoole-bundle-composer-81-latest-feature-tests - releaser: context: swoole-bundle-github requires: diff --git a/.travis.yml b/.travis.yml index e398efb4..f3a8dc0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,13 +20,28 @@ matrix: - php: "8.0" env: - OPENSWOOLE_LATEST=1 - - COMPOSER_ARGS='update --ignore-platform-reqs' + - COMPOSER_ARGS='update' + - NO_ANALYSE=1 + - php: "8.1" + env: + - PHP_CS_FIXER_IGNORE_ENV=1 + - php: "8.1" + env: + - OPENSWOOLE_LATEST=1 + - COMPOSER_ARGS='update' + - NO_ANALYSE=1 fast_finish: true allow_failures: - php: "8.0" env: - OPENSWOOLE_LATEST=1 - - COMPOSER_ARGS='update --ignore-platform-reqs' + - COMPOSER_ARGS='update' + - NO_ANALYSE=1 + - php: "8.1" + env: + - OPENSWOOLE_LATEST=1 + - COMPOSER_ARGS='update' + - NO_ANALYSE=1 before_script: - phpenv config-rm xdebug.ini @@ -50,6 +65,9 @@ install: - composer $COMPOSER_ARGS script: - - composer analyse + - >- + if [[ "NO_ANALYSE" != "1" ]]; then + composer analyse + fi - composer unit-tests - composer feature-tests diff --git a/Dockerfile b/Dockerfile index 32475d57..3eaf1158 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG PHP_TAG="7.4-cli-alpine3.13" +ARG PHP_TAG="7.4-cli-alpine3.16" ARG COMPOSER_TAG="2.2.9" FROM php:$PHP_TAG as ext-builder diff --git a/README.md b/README.md index 8ccd7d14..d10ea3f2 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Symfony integration with [Open Swoole](https://openswoole.com/) to speed up your - PHP version `>= 8.0` - Swoole PHP Extension `>= 4.10.0` -- Symfony `>= 5.4` +- Symfony `>= 6.0` Additional requirements to enable specific features: diff --git a/composer.json b/composer.json index 4ccc11f4..b17a2195 100644 --- a/composer.json +++ b/composer.json @@ -29,13 +29,13 @@ "ext-json": "*", "ext-openswoole": "^4.10.0", "beberlei/assert": "^3.3", - "symfony/config": "^5.4", - "symfony/console": "^5.4", - "symfony/dependency-injection": "^5.4", - "symfony/http-foundation": "^5.4", - "symfony/http-kernel": "^5.4", - "symfony/process": "^5.4", - "symfony/proxy-manager-bridge": "^5.4" + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/proxy-manager-bridge": "^5.4|^6.0" }, "require-dev": { "doctrine/annotations": "^1.13", @@ -54,16 +54,16 @@ "ramsey/uuid": "^4.1", "swoole/ide-helper": "^4.8", "symfony/debug-pack": "^1.0", - "symfony/doctrine-messenger": "^5.4", - "symfony/error-handler": "^5.4", - "symfony/framework-bundle": "^5.4", - "symfony/messenger": "^5.4", - "symfony/monolog-bridge": "^5.4", + "symfony/doctrine-messenger": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/monolog-bridge": "^5.4|^6.0", "symfony/monolog-bundle": "^3.3", "symfony/profiler-pack": "^1.0", - "symfony/twig-bundle": "^5.4", - "symfony/var-dumper": "^5.4", - "symfony/yaml": "^5.4", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", "upscale/swoole-blackfire": "^3.1" }, "suggest": { @@ -141,7 +141,6 @@ "process-timeout": 1200, "sort-packages": true, "platform": { - "php": "7.4.13", "ext-swoole": "4.5.10" }, "allow-plugins": { diff --git a/composer.lock b/composer.lock index 9dcfae68..34250609 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "410fa0f861233085ab8f827e2464e13f", + "content-hash": "13c6c91c29c8eae88c9bb4f5a66c8a24", "packages": [ { "name": "beberlei/assert", @@ -8529,8 +8529,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "7.4.13", "ext-swoole": "4.5.10" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.3.0" } diff --git a/docker-compose.yml b/docker-compose.yml index 15bf6a16..275f401e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ version: "3.8" x-env-aliases: - &DEFAULT_BUILD_ARGS - PHP_TAG: "${PHP_VERSION:-7.4}-cli-alpine${ALPINE_VERSION:-3.13}" + PHP_TAG: "${PHP_VERSION:-7.4}-cli-alpine${ALPINE_VERSION:-3.16}" PHP_API_VERSION: "${PHP_API_VERSION:-20190902}" COMPOSER_ARGS: "${COMPOSER_ARGS:-install}" SWOOLE_VERSION: "${SWOOLE_VERSION:-4.10.0}" - COMPOSER_TAG: "${COMPOSER_TAG:-2.0.11}" + COMPOSER_TAG: "${COMPOSER_TAG:-2.3.10}" volumes: coverage: {} @@ -90,7 +90,7 @@ services: - coverage:/usr/src/app/cov coverage-volume-helper: - image: "alpine:${ALPINE_VERSION:-3.13}" + image: "alpine:${ALPINE_VERSION:-3.16}" entrypoint: sleep command: infinity working_dir: /usr/src/app diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/CompilerPass/SessionStorageListenerPass.php b/src/Bridge/Symfony/Bundle/DependencyInjection/CompilerPass/SessionStorageListenerPass.php new file mode 100644 index 00000000..713f2971 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/CompilerPass/SessionStorageListenerPass.php @@ -0,0 +1,37 @@ +hasAlias('session.storage.factory')) { + return; + } + + $factoryAlias = $container->getAlias('session.storage.factory'); + + if ('swoole_bundle.session.table_storage_factory' !== (string) $factoryAlias) { + return; + } + + $cookieListenerDef = new Definition(SessionCookieEventListener::class); + $cookieListenerDef->setPublic(false); + $cookieListenerDef->setArgument('$requestStack', new Reference('request_stack')); + $cookieListenerDef->setArgument('$dispatcher', new Reference('event_dispatcher')); + $cookieListenerDef->setArgument('$swooleStorage', new Reference(StorageInterface::class)); + $cookieListenerDef->setArgument('$sessionOptions', $container->getParameter('session.storage.options')); + $cookieListenerDef->addTag('kernel.event_subscriber'); + $container->setDefinition(SessionCookieEventListener::class, $cookieListenerDef); + } +} diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index 1de46035..c60b56da 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -205,10 +205,6 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('blackfire_profiler') ->defaultNull() ->end() - ->booleanNode('session_cookie_event_listener') - ->defaultFalse() - ->treatNullLike(false) - ->end() ->end() ->end() // drivers ->arrayNode('settings') diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/SwooleExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/SwooleExtension.php index 7eec0887..dd90e027 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/SwooleExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/SwooleExtension.php @@ -10,7 +10,6 @@ use K911\Swoole\Bridge\Symfony\ErrorHandler\ThrowableHandlerFactory; use K911\Swoole\Bridge\Symfony\HttpFoundation\CloudFrontRequestFactory; use K911\Swoole\Bridge\Symfony\HttpFoundation\RequestFactoryInterface; -use K911\Swoole\Bridge\Symfony\HttpFoundation\Session\SetSessionCookieEventListener; use K911\Swoole\Bridge\Symfony\HttpFoundation\TrustAllProxiesRequestHandler; use K911\Swoole\Bridge\Symfony\Messenger\SwooleServerTaskTransportFactory; use K911\Swoole\Bridge\Symfony\Messenger\SwooleServerTaskTransportHandler; @@ -290,14 +289,6 @@ private function registerHttpServerServices(array $config, ContainerBuilder $con ; } - if ($config['session_cookie_event_listener']) { - $container->register(SetSessionCookieEventListener::class) - ->setAutowired(true) - ->setAutoconfigured(true) - ->setPublic(false) - ; - } - if ($config['blackfire_profiler'] || (null === $config['blackfire_profiler'] && \class_exists(Profiler::class))) { $container->register(Profiler::class) ->setClass(Profiler::class) diff --git a/src/Bridge/Symfony/Bundle/Resources/config/services.yaml b/src/Bridge/Symfony/Bundle/Resources/config/services.yaml index 24490f5d..b26fb979 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/services.yaml +++ b/src/Bridge/Symfony/Bundle/Resources/config/services.yaml @@ -56,6 +56,8 @@ services: K911\Swoole\Bridge\Symfony\HttpKernel\HttpKernelRequestHandler: + K911\Swoole\Bridge\Symfony\HttpFoundation\Session\SwooleSessionStorageFactory: + K911\Swoole\Server\RequestHandler\LimitedRequestHandler: K911\Swoole\Server\LifecycleHandler\SigIntHandler: @@ -155,6 +157,9 @@ services: swoole_bundle.session.table_storage: alias: K911\Swoole\Bridge\Symfony\HttpFoundation\Session\SwooleSessionStorage + swoole_bundle.session.table_storage_factory: + alias: K911\Swoole\Bridge\Symfony\HttpFoundation\Session\SwooleSessionStorageFactory + swoole_bundle.server.http_server.configurator.with_request_handler: class: K911\Swoole\Server\Configurator\WithRequestHandler autoconfigure: false diff --git a/src/Bridge/Symfony/Bundle/SwooleBundle.php b/src/Bridge/Symfony/Bundle/SwooleBundle.php index 965c9583..85e740e4 100644 --- a/src/Bridge/Symfony/Bundle/SwooleBundle.php +++ b/src/Bridge/Symfony/Bundle/SwooleBundle.php @@ -5,6 +5,7 @@ namespace K911\Swoole\Bridge\Symfony\Bundle; use K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\CompilerPass\DebugLogProcessorPass; +use K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\CompilerPass\SessionStorageListenerPass; use K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\CompilerPass\StreamedResponseListenerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -15,5 +16,6 @@ public function build(ContainerBuilder $container): void { $container->addCompilerPass(new DebugLogProcessorPass()); $container->addCompilerPass(new StreamedResponseListenerPass()); + $container->addCompilerPass(new SessionStorageListenerPass()); } } diff --git a/src/Bridge/Symfony/Event/SessionResetEvent.php b/src/Bridge/Symfony/Event/SessionResetEvent.php new file mode 100644 index 00000000..e9b49237 --- /dev/null +++ b/src/Bridge/Symfony/Event/SessionResetEvent.php @@ -0,0 +1,24 @@ +sessionId = $sessionId; + } + + public function getSessionId(): string + { + return $this->sessionId; + } +} diff --git a/src/Bridge/Symfony/HttpFoundation/Session/SetSessionCookieEventListener.php b/src/Bridge/Symfony/HttpFoundation/Session/SessionCookieEventListener.php similarity index 61% rename from src/Bridge/Symfony/HttpFoundation/Session/SetSessionCookieEventListener.php rename to src/Bridge/Symfony/HttpFoundation/Session/SessionCookieEventListener.php index c84ea802..18a6c929 100644 --- a/src/Bridge/Symfony/HttpFoundation/Session/SetSessionCookieEventListener.php +++ b/src/Bridge/Symfony/HttpFoundation/Session/SessionCookieEventListener.php @@ -4,11 +4,14 @@ namespace K911\Swoole\Bridge\Symfony\HttpFoundation\Session; +use K911\Swoole\Bridge\Symfony\Event\SessionResetEvent; use K911\Swoole\Server\Session\StorageInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -18,66 +21,80 @@ /** * Sets the session in the request. */ -final class SetSessionCookieEventListener implements EventSubscriberInterface +final class SessionCookieEventListener implements EventSubscriberInterface { - private $sessionStorage; - private $sessionCookieParameters; - private $swooleStorage; + private RequestStack $requestStack; - public function __construct(SessionStorageInterface $sessionStorage, StorageInterface $swooleStorage, array $sessionOptions = []) - { - $this->sessionStorage = $sessionStorage; + private StorageInterface $swooleStorage; + + private array $sessionCookieParameters; + + private EventDispatcherInterface $dispatcher; + + public function __construct( + RequestStack $requestStack, + EventDispatcherInterface $dispatcher, + StorageInterface $swooleStorage, + array $sessionOptions = [] + ) { + $this->requestStack = $requestStack; $this->swooleStorage = $swooleStorage; + $this->dispatcher = $dispatcher; $this->sessionCookieParameters = $this->mergeCookieParams($sessionOptions); } + public function onFinishRequest(FinishRequestEvent $event): void + { + if (!$event->isMainRequest() || !$this->isSessionRelated($event)) { + return; + } + + if ($this->session()->isStarted()) { + $this->dispatcher->dispatch( + new SessionResetEvent($this->session()->getId()), + SessionResetEvent::NAME + ); + } + + $this->swooleStorage->garbageCollect(); + } + public function onKernelRequest(RequestEvent $event): void { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest() || !$this->isSessionRelated($event)) { return; } $cookies = $event->getRequest()->cookies; + $sessionName = $this->session()->getName(); - $sessionName = $this->sessionStorage->getName(); - if ($cookies->has($sessionName)) { - $sessionId = (string) $cookies->get($sessionName); - $this->sessionStorage->setId($sessionId); + if (!$cookies->has($sessionName)) { + return; } + + $sessionId = (string) $cookies->get($sessionName); + $this->session()->setId($sessionId); } public function onKernelResponse(ResponseEvent $event): void { - if (!$event->isMasterRequest() || !$this->isSessionRelated($event)) { + if (!$event->isMainRequest() || !$this->isSessionRelated($event)) { return; } $session = $event->getRequest()->getSession(); - if (!$session instanceof SessionInterface || !$session->isStarted()) { + if (!$session->isStarted()) { return; } $responseHeaderBag = $event->getResponse()->headers; - foreach ($responseHeaderBag->getCookies() as $cookie) { - if ($this->isSessionCookie($cookie, $session->getName())) { - return; - } - } + $cookie = $this->findSessionCookie($responseHeaderBag, $session->getName()); - $responseHeaderBag->setCookie($this->makeSessionCookie($session)); - } - - public function onFinishRequest(FinishRequestEvent $event): void - { - if (!$event->isMasterRequest() || !$this->isSessionRelated($event)) { + if (null !== $cookie) { return; } - if ($this->sessionStorage instanceof SwooleSessionStorage && $this->sessionStorage->isStarted()) { - $this->sessionStorage->reset(); - } - - $this->swooleStorage->garbageCollect(); + $responseHeaderBag->setCookie($this->makeSessionCookie($session)); } public static function getSubscribedEvents(): array @@ -89,6 +106,17 @@ public static function getSubscribedEvents(): array ]; } + private function findSessionCookie(ResponseHeaderBag $headers, string $sessionName): ?Cookie + { + foreach ($headers->getCookies() as $cookie) { + if ($this->isSessionCookie($cookie, $sessionName)) { + return $cookie; + } + } + + return null; + } + private function isSessionCookie(Cookie $cookie, string $sessionName): bool { return $this->sessionCookieParameters['path'] === $cookie->getPath() && @@ -127,4 +155,9 @@ private function isSessionRelated(KernelEvent $event): bool { return $event->getRequest()->hasSession(); } + + private function session(): SessionInterface + { + return $this->requestStack->getSession(); + } } diff --git a/src/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorage.php b/src/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorage.php index ec14f9ec..c48642bb 100644 --- a/src/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorage.php +++ b/src/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorage.php @@ -16,45 +16,24 @@ final class SwooleSessionStorage implements SessionStorageInterface { public const DEFAULT_SESSION_NAME = 'SWOOLESSID'; - /** - * @var StorageInterface - */ - private $storage; + private StorageInterface $storage; - /** - * @var string - */ - private $name; + private string $name; - /** - * @var string - */ - private $currentId; + private string $currentId; /** * @var SessionBagInterface[] */ - private $bags; + private array $bags; - /** - * @var array - */ - private $data; + private array $data; - /** - * @var MetadataBag - */ - private $metadataBag; + private MetadataBag $metadataBag; - /** - * @var bool - */ - private $started; + private bool $started; - /** - * @var int - */ - private $sessionLifetimeSeconds; + private int $sessionLifetimeSeconds; public function __construct(StorageInterface $storage, string $name = self::DEFAULT_SESSION_NAME, int $lifetimeSeconds = 86400, MetadataBag $metadataBag = null) { @@ -97,13 +76,18 @@ public function start(): bool * {@inheritdoc} * * @throws \Exception + * @SuppressWarnings(PHPMD.BooleanArgumentFlag) */ - public function regenerate($destroy = false, $lifetime = null): bool + public function regenerate(bool $destroy = false, int $lifetime = null): bool { if ($destroy) { $this->storage->delete($this->currentId); } + if (null !== $lifetime && $lifetime != ini_get('session.cookie_lifetime')) { + ini_set('session.cookie_lifetime', (string) $lifetime); + } + $this->getMetadataBag()->stampNew($lifetime ?? $this->sessionLifetimeSeconds); $this->currentId = $this->generateId(); @@ -163,11 +147,9 @@ public function getId(): string } /** - * @param string $id - * * @throws \Exception */ - public function setId($id): void + public function setId(string $id): void { if ($this->started) { throw new LogicException('Cannot set session ID after the session has started.'); @@ -176,10 +158,7 @@ public function setId($id): void $this->currentId = \preg_match('/^[a-f0-9]{63}$/', $id) ? $id : $this->generateId(); } - /** - * @return string - */ - public function getName() + public function getName(): string { return $this->name; } @@ -193,11 +172,9 @@ public function setName($name): void } /** - * @param string $name - * * @throws \Assert\AssertionFailedException */ - public function getBag($name): SessionBagInterface + public function getBag(string $name): SessionBagInterface { if (!isset($this->bags[$name])) { throw new \InvalidArgumentException(\sprintf('The SessionBagInterface `%s` is not registered.', $name)); @@ -233,7 +210,10 @@ public function getMetadataBag(): MetadataBag private function setLifetimeSeconds(int $lifetimeSeconds): void { $this->sessionLifetimeSeconds = $lifetimeSeconds; - \ini_set('session.cookie_lifetime', (string) $lifetimeSeconds); + + if (null !== ini_get('session.cookie_lifetime') && $lifetimeSeconds !== (int) ini_get('session.cookie_lifetime')) { + @ini_set('session.cookie_lifetime', (string) $lifetimeSeconds); + } } /** diff --git a/src/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorageFactory.php b/src/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorageFactory.php new file mode 100644 index 00000000..e9524bee --- /dev/null +++ b/src/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorageFactory.php @@ -0,0 +1,60 @@ +storage = $storage; + $this->dispatcher = $dispatcher; + $this->metadataBag = $metadataBag; + $this->lifetimeSeconds = $lifetimeSeconds; + } + + /** + * {@inheritDoc} + */ + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new SwooleSessionStorage( + $this->storage, + SwooleSessionStorage::DEFAULT_SESSION_NAME, + $this->lifetimeSeconds, + $this->metadataBag + ); + + $this->dispatcher->addListener( + SessionResetEvent::NAME, + function (SessionResetEvent $event) use ($storage) { + if ($storage->isStarted() && $event->getSessionId() === $storage->getId()) { + $storage->reset(); + } + } + ); + + return $storage; + } +} diff --git a/src/Bridge/Symfony/HttpFoundation/StreamedResponseListener.php b/src/Bridge/Symfony/HttpFoundation/StreamedResponseListener.php index 20163a0f..f0aec74c 100644 --- a/src/Bridge/Symfony/HttpFoundation/StreamedResponseListener.php +++ b/src/Bridge/Symfony/HttpFoundation/StreamedResponseListener.php @@ -21,7 +21,7 @@ public function __construct(?HttpFoundationStreamedResponseListener $delegate = public function onKernelResponse(ResponseEvent $event): void { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } diff --git a/src/Client/HttpClient.php b/src/Client/HttpClient.php index fe60cf11..6b189d8a 100644 --- a/src/Client/HttpClient.php +++ b/src/Client/HttpClient.php @@ -17,7 +17,7 @@ * * @internal Class API is not stable, nor it is guaranteed to exists in next releases, use at own risk */ -final class HttpClient implements \Serializable +final class HttpClient { private const SUPPORTED_HTTP_METHODS = [ Http::METHOD_GET, @@ -48,6 +48,21 @@ public function __construct(Client $client) $this->client = $client; } + public function __serialize(): array + { + return [ + 'host' => $this->client->host, + 'port' => $this->client->port, + 'ssl' => $this->client->ssl, + 'options' => $this->client->setting, + ]; + } + + public function __unserialize(array $spec): void + { + $this->client = self::makeSwooleClient($spec['host'], $spec['port'], $spec['ssl'], $spec['options']); + } + public static function fromSocket(Socket $socket, array $options = []): self { return self::fromDomain( @@ -116,30 +131,6 @@ public function send(string $path, string $method = Http::METHOD_GET, array $hea return $this->resolveResponse($this->client, $timeout); } - /** - * {@inheritdoc} - */ - public function serialize(): string - { - return \json_encode([ - 'host' => $this->client->host, - 'port' => $this->client->port, - 'ssl' => $this->client->ssl, - 'options' => $this->client->setting, - ], \JSON_THROW_ON_ERROR); - } - - /** - * {@inheritdoc} - * - * @param string $serialized - */ - public function unserialize($serialized): void - { - $spec = \json_decode($serialized, true, 512, \JSON_THROW_ON_ERROR); - $this->client = self::makeSwooleClient($spec['host'], $spec['port'], $spec['ssl'], $spec['options']); - } - private static function makeSwooleClient(string $host, int $port = 443, bool $ssl = true, array $options = []): Client { $client = new Client( diff --git a/tests/Feature/SwooleCommandsRegisteredTest.php b/tests/Feature/SwooleCommandsRegisteredTest.php index 28cacfc2..e774dc46 100644 --- a/tests/Feature/SwooleCommandsRegisteredTest.php +++ b/tests/Feature/SwooleCommandsRegisteredTest.php @@ -77,7 +77,7 @@ public function testSwooleCommandsRegisteredWithCacheClearAppEnvExceptionHandler public function testSwooleCommandsRegisteredWithCacheClearAppEnvSession(): void { - $kernel = static::createKernel(['environment' => 'session']); + $kernel = static::createKernel(['environment' => 'session_factory']); $application = new Application($kernel); $cacheClear = $application->find('cache:clear'); diff --git a/tests/Feature/SwooleServerReloadViaApiClientTest.php b/tests/Feature/SwooleServerReloadViaApiClientTest.php index 2ffe8e51..5ed01749 100644 --- a/tests/Feature/SwooleServerReloadViaApiClientTest.php +++ b/tests/Feature/SwooleServerReloadViaApiClientTest.php @@ -26,9 +26,9 @@ protected function setUp(): void public function testStartRequestApiToReloadCallStop(): void { static::bootKernel(); - $sockets = static::$container->get(Sockets::class); + $sockets = static::getContainer()->get(Sockets::class); $sockets->changeApiSocket(new Socket('0.0.0.0', 9998)); - $apiClient = static::$container->get(ApiServerClientFactory::class) + $apiClient = static::getContainer()->get(ApiServerClientFactory::class) ->newClient() ; @@ -80,7 +80,7 @@ public function testStartRequestApiToReloadCallStop(): void public function testStartRequestApiToReloadCallStopUsingApiEnv(): void { static::bootKernel(['environment' => 'api']); - $apiClient = static::$container->get(ApiServerClientFactory::class) + $apiClient = static::getContainer()->get(ApiServerClientFactory::class) ->newClient() ; diff --git a/tests/Feature/SymfonySessionSwooleSessionStorageTest.php b/tests/Feature/SymfonySessionSwooleSessionIdStorageTest.php similarity index 98% rename from tests/Feature/SymfonySessionSwooleSessionStorageTest.php rename to tests/Feature/SymfonySessionSwooleSessionIdStorageTest.php index 131d1f76..11bbf34b 100644 --- a/tests/Feature/SymfonySessionSwooleSessionStorageTest.php +++ b/tests/Feature/SymfonySessionSwooleSessionIdStorageTest.php @@ -8,7 +8,7 @@ use K911\Swoole\Tests\Fixtures\Symfony\TestBundle\Test\ServerTestCase; use Swoole\Coroutine; -final class SymfonySessionSwooleSessionStorageTest extends ServerTestCase +final class SymfonySessionSwooleSessionIdStorageTest extends ServerTestCase { protected function setUp(): void { @@ -173,7 +173,6 @@ public function testUpdateSession(): void $response1 = $client->send('/session/1')['response']; $this->assertSame(200, $response1['statusCode']); $this->assertArrayHasKey('SWOOLESSID', $response1['cookies']); - $sessionId1 = $response1['cookies']['SWOOLESSID']; $setCookieHeader1 = $response1['headers']['set-cookie']; $body1 = $response1['body']; @@ -185,6 +184,9 @@ public function testUpdateSession(): void $this->assertArrayHasKey('SWOOLESSID', $response2['cookies']); $sessionId2 = $response2['cookies']['SWOOLESSID']; + + $this->assertNotNull($response2['headers']['set-cookie']); + $setCookieHeader2 = $response2['headers']['set-cookie']; $body2 = $response2['body']; diff --git a/tests/Fixtures/Symfony/TestAppKernel.php b/tests/Fixtures/Symfony/TestAppKernel.php index 3b70c1d8..25ed86b0 100644 --- a/tests/Fixtures/Symfony/TestAppKernel.php +++ b/tests/Fixtures/Symfony/TestAppKernel.php @@ -17,6 +17,7 @@ use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; @@ -100,7 +101,10 @@ public function getProjectDir(): string return __DIR__.'/app'; } - public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + /** + * {@inheritDoc} + */ + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { // Use CacheKernel if available. if (null !== $this->cacheKernel) { @@ -116,6 +120,14 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ return parent::handle($request, $type, $catch); } + /** + * This should always return bool, but we need to coerce it depending on the Symfony version in use. + */ + public function isDebug(): bool + { + return (bool) $this->debug; + } + /** * {@inheritdoc} * diff --git a/tests/Fixtures/Symfony/app/config/session/swoole.yaml b/tests/Fixtures/Symfony/app/config/session/swoole.yaml index e2673a5e..120456e9 100644 --- a/tests/Fixtures/Symfony/app/config/session/swoole.yaml +++ b/tests/Fixtures/Symfony/app/config/session/swoole.yaml @@ -1,12 +1,7 @@ framework: session: enabled: true - storage_id: swoole_bundle.session.table_storage - -swoole: - http_server: - services: - session_cookie_event_listener: true + storage_factory_id: swoole_bundle.session.table_storage_factory parameters: env(COOKIE_LIFETIME): 60 @@ -17,6 +12,6 @@ services: autoconfigure: true public: false - K911\Swoole\Bridge\Symfony\HttpFoundation\Session\SwooleSessionStorage: + K911\Swoole\Bridge\Symfony\HttpFoundation\Session\SwooleSessionStorageFactory: arguments: $lifetimeSeconds: '%env(int:COOKIE_LIFETIME)%' diff --git a/tests/Unit/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorageFactoryTest.php b/tests/Unit/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorageFactoryTest.php new file mode 100644 index 00000000..0cd0ec83 --- /dev/null +++ b/tests/Unit/Bridge/Symfony/HttpFoundation/Session/SwooleSessionStorageFactoryTest.php @@ -0,0 +1,57 @@ +prophesize(StorageInterface::class)->reveal(), + $this->prophesize(EventDispatcherInterface::class)->reveal(), + ); + + $result = $subject->createStorage(new Request()); + + $this->assertInstanceOf( + SwooleSessionStorage::class, + $result + ); + $this->assertFalse($result->isStarted()); + $this->assertSame( + '', + $result->getId() + ); + } + + public function testCreateStorageAddsListenerForSwooleSessionResetEvent(): void + { + $dispatcher = $this->prophesize(EventDispatcherInterface::class); + + $dispatcher->addListener() + ->withArguments([SessionResetEvent::NAME, Argument::type('closure')]) + ->shouldBeCalled() + ; + + $subject = new SwooleSessionStorageFactory( + $this->prophesize(StorageInterface::class)->reveal(), + $dispatcher->reveal(), + ); + + $subject->createStorage(new Request()); + } +} diff --git a/tests/Unit/Client/HttpClientTest.php b/tests/Unit/Client/HttpClientTest.php index 980cf8cd..cbe35402 100644 --- a/tests/Unit/Client/HttpClientTest.php +++ b/tests/Unit/Client/HttpClientTest.php @@ -25,7 +25,7 @@ public function testThatClientSerializesProperly(): void 'options' => $options, ]; - self::assertSame(\json_encode($expected, \JSON_THROW_ON_ERROR), $client->serialize()); + self::assertSame($expected, $client->__serialize()); $serializedClient = \serialize($client); $unserializedClient = \unserialize($serializedClient, ['allowed_classes' => [HttpClient::class]]);