mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
commit
afa2a5b0f0
26 changed files with 510 additions and 33 deletions
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
3
bin/cli
3
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();
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
|
@ -10,9 +10,9 @@ return [
|
|||
'proxies_dir' => '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',
|
||||
],
|
||||
],
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
return [
|
||||
|
||||
'logger' => [
|
||||
'handlers' => [
|
||||
'rotating_file_handler' => [
|
||||
'shlink_rotating_handler' => [
|
||||
'level' => Logger::DEBUG,
|
||||
],
|
||||
],
|
||||
|
|
|
@ -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' => [
|
||||
|
|
14
config/autoload/swoole.global.php
Normal file
14
config/autoload/swoole.global.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'zend-expressive-swoole' => [
|
||||
'enable_coroutine' => true,
|
||||
|
||||
'swoole-http-server' => [
|
||||
'host' => '0.0.0.0',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -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,
|
||||
|
|
6
config/pipeline.php
Normal file
6
config/pipeline.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
// FIXME Dummy file just to prevent expressive-swoole fail while loading
|
||||
return function () {
|
||||
};
|
6
config/routes.php
Normal file
6
config/routes.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
// FIXME Dummy file just to prevent expressive-swoole fail while loading
|
||||
return function () {
|
||||
};
|
98
data/infra/swoole.Dockerfile
Normal file
98
data/infra/swoole.Dockerfile
Normal file
|
@ -0,0 +1,98 @@
|
|||
FROM php:7.1.22-cli-alpine3.7
|
||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
2
indocker
2
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 "$*"
|
||||
|
|
|
@ -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],
|
||||
|
|
18
module/Common/src/Exec/ExecutionContext.php
Normal file
18
module/Common/src/Exec/ExecutionContext.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Exec;
|
||||
|
||||
use const PHP_SAPI;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
abstract class ExecutionContext
|
||||
{
|
||||
public const WEB = 'shlink_web';
|
||||
public const CLI = 'shlink_cli';
|
||||
|
||||
public static function currentContextIsSwoole(): bool
|
||||
{
|
||||
return PHP_SAPI === 'cli' && env('CURRENT_SHLINK_CONTEXT', self::WEB) === self::WEB;
|
||||
}
|
||||
}
|
|
@ -27,6 +27,6 @@ class TranslatorFactory implements FactoryInterface
|
|||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
$config = $container->get('config');
|
||||
return Translator::factory(isset($config['translator']) ? $config['translator'] : []);
|
||||
return Translator::factory($config['translator'] ?? []);
|
||||
}
|
||||
}
|
||||
|
|
51
module/Common/src/Logger/Swoole/AccessLogFactory.php
Normal file
51
module/Common/src/Logger/Swoole/AccessLogFactory.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Logger\Swoole;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Zend\Expressive\Swoole\Log;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class AccessLogFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when creating a service.
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
$config = $container->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);
|
||||
}
|
||||
}
|
34
module/Common/src/Middleware/CloseDbConnectionMiddleware.php
Normal file
34
module/Common/src/Middleware/CloseDbConnectionMiddleware.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class CloseDbConnectionMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/** @var EntityManagerInterface */
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
138
module/Common/test/Logger/Swoole/AccessLogFactoryTest.php
Normal file
138
module/Common/test/Logger/Swoole/AccessLogFactoryTest.php
Normal file
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Logger\Swoole;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use ReflectionObject;
|
||||
use Shlinkio\Shlink\Common\Logger\Swoole\AccessLogFactory;
|
||||
use Zend\Expressive\Swoole\Log\AccessLogFormatter;
|
||||
use Zend\Expressive\Swoole\Log\AccessLogFormatterInterface;
|
||||
use Zend\Expressive\Swoole\Log\Psr3AccessLogDecorator;
|
||||
use Zend\Expressive\Swoole\Log\StdoutLogger;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
use function is_string;
|
||||
|
||||
class AccessLogFactoryTest extends TestCase
|
||||
{
|
||||
/** @var AccessLogFactory */
|
||||
private $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->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];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Common\Middleware\CloseDbConnectionMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
|
||||
class CloseDbConnectionMiddlewareTest extends TestCase
|
||||
{
|
||||
/** @var CloseDbConnectionMiddleware */
|
||||
private $middleware;
|
||||
/** @var ObjectProphecy */
|
||||
private $handler;
|
||||
/** @var ObjectProphecy */
|
||||
private $em;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -56,4 +56,5 @@
|
|||
<file>config</file>
|
||||
<file>public/index.php</file>
|
||||
<exclude-pattern>config/params/*</exclude-pattern>
|
||||
<exclude-pattern>public/index.php</exclude-pattern>
|
||||
</ruleset>
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue