mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Merge pull request #462 from acelaya/feature/external-shlink-common
Feature/external shlink common
This commit is contained in:
commit
456765e55b
71 changed files with 23 additions and 2683 deletions
|
@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
|||
|
||||
* [#450](https://github.com/shlinkio/shlink/issues/450) Added PHP 7.4 to the build matrix, as an allowed-to-fail env.
|
||||
* [#443](https://github.com/shlinkio/shlink/issues/443) Split some logic into independent modules.
|
||||
* [#451](https://github.com/shlinkio/shlink/issues/451) Updated to infection 0.13.
|
||||
|
||||
#### Deprecated
|
||||
|
||||
|
|
|
@ -25,19 +25,20 @@
|
|||
"endroid/qr-code": "^1.7",
|
||||
"firebase/php-jwt": "^4.0",
|
||||
"geoip2/geoip2": "^2.9",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
"lstrojny/functional-php": "^1.8",
|
||||
"guzzlehttp/guzzle": "^6.3",
|
||||
"lstrojny/functional-php": "^1.9",
|
||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||
"monolog/monolog": "^1.21",
|
||||
"monolog/monolog": "^1.24",
|
||||
"ocramius/proxy-manager": "~2.2.2",
|
||||
"phly/phly-event-dispatcher": "^1.0",
|
||||
"predis/predis": "^1.1",
|
||||
"shlinkio/shlink-common": "^1.0",
|
||||
"shlinkio/shlink-installer": "^1.2.1",
|
||||
"symfony/console": "^4.3",
|
||||
"symfony/filesystem": "^4.3",
|
||||
"symfony/lock": "^4.3",
|
||||
"symfony/process": "^4.3",
|
||||
"theorchard/monolog-cascade": "^0.4",
|
||||
"theorchard/monolog-cascade": "^0.5",
|
||||
"zendframework/zend-config": "^3.3",
|
||||
"zendframework/zend-config-aggregator": "^1.1",
|
||||
"zendframework/zend-diactoros": "^2.1.3",
|
||||
|
@ -56,7 +57,7 @@
|
|||
"devster/ubench": "^2.0",
|
||||
"eaglewu/swoole-ide-helper": "dev-master",
|
||||
"filp/whoops": "^2.4",
|
||||
"infection/infection": "^0.12.2",
|
||||
"infection/infection": "^0.13.4",
|
||||
"phpstan/phpstan": "^0.11.2",
|
||||
"phpunit/phpcov": "^6.0",
|
||||
"phpunit/phpunit": "^8.3",
|
||||
|
@ -73,13 +74,11 @@
|
|||
"Shlinkio\\Shlink\\CLI\\": "module/CLI/src",
|
||||
"Shlinkio\\Shlink\\Rest\\": "module/Rest/src",
|
||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src",
|
||||
"Shlinkio\\Shlink\\Common\\": "module/Common/src",
|
||||
"Shlinkio\\Shlink\\EventDispatcher\\": "module/EventDispatcher/src",
|
||||
"Shlinkio\\Shlink\\IpGeolocation\\": "module/IpGeolocation/src/",
|
||||
"Shlinkio\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/src/"
|
||||
},
|
||||
"files": [
|
||||
"module/Common/functions/functions.php",
|
||||
"module/EventDispatcher/functions/functions.php"
|
||||
]
|
||||
},
|
||||
|
@ -92,7 +91,6 @@
|
|||
"module/Core/test",
|
||||
"module/Core/test-db"
|
||||
],
|
||||
"ShlinkioTest\\Shlink\\Common\\": "module/Common/test",
|
||||
"ShlinkioTest\\Shlink\\EventDispatcher\\": "module/EventDispatcher/test",
|
||||
"ShlinkioTest\\Shlink\\IpGeolocation\\": "module/IpGeolocation/test",
|
||||
"ShlinkioTest\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/test"
|
||||
|
@ -108,7 +106,7 @@
|
|||
|
||||
"cs": "phpcs",
|
||||
"cs:fix": "phpcbf",
|
||||
"stan": "phpstan analyse module/*/src/ --level=5 -c phpstan.neon",
|
||||
"stan": "phpstan analyse module/*/src/ module/*/config config --level=5 -c phpstan.neon",
|
||||
|
||||
"test": [
|
||||
"@test:unit",
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Alejandro Celaya
|
||||
|
||||
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.
|
|
@ -1,89 +0,0 @@
|
|||
# Shlink Common
|
||||
|
||||
This library provides some utils and conventions for web apps. It's main purpose is to be used on [Shlink](https://github.com/shlinkio/shlink) project, but any PHP project can take advantage.
|
||||
|
||||
Most of the elements it provides require a [PSR-11] container, and it's easy to integrate on [expressive] applications thanks to the `ConfigProvider` it includes.
|
||||
|
||||
## Install
|
||||
|
||||
Install this library using composer:
|
||||
|
||||
composer require shlinkio/shlink-common
|
||||
|
||||
> This library is also an expressive module which provides its own `ConfigProvider`. Add it to your configuration to get everything automatically set up.
|
||||
|
||||
## Cache
|
||||
|
||||
A [doctrine cache] adapter is registered, which returns different instances depending on your configuration:
|
||||
|
||||
* An `ArrayCache` instance when the `debug` config is set to true or when the APUc extension is not installed and the `cache.redis` config is not defined.
|
||||
* An `ApcuCache`instance when no `cache.redis` is defined and the APCu extension is installed.
|
||||
* A `PredisCache` instance when the `cache.redis` config is defined.
|
||||
|
||||
Any of the adapters will use the namespace defined in `cache.namespace` config entry.
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'debug' => false,
|
||||
|
||||
'cache' => [
|
||||
'namespace' => 'my_namespace',
|
||||
'redis' => [
|
||||
'servers' => [
|
||||
'tcp://1.1.1.1:6379',
|
||||
'tcp://2.2.2.2:6379',
|
||||
'tcp://3.3.3.3:6379',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
```
|
||||
|
||||
When the `cache.redis` config is provided, a set of servers is expected. If only one server is provided, this library will treat it as a regular server, but if several servers are defined, it will treat them as a redis cluster and expect the servers to be configured as such.
|
||||
|
||||
## Middlewares
|
||||
|
||||
This module provides a set of useful middlewares, all registered as services in the container:
|
||||
|
||||
* **CloseDatabaseConnectionMiddleware**:
|
||||
|
||||
Should be an early middleware in the pipeline. It makes use of the EntityManager that ensure the database connection is closed at the end of the request.
|
||||
|
||||
It should be used when serving an app with a non-blocking IO server (like Swoole or ReactPHP), which persist services between requests.
|
||||
|
||||
* **LocaleMiddleware**:
|
||||
|
||||
Sets the locale in the translator, based on the `Accapt-Language` header.
|
||||
|
||||
* **IpAddress** (from [akrabat/ip-address-middleware] package):
|
||||
|
||||
Improves detection of the remote IP address.
|
||||
|
||||
The set of headers which are inspected in order to search for the address can be customized using this configuration:
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'ip_address_resolution' => [
|
||||
'headers_to_inspect' => [
|
||||
'CF-Connecting-IP',
|
||||
'True-Client-IP',
|
||||
'X-Real-IP',
|
||||
'Forwarded',
|
||||
'X-Forwarded-For',
|
||||
'X-Forwarded',
|
||||
'X-Cluster-Client-Ip',
|
||||
'Client-Ip',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
```
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use Doctrine\Common\Cache as DoctrineCache;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
DoctrineCache\Cache::class => Cache\CacheFactory::class,
|
||||
Cache\RedisFactory::SERVICE_NAME => Cache\RedisFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RKA\Middleware\IpAddress;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
GuzzleClient::class => InvokableFactory::class,
|
||||
Filesystem::class => InvokableFactory::class,
|
||||
|
||||
Translator::class => I18n\TranslatorFactory::class,
|
||||
Template\Extension\TranslatorExtension::class => ConfigAbstractFactory::class,
|
||||
|
||||
Middleware\LocaleMiddleware::class => ConfigAbstractFactory::class,
|
||||
Middleware\CloseDbConnectionMiddleware::class => ConfigAbstractFactory::class,
|
||||
IpAddress::class => Middleware\IpAddressMiddlewareFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'httpClient' => GuzzleClient::class,
|
||||
'translator' => Translator::class,
|
||||
|
||||
'logger' => LoggerInterface::class,
|
||||
Logger::class => 'Logger_Shlink',
|
||||
LoggerInterface::class => 'Logger_Shlink',
|
||||
],
|
||||
'abstract_factories' => [
|
||||
Factory\DottedAccessConfigAbstractFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Template\Extension\TranslatorExtension::class => ['translator'],
|
||||
Middleware\LocaleMiddleware::class => ['translator'],
|
||||
Middleware\CloseDbConnectionMiddleware::class => ['em'],
|
||||
],
|
||||
|
||||
];
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
'orm' => [
|
||||
'types' => [
|
||||
Doctrine\Type\ChronosDateTimeType::CHRONOS_DATETIME => Doctrine\Type\ChronosDateTimeType::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
EntityManager::class => Doctrine\EntityManagerFactory::class,
|
||||
Connection::class => Doctrine\ConnectionFactory::class,
|
||||
Doctrine\NoDbNameConnectionFactory::SERVICE_NAME => Doctrine\NoDbNameConnectionFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'em' => EntityManager::class,
|
||||
],
|
||||
'delegators' => [
|
||||
EntityManager::class => [
|
||||
Doctrine\ReopeningEntityManagerDelegator::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common\Template\Extension\TranslatorExtension;
|
||||
|
||||
return [
|
||||
|
||||
'plates' => [
|
||||
'extensions' => [
|
||||
TranslatorExtension::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use Zend\Config\Factory;
|
||||
use Zend\Stdlib\Glob;
|
||||
|
||||
use function getenv;
|
||||
use function json_decode as spl_json_decode;
|
||||
use function json_last_error;
|
||||
use function json_last_error_msg;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
|
||||
use const JSON_ERROR_NONE;
|
||||
|
||||
/**
|
||||
* Gets the value of an environment variable. Supports boolean, empty and null.
|
||||
* This is basically Laravel's env helper
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
* @link https://github.com/laravel/framework/blob/5.2/src/Illuminate/Foundation/helpers.php#L369
|
||||
*/
|
||||
function env($key, $default = null)
|
||||
{
|
||||
$value = getenv($key);
|
||||
if ($value === false) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
switch (strtolower($value)) {
|
||||
case 'true':
|
||||
case '(true)':
|
||||
return true;
|
||||
case 'false':
|
||||
case '(false)':
|
||||
return false;
|
||||
case 'empty':
|
||||
case '(empty)':
|
||||
return '';
|
||||
case 'null':
|
||||
case '(null)':
|
||||
return null;
|
||||
}
|
||||
|
||||
return trim($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
function json_decode(string $json, int $depth = 512, int $options = 0): array
|
||||
{
|
||||
$data = spl_json_decode($json, true, $depth, $options);
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new Exception\InvalidArgumentException(sprintf('Error decoding JSON: %s', json_last_error_msg()));
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration files which match provided glob pattern, and returns the merged result as array
|
||||
*/
|
||||
function loadConfigFromGlob(string $globPattern): array
|
||||
{
|
||||
return Factory::fromFiles(Glob::glob($globPattern, Glob::GLOB_BRACE));
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Cache;
|
||||
|
||||
use Doctrine\Common\Cache;
|
||||
use Predis\Client as PredisClient;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function extension_loaded;
|
||||
|
||||
class CacheFactory
|
||||
{
|
||||
/** @var callable|null */
|
||||
private $apcuEnabled;
|
||||
|
||||
public function __construct(?callable $apcuEnabled = null)
|
||||
{
|
||||
$this->apcuEnabled = $apcuEnabled ?? function () {
|
||||
return extension_loaded('apcu');
|
||||
};
|
||||
}
|
||||
|
||||
public function __invoke(ContainerInterface $container): Cache\CacheProvider
|
||||
{
|
||||
$config = $container->get('config');
|
||||
$adapter = $this->buildAdapter($config, $container);
|
||||
$adapter->setNamespace($config['cache']['namespace'] ?? '');
|
||||
|
||||
return $adapter;
|
||||
}
|
||||
|
||||
private function buildAdapter(array $config, ContainerInterface $container): Cache\CacheProvider
|
||||
{
|
||||
$isDebug = (bool) ($config['debug'] ?? false);
|
||||
$redisConfig = $config['cache']['redis'] ?? null;
|
||||
$apcuEnabled = ($this->apcuEnabled)();
|
||||
|
||||
if ($isDebug || (! $apcuEnabled && $redisConfig === null)) {
|
||||
return new Cache\ArrayCache();
|
||||
}
|
||||
|
||||
if ($redisConfig === null) {
|
||||
return new Cache\ApcuCache();
|
||||
}
|
||||
|
||||
/** @var PredisClient $predis */
|
||||
$predis = $container->get(RedisFactory::SERVICE_NAME);
|
||||
return new Cache\PredisCache($predis);
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Cache;
|
||||
|
||||
use Predis\Client as PredisClient;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function count;
|
||||
use function explode;
|
||||
use function is_string;
|
||||
|
||||
class RedisFactory
|
||||
{
|
||||
public const SERVICE_NAME = 'Shlinkio\Shlink\Common\Cache\Redis';
|
||||
|
||||
public function __invoke(ContainerInterface $container): PredisClient
|
||||
{
|
||||
$config = $container->get('config');
|
||||
$redisConfig = $config['cache']['redis'] ?? $config['redis'] ?? [];
|
||||
|
||||
$servers = $redisConfig['servers'] ?? [];
|
||||
$servers = is_string($servers) ? explode(',', $servers) : $servers;
|
||||
$options = count($servers) <= 1 ? null : ['cluster' => 'redis'];
|
||||
|
||||
return new PredisClient($servers, $options);
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
public function __invoke(): array
|
||||
{
|
||||
return loadConfigFromGlob(__DIR__ . '/../config/{,*.}config.php');
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class ConnectionFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container): Connection
|
||||
{
|
||||
$em = $container->get(EntityManager::class);
|
||||
return $em->getConnection();
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\Common\Persistence\Mapping\Driver\PHPDriver;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class EntityManagerFactory
|
||||
{
|
||||
/**
|
||||
* @throws ORMException
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container): EntityManager
|
||||
{
|
||||
$globalConfig = $container->get('config');
|
||||
$isDevMode = (bool) ($globalConfig['debug'] ?? false);
|
||||
$cache = $container->has(Cache::class) ? $container->get(Cache::class) : new ArrayCache();
|
||||
$emConfig = $globalConfig['entity_manager'] ?? [];
|
||||
$connectionConfig = $emConfig['connection'] ?? [];
|
||||
$ormConfig = $emConfig['orm'] ?? [];
|
||||
|
||||
$this->registerTypes($ormConfig);
|
||||
|
||||
$config = Setup::createConfiguration($isDevMode, $ormConfig['proxies_dir'] ?? null, $cache);
|
||||
$config->setMetadataDriverImpl(new PHPDriver($ormConfig['entities_mappings'] ?? []));
|
||||
|
||||
return EntityManager::create($connectionConfig, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
private function registerTypes(array $ormConfig): void
|
||||
{
|
||||
$types = $ormConfig['types'] ?? [];
|
||||
|
||||
foreach ($types as $name => $className) {
|
||||
if (! Type::hasType($name)) {
|
||||
Type::addType($name, $className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class NoDbNameConnectionFactory
|
||||
{
|
||||
public const SERVICE_NAME = 'Shlinkio\Shlink\Common\Doctrine\NoDbNameConnection';
|
||||
|
||||
public function __invoke(ContainerInterface $container): Connection
|
||||
{
|
||||
$conn = $container->get(Connection::class);
|
||||
$params = $conn->getParams();
|
||||
unset($params['dbname']);
|
||||
|
||||
return new Connection($params, $conn->getDriver(), $conn->getConfiguration(), $conn->getEventManager());
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\ORM\Decorator\EntityManagerDecorator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class ReopeningEntityManager extends EntityManagerDecorator
|
||||
{
|
||||
/** @var callable */
|
||||
private $emFactory;
|
||||
|
||||
public function __construct(EntityManagerInterface $wrapped, callable $emFactory)
|
||||
{
|
||||
parent::__construct($wrapped);
|
||||
$this->emFactory = $emFactory;
|
||||
}
|
||||
|
||||
protected function getWrappedEntityManager(): EntityManagerInterface
|
||||
{
|
||||
if (! $this->wrapped->isOpen()) {
|
||||
$this->wrapped = ($this->emFactory)(
|
||||
$this->wrapped->getConnection(),
|
||||
$this->wrapped->getConfiguration(),
|
||||
$this->wrapped->getEventManager()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->wrapped;
|
||||
}
|
||||
|
||||
public function flush($entity = null): void
|
||||
{
|
||||
$this->getWrappedEntityManager()->flush($entity);
|
||||
}
|
||||
|
||||
public function persist($object): void
|
||||
{
|
||||
$this->getWrappedEntityManager()->persist($object);
|
||||
}
|
||||
|
||||
public function remove($object): void
|
||||
{
|
||||
$this->getWrappedEntityManager()->remove($object);
|
||||
}
|
||||
|
||||
public function refresh($object): void
|
||||
{
|
||||
$this->getWrappedEntityManager()->refresh($object);
|
||||
}
|
||||
|
||||
public function merge($object)
|
||||
{
|
||||
return $this->getWrappedEntityManager()->merge($object);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class ReopeningEntityManagerDelegator
|
||||
{
|
||||
public function __invoke(ContainerInterface $container, string $name, callable $callback): ReopeningEntityManager
|
||||
{
|
||||
return new ReopeningEntityManager($callback(), [EntityManager::class, 'create']);
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Doctrine\Type;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\DateTimeImmutableType;
|
||||
|
||||
class ChronosDateTimeType extends DateTimeImmutableType
|
||||
{
|
||||
public const CHRONOS_DATETIME = 'chronos_datetime';
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return self::CHRONOS_DATETIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConversionException
|
||||
*/
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform): ?Chronos
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dateTime = parent::convertToPHPValue($value, $platform);
|
||||
return Chronos::instance($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConversionException
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
|
||||
{
|
||||
if (null === $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
return $value->format($platform->getDateTimeFormatString());
|
||||
}
|
||||
|
||||
throw ConversionException::conversionFailedInvalidType(
|
||||
$value,
|
||||
$this->getName(),
|
||||
['null', DateTimeInterface::class]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Entity;
|
||||
|
||||
abstract class AbstractEntity
|
||||
{
|
||||
/** @var string */
|
||||
protected $id;
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setId(string $id): self
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface ExceptionInterface extends Throwable
|
||||
{
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
|
||||
use InvalidArgumentException as SplInvalidArgumentException;
|
||||
|
||||
class InvalidArgumentException extends SplInvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
|
||||
use ArrayAccess;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\AbstractFactoryInterface;
|
||||
|
||||
use function array_shift;
|
||||
use function explode;
|
||||
use function is_array;
|
||||
use function sprintf;
|
||||
use function substr_count;
|
||||
|
||||
class DottedAccessConfigAbstractFactory implements AbstractFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Can the factory create an instance for the service?
|
||||
*
|
||||
* @param string $requestedName
|
||||
*/
|
||||
public function canCreate(ContainerInterface $container, $requestedName): bool
|
||||
{
|
||||
return substr_count($requestedName, '.') > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when
|
||||
* creating a service.
|
||||
* @throws ContainerException if any other error occurs
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null)
|
||||
{
|
||||
$parts = explode('.', $requestedName);
|
||||
$serviceName = array_shift($parts);
|
||||
if (! $container->has($serviceName)) {
|
||||
throw new ServiceNotCreatedException(sprintf(
|
||||
'Defined service "%s" could not be found in container after resolving dotted expression "%s".',
|
||||
$serviceName,
|
||||
$requestedName
|
||||
));
|
||||
}
|
||||
|
||||
$array = $container->get($serviceName);
|
||||
return $this->readKeysFromArray($parts, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @param array|\ArrayAccess $array
|
||||
* @return mixed|null
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function readKeysFromArray(array $keys, $array)
|
||||
{
|
||||
$key = array_shift($keys);
|
||||
|
||||
// When one of the provided keys is not found, throw an exception
|
||||
if (! isset($array[$key])) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The key "%s" provided in the dotted notation could not be found in the array service',
|
||||
$key
|
||||
));
|
||||
}
|
||||
|
||||
$value = $array[$key];
|
||||
if (! empty($keys) && (is_array($value) || $value instanceof ArrayAccess)) {
|
||||
$value = $this->readKeysFromArray($keys, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\I18n;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class TranslatorFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container): Translator
|
||||
{
|
||||
$config = $container->get('config');
|
||||
return Translator::factory($config['translator'] ?? []);
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Lock;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Symfony\Component\Lock\Store\RetryTillSaveStore;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
|
||||
class RetryLockStoreDelegatorFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container, $name, callable $callback): RetryTillSaveStore
|
||||
{
|
||||
/** @var StoreInterface $originalStore */
|
||||
$originalStore = $callback();
|
||||
return new RetryTillSaveStore($originalStore);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Logger;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log;
|
||||
|
||||
class LoggerAwareDelegatorFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container, $name, callable $callback)
|
||||
{
|
||||
$instance = $callback();
|
||||
if ($instance instanceof Log\LoggerAwareInterface) {
|
||||
$instance->setLogger($container->get(Log\LoggerInterface::class));
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Logger;
|
||||
|
||||
use Cascade\Cascade;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Monolog\Logger;
|
||||
|
||||
use function count;
|
||||
use function explode;
|
||||
|
||||
class LoggerFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container, string $requestedName, ?array $options = null): Logger
|
||||
{
|
||||
$config = $container->has('config') ? $container->get('config') : [];
|
||||
Cascade::fileConfig($config['logger'] ?? ['loggers' => []]);
|
||||
|
||||
// Compose requested logger name
|
||||
$loggerName = $options['logger_name'] ?? 'Logger';
|
||||
$nameParts = explode('_', $requestedName);
|
||||
if (count($nameParts) > 1) {
|
||||
$loggerName = $nameParts[1];
|
||||
}
|
||||
|
||||
return Cascade::getLogger($loggerName);
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Logger\Processor;
|
||||
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
|
||||
use const PHP_EOL;
|
||||
|
||||
final class ExceptionWithNewLineProcessor
|
||||
{
|
||||
private const EXCEPTION_PLACEHOLDER = '{e}';
|
||||
|
||||
public function __invoke(array $record)
|
||||
{
|
||||
$message = $record['message'];
|
||||
$messageHasExceptionPlaceholder = strpos($message, self::EXCEPTION_PLACEHOLDER) !== false;
|
||||
|
||||
if ($messageHasExceptionPlaceholder) {
|
||||
$record['message'] = str_replace(
|
||||
self::EXCEPTION_PLACEHOLDER,
|
||||
PHP_EOL . self::EXCEPTION_PLACEHOLDER,
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
try {
|
||||
return $handler->handle($request);
|
||||
} finally {
|
||||
$this->em->getConnection()->close();
|
||||
$this->em->clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use RKA\Middleware\IpAddress;
|
||||
|
||||
class IpAddressMiddlewareFactory
|
||||
{
|
||||
public const REQUEST_ATTR = 'remote_address';
|
||||
|
||||
public function __invoke(ContainerInterface $container): IpAddress
|
||||
{
|
||||
$config = $container->get('config');
|
||||
$headersToInspect = $config['ip_address_resolution']['headers_to_inspect'] ?? [];
|
||||
return new IpAddress(true, [], self::REQUEST_ATTR, $headersToInspect);
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as DelegateInterface;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
use function count;
|
||||
use function explode;
|
||||
|
||||
class LocaleMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private const ACCEPT_LANGUAGE = 'Accept-Language';
|
||||
|
||||
/** @var Translator */
|
||||
private $translator;
|
||||
|
||||
public function __construct(Translator $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate): Response
|
||||
{
|
||||
if (! $request->hasHeader(self::ACCEPT_LANGUAGE)) {
|
||||
return $delegate->handle($request);
|
||||
}
|
||||
|
||||
$locale = $request->getHeaderLine(self::ACCEPT_LANGUAGE);
|
||||
$this->translator->setLocale($this->normalizeLocale($locale));
|
||||
return $delegate->handle($request);
|
||||
}
|
||||
|
||||
private function normalizeLocale(string $locale): string
|
||||
{
|
||||
$parts = explode('_', $locale);
|
||||
if (count($parts) > 1) {
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
$parts = explode('-', $locale);
|
||||
if (count($parts) > 1) {
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Paginator\Util;
|
||||
|
||||
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
|
||||
use Zend\Paginator\Paginator;
|
||||
use Zend\Stdlib\ArrayUtils;
|
||||
|
||||
use function array_map;
|
||||
use function sprintf;
|
||||
|
||||
trait PaginatorUtilsTrait
|
||||
{
|
||||
private function serializePaginator(Paginator $paginator, ?DataTransformerInterface $transformer = null): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->serializeItems(ArrayUtils::iteratorToArray($paginator->getCurrentItems()), $transformer),
|
||||
'pagination' => [
|
||||
'currentPage' => $paginator->getCurrentPageNumber(),
|
||||
'pagesCount' => $paginator->count(),
|
||||
'itemsPerPage' => $paginator->getItemCountPerPage(),
|
||||
'itemsInCurrentPage' => $paginator->getCurrentItemCount(),
|
||||
'totalItems' => $paginator->getTotalItemCount(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function serializeItems(array $items, ?DataTransformerInterface $transformer = null): array
|
||||
{
|
||||
return $transformer === null ? $items : array_map([$transformer, 'transform'], $items);
|
||||
}
|
||||
|
||||
private function isLastPage(Paginator $paginator): bool
|
||||
{
|
||||
return $paginator->getCurrentPageNumber() >= $paginator->count();
|
||||
}
|
||||
|
||||
private function formatCurrentPageMessage(Paginator $paginator, string $pattern): string
|
||||
{
|
||||
return sprintf($pattern, $paginator->getCurrentPageNumber(), $paginator->count());
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Response;
|
||||
|
||||
use Fig\Http\Message\StatusCodeInterface as StatusCode;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\Stream;
|
||||
|
||||
use function base64_decode;
|
||||
|
||||
class PixelResponse extends Response
|
||||
{
|
||||
private const BASE_64_IMAGE = 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw==';
|
||||
private const CONTENT_TYPE = 'image/gif';
|
||||
|
||||
public function __construct(int $status = StatusCode::STATUS_OK, array $headers = [])
|
||||
{
|
||||
$headers['content-type'] = self::CONTENT_TYPE;
|
||||
parent::__construct($this->createBody(), $status, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the message body.
|
||||
*
|
||||
* @return StreamInterface
|
||||
*/
|
||||
private function createBody(): StreamInterface
|
||||
{
|
||||
$body = new Stream('php://temp', 'wb+');
|
||||
$body->write(base64_decode(self::BASE_64_IMAGE));
|
||||
$body->rewind();
|
||||
return $body;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Response;
|
||||
|
||||
use Endroid\QrCode\QrCode;
|
||||
use Fig\Http\Message\StatusCodeInterface as StatusCode;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\Stream;
|
||||
|
||||
class QrCodeResponse extends Response
|
||||
{
|
||||
use Response\InjectContentTypeTrait;
|
||||
|
||||
public function __construct(QrCode $qrCode, int $status = StatusCode::STATUS_OK, array $headers = [])
|
||||
{
|
||||
parent::__construct(
|
||||
$this->createBody($qrCode),
|
||||
$status,
|
||||
$this->injectContentType($qrCode->getContentType(), $headers)
|
||||
);
|
||||
}
|
||||
|
||||
private function createBody(QrCode $qrCode): StreamInterface
|
||||
{
|
||||
$body = new Stream('php://temp', 'wb+');
|
||||
$body->write($qrCode->get());
|
||||
$body->rewind();
|
||||
return $body;
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Response;
|
||||
|
||||
use Fig\Http\Message\StatusCodeInterface as StatusCode;
|
||||
use finfo;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\Stream;
|
||||
use Zend\Stdlib\ArrayUtils;
|
||||
|
||||
use const FILEINFO_MIME;
|
||||
|
||||
trait ResponseUtilsTrait
|
||||
{
|
||||
private function generateImageResponse(string $imagePath): ResponseInterface
|
||||
{
|
||||
return $this->generateBinaryResponse($imagePath);
|
||||
}
|
||||
|
||||
private function generateBinaryResponse(string $path, array $extraHeaders = []): ResponseInterface
|
||||
{
|
||||
$body = new Stream($path);
|
||||
return new Response($body, StatusCode::STATUS_OK, ArrayUtils::merge([
|
||||
'Content-Type' => (new finfo(FILEINFO_MIME))->file($path),
|
||||
'Content-Length' => (string) $body->getSize(),
|
||||
], $extraHeaders));
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Rest;
|
||||
|
||||
interface DataTransformerInterface
|
||||
{
|
||||
public function transform($value): array;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Template\Extension;
|
||||
|
||||
use League\Plates\Engine;
|
||||
use League\Plates\Extension\ExtensionInterface;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class TranslatorExtension implements ExtensionInterface
|
||||
{
|
||||
/** @var TranslatorInterface */
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function register(Engine $engine): void
|
||||
{
|
||||
$engine->registerFunction('translate', [$this->translator, 'translate']);
|
||||
$engine->registerFunction('locale', [$this->translator, 'getLocale']);
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Util;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
|
||||
final class DateRange
|
||||
{
|
||||
/** @var Chronos|null */
|
||||
private $startDate;
|
||||
/** @var Chronos|null */
|
||||
private $endDate;
|
||||
|
||||
public function __construct(?Chronos $startDate = null, ?Chronos $endDate = null)
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
|
||||
public function getStartDate(): ?Chronos
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getEndDate(): ?Chronos
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->startDate === null && $this->endDate === null;
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Util;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
|
||||
use function count;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function sprintf;
|
||||
use function trim;
|
||||
|
||||
final class IpAddress
|
||||
{
|
||||
private const IPV4_PARTS_COUNT = 4;
|
||||
private const OBFUSCATED_OCTET = '0';
|
||||
public const LOCALHOST = '127.0.0.1';
|
||||
|
||||
/** @var string */
|
||||
private $firstOctet;
|
||||
/** @var string */
|
||||
private $secondOctet;
|
||||
/** @var string */
|
||||
private $thirdOctet;
|
||||
/** @var string */
|
||||
private $fourthOctet;
|
||||
|
||||
private function __construct(string $firstOctet, string $secondOctet, string $thirdOctet, string $fourthOctet)
|
||||
{
|
||||
$this->firstOctet = $firstOctet;
|
||||
$this->secondOctet = $secondOctet;
|
||||
$this->thirdOctet = $thirdOctet;
|
||||
$this->fourthOctet = $fourthOctet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address
|
||||
* @return IpAddress
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function fromString(string $address): self
|
||||
{
|
||||
$address = trim($address);
|
||||
$parts = explode('.', $address);
|
||||
if (count($parts) !== self::IPV4_PARTS_COUNT) {
|
||||
throw new InvalidArgumentException(sprintf('Provided IP "%s" is invalid', $address));
|
||||
}
|
||||
|
||||
return new self(...$parts);
|
||||
}
|
||||
|
||||
public function getObfuscatedCopy(): self
|
||||
{
|
||||
return new self(
|
||||
$this->firstOctet,
|
||||
$this->secondOctet,
|
||||
$this->thirdOctet,
|
||||
self::OBFUSCATED_OCTET
|
||||
);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return implode('.', [
|
||||
$this->firstOctet,
|
||||
$this->secondOctet,
|
||||
$this->thirdOctet,
|
||||
$this->fourthOctet,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Util;
|
||||
|
||||
use function random_int;
|
||||
use function sprintf;
|
||||
use function strlen;
|
||||
|
||||
trait StringUtilsTrait
|
||||
{
|
||||
private function generateRandomString(int $length = 10): string
|
||||
{
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$charactersLength = strlen($characters);
|
||||
$randomString = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters[random_int(0, $charactersLength - 1)];
|
||||
}
|
||||
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
private function generateV4Uuid(): string
|
||||
{
|
||||
return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
// 32 bits for "time_low"
|
||||
random_int(0, 0xffff),
|
||||
random_int(0, 0xffff),
|
||||
// 16 bits for "time_mid"
|
||||
random_int(0, 0xffff),
|
||||
// 16 bits for "time_hi_and_version",
|
||||
// four most significant bits holds version number 4
|
||||
random_int(0, 0x0fff) | 0x4000,
|
||||
// 16 bits, 8 bits for "clk_seq_hi_res",
|
||||
// 8 bits for "clk_seq_low",
|
||||
// two most significant bits holds zero and one for variant DCE1.1
|
||||
random_int(0, 0x3fff) | 0x8000,
|
||||
// 48 bits for "node"
|
||||
random_int(0, 0xffff),
|
||||
random_int(0, 0xffff),
|
||||
random_int(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Validation;
|
||||
|
||||
use Zend\Filter;
|
||||
use Zend\InputFilter\Input;
|
||||
use Zend\Validator;
|
||||
|
||||
trait InputFactoryTrait
|
||||
{
|
||||
private function createInput($name, $required = true): Input
|
||||
{
|
||||
$input = new Input($name);
|
||||
$input->setRequired($required)
|
||||
->getFilterChain()->attach(new Filter\StripTags())
|
||||
->attach(new Filter\StringTrim());
|
||||
return $input;
|
||||
}
|
||||
|
||||
private function createBooleanInput(string $name, bool $required = true): Input
|
||||
{
|
||||
$input = $this->createInput($name, $required);
|
||||
$input->getFilterChain()->attach(new Filter\Boolean());
|
||||
$input->getValidatorChain()->attach(new Validator\NotEmpty(['type' => [
|
||||
Validator\NotEmpty::OBJECT,
|
||||
Validator\NotEmpty::SPACE,
|
||||
Validator\NotEmpty::NULL,
|
||||
Validator\NotEmpty::EMPTY_ARRAY,
|
||||
Validator\NotEmpty::STRING,
|
||||
]]));
|
||||
|
||||
return $input;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Validation;
|
||||
|
||||
use Cocur\Slugify;
|
||||
use Zend\Filter\Exception;
|
||||
use Zend\Filter\FilterInterface;
|
||||
|
||||
class SluggerFilter implements FilterInterface
|
||||
{
|
||||
/** @var Slugify\SlugifyInterface */
|
||||
private $slugger;
|
||||
|
||||
public function __construct(?Slugify\SlugifyInterface $slugger = null)
|
||||
{
|
||||
$this->slugger = $slugger ?: new Slugify\Slugify(['lowercase' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of filtering $value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws Exception\RuntimeException If filtering $value is impossible
|
||||
* @return mixed
|
||||
*/
|
||||
public function filter($value)
|
||||
{
|
||||
return ! empty($value) ? $this->slugger->slugify($value) : null;
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Cache;
|
||||
|
||||
use Doctrine\Common\Cache;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\ClientInterface;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Shlinkio\Shlink\Common\Cache\CacheFactory;
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
|
||||
class CacheFactoryTest extends TestCase
|
||||
{
|
||||
/** @var ObjectProphecy */
|
||||
private $container;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideCacheConfig
|
||||
*/
|
||||
public function expectedCacheAdapterIsReturned(
|
||||
array $config,
|
||||
string $expectedAdapterClass,
|
||||
string $expectedNamespace,
|
||||
?callable $apcuEnabled = null
|
||||
): void {
|
||||
$factory = new CacheFactory($apcuEnabled);
|
||||
|
||||
$getConfig = $this->container->get('config')->willReturn($config);
|
||||
$getRedis = $this->container->get(RedisFactory::SERVICE_NAME)->willReturn(
|
||||
$this->prophesize(ClientInterface::class)->reveal()
|
||||
);
|
||||
|
||||
$cache = $factory($this->container->reveal());
|
||||
|
||||
$this->assertInstanceOf($expectedAdapterClass, $cache);
|
||||
$this->assertEquals($expectedNamespace, $cache->getNamespace());
|
||||
$getConfig->shouldHaveBeenCalledOnce();
|
||||
$getRedis->shouldHaveBeenCalledTimes($expectedAdapterClass === Cache\PredisCache::class ? 1 :0);
|
||||
}
|
||||
|
||||
public function provideCacheConfig(): iterable
|
||||
{
|
||||
yield 'debug true' => [['debug' => true], Cache\ArrayCache::class, ''];
|
||||
yield 'debug false' => [['debug' => false], Cache\ApcuCache::class, ''];
|
||||
yield 'no debug' => [[], Cache\ApcuCache::class, ''];
|
||||
yield 'with redis' => [['cache' => [
|
||||
'namespace' => $namespace = 'some_namespace',
|
||||
'redis' => [],
|
||||
]], Cache\PredisCache::class, $namespace];
|
||||
yield 'debug false and no apcu' => [['debug' => false], Cache\ArrayCache::class, '', function () {
|
||||
return false;
|
||||
}];
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Cache;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\Connection\Aggregate\PredisCluster;
|
||||
use Predis\Connection\Aggregate\RedisCluster;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
|
||||
class RedisFactoryTest extends TestCase
|
||||
{
|
||||
/** @var RedisFactory */
|
||||
private $factory;
|
||||
/** @var ObjectProphecy */
|
||||
private $container;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
$this->factory = new RedisFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideRedisConfig
|
||||
*/
|
||||
public function createsRedisClientBasedOnRedisConfig(?array $config, string $expectedCluster): void
|
||||
{
|
||||
$getConfig = $this->container->get('config')->willReturn([
|
||||
'redis' => $config,
|
||||
]);
|
||||
|
||||
$client = ($this->factory)($this->container->reveal());
|
||||
|
||||
$getConfig->shouldHaveBeenCalledOnce();
|
||||
$this->assertInstanceOf($expectedCluster, $client->getOptions()->cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideRedisConfig
|
||||
*/
|
||||
public function createsRedisClientBasedOnCacheConfig(?array $config, string $expectedCluster): void
|
||||
{
|
||||
$getConfig = $this->container->get('config')->willReturn([
|
||||
'cache' => [
|
||||
'redis' => $config,
|
||||
],
|
||||
]);
|
||||
|
||||
$client = ($this->factory)($this->container->reveal());
|
||||
|
||||
$getConfig->shouldHaveBeenCalledOnce();
|
||||
$this->assertInstanceOf($expectedCluster, $client->getOptions()->cluster);
|
||||
}
|
||||
|
||||
public function provideRedisConfig(): iterable
|
||||
{
|
||||
yield 'no config' => [null, PredisCluster::class];
|
||||
yield 'single server as string' => [[
|
||||
'servers' => 'tcp://127.0.0.1:6379',
|
||||
], PredisCluster::class];
|
||||
yield 'single server as array' => [[
|
||||
'servers' => ['tcp://127.0.0.1:6379'],
|
||||
], PredisCluster::class];
|
||||
yield 'cluster of servers' => [[
|
||||
'servers' => ['tcp://1.1.1.1:6379', 'tcp://2.2.2.2:6379'],
|
||||
], RedisCluster::class];
|
||||
yield 'empty cluster of servers' => [[
|
||||
'servers' => [],
|
||||
], PredisCluster::class];
|
||||
yield 'cluster of servers as string' => [[
|
||||
'servers' => 'tcp://1.1.1.1:6379,tcp://2.2.2.2:6379',
|
||||
], RedisCluster::class];
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\ConfigProvider;
|
||||
|
||||
class ConfigProviderTest extends TestCase
|
||||
{
|
||||
/** @var ConfigProvider */
|
||||
private $configProvider;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->configProvider = new ConfigProvider();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function configIsReturned()
|
||||
{
|
||||
$config = $this->configProvider->__invoke();
|
||||
|
||||
$this->assertArrayHasKey('dependencies', $config);
|
||||
$this->assertArrayHasKey('plates', $config);
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Shlinkio\Shlink\Common\Doctrine\ConnectionFactory;
|
||||
|
||||
class ConnectionFactoryTest extends TestCase
|
||||
{
|
||||
/** @var ConnectionFactory */
|
||||
private $factory;
|
||||
/** @var ObjectProphecy */
|
||||
private $container;
|
||||
/** @var ObjectProphecy */
|
||||
private $em;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
$this->em = $this->prophesize(EntityManagerInterface::class);
|
||||
$this->container->get(EntityManager::class)->willReturn($this->em->reveal());
|
||||
|
||||
$this->factory = new ConnectionFactory();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function properServiceFallbackOccursWhenInvoked(): void
|
||||
{
|
||||
$connection = $this->prophesize(Connection::class)->reveal();
|
||||
$getConnection = $this->em->getConnection()->willReturn($connection);
|
||||
|
||||
$result = ($this->factory)($this->container->reveal());
|
||||
|
||||
$this->assertSame($connection, $result);
|
||||
$getConnection->shouldHaveBeenCalledOnce();
|
||||
$this->container->get(EntityManager::class)->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Doctrine\EntityManagerFactory;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class EntityManagerFactoryTest extends TestCase
|
||||
{
|
||||
/** @var EntityManagerFactory */
|
||||
private $factory;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->factory = new EntityManagerFactory();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function serviceIsCreated(): void
|
||||
{
|
||||
$sm = new ServiceManager(['services' => [
|
||||
'config' => [
|
||||
'debug' => true,
|
||||
'entity_manager' => [
|
||||
'orm' => [
|
||||
'types' => [
|
||||
ChronosDateTimeType::CHRONOS_DATETIME => ChronosDateTimeType::class,
|
||||
],
|
||||
],
|
||||
'connection' => [
|
||||
'driver' => 'pdo_sqlite',
|
||||
],
|
||||
],
|
||||
],
|
||||
]]);
|
||||
|
||||
$em = ($this->factory)($sm, EntityManager::class);
|
||||
$this->assertInstanceOf(EntityManager::class, $em);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Shlinkio\Shlink\Common\Doctrine\NoDbNameConnectionFactory;
|
||||
|
||||
class NoDbNameConnectionFactoryTest extends TestCase
|
||||
{
|
||||
/** @var NoDbNameConnectionFactory */
|
||||
private $factory;
|
||||
/** @var ObjectProphecy */
|
||||
private $container;
|
||||
/** @var ObjectProphecy */
|
||||
private $originalConn;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
$this->originalConn = $this->prophesize(Connection::class);
|
||||
$this->container->get(Connection::class)->willReturn($this->originalConn->reveal());
|
||||
|
||||
$this->factory = new NoDbNameConnectionFactory();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function createsNewConnectionRemovingDbNameFromOriginalConnectionParams(): void
|
||||
{
|
||||
$params = [
|
||||
'username' => 'foo',
|
||||
'password' => 'bar',
|
||||
'dbname' => 'something',
|
||||
];
|
||||
$getOriginalParams = $this->originalConn->getParams()->willReturn($params);
|
||||
$getOriginalDriver = $this->originalConn->getDriver()->willReturn($this->prophesize(Driver::class)->reveal());
|
||||
$getOriginalConfig = $this->originalConn->getConfiguration()->willReturn(null);
|
||||
$getOriginalEvents = $this->originalConn->getEventManager()->willReturn(null);
|
||||
|
||||
$conn = ($this->factory)($this->container->reveal());
|
||||
|
||||
$this->assertEquals([
|
||||
'username' => 'foo',
|
||||
'password' => 'bar',
|
||||
], $conn->getParams());
|
||||
$getOriginalParams->shouldHaveBeenCalledOnce();
|
||||
$getOriginalDriver->shouldHaveBeenCalledOnce();
|
||||
$getOriginalConfig->shouldHaveBeenCalledOnce();
|
||||
$getOriginalEvents->shouldHaveBeenCalledOnce();
|
||||
$this->container->get(Connection::class)->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionObject;
|
||||
use Shlinkio\Shlink\Common\Doctrine\ReopeningEntityManagerDelegator;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class ReopeningEntityManagerDelegatorTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function decoratesEntityManagerFromCallback(): void
|
||||
{
|
||||
$em = $this->prophesize(EntityManagerInterface::class)->reveal();
|
||||
$result = (new ReopeningEntityManagerDelegator())(new ServiceManager(), '', function () use ($em) {
|
||||
return $em;
|
||||
});
|
||||
|
||||
$ref = new ReflectionObject($result);
|
||||
$prop = $ref->getProperty('wrapped');
|
||||
$prop->setAccessible(true);
|
||||
|
||||
$this->assertSame($em, $prop->getValue($result));
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Doctrine;
|
||||
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Doctrine\ReopeningEntityManager;
|
||||
use stdClass;
|
||||
|
||||
class ReopeningEntityManagerTest extends TestCase
|
||||
{
|
||||
/** @var ReopeningEntityManager */
|
||||
private $decoratorEm;
|
||||
/** @var ObjectProphecy */
|
||||
private $wrapped;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->wrapped = $this->prophesize(EntityManagerInterface::class);
|
||||
$this->wrapped->getConnection()->willReturn($this->prophesize(Connection::class));
|
||||
$this->wrapped->getConfiguration()->willReturn($this->prophesize(Configuration::class));
|
||||
$this->wrapped->getEventManager()->willReturn($this->prophesize(EventManager::class));
|
||||
|
||||
$wrappedMock = $this->wrapped->reveal();
|
||||
$this->decoratorEm = new ReopeningEntityManager($wrappedMock, function () use ($wrappedMock) {
|
||||
return $wrappedMock;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideMethodNames
|
||||
*/
|
||||
public function wrappedInstanceIsTransparentlyCalledWhenItIsNotClosed(string $methodName): void
|
||||
{
|
||||
$method = $this->wrapped->__call($methodName, [Argument::cetera()])->willReturnArgument();
|
||||
$isOpen = $this->wrapped->isOpen()->willReturn(true);
|
||||
|
||||
$this->decoratorEm->{$methodName}(new stdClass());
|
||||
|
||||
$method->shouldHaveBeenCalledOnce();
|
||||
$isOpen->shouldHaveBeenCalledOnce();
|
||||
$this->wrapped->getConnection()->shouldNotHaveBeenCalled();
|
||||
$this->wrapped->getConfiguration()->shouldNotHaveBeenCalled();
|
||||
$this->wrapped->getEventManager()->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideMethodNames
|
||||
*/
|
||||
public function wrappedInstanceIsRecreatedWhenItIsClosed(string $methodName): void
|
||||
{
|
||||
$method = $this->wrapped->__call($methodName, [Argument::cetera()])->willReturnArgument();
|
||||
$isOpen = $this->wrapped->isOpen()->willReturn(false);
|
||||
|
||||
$this->decoratorEm->{$methodName}(new stdClass());
|
||||
|
||||
$method->shouldHaveBeenCalledOnce();
|
||||
$isOpen->shouldHaveBeenCalledOnce();
|
||||
$this->wrapped->getConnection()->shouldHaveBeenCalledOnce();
|
||||
$this->wrapped->getConfiguration()->shouldHaveBeenCalledOnce();
|
||||
$this->wrapped->getEventManager()->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
public function provideMethodNames(): iterable
|
||||
{
|
||||
yield 'flush' => ['flush'];
|
||||
yield 'persist' => ['persist'];
|
||||
yield 'remove' => ['remove'];
|
||||
yield 'refresh' => ['refresh'];
|
||||
yield 'merge' => ['merge'];
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Doctrine\Type;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
use stdClass;
|
||||
|
||||
class ChronosDateTimeTypeTest extends TestCase
|
||||
{
|
||||
/** @var ChronosDateTimeType */
|
||||
private $type;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
if (! Type::hasType(ChronosDateTimeType::CHRONOS_DATETIME)) {
|
||||
Type::addType(ChronosDateTimeType::CHRONOS_DATETIME, ChronosDateTimeType::class);
|
||||
}
|
||||
|
||||
$this->type = Type::getType(ChronosDateTimeType::CHRONOS_DATETIME);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function nameIsReturned(): void
|
||||
{
|
||||
$this->assertEquals(ChronosDateTimeType::CHRONOS_DATETIME, $this->type->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideValues
|
||||
*/
|
||||
public function valueIsConverted(?string $value, ?string $expected): void
|
||||
{
|
||||
$platform = $this->prophesize(AbstractPlatform::class);
|
||||
$platform->getDateTimeFormatString()->willReturn('Y-m-d H:i:s');
|
||||
|
||||
$result = $this->type->convertToPHPValue($value, $platform->reveal());
|
||||
|
||||
if ($expected === null) {
|
||||
$this->assertNull($result);
|
||||
} else {
|
||||
$this->assertInstanceOf($expected, $result);
|
||||
}
|
||||
}
|
||||
|
||||
public function provideValues(): iterable
|
||||
{
|
||||
yield 'null date' => [null, null];
|
||||
yield 'human friendly date' => ['now', Chronos::class];
|
||||
yield 'numeric date' => ['2017-01-01', Chronos::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider providePhpValues
|
||||
*/
|
||||
public function valueIsConvertedToDatabaseFormat(?DateTimeInterface $value, ?string $expected): void
|
||||
{
|
||||
$platform = $this->prophesize(AbstractPlatform::class);
|
||||
$platform->getDateTimeFormatString()->willReturn('Y-m-d');
|
||||
|
||||
$this->assertEquals($expected, $this->type->convertToDatabaseValue($value, $platform->reveal()));
|
||||
}
|
||||
|
||||
public function providePhpValues(): iterable
|
||||
{
|
||||
yield 'null date' => [null, null];
|
||||
yield 'DateTimeImmutable date' => [new DateTimeImmutable('2017-01-01'), '2017-01-01'];
|
||||
yield 'Chronos date' => [Chronos::parse('2017-02-01'), '2017-02-01'];
|
||||
yield 'DateTime date' => [new DateTime('2017-03-01'), '2017-03-01'];
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function exceptionIsThrownIfInvalidValueIsParsedToDatabase(): void
|
||||
{
|
||||
$this->expectException(ConversionException::class);
|
||||
$this->type->convertToDatabaseValue(new stdClass(), $this->prophesize(AbstractPlatform::class)->reveal());
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Factory\DottedAccessConfigAbstractFactory;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class DottedAccessConfigAbstractFactoryTest extends TestCase
|
||||
{
|
||||
/** @var DottedAccessConfigAbstractFactory */
|
||||
private $factory;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->factory = new DottedAccessConfigAbstractFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideDotNames
|
||||
*/
|
||||
public function canCreateOnlyServicesWithDot(string $serviceName, bool $canCreate): void
|
||||
{
|
||||
$this->assertEquals($canCreate, $this->factory->canCreate(new ServiceManager(), $serviceName));
|
||||
}
|
||||
|
||||
public function provideDotNames(): iterable
|
||||
{
|
||||
yield 'with a valid service' => ['foo.bar', true];
|
||||
yield 'with another valid service' => ['config.something', true];
|
||||
yield 'with an invalid service' => ['config_something', false];
|
||||
yield 'with another invalid service' => ['foo', false];
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function throwsExceptionWhenFirstPartOfTheServiceIsNotRegistered()
|
||||
{
|
||||
$this->expectException(ServiceNotCreatedException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'Defined service "foo" could not be found in container after resolving dotted expression "foo.bar"'
|
||||
);
|
||||
|
||||
$this->factory->__invoke(new ServiceManager(), 'foo.bar');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function dottedNotationIsRecursivelyResolvedUntilLastValueIsFoundAndReturned()
|
||||
{
|
||||
$expected = 'this is the result';
|
||||
|
||||
$result = $this->factory->__invoke(new ServiceManager(['services' => [
|
||||
'foo' => [
|
||||
'bar' => ['baz' => $expected],
|
||||
],
|
||||
]]), 'foo.bar.baz');
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function exceptionIsThrownIfAnyStepCannotBeResolved()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'The key "baz" provided in the dotted notation could not be found in the array service'
|
||||
);
|
||||
|
||||
$this->factory->__invoke(new ServiceManager(['services' => [
|
||||
'foo' => [
|
||||
'bar' => ['something' => 123],
|
||||
],
|
||||
]]), 'foo.bar.baz');
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\I18n;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\I18n\TranslatorFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class TranslatorFactoryTest extends TestCase
|
||||
{
|
||||
/** @var TranslatorFactory */
|
||||
private $factory;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->factory = new TranslatorFactory();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function serviceIsCreated(): void
|
||||
{
|
||||
$instance = ($this->factory)(new ServiceManager(['services' => [
|
||||
'config' => [],
|
||||
]]));
|
||||
$this->assertInstanceOf(Translator::class, $instance);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Lock;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use ReflectionObject;
|
||||
use Shlinkio\Shlink\Common\Lock\RetryLockStoreDelegatorFactory;
|
||||
use Symfony\Component\Lock\StoreInterface;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class RetryLockStoreDelegatorFactoryTest extends TestCase
|
||||
{
|
||||
/** @var RetryLockStoreDelegatorFactory */
|
||||
private $delegator;
|
||||
/** @var ObjectProphecy */
|
||||
private $originalStore;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->originalStore = $this->prophesize(StoreInterface::class)->reveal();
|
||||
$this->delegator = new RetryLockStoreDelegatorFactory();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function originalStoreIsWrappedInRetryStore(): void
|
||||
{
|
||||
$callback = function () {
|
||||
return $this->originalStore;
|
||||
};
|
||||
|
||||
$result = ($this->delegator)(new ServiceManager(), '', $callback);
|
||||
|
||||
$ref = new ReflectionObject($result);
|
||||
$prop = $ref->getProperty('decorated');
|
||||
$prop->setAccessible(true);
|
||||
|
||||
$this->assertSame($this->originalStore, $prop->getValue($result));
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Logger;
|
||||
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerAwareDelegatorFactory;
|
||||
use stdClass;
|
||||
|
||||
class LoggerAwareDelegatorFactoryTest extends TestCase
|
||||
{
|
||||
/** @var LoggerAwareDelegatorFactory */
|
||||
private $delegator;
|
||||
/** @var ObjectProphecy */
|
||||
private $container;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
$this->delegator = new LoggerAwareDelegatorFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideInstances
|
||||
*/
|
||||
public function injectsLoggerOnInstanceWhenImplementingLoggerAware($instance, int $expectedCalls): void
|
||||
{
|
||||
$callback = function () use ($instance) {
|
||||
return $instance;
|
||||
};
|
||||
$getLogger = $this->container->get(Log\LoggerInterface::class)->willReturn(new Log\NullLogger());
|
||||
|
||||
$result = ($this->delegator)($this->container->reveal(), '', $callback);
|
||||
|
||||
$this->assertSame($instance, $result);
|
||||
$getLogger->shouldHaveBeenCalledTimes($expectedCalls);
|
||||
}
|
||||
|
||||
public function provideInstances(): iterable
|
||||
{
|
||||
yield 'no logger aware' => [new stdClass(), 0];
|
||||
yield 'logger aware' => [new class implements Log\LoggerAwareInterface {
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
Assert::assertInstanceOf(Log\NullLogger::class, $logger);
|
||||
}
|
||||
}, 1];
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Logger;
|
||||
|
||||
use Monolog\Logger;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class LoggerFactoryTest extends TestCase
|
||||
{
|
||||
/** @var LoggerFactory */
|
||||
private $factory;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->factory = new LoggerFactory();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function serviceIsCreated()
|
||||
{
|
||||
/** @var Logger $instance */
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), '');
|
||||
$this->assertInstanceOf(LoggerInterface::class, $instance);
|
||||
$this->assertEquals('Logger', $instance->getName());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function nameIsSetFromOptions()
|
||||
{
|
||||
/** @var Logger $instance */
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), '', ['logger_name' => 'Foo']);
|
||||
$this->assertInstanceOf(LoggerInterface::class, $instance);
|
||||
$this->assertEquals('Foo', $instance->getName());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function serviceNameOverwritesOptionsLoggerName()
|
||||
{
|
||||
/** @var Logger $instance */
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), 'Logger_Shlink', ['logger_name' => 'Foo']);
|
||||
$this->assertInstanceOf(LoggerInterface::class, $instance);
|
||||
$this->assertEquals('Shlink', $instance->getName());
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Logger\Processor;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Logger\Processor\ExceptionWithNewLineProcessor;
|
||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||
|
||||
use function Functional\map;
|
||||
use function range;
|
||||
|
||||
use const PHP_EOL;
|
||||
|
||||
class ExceptionWithNewLineProcessorTest extends TestCase
|
||||
{
|
||||
use StringUtilsTrait;
|
||||
|
||||
/** @var ExceptionWithNewLineProcessor */
|
||||
private $processor;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->processor = new ExceptionWithNewLineProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideNoPlaceholderRecords
|
||||
*/
|
||||
public function keepsRecordAsIsWhenNoPlaceholderExists(array $record): void
|
||||
{
|
||||
$this->assertSame($record, ($this->processor)($record));
|
||||
}
|
||||
|
||||
public function provideNoPlaceholderRecords(): iterable
|
||||
{
|
||||
return map(range(1, 5), function () {
|
||||
return [['message' => $this->generateRandomString()]];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider providePlaceholderRecords
|
||||
*/
|
||||
public function properlyReplacesExceptionPlaceholderAddingNewLine(array $record, array $expected): void
|
||||
{
|
||||
$this->assertEquals($expected, ($this->processor)($record));
|
||||
}
|
||||
|
||||
public function providePlaceholderRecords(): iterable
|
||||
{
|
||||
yield [
|
||||
['message' => 'Hello World with placeholder {e}'],
|
||||
['message' => 'Hello World with placeholder ' . PHP_EOL . '{e}'],
|
||||
];
|
||||
yield [
|
||||
['message' => '{e} Shlink'],
|
||||
['message' => PHP_EOL . '{e} Shlink'],
|
||||
];
|
||||
yield [
|
||||
['message' => 'Foo {e} bar'],
|
||||
['message' => 'Foo ' . PHP_EOL . '{e} bar'],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
<?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 RuntimeException;
|
||||
use Shlinkio\Shlink\Common\Middleware\CloseDbConnectionMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
|
||||
class CloseDbConnectionMiddlewareTest extends TestCase
|
||||
{
|
||||
/** @var CloseDbConnectionMiddleware */
|
||||
private $middleware;
|
||||
/** @var ObjectProphecy */
|
||||
private $handler;
|
||||
/** @var ObjectProphecy */
|
||||
private $em;
|
||||
/** @var ObjectProphecy */
|
||||
private $conn;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->handler = $this->prophesize(RequestHandlerInterface::class);
|
||||
$this->em = $this->prophesize(EntityManagerInterface::class);
|
||||
$this->conn = $this->prophesize(Connection::class);
|
||||
$this->conn->close()->will(function () {
|
||||
});
|
||||
$this->em->getConnection()->willReturn($this->conn->reveal());
|
||||
$this->em->clear()->will(function () {
|
||||
});
|
||||
|
||||
$this->middleware = new CloseDbConnectionMiddleware($this->em->reveal());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function connectionIsClosedWhenMiddlewareIsProcessed(): void
|
||||
{
|
||||
$req = new ServerRequest();
|
||||
$resp = new Response();
|
||||
$handle = $this->handler->handle($req)->willReturn($resp);
|
||||
|
||||
$result = $this->middleware->process($req, $this->handler->reveal());
|
||||
|
||||
$this->assertSame($result, $resp);
|
||||
$this->em->getConnection()->shouldHaveBeenCalledOnce();
|
||||
$this->conn->close()->shouldHaveBeenCalledOnce();
|
||||
$this->em->clear()->shouldHaveBeenCalledOnce();
|
||||
$handle->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function connectionIsClosedEvenIfExceptionIsThrownOnInnerMiddlewares(): void
|
||||
{
|
||||
$req = new ServerRequest();
|
||||
$expectedError = new RuntimeException();
|
||||
$this->handler->handle($req)->willThrow($expectedError)
|
||||
->shouldBeCalledOnce();
|
||||
|
||||
$this->em->getConnection()->shouldBeCalledOnce();
|
||||
$this->conn->close()->shouldBeCalledOnce();
|
||||
$this->em->clear()->shouldBeCalledOnce();
|
||||
$this->expectExceptionObject($expectedError);
|
||||
|
||||
$this->middleware->process($req, $this->handler->reveal());
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Middleware;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionObject;
|
||||
use Shlinkio\Shlink\Common\Middleware\IpAddressMiddlewareFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class IpAddressMiddlewareFactoryTest extends TestCase
|
||||
{
|
||||
private $factory;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->factory = new IpAddressMiddlewareFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideConfigs
|
||||
*/
|
||||
public function returnedInstanceIsProperlyConfigured(array $config, array $expectedHeadersToInspect): void
|
||||
{
|
||||
$instance = ($this->factory)(new ServiceManager(['services' => [
|
||||
'config' => $config,
|
||||
]]));
|
||||
|
||||
$ref = new ReflectionObject($instance);
|
||||
$checkProxyHeaders = $ref->getProperty('checkProxyHeaders');
|
||||
$checkProxyHeaders->setAccessible(true);
|
||||
$trustedProxies = $ref->getProperty('trustedProxies');
|
||||
$trustedProxies->setAccessible(true);
|
||||
$attributeName = $ref->getProperty('attributeName');
|
||||
$attributeName->setAccessible(true);
|
||||
$headersToInspect = $ref->getProperty('headersToInspect');
|
||||
$headersToInspect->setAccessible(true);
|
||||
|
||||
$this->assertTrue($checkProxyHeaders->getValue($instance));
|
||||
$this->assertEquals([], $trustedProxies->getValue($instance));
|
||||
$this->assertEquals(IpAddressMiddlewareFactory::REQUEST_ATTR, $attributeName->getValue($instance));
|
||||
$this->assertEquals($expectedHeadersToInspect, $headersToInspect->getValue($instance));
|
||||
}
|
||||
|
||||
public function provideConfigs(): iterable
|
||||
{
|
||||
$defaultHeadersToInspect = [
|
||||
'Forwarded',
|
||||
'X-Forwarded-For',
|
||||
'X-Forwarded',
|
||||
'X-Cluster-Client-Ip',
|
||||
'Client-Ip',
|
||||
];
|
||||
|
||||
yield 'no ip_address_resolution config' => [[], $defaultHeadersToInspect];
|
||||
yield 'no headers_to_inspect config' => [['ip_address_resolution' => []], $defaultHeadersToInspect];
|
||||
yield 'null headers_to_inspect' => [['ip_address_resolution' => [
|
||||
'headers_to_inspect' => null,
|
||||
]], $defaultHeadersToInspect];
|
||||
yield 'empty headers_to_inspect' => [['ip_address_resolution' => [
|
||||
'headers_to_inspect' => [],
|
||||
]], $defaultHeadersToInspect];
|
||||
yield 'some headers_to_inspect' => [['ip_address_resolution' => [
|
||||
'headers_to_inspect' => [
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
],
|
||||
]], [
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
]];
|
||||
yield 'some other headers_to_inspect' => [['ip_address_resolution' => [
|
||||
'headers_to_inspect' => [
|
||||
'something',
|
||||
'something_else',
|
||||
],
|
||||
]], [
|
||||
'something',
|
||||
'something_else',
|
||||
]];
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Middleware;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class LocaleMiddlewareTest extends TestCase
|
||||
{
|
||||
/** @var LocaleMiddleware */
|
||||
private $middleware;
|
||||
/** @var Translator */
|
||||
private $translator;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->translator = Translator::factory(['locale' => 'ru']);
|
||||
$this->middleware = new LocaleMiddleware($this->translator);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function whenNoHeaderIsPresentLocaleIsNotChanged(): void
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
$this->middleware->process(new ServerRequest(), TestUtils::createReqHandlerMock()->reveal());
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function whenTheHeaderIsPresentLocaleIsChanged(): void
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
$request = (new ServerRequest())->withHeader('Accept-Language', 'es');
|
||||
$this->middleware->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
$this->assertEquals('es', $this->translator->getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideLanguages
|
||||
*/
|
||||
public function localeGetsNormalized(string $lang, string $expected): void
|
||||
{
|
||||
$handler = TestUtils::createReqHandlerMock();
|
||||
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
|
||||
$request = (new ServerRequest())->withHeader('Accept-Language', $lang);
|
||||
$this->middleware->process($request, $handler->reveal());
|
||||
$this->assertEquals($expected, $this->translator->getLocale());
|
||||
}
|
||||
|
||||
public function provideLanguages(): iterable
|
||||
{
|
||||
yield 'language only' => ['ru', 'ru'];
|
||||
yield 'country and language with underscore' => ['es_ES', 'es'];
|
||||
yield 'country and language with dash' => ['en-US', 'en'];
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Response;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Response\PixelResponse;
|
||||
|
||||
class PixelResponseTest extends TestCase
|
||||
{
|
||||
/** @var PixelResponse */
|
||||
private $resp;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->resp = new PixelResponse();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function responseHasGifTypeAndIsNotEmpty()
|
||||
{
|
||||
$this->assertEquals('image/gif', $this->resp->getHeaderLine('Content-Type'));
|
||||
$this->assertNotEmpty((string) $this->resp->getBody());
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Response;
|
||||
|
||||
use Endroid\QrCode\QrCode;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Response\QrCodeResponse;
|
||||
|
||||
class QrCodeResponseTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function providedQrCoideIsSetAsBody()
|
||||
{
|
||||
$qrCode = new QrCode('Hello');
|
||||
$resp = new QrCodeResponse($qrCode);
|
||||
|
||||
$this->assertEquals($qrCode->getContentType(), $resp->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals($qrCode->get(), (string) $resp->getBody());
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Template\Extension;
|
||||
|
||||
use League\Plates\Engine;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Shlinkio\Shlink\Common\Template\Extension\TranslatorExtension;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class TranslatorExtensionTest extends TestCase
|
||||
{
|
||||
/** @var TranslatorExtension */
|
||||
private $extension;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->extension = new TranslatorExtension($this->prophesize(Translator::class)->reveal());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function properFunctionsAreReturned()
|
||||
{
|
||||
$engine = $this->prophesize(Engine::class);
|
||||
$registerTranslate = $engine->registerFunction('translate', Argument::type('callable'))->will(function () {
|
||||
});
|
||||
$registerLocale = $engine->registerFunction('locale', Argument::type('array'))->will(function () {
|
||||
});
|
||||
|
||||
$this->extension->register($engine->reveal());
|
||||
|
||||
$registerTranslate->shouldHaveBeenCalledOnce();
|
||||
$registerLocale->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Util;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
|
||||
class DateRangeTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function defaultConstructorSetDatesToNull()
|
||||
{
|
||||
$range = new DateRange();
|
||||
$this->assertNull($range->getStartDate());
|
||||
$this->assertNull($range->getEndDate());
|
||||
$this->assertTrue($range->isEmpty());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function providedDatesAreSet()
|
||||
{
|
||||
$startDate = Chronos::now();
|
||||
$endDate = Chronos::now();
|
||||
$range = new DateRange($startDate, $endDate);
|
||||
$this->assertSame($startDate, $range->getStartDate());
|
||||
$this->assertSame($endDate, $range->getEndDate());
|
||||
$this->assertFalse($range->isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideDates
|
||||
*/
|
||||
public function isConsideredEmptyOnlyIfNoneOfTheDatesIsSet(
|
||||
?Chronos $startDate,
|
||||
?Chronos $endDate,
|
||||
bool $isEmpty
|
||||
): void {
|
||||
$range = new DateRange($startDate, $endDate);
|
||||
$this->assertEquals($isEmpty, $range->isEmpty());
|
||||
}
|
||||
|
||||
public function provideDates(): iterable
|
||||
{
|
||||
yield 'both are null' => [null, null, true];
|
||||
yield 'start is null' => [null, Chronos::now(), false];
|
||||
yield 'end is null' => [Chronos::now(), null, false];
|
||||
yield 'none are null' => [Chronos::now(), Chronos::now(), false];
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||
|
||||
use function Functional\map;
|
||||
use function range;
|
||||
use function strlen;
|
||||
|
||||
class StringUtilsTraitTest extends TestCase
|
||||
{
|
||||
use StringUtilsTrait;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideLengths
|
||||
*/
|
||||
public function generateRandomStringGeneratesStringOfProvidedLength(int $length): void
|
||||
{
|
||||
$this->assertEquals($length, strlen($this->generateRandomString($length)));
|
||||
}
|
||||
|
||||
public function provideLengths(): array
|
||||
{
|
||||
return map(range(10, 50, 5), function (int $i) {
|
||||
return [$i];
|
||||
});
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function generatesUuidV4()
|
||||
{
|
||||
$uuidPattern = '/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/';
|
||||
|
||||
$this->assertRegExp($uuidPattern, $this->generateV4Uuid());
|
||||
$this->assertRegExp($uuidPattern, $this->generateV4Uuid());
|
||||
$this->assertRegExp($uuidPattern, $this->generateV4Uuid());
|
||||
$this->assertRegExp($uuidPattern, $this->generateV4Uuid());
|
||||
$this->assertRegExp($uuidPattern, $this->generateV4Uuid());
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Util;
|
||||
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophet;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
|
||||
class TestUtils
|
||||
{
|
||||
private static $prophet;
|
||||
|
||||
public static function createReqHandlerMock(?ResponseInterface $response = null, ?RequestInterface $request = null)
|
||||
{
|
||||
$argument = $request ?: Argument::any();
|
||||
$delegate = static::getProphet()->prophesize(RequestHandlerInterface::class);
|
||||
$delegate->handle($argument)->willReturn($response ?: new Response());
|
||||
|
||||
return $delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Prophet
|
||||
*/
|
||||
private static function getProphet()
|
||||
{
|
||||
if (static::$prophet === null) {
|
||||
static::$prophet = new Prophet();
|
||||
}
|
||||
|
||||
return static::$prophet;
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Validation;
|
||||
|
||||
use Cocur\Slugify\SlugifyInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Validation\SluggerFilter;
|
||||
|
||||
class SluggerFilterTest extends TestCase
|
||||
{
|
||||
/** @var SluggerFilter */
|
||||
private $filter;
|
||||
/** @var ObjectProphecy */
|
||||
private $slugger;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->slugger = $this->prophesize(SlugifyInterface::class);
|
||||
$this->filter = new SluggerFilter($this->slugger->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideValuesToFilter
|
||||
*/
|
||||
public function providedValueIsFilteredAsExpected($providedValue, $expectedValue): void
|
||||
{
|
||||
$slugify = $this->slugger->slugify($providedValue)->willReturn('slug');
|
||||
|
||||
$result = $this->filter->filter($providedValue);
|
||||
|
||||
$this->assertEquals($expectedValue, $result);
|
||||
$slugify->shouldHaveBeenCalledTimes($expectedValue !== null ? 1 : 0);
|
||||
}
|
||||
|
||||
public function provideValuesToFilter(): iterable
|
||||
{
|
||||
yield 'null' => [null, null];
|
||||
yield 'empty string' => ['', null];
|
||||
yield 'not empty string' => ['foo', 'slug'];
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\Action;
|
|||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Common\Response\PixelResponse;
|
||||
use Shlinkio\Shlink\Core\Action\PixelAction;
|
||||
use Shlinkio\Shlink\Core\Action\RedirectAction;
|
||||
|
@ -13,7 +14,6 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
|||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
|
||||
class PixelActionTest extends TestCase
|
||||
|
@ -38,7 +38,7 @@ class PixelActionTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function imageIsReturned()
|
||||
public function imageIsReturned(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(
|
||||
|
@ -47,7 +47,7 @@ class PixelActionTest extends TestCase
|
|||
$this->visitTracker->track(Argument::cetera())->shouldBeCalledOnce();
|
||||
|
||||
$request = (new ServerRequest())->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
$response = $this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal());
|
||||
|
||||
$this->assertInstanceOf(PixelResponse::class, $response);
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
|
|
@ -14,7 +14,6 @@ use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
|||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\PreviewGenerator\Service\PreviewGenerator;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
|
||||
|
@ -39,7 +38,7 @@ class PreviewActionTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function invalidShortCodeFallsBackToNextMiddleware()
|
||||
public function invalidShortCodeFallsBackToNextMiddleware(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class)
|
||||
|
@ -52,7 +51,7 @@ class PreviewActionTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function correctShortCodeReturnsImageResponse()
|
||||
public function correctShortCodeReturnsImageResponse(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$url = 'foobar.com';
|
||||
|
@ -63,7 +62,7 @@ class PreviewActionTest extends TestCase
|
|||
|
||||
$resp = $this->action->process(
|
||||
(new ServerRequest())->withAttribute('shortCode', $shortCode),
|
||||
TestUtils::createReqHandlerMock()->reveal()
|
||||
$this->prophesize(RequestHandlerInterface::class)->reveal()
|
||||
);
|
||||
|
||||
$this->assertEquals(filesize($path), $resp->getHeaderLine('Content-length'));
|
||||
|
@ -71,7 +70,7 @@ class PreviewActionTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function invalidShortCodeExceptionFallsBackToNextMiddleware()
|
||||
public function invalidShortCodeExceptionFallsBackToNextMiddleware(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(InvalidShortCodeException::class)
|
||||
|
|
|
@ -13,7 +13,6 @@ use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
|||
use Shlinkio\Shlink\Core\Options;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
|
||||
|
@ -43,7 +42,7 @@ class RedirectActionTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function redirectionIsPerformedToLongUrl()
|
||||
public function redirectionIsPerformedToLongUrl(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
|
@ -53,7 +52,7 @@ class RedirectActionTest extends TestCase
|
|||
$this->visitTracker->track(Argument::cetera())->shouldBeCalledOnce();
|
||||
|
||||
$request = (new ServerRequest())->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
$response = $this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal());
|
||||
|
||||
$this->assertInstanceOf(Response\RedirectResponse::class, $response);
|
||||
$this->assertEquals(302, $response->getStatusCode());
|
||||
|
@ -62,7 +61,7 @@ class RedirectActionTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function nextMiddlewareIsInvokedIfLongUrlIsNotFound()
|
||||
public function nextMiddlewareIsInvokedIfLongUrlIsNotFound(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class)
|
||||
|
@ -79,7 +78,7 @@ class RedirectActionTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function redirectToCustomUrlIsReturnedIfConfiguredSoAndShortUrlIsNotFound()
|
||||
public function redirectToCustomUrlIsReturnedIfConfiguredSoAndShortUrlIsNotFound(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$shortCodeToUrl = $this->urlShortener->shortCodeToUrl($shortCode)->willThrow(
|
||||
|
@ -102,7 +101,7 @@ class RedirectActionTest extends TestCase
|
|||
}
|
||||
|
||||
/** @test */
|
||||
public function visitIsNotTrackedIfDisableParamIsProvided()
|
||||
public function visitIsNotTrackedIfDisableParamIsProvided(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
|
@ -113,7 +112,7 @@ class RedirectActionTest extends TestCase
|
|||
|
||||
$request = (new ServerRequest())->withAttribute('shortCode', $shortCode)
|
||||
->withQueryParams(['foobar' => true]);
|
||||
$response = $this->action->process($request, TestUtils::createReqHandlerMock()->reveal());
|
||||
$response = $this->action->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal());
|
||||
|
||||
$this->assertInstanceOf(Response\RedirectResponse::class, $response);
|
||||
$this->assertEquals(302, $response->getStatusCode());
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
parameters:
|
||||
ignoreErrors:
|
||||
- '#League\\Plates\\callback#'
|
||||
- '#is not subtype of Throwable#'
|
||||
- '#ObjectManager::flush()#'
|
||||
- '#\$metadata ClassMetadata#'
|
||||
- '#Undefined variable: \$metadata#'
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Common">
|
||||
<directory>./module/Common/test</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Core">
|
||||
<directory>./module/Core/test</directory>
|
||||
</testsuite>
|
||||
|
|
Loading…
Add table
Reference in a new issue