diff --git a/.travis.yml b/.travis.yml
index 247e7bfe..38d21750 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,6 +18,7 @@ matrix:
before_install:
- echo 'extension = memcached.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- echo 'extension = apcu.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+ - yes | pecl install swoole
- phpenv config-rm xdebug.ini || return 0
install:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b51ddc59..8047d86d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
#### Added
-* *Nothing*
+* [#208](https://github.com/shlinkio/shlink/issues/208) Added initial support to run shlink using [swoole](https://www.swoole.co.uk/), a non-blocking IO server which improves the performance of shlink from 4 to 10 times.
+
+ Run shlink with `./vendor/bin/zend-expressive-swoole start` to start-up the service, which will be exposed in port `8080`.
+
+ Adding the `-d` flag, it will be started as a background service. Then you can use the `./vendor/bin/zend-expressive-swoole stop` command in order to stop it.
#### Changed
diff --git a/bin/cli b/bin/cli
index ea8cb5c3..3284eea8 100755
--- a/bin/cli
+++ b/bin/cli
@@ -3,8 +3,11 @@
declare(strict_types=1);
use Interop\Container\ContainerInterface;
+use Shlinkio\Shlink\Common\Exec\ExecutionContext;
use Symfony\Component\Console\Application as CliApp;
/** @var ContainerInterface $container */
$container = include __DIR__ . '/../config/container.php';
+
+putenv(sprintf('CURRENT_SHLINK_CONTEXT=%s', ExecutionContext::CLI));
$container->get(CliApp::class)->run();
diff --git a/composer.json b/composer.json
index 3d325b03..8c79a730 100644
--- a/composer.json
+++ b/composer.json
@@ -42,6 +42,7 @@
"zendframework/zend-expressive-fastroute": "^3.0",
"zendframework/zend-expressive-helpers": "^5.0",
"zendframework/zend-expressive-platesrenderer": "^2.0",
+ "zendframework/zend-expressive-swoole": "^2.0",
"zendframework/zend-i18n": "^2.7",
"zendframework/zend-inputfilter": "^2.8",
"zendframework/zend-paginator": "^2.6",
diff --git a/config/autoload/entity-manager.global.php b/config/autoload/entity-manager.global.php
index 89961f77..951c909f 100644
--- a/config/autoload/entity-manager.global.php
+++ b/config/autoload/entity-manager.global.php
@@ -1,7 +1,7 @@
'data/proxies',
],
'connection' => [
- 'user' => Common\env('DB_USER'),
- 'password' => Common\env('DB_PASSWORD'),
- 'dbname' => Common\env('DB_NAME', 'shlink'),
+ 'user' => env('DB_USER'),
+ 'password' => env('DB_PASSWORD'),
+ 'dbname' => env('DB_NAME', 'shlink'),
'charset' => 'utf8',
],
],
diff --git a/config/autoload/logger.global.php b/config/autoload/logger.global.php
index 363e9210..0477f4b2 100644
--- a/config/autoload/logger.global.php
+++ b/config/autoload/logger.global.php
@@ -4,8 +4,10 @@ declare(strict_types=1);
namespace Shlinkio\Shlink;
use Monolog\Handler\RotatingFileHandler;
+use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Monolog\Processor;
+use Zend\Expressive\Swoole\Log\AccessLogInterface;
use const PHP_EOL;
return [
@@ -19,13 +21,19 @@ return [
],
'handlers' => [
- 'rotating_file_handler' => [
+ 'shlink_rotating_handler' => [
'class' => RotatingFileHandler::class,
'level' => Logger::INFO,
'filename' => 'data/log/shlink_log.log',
'max_files' => 30,
'formatter' => 'dashed',
],
+ 'swoole_access_handler' => [
+ 'class' => StreamHandler::class,
+ 'level' => Logger::INFO,
+ 'stream' => 'php://stdout',
+ 'formatter' => 'dashed',
+ ],
],
'processors' => [
@@ -39,9 +47,30 @@ return [
'loggers' => [
'Shlink' => [
- 'handlers' => ['rotating_file_handler'],
+ 'handlers' => ['shlink_rotating_handler'],
'processors' => ['exception_with_new_line', 'psr3'],
],
+ 'Swoole' => [
+ 'handlers' => ['swoole_access_handler'],
+ 'processors' => ['psr3'],
+ ],
+ ],
+ ],
+
+ 'dependencies' => [
+ 'factories' => [
+ 'Logger_Shlink' => Common\Factory\LoggerFactory::class,
+ 'Logger_Swoole' => Common\Factory\LoggerFactory::class,
+
+ AccessLogInterface::class => Common\Logger\Swoole\AccessLogFactory::class,
+ ],
+ ],
+
+ 'zend-expressive-swoole' => [
+ 'swoole-http-server' => [
+ 'logger' => [
+ 'logger_name' => 'Logger_Swoole',
+ ],
],
],
diff --git a/config/autoload/logger.local.php.dist b/config/autoload/logger.local.php.dist
index 951a3af0..47203ed0 100644
--- a/config/autoload/logger.local.php.dist
+++ b/config/autoload/logger.local.php.dist
@@ -1,11 +1,13 @@
[
'handlers' => [
- 'rotating_file_handler' => [
+ 'shlink_rotating_handler' => [
'level' => Logger::DEBUG,
],
],
diff --git a/config/autoload/middleware-pipeline.global.php b/config/autoload/middleware-pipeline.global.php
index 100fa714..1e7877f5 100644
--- a/config/autoload/middleware-pipeline.global.php
+++ b/config/autoload/middleware-pipeline.global.php
@@ -10,10 +10,18 @@ return [
'middleware_pipeline' => [
'pre-routing' => [
- 'middleware' => [
- ErrorHandler::class,
- Expressive\Helper\ContentLengthMiddleware::class,
- ],
+ 'middleware' => (function () {
+ $middleware = [
+ ErrorHandler::class,
+ Expressive\Helper\ContentLengthMiddleware::class,
+ ];
+
+ if (Common\Exec\ExecutionContext::currentContextIsSwoole()) {
+ $middleware[] = Common\Middleware\CloseDbConnectionMiddleware::class;
+ }
+
+ return $middleware;
+ })(),
'priority' => 12,
],
'pre-routing-rest' => [
diff --git a/config/autoload/swoole.global.php b/config/autoload/swoole.global.php
new file mode 100644
index 00000000..ba60df5f
--- /dev/null
+++ b/config/autoload/swoole.global.php
@@ -0,0 +1,14 @@
+ [
+ 'enable_coroutine' => true,
+
+ 'swoole-http-server' => [
+ 'host' => '0.0.0.0',
+ ],
+ ],
+
+];
diff --git a/config/config.php b/config/config.php
index d9d9132e..e52065d1 100644
--- a/config/config.php
+++ b/config/config.php
@@ -6,7 +6,6 @@ namespace Shlinkio\Shlink;
use Acelaya\ExpressiveErrorHandler;
use Zend\ConfigAggregator;
use Zend\Expressive;
-use function class_exists;
return (new ConfigAggregator\ConfigAggregator([
Expressive\ConfigProvider::class,
@@ -14,9 +13,7 @@ return (new ConfigAggregator\ConfigAggregator([
Expressive\Router\FastRouteRouter\ConfigProvider::class,
Expressive\Plates\ConfigProvider::class,
Expressive\Helper\ConfigProvider::class,
- class_exists(Expressive\Swoole\ConfigProvider::class)
- ? Expressive\Swoole\ConfigProvider::class
- : new ConfigAggregator\ArrayProvider([]),
+ Expressive\Swoole\ConfigProvider::class,
ExpressiveErrorHandler\ConfigProvider::class,
Common\ConfigProvider::class,
Core\ConfigProvider::class,
diff --git a/config/pipeline.php b/config/pipeline.php
new file mode 100644
index 00000000..82d3aeea
--- /dev/null
+++ b/config/pipeline.php
@@ -0,0 +1,6 @@
+
+
+RUN apk update
+
+# Install common php extensions
+RUN docker-php-ext-install pdo_mysql
+RUN docker-php-ext-install iconv
+RUN docker-php-ext-install mbstring
+RUN docker-php-ext-install calendar
+
+RUN apk add --no-cache --virtual sqlite-libs
+RUN apk add --no-cache --virtual sqlite-dev
+RUN docker-php-ext-install pdo_sqlite
+
+RUN apk add --no-cache --virtual icu-dev
+RUN docker-php-ext-install intl
+
+RUN apk add --no-cache --virtual zlib-dev
+RUN docker-php-ext-install zip
+
+RUN apk add --no-cache --virtual libmcrypt-dev
+RUN docker-php-ext-install mcrypt
+
+RUN apk add --no-cache --virtual libpng-dev
+RUN docker-php-ext-install gd
+
+# Install redis extension
+ADD https://github.com/phpredis/phpredis/archive/3.1.4.tar.gz /tmp/phpredis.tar.gz
+RUN mkdir -p /usr/src/php/ext/redis\
+ && tar xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1
+# configure and install
+RUN docker-php-ext-configure redis\
+ && docker-php-ext-install redis
+# cleanup
+RUN rm /tmp/phpredis.tar.gz
+
+# Install memcached extension
+RUN apk add --no-cache --virtual cyrus-sasl-dev
+RUN apk add --no-cache --virtual libmemcached-dev
+ADD https://github.com/php-memcached-dev/php-memcached/archive/php7.tar.gz /tmp/memcached.tar.gz
+RUN mkdir -p /usr/src/php/ext/memcached\
+ && tar xf /tmp/memcached.tar.gz -C /usr/src/php/ext/memcached --strip-components=1
+# configure and install
+RUN docker-php-ext-configure memcached\
+ && docker-php-ext-install memcached
+# cleanup
+RUN rm /tmp/memcached.tar.gz
+
+# Install APCu extension
+ADD https://pecl.php.net/get/apcu-5.1.3.tgz /tmp/apcu.tar.gz
+RUN mkdir -p /usr/src/php/ext/apcu\
+ && tar xf /tmp/apcu.tar.gz -C /usr/src/php/ext/apcu --strip-components=1
+# configure and install
+RUN docker-php-ext-configure apcu\
+ && docker-php-ext-install apcu
+# cleanup
+RUN rm /tmp/apcu.tar.gz
+
+# Install APCu-BC extension
+ADD https://pecl.php.net/get/apcu_bc-1.0.3.tgz /tmp/apcu_bc.tar.gz
+RUN mkdir -p /usr/src/php/ext/apcu-bc\
+ && tar xf /tmp/apcu_bc.tar.gz -C /usr/src/php/ext/apcu-bc --strip-components=1
+# configure and install
+RUN docker-php-ext-configure apcu-bc\
+ && docker-php-ext-install apcu-bc
+# cleanup
+RUN rm /tmp/apcu_bc.tar.gz
+
+# Load APCU.ini before APC.ini
+RUN rm /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini
+RUN echo extension=apcu.so > /usr/local/etc/php/conf.d/20-php-ext-apcu.ini
+
+# Install swoole
+# First line fixes an error when installing pecl extensions. Found in https://github.com/docker-library/php/issues/233
+RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS && \
+ pecl install swoole && \
+ docker-php-ext-enable swoole && \
+ apk del .phpize-deps
+
+# Install composer
+RUN php -r "readfile('https://getcomposer.org/installer');" | php
+RUN chmod +x composer.phar
+RUN mv composer.phar /usr/local/bin/composer
+
+# Make home directory writable by anyone
+RUN chmod 777 /home
+
+VOLUME /home/shlink
+WORKDIR /home/shlink
+
+# Expose swoole port
+EXPOSE 8080
+
+CMD /usr/local/bin/composer update && \
+ # When restarting the container, swoole might think it is already in execution
+ # This forces the app to be started every second until the exit code is 0
+ until php ./vendor/bin/zend-expressive-swoole start; do sleep 1 ; done
diff --git a/docker-compose.override.yml.dist b/docker-compose.override.yml.dist
index b347b9e8..6e40c113 100644
--- a/docker-compose.override.yml.dist
+++ b/docker-compose.override.yml.dist
@@ -6,3 +6,9 @@ services:
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
+
+ shlink_swoole:
+ user: 1000:1000
+ volumes:
+ - /etc/passwd:/etc/passwd:ro
+ - /etc/group:/etc/group:ro
diff --git a/docker-compose.yml b/docker-compose.yml
index 217ae629..3424d925 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -26,6 +26,18 @@ services:
links:
- shlink_db
+ shlink_swoole:
+ container_name: shlink_swoole
+ build:
+ context: .
+ dockerfile: ./data/infra/swoole.Dockerfile
+ ports:
+ - "8080:8080"
+ volumes:
+ - ./:/home/shlink
+ links:
+ - shlink_db
+
shlink_db:
container_name: shlink_db
build:
diff --git a/indocker b/indocker
index c4348e6c..77a06d33 100755
--- a/indocker
+++ b/indocker
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
-docker exec -it shlink_php /bin/sh -c "cd /home/shlink/www && $*"
+docker exec -it shlink_swoole /bin/sh -c "$*"
diff --git a/module/Common/config/dependencies.config.php b/module/Common/config/dependencies.config.php
index b03520a3..64e20563 100644
--- a/module/Common/config/dependencies.config.php
+++ b/module/Common/config/dependencies.config.php
@@ -23,7 +23,6 @@ return [
EntityManager::class => Factory\EntityManagerFactory::class,
GuzzleClient::class => InvokableFactory::class,
Cache::class => Factory\CacheFactory::class,
- 'Logger_Shlink' => Factory\LoggerFactory::class,
Filesystem::class => InvokableFactory::class,
Reader::class => ConfigAbstractFactory::class,
@@ -31,6 +30,7 @@ return [
Template\Extension\TranslatorExtension::class => ConfigAbstractFactory::class,
Middleware\LocaleMiddleware::class => ConfigAbstractFactory::class,
+ Middleware\CloseDbConnectionMiddleware::class => ConfigAbstractFactory::class,
IpAddress::class => Middleware\IpAddressMiddlewareFactory::class,
Image\ImageBuilder::class => Image\ImageBuilderFactory::class,
@@ -78,6 +78,7 @@ return [
Template\Extension\TranslatorExtension::class => ['translator'],
Middleware\LocaleMiddleware::class => ['translator'],
+ Middleware\CloseDbConnectionMiddleware::class => ['em'],
IpGeolocation\IpApiLocationResolver::class => ['httpClient'],
IpGeolocation\GeoLite2LocationResolver::class => [Reader::class],
diff --git a/module/Common/src/Exec/ExecutionContext.php b/module/Common/src/Exec/ExecutionContext.php
new file mode 100644
index 00000000..214d7571
--- /dev/null
+++ b/module/Common/src/Exec/ExecutionContext.php
@@ -0,0 +1,18 @@
+get('config');
- return Translator::factory(isset($config['translator']) ? $config['translator'] : []);
+ return Translator::factory($config['translator'] ?? []);
}
}
diff --git a/module/Common/src/Logger/Swoole/AccessLogFactory.php b/module/Common/src/Logger/Swoole/AccessLogFactory.php
new file mode 100644
index 00000000..60822762
--- /dev/null
+++ b/module/Common/src/Logger/Swoole/AccessLogFactory.php
@@ -0,0 +1,51 @@
+has('config') ? $container->get('config') : [];
+ $config = $config['zend-expressive-swoole']['swoole-http-server']['logger'] ?? [];
+
+ return new Log\Psr3AccessLogDecorator(
+ $this->getLogger($container, $config),
+ $this->getFormatter($container, $config),
+ $config['use-hostname-lookups'] ?? false
+ );
+ }
+
+ private function getLogger(ContainerInterface $container, array $config): LoggerInterface
+ {
+ $loggerName = $config['logger_name'] ?? LoggerInterface::class;
+ return $container->has($loggerName) ? $container->get($loggerName) : new Log\StdoutLogger();
+ }
+
+ private function getFormatter(ContainerInterface $container, array $config): Log\AccessLogFormatterInterface
+ {
+ if ($container->has(Log\AccessLogFormatterInterface::class)) {
+ return $container->get(Log\AccessLogFormatterInterface::class);
+ }
+
+ return new Log\AccessLogFormatter($config['format'] ?? Log\AccessLogFormatter::FORMAT_COMMON);
+ }
+}
diff --git a/module/Common/src/Middleware/CloseDbConnectionMiddleware.php b/module/Common/src/Middleware/CloseDbConnectionMiddleware.php
new file mode 100644
index 00000000..c365da51
--- /dev/null
+++ b/module/Common/src/Middleware/CloseDbConnectionMiddleware.php
@@ -0,0 +1,34 @@
+em = $em;
+ }
+
+ /**
+ * Process an incoming server request and return a response, optionally delegating
+ * response creation to a handler.
+ */
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+ {
+ $handledRequest = $handler->handle($request);
+ $this->em->getConnection()->close();
+ $this->em->clear();
+
+ return $handledRequest;
+ }
+}
diff --git a/module/Common/test/Logger/Swoole/AccessLogFactoryTest.php b/module/Common/test/Logger/Swoole/AccessLogFactoryTest.php
new file mode 100644
index 00000000..986c117a
--- /dev/null
+++ b/module/Common/test/Logger/Swoole/AccessLogFactoryTest.php
@@ -0,0 +1,138 @@
+factory = new AccessLogFactory();
+ }
+
+ /**
+ * @test
+ */
+ public function createsService()
+ {
+ $service = ($this->factory)(new ServiceManager(), '');
+ $this->assertInstanceOf(Psr3AccessLogDecorator::class, $service);
+ }
+
+ /**
+ * @test
+ * @dataProvider provideLoggers
+ * @param array $config
+ * @param string|LoggerInterface $expectedLogger
+ */
+ public function wrapsProperLogger(array $config, $expectedLogger)
+ {
+ $service = ($this->factory)(new ServiceManager(['services' => $config]), '');
+
+ $ref = new ReflectionObject($service);
+ $loggerProp = $ref->getProperty('logger');
+ $loggerProp->setAccessible(true);
+ $logger = $loggerProp->getValue($service);
+
+ if (is_string($expectedLogger)) {
+ $this->assertInstanceOf($expectedLogger, $logger);
+ } else {
+ $this->assertSame($expectedLogger, $logger);
+ }
+ }
+
+ public function provideLoggers(): iterable
+ {
+ yield 'without-any-logger' => [[], StdoutLogger::class];
+ yield 'with-standard-logger' => (function () {
+ $logger = new NullLogger();
+ return [[LoggerInterface::class => $logger], $logger];
+ })();
+ yield 'with-custom-logger' => (function () {
+ $logger = new NullLogger();
+ return [[
+ 'config' => [
+ 'zend-expressive-swoole' => [
+ 'swoole-http-server' => [
+ 'logger' => [
+ 'logger_name' => 'my-logger',
+ ],
+ ],
+ ],
+ ],
+ 'my-logger' => $logger,
+ ], $logger];
+ })();
+ }
+
+ /**
+ * @test
+ * @dataProvider provideFormatters
+ * @param array $config
+ * @param string|AccessLogFormatterInterface $expectedFormatter
+ */
+ public function wrappsProperFormatter(array $config, $expectedFormatter, string $expectedFormat)
+ {
+ $service = ($this->factory)(new ServiceManager(['services' => $config]), '');
+
+ $ref = new ReflectionObject($service);
+ $formatterProp = $ref->getProperty('formatter');
+ $formatterProp->setAccessible(true);
+ $formatter = $formatterProp->getValue($service);
+
+ $ref = new ReflectionObject($formatter);
+ $formatProp = $ref->getProperty('format');
+ $formatProp->setAccessible(true);
+ $format = $formatProp->getValue($formatter);
+
+ if (is_string($expectedFormatter)) {
+ $this->assertInstanceOf($expectedFormatter, $formatter);
+ } else {
+ $this->assertSame($expectedFormatter, $formatter);
+ }
+ $this->assertSame($expectedFormat, $format);
+ }
+
+ public function provideFormatters(): iterable
+ {
+ yield 'with-registered-formatter-and-default-format' => (function () {
+ $formatter = new AccessLogFormatter();
+ return [[AccessLogFormatterInterface::class => $formatter], $formatter, AccessLogFormatter::FORMAT_COMMON];
+ })();
+ yield 'with-registered-formatter-and-custom-format' => (function () {
+ $formatter = new AccessLogFormatter(AccessLogFormatter::FORMAT_AGENT);
+ return [[AccessLogFormatterInterface::class => $formatter], $formatter, AccessLogFormatter::FORMAT_AGENT];
+ })();
+ yield 'with-no-formatter-and-not-configured-format' => [
+ [],
+ AccessLogFormatter::class,
+ AccessLogFormatter::FORMAT_COMMON,
+ ];
+ yield 'with-no-formatter-and-configured-format' => [[
+ 'config' => [
+ 'zend-expressive-swoole' => [
+ 'swoole-http-server' => [
+ 'logger' => [
+ 'format' => AccessLogFormatter::FORMAT_COMBINED_DEBIAN,
+ ],
+ ],
+ ],
+ ],
+ ], AccessLogFormatter::class, AccessLogFormatter::FORMAT_COMBINED_DEBIAN];
+ }
+}
diff --git a/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php b/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php
new file mode 100644
index 00000000..36bdff87
--- /dev/null
+++ b/module/Common/test/Middleware/CloseDbConnectionMiddlewareTest.php
@@ -0,0 +1,56 @@
+handler = $this->prophesize(RequestHandlerInterface::class);
+ $this->em = $this->prophesize(EntityManagerInterface::class);
+
+ $this->middleware = new CloseDbConnectionMiddleware($this->em->reveal());
+ }
+
+ /**
+ * @test
+ */
+ public function connectionIsClosedWhenMiddlewareIsProcessed()
+ {
+ $req = ServerRequestFactory::fromGlobals();
+ $resp = new Response();
+
+ $conn = $this->prophesize(Connection::class);
+ $closeConn = $conn->close()->will(function () {
+ });
+ $getConn = $this->em->getConnection()->willReturn($conn->reveal());
+ $clear = $this->em->clear()->will(function () {
+ });
+ $handle = $this->handler->handle($req)->willReturn($resp);
+
+ $result = $this->middleware->process($req, $this->handler->reveal());
+
+ $this->assertSame($result, $resp);
+ $getConn->shouldHaveBeenCalledOnce();
+ $closeConn->shouldHaveBeenCalledOnce();
+ $clear->shouldHaveBeenCalledOnce();
+ $handle->shouldHaveBeenCalledOnce();
+ }
+}
diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php
index 89627675..4a7edcfb 100644
--- a/module/Core/src/Service/UrlShortener.php
+++ b/module/Core/src/Service/UrlShortener.php
@@ -107,13 +107,7 @@ class UrlShortener implements UrlShortenerInterface
}
}
- /**
- * Tries to perform a GET request to provided url, returning true on success and false on failure
- *
- * @param UriInterface $url
- * @return void
- */
- private function checkUrlExists(UriInterface $url)
+ private function checkUrlExists(UriInterface $url): void
{
try {
$this->httpClient->request('GET', $url, ['allow_redirects' => [
@@ -124,12 +118,6 @@ class UrlShortener implements UrlShortenerInterface
}
}
- /**
- * Generates the unique shortcode for an autoincrement ID
- *
- * @param float $id
- * @return string
- */
private function convertAutoincrementIdToShortCode(float $id): string
{
$id += self::ID_INCREMENT; // Increment the Id so that the generated shortcode is not too short
@@ -145,7 +133,7 @@ class UrlShortener implements UrlShortenerInterface
return $this->chars[(int) $id] . $code;
}
- private function processCustomSlug($customSlug)
+ private function processCustomSlug(?string $customSlug): ?string
{
if ($customSlug === null) {
return null;
diff --git a/phpcs.xml b/phpcs.xml
index e1d56d5a..40a10e91 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -56,4 +56,5 @@
config
public/index.php
config/params/*
+ public/index.php
diff --git a/public/index.php b/public/index.php
index fa5d778c..f9469830 100644
--- a/public/index.php
+++ b/public/index.php
@@ -2,8 +2,11 @@
declare(strict_types=1);
use Psr\Container\ContainerInterface;
+use Shlinkio\Shlink\Common\Exec\ExecutionContext;
use Zend\Expressive\Application;
/** @var ContainerInterface $container */
$container = include __DIR__ . '/../config/container.php';
+
+putenv(sprintf('CURRENT_SHLINK_CONTEXT=%s', ExecutionContext::WEB));
$container->get(Application::class)->run();