mirror of
https://github.com/shlinkio/shlink.git
synced 2024-12-27 17:31:20 -06:00
Merge pull request #456 from acelaya/feature/common-module
Feature/common module
This commit is contained in:
commit
da88ec6807
@ -1,18 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common\Factory\EmptyResponseImplicitOptionsMiddlewareFactory;
|
||||
use Zend\Expressive;
|
||||
use Zend\Expressive\Container;
|
||||
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
ImplicitOptionsMiddleware::class => EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
||||
],
|
||||
|
||||
'delegators' => [
|
||||
Expressive\Application::class => [
|
||||
Container\ApplicationConfigInjectionDelegator::class,
|
||||
|
@ -7,7 +7,7 @@ use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use PDO;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
|
||||
/**
|
||||
@ -60,7 +60,7 @@ final class Version20180913205455 extends AbstractMigration
|
||||
|
||||
try {
|
||||
return (string) IpAddress::fromString($addr)->getObfuscatedCopy();
|
||||
} catch (WrongIpException $e) {
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\Common\Console\ShlinkTable;
|
||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\Common\Console\ShlinkTable;
|
||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Model\VisitsParams;
|
||||
|
@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\Common\Console\ShlinkTable;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
@ -62,7 +62,7 @@ class ListShortUrlsCommand extends Command
|
||||
'page',
|
||||
'p',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
sprintf('The first page to list (%s items per page)', PaginableRepositoryAdapter::ITEMS_PER_PAGE),
|
||||
sprintf('The first page to list (%s items per page)', ShortUrlRepositoryAdapter::ITEMS_PER_PAGE),
|
||||
'1'
|
||||
)
|
||||
->addOption(
|
||||
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\CLI\Command\Tag;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\Common\Console\ShlinkTable;
|
||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
@ -9,12 +9,12 @@ use Shlinkio\Shlink\CLI\Command\Util\LockedCommandConfig;
|
||||
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
|
||||
use Shlinkio\Shlink\Core\Service\VisitServiceInterface;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\CLI\Command\Visit;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdaterInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
|
@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\CLI\Util;
|
||||
use Cake\Chronos\Chronos;
|
||||
use GeoIp2\Database\Reader;
|
||||
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdaterInterface;
|
||||
use Symfony\Component\Lock\Factory as Locker;
|
||||
use Throwable;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Console;
|
||||
namespace Shlinkio\Shlink\CLI\Util;
|
||||
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
@ -9,13 +9,13 @@ use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Visit\LocateVisitsCommand;
|
||||
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Service\VisitService;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpApiLocationResolver;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
@ -8,7 +8,7 @@ use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Visit\UpdateDbCommand;
|
||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdaterInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
@ -11,7 +11,7 @@ use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdater;
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdaterInterface;
|
||||
use Symfony\Component\Lock;
|
||||
use Throwable;
|
||||
|
@ -1,13 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Console;
|
||||
namespace ShlinkioTest\Shlink\CLI\Util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use ReflectionObject;
|
||||
use Shlinkio\Shlink\Common\Console\ShlinkTable;
|
||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
@ -26,7 +26,7 @@ class ShlinkTableTest extends TestCase
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function renderMakesTableToBeRenderedWithProvidedInfo()
|
||||
public function renderMakesTableToBeRenderedWithProvidedInfo(): void
|
||||
{
|
||||
$headers = [];
|
||||
$rows = [[]];
|
||||
@ -53,7 +53,7 @@ class ShlinkTableTest extends TestCase
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function newTableIsCreatedForFactoryMethod()
|
||||
public function newTableIsCreatedForFactoryMethod(): void
|
||||
{
|
||||
$instance = ShlinkTable::fromOutput($this->prophesize(OutputInterface::class)->reveal());
|
||||
|
21
module/Common/LICENSE
Normal file
21
module/Common/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
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.
|
89
module/Common/README.md
Normal file
89
module/Common/README.md
Normal file
@ -0,0 +1,89 @@
|
||||
# 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',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
```
|
@ -3,7 +3,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use GeoIp2\Database\Reader;
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@ -12,7 +11,6 @@ use Symfony\Component\Filesystem\Filesystem;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
use Zend\ServiceManager\Proxy\LazyServiceFactory;
|
||||
|
||||
return [
|
||||
|
||||
@ -20,9 +18,8 @@ return [
|
||||
'factories' => [
|
||||
GuzzleClient::class => InvokableFactory::class,
|
||||
Filesystem::class => InvokableFactory::class,
|
||||
Reader::class => ConfigAbstractFactory::class,
|
||||
|
||||
Translator::class => Factory\TranslatorFactory::class,
|
||||
Translator::class => I18n\TranslatorFactory::class,
|
||||
Template\Extension\TranslatorExtension::class => ConfigAbstractFactory::class,
|
||||
|
||||
Middleware\LocaleMiddleware::class => ConfigAbstractFactory::class,
|
||||
@ -44,24 +41,9 @@ return [
|
||||
'abstract_factories' => [
|
||||
Factory\DottedAccessConfigAbstractFactory::class,
|
||||
],
|
||||
'delegators' => [
|
||||
// The GeoLite2 db reader has to be lazy so that it does not try to load the DB file at app bootstrapping.
|
||||
// By doing so, it would fail the first time shlink tries to download it.
|
||||
Reader::class => [
|
||||
LazyServiceFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
'lazy_services' => [
|
||||
'class_map' => [
|
||||
Reader::class => Reader::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Reader::class => ['config.geolite2.db_location'],
|
||||
|
||||
Template\Extension\TranslatorExtension::class => ['translator'],
|
||||
Middleware\LocaleMiddleware::class => ['translator'],
|
||||
Middleware\CloseDbConnectionMiddleware::class => ['em'],
|
||||
|
@ -11,7 +11,7 @@ return [
|
||||
'entity_manager' => [
|
||||
'orm' => [
|
||||
'types' => [
|
||||
Type\ChronosDateTimeType::CHRONOS_DATETIME => Type\ChronosDateTimeType::class,
|
||||
Doctrine\Type\ChronosDateTimeType::CHRONOS_DATETIME => Doctrine\Type\ChronosDateTimeType::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
@ -4,22 +4,48 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Common\Cache;
|
||||
|
||||
use Doctrine\Common\Cache;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
use Predis\Client as PredisClient;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
use function extension_loaded;
|
||||
|
||||
class CacheFactory implements FactoryInterface
|
||||
class CacheFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): Cache\Cache
|
||||
{
|
||||
// TODO Make use of the redis cache via RedisFactory when possible
|
||||
/** @var callable|null */
|
||||
private $apcuEnabled;
|
||||
|
||||
$appOptions = $container->get(AppOptions::class);
|
||||
$adapter = env('APP_ENV', 'pro') === 'pro' ? new Cache\ApcuCache() : new Cache\ArrayCache();
|
||||
$adapter->setNamespace((string) $appOptions);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ class RedisFactory
|
||||
|
||||
public function __invoke(ContainerInterface $container): PredisClient
|
||||
{
|
||||
$redisConfig = $container->get('config')['redis'] ?? [];
|
||||
$config = $container->get('config');
|
||||
$redisConfig = $config['cache']['redis'] ?? $config['redis'] ?? [];
|
||||
|
||||
$servers = $redisConfig['servers'] ?? [];
|
||||
$servers = is_string($servers) ? explode(',', $servers) : $servers;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Type;
|
||||
namespace Shlinkio\Shlink\Common\Doctrine\Type;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use DateTimeInterface;
|
@ -3,16 +3,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
abstract class AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||
* @ORM\Column(name="id", type="bigint", options={"unsigned"=true})
|
||||
*/
|
||||
/** @var string */
|
||||
protected $id;
|
||||
|
||||
public function getId(): string
|
||||
|
@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @deprecated */
|
||||
|
@ -22,11 +22,9 @@ class DottedAccessConfigAbstractFactory implements AbstractFactoryInterface
|
||||
/**
|
||||
* Can the factory create an instance for the service?
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @return bool
|
||||
*/
|
||||
public function canCreate(ContainerInterface $container, $requestedName)
|
||||
public function canCreate(ContainerInterface $container, $requestedName): bool
|
||||
{
|
||||
return substr_count($requestedName, '.') > 0;
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Zend\Diactoros\Response\EmptyResponse;
|
||||
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class EmptyResponseImplicitOptionsMiddlewareFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when
|
||||
* creating a service.
|
||||
* @throws ContainerException if any other error occurs
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null)
|
||||
{
|
||||
return new ImplicitOptionsMiddleware(function () {
|
||||
return new EmptyResponse();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class TranslatorFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when
|
||||
* creating a service.
|
||||
* @throws ContainerException if any other error occurs
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null)
|
||||
{
|
||||
$config = $container->get('config');
|
||||
return Translator::factory($config['translator'] ?? []);
|
||||
}
|
||||
}
|
16
module/Common/src/I18n/TranslatorFactory.php
Normal file
16
module/Common/src/I18n/TranslatorFactory.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?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'] ?? []);
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Common\Image;
|
||||
use mikehaertl\wkhtmlto\Image;
|
||||
use Zend\ServiceManager\AbstractPluginManager;
|
||||
|
||||
/** @deprecated */
|
||||
class ImageBuilder extends AbstractPluginManager implements ImageBuilderInterface
|
||||
{
|
||||
protected $instanceOf = Image::class;
|
||||
|
@ -10,6 +10,7 @@ use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
/** @deprecated */
|
||||
class ImageBuilderFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
|
@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Common\Image;
|
||||
|
||||
use Zend\ServiceManager\ServiceLocatorInterface;
|
||||
|
||||
/** @deprecated */
|
||||
interface ImageBuilderInterface extends ServiceLocatorInterface
|
||||
{
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
/** @deprecated */
|
||||
class ImageFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
|
@ -5,29 +5,14 @@ namespace Shlinkio\Shlink\Common\Logger;
|
||||
|
||||
use Cascade\Cascade;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
use Monolog\Logger;
|
||||
|
||||
use function count;
|
||||
use function explode;
|
||||
|
||||
class LoggerFactory implements FactoryInterface
|
||||
class LoggerFactory
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when
|
||||
* creating a service.
|
||||
* @throws ContainerException if any other error occurs
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null)
|
||||
public function __invoke(ContainerInterface $container, string $requestedName, ?array $options = null): Logger
|
||||
{
|
||||
$config = $container->has('config') ? $container->get('config') : [];
|
||||
Cascade::fileConfig($config['logger'] ?? ['loggers' => []]);
|
||||
|
@ -3,28 +3,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use RKA\Middleware\IpAddress;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class IpAddressMiddlewareFactory implements FactoryInterface
|
||||
class IpAddressMiddlewareFactory
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when creating a service.
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): IpAddress
|
||||
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, [], Visitor::REMOTE_ADDRESS_ATTR, $headersToInspect);
|
||||
return new IpAddress(true, [], self::REQUEST_ATTR, $headersToInspect);
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,6 @@ class LocaleMiddleware implements MiddlewareInterface
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
|
@ -31,12 +31,6 @@ trait PaginatorUtilsTrait
|
||||
return $transformer === null ? $items : array_map([$transformer, 'transform'], $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if provided paginator is in last page
|
||||
*
|
||||
* @param Paginator $paginator
|
||||
* @return bool
|
||||
*/
|
||||
private function isLastPage(Paginator $paginator): bool
|
||||
{
|
||||
return $paginator->getCurrentPageNumber() >= $paginator->count();
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Repository;
|
||||
|
||||
interface PaginableRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Gets a list of elements using provided filtering data
|
||||
*
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @param string|null $searchTerm
|
||||
* @param array $tags
|
||||
* @param string|array|null $orderBy
|
||||
* @return array
|
||||
*/
|
||||
public function findList(
|
||||
?int $limit = null,
|
||||
?int $offset = null,
|
||||
?string $searchTerm = null,
|
||||
array $tags = [],
|
||||
$orderBy = null
|
||||
): array;
|
||||
|
||||
/**
|
||||
* Counts the number of elements in a list using provided filtering data
|
||||
*
|
||||
* @param string|null $searchTerm
|
||||
* @param array $tags
|
||||
* @return int
|
||||
*/
|
||||
public function countList(?string $searchTerm = null, array $tags = []): int;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Util;
|
||||
namespace Shlinkio\Shlink\Common\Response;
|
||||
|
||||
use Fig\Http\Message\StatusCodeInterface as StatusCode;
|
||||
use finfo;
|
@ -3,11 +3,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Util;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
|
||||
use function count;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function sprintf;
|
||||
use function trim;
|
||||
|
||||
final class IpAddress
|
||||
@ -36,14 +37,14 @@ final class IpAddress
|
||||
/**
|
||||
* @param string $address
|
||||
* @return IpAddress
|
||||
* @throws WrongIpException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function fromString(string $address): self
|
||||
{
|
||||
$address = trim($address);
|
||||
$parts = explode('.', $address);
|
||||
if (count($parts) !== self::IPV4_PARTS_COUNT) {
|
||||
throw WrongIpException::fromIpAddress($address);
|
||||
throw new InvalidArgumentException(sprintf('Provided IP "%s" is invalid', $address));
|
||||
}
|
||||
|
||||
return new self(...$parts);
|
||||
|
@ -9,7 +9,6 @@ use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Shlinkio\Shlink\Rest\Authentication\Plugin\ApiKeyHeaderPlugin;
|
||||
|
||||
use function Shlinkio\Shlink\Common\json_decode;
|
||||
use function sprintf;
|
||||
@ -48,7 +47,7 @@ abstract class ApiTestCase extends TestCase implements StatusCodeInterface, Requ
|
||||
protected function callApiWithKey(string $method, string $uri, array $options = []): ResponseInterface
|
||||
{
|
||||
$headers = $options[RequestOptions::HEADERS] ?? [];
|
||||
$headers[ApiKeyHeaderPlugin::HEADER_NAME] = 'valid_api_key';
|
||||
$headers['X-Api-Key'] = 'valid_api_key';
|
||||
$options[RequestOptions::HEADERS] = $headers;
|
||||
|
||||
return $this->callApi($method, $uri, $options);
|
||||
|
@ -3,48 +3,60 @@ declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Cache;
|
||||
|
||||
use Doctrine\Common\Cache\ApcuCache;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
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\Core\Options\AppOptions;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
use function putenv;
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
|
||||
class CacheFactoryTest extends TestCase
|
||||
{
|
||||
/** @var CacheFactory */
|
||||
private $factory;
|
||||
/** @var ServiceManager */
|
||||
private $sm;
|
||||
/** @var ObjectProphecy */
|
||||
private $container;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->factory = new CacheFactory();
|
||||
$this->sm = new ServiceManager(['services' => [
|
||||
AppOptions::class => new AppOptions(),
|
||||
]]);
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
putenv('APP_ENV');
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function productionReturnsApcAdapter(): void
|
||||
public function provideCacheConfig(): iterable
|
||||
{
|
||||
putenv('APP_ENV=pro');
|
||||
$instance = ($this->factory)($this->sm, '');
|
||||
$this->assertInstanceOf(ApcuCache::class, $instance);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function developmentReturnsArrayAdapter(): void
|
||||
{
|
||||
putenv('APP_ENV=dev');
|
||||
$instance = ($this->factory)($this->sm, '');
|
||||
$this->assertInstanceOf(ArrayCache::class, $instance);
|
||||
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;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class RedisFactoryTest extends TestCase
|
||||
* @test
|
||||
* @dataProvider provideRedisConfig
|
||||
*/
|
||||
public function createsRedisClientBasedOnConfig(?array $config, string $expectedCluster): void
|
||||
public function createsRedisClientBasedOnRedisConfig(?array $config, string $expectedCluster): void
|
||||
{
|
||||
$getConfig = $this->container->get('config')->willReturn([
|
||||
'redis' => $config,
|
||||
@ -39,6 +39,24 @@ class RedisFactoryTest extends TestCase
|
||||
$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];
|
||||
|
@ -6,7 +6,7 @@ namespace ShlinkioTest\Shlink\Common\Doctrine;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Doctrine\EntityManagerFactory;
|
||||
use Shlinkio\Shlink\Common\Type\ChronosDateTimeType;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class EntityManagerFactoryTest extends TestCase
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Type;
|
||||
namespace ShlinkioTest\Shlink\Common\Doctrine\Type;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use DateTime;
|
||||
@ -11,7 +11,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Type\ChronosDateTimeType;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
use stdClass;
|
||||
|
||||
class ChronosDateTimeTypeTest extends TestCase
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
namespace ShlinkioTest\Shlink\Common\I18n;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Factory\TranslatorFactory;
|
||||
use Shlinkio\Shlink\Common\I18n\TranslatorFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
@ -19,11 +19,11 @@ class TranslatorFactoryTest extends TestCase
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function serviceIsCreated()
|
||||
public function serviceIsCreated(): void
|
||||
{
|
||||
$instance = $this->factory->__invoke(new ServiceManager(['services' => [
|
||||
$instance = ($this->factory)(new ServiceManager(['services' => [
|
||||
'config' => [],
|
||||
]]), '');
|
||||
]]));
|
||||
$this->assertInstanceOf(Translator::class, $instance);
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ namespace ShlinkioTest\Shlink\Common\Middleware;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionObject;
|
||||
use Shlinkio\Shlink\Common\Middleware\IpAddressMiddlewareFactory;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class IpAddressMiddlewareFactoryTest extends TestCase
|
||||
@ -26,7 +25,7 @@ class IpAddressMiddlewareFactoryTest extends TestCase
|
||||
{
|
||||
$instance = ($this->factory)(new ServiceManager(['services' => [
|
||||
'config' => $config,
|
||||
]]), '');
|
||||
]]));
|
||||
|
||||
$ref = new ReflectionObject($instance);
|
||||
$checkProxyHeaders = $ref->getProperty('checkProxyHeaders');
|
||||
@ -40,7 +39,7 @@ class IpAddressMiddlewareFactoryTest extends TestCase
|
||||
|
||||
$this->assertTrue($checkProxyHeaders->getValue($instance));
|
||||
$this->assertEquals([], $trustedProxies->getValue($instance));
|
||||
$this->assertEquals(Visitor::REMOTE_ADDRESS_ATTR, $attributeName->getValue($instance));
|
||||
$this->assertEquals(IpAddressMiddlewareFactory::REQUEST_ATTR, $attributeName->getValue($instance));
|
||||
$this->assertEquals($expectedHeadersToInspect, $headersToInspect->getValue($instance));
|
||||
}
|
||||
|
||||
|
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Paginator\Adapter;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
|
||||
|
||||
class PaginableRepositoryAdapterTest extends TestCase
|
||||
{
|
||||
/** @var PaginableRepositoryAdapter */
|
||||
private $adapter;
|
||||
/** @var ObjectProphecy */
|
||||
private $repo;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->repo = $this->prophesize(PaginableRepositoryInterface::class);
|
||||
$this->adapter = new PaginableRepositoryAdapter($this->repo->reveal(), 'search', ['foo', 'bar'], 'order');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function getItemsFallbacksToFindList()
|
||||
{
|
||||
$this->repo->findList(10, 5, 'search', ['foo', 'bar'], 'order')->shouldBeCalledOnce();
|
||||
$this->adapter->getItems(5, 10);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function countFallbacksToCountList()
|
||||
{
|
||||
$this->repo->countList('search', ['foo', 'bar'])->shouldBeCalledOnce();
|
||||
$this->adapter->count();
|
||||
}
|
||||
}
|
44
module/Common/test/Validation/SluggerFilterTest.php
Normal file
44
module/Common/test/Validation/SluggerFilterTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?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'];
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\Core;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
|
||||
use Shlinkio\Shlink\Common\Type\ChronosDateTimeType;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
|
||||
/** @var $metadata ClassMetadata */
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\Core;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
|
||||
use Shlinkio\Shlink\Common\Type\ChronosDateTimeType;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
|
||||
/** @var $metadata ClassMetadata */
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
@ -10,8 +10,8 @@ use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Shlinkio\Shlink\Common\Exception\PreviewGenerationException;
|
||||
use Shlinkio\Shlink\Common\Response\ResponseUtilsTrait;
|
||||
use Shlinkio\Shlink\Common\Service\PreviewGeneratorInterface;
|
||||
use Shlinkio\Shlink\Common\Util\ResponseUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
|
@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\Core\Entity;
|
||||
use Cake\Chronos\Chronos;
|
||||
use JsonSerializable;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\UnknownVisitLocation;
|
||||
@ -45,7 +45,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
||||
|
||||
try {
|
||||
return (string) IpAddress::fromString($address)->getObfuscatedCopy();
|
||||
} catch (WrongIpException $e) {
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
|
||||
|
||||
|
@ -4,11 +4,10 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Model;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Common\Middleware\IpAddressMiddlewareFactory;
|
||||
|
||||
final class Visitor
|
||||
{
|
||||
public const REMOTE_ADDRESS_ATTR = 'remote_address';
|
||||
|
||||
/** @var string */
|
||||
private $userAgent;
|
||||
/** @var string */
|
||||
@ -28,7 +27,7 @@ final class Visitor
|
||||
return new self(
|
||||
$request->getHeaderLine('User-Agent'),
|
||||
$request->getHeaderLine('Referer'),
|
||||
$request->getAttribute(self::REMOTE_ADDRESS_ATTR)
|
||||
$request->getAttribute(IpAddressMiddlewareFactory::REQUEST_ATTR)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Paginator\Adapter;
|
||||
namespace Shlinkio\Shlink\Core\Paginator\Adapter;
|
||||
|
||||
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Zend\Paginator\Adapter\AdapterInterface;
|
||||
|
||||
use function strip_tags;
|
||||
use function trim;
|
||||
|
||||
class PaginableRepositoryAdapter implements AdapterInterface
|
||||
class ShortUrlRepositoryAdapter implements AdapterInterface
|
||||
{
|
||||
public const ITEMS_PER_PAGE = 10;
|
||||
|
||||
/** @var PaginableRepositoryInterface */
|
||||
private $paginableRepository;
|
||||
/** @var ShortUrlRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var null|string */
|
||||
private $searchTerm;
|
||||
/** @var null|array|string */
|
||||
@ -23,12 +23,12 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
private $tags;
|
||||
|
||||
public function __construct(
|
||||
PaginableRepositoryInterface $paginableRepository,
|
||||
ShortUrlRepositoryInterface $repository,
|
||||
$searchTerm = null,
|
||||
array $tags = [],
|
||||
$orderBy = null
|
||||
) {
|
||||
$this->paginableRepository = $paginableRepository;
|
||||
$this->repository = $repository;
|
||||
$this->searchTerm = $searchTerm !== null ? trim(strip_tags($searchTerm)) : null;
|
||||
$this->orderBy = $orderBy;
|
||||
$this->tags = $tags;
|
||||
@ -43,7 +43,7 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
*/
|
||||
public function getItems($offset, $itemCountPerPage): array
|
||||
{
|
||||
return $this->paginableRepository->findList(
|
||||
return $this->repository->findList(
|
||||
$itemCountPerPage,
|
||||
$offset,
|
||||
$this->searchTerm,
|
||||
@ -63,6 +63,6 @@ class PaginableRepositoryAdapter implements AdapterInterface
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return $this->paginableRepository->countList($this->searchTerm, $this->tags);
|
||||
return $this->repository->countList($this->searchTerm, $this->tags);
|
||||
}
|
||||
}
|
@ -4,10 +4,27 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Repository;
|
||||
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
|
||||
interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepositoryInterface
|
||||
interface ShortUrlRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
/**
|
||||
* Gets a list of elements using provided filtering data
|
||||
*
|
||||
* @param string|array|null $orderBy
|
||||
*/
|
||||
public function findList(
|
||||
?int $limit = null,
|
||||
?int $offset = null,
|
||||
?string $searchTerm = null,
|
||||
array $tags = [],
|
||||
$orderBy = null
|
||||
): array;
|
||||
|
||||
/**
|
||||
* Counts the number of elements in a list using provided filtering data
|
||||
*/
|
||||
public function countList(?string $searchTerm = null, array $tags = []): int;
|
||||
|
||||
public function findOneByShortCode(string $shortCode): ?ShortUrl;
|
||||
}
|
||||
|
@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrl\FindShortCodeTrait;
|
||||
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
|
||||
@ -35,8 +35,8 @@ class ShortUrlService implements ShortUrlServiceInterface
|
||||
{
|
||||
/** @var ShortUrlRepository $repo */
|
||||
$repo = $this->em->getRepository(ShortUrl::class);
|
||||
$paginator = new Paginator(new PaginableRepositoryAdapter($repo, $searchQuery, $tags, $orderBy));
|
||||
$paginator->setItemCountPerPage(PaginableRepositoryAdapter::ITEMS_PER_PAGE)
|
||||
$paginator = new Paginator(new ShortUrlRepositoryAdapter($repo, $searchQuery, $tags, $orderBy));
|
||||
$paginator->setItemCountPerPage(ShortUrlRepositoryAdapter::ITEMS_PER_PAGE)
|
||||
->setCurrentPageNumber($page);
|
||||
|
||||
return $paginator;
|
||||
|
@ -10,7 +10,6 @@ use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
|
||||
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
@ -19,6 +18,7 @@ use Shlinkio\Shlink\Core\EventDispatcher\LocateShortUrlVisit;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\ShortUrlVisited;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\UnknownVisitLocation;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Paginator\Adapter;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
|
||||
class ShortUrlRepositoryAdapterTest extends TestCase
|
||||
{
|
||||
/** @var ShortUrlRepositoryAdapter */
|
||||
private $adapter;
|
||||
/** @var ObjectProphecy */
|
||||
private $repo;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->repo = $this->prophesize(ShortUrlRepositoryInterface::class);
|
||||
$this->adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), 'search', ['foo', 'bar'], 'order');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function getItemsFallbacksToFindList(): void
|
||||
{
|
||||
$this->repo->findList(10, 5, 'search', ['foo', 'bar'], 'order')->shouldBeCalledOnce();
|
||||
$this->adapter->getItems(5, 10);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function countFallbacksToCountList(): void
|
||||
{
|
||||
$this->repo->countList('search', ['foo', 'bar'])->shouldBeCalledOnce();
|
||||
$this->adapter->count();
|
||||
}
|
||||
}
|
21
module/EventDispatcher/LICENSE
Normal file
21
module/EventDispatcher/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
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.
|
13
module/EventDispatcher/README.md
Normal file
13
module/EventDispatcher/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Shlink Event Dispatcher
|
||||
|
||||
This library provides a PSR-14 EventDispatcher which is capable of dispatching both regular listeners and async listeners which are run using [swoole]'s task system.
|
||||
|
||||
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-event-dispatcher
|
||||
|
||||
> This library is also an expressive module which provides its own `ConfigProvider`. Add it to your configuration to get everything automatically set up.
|
@ -23,6 +23,8 @@ return [
|
||||
Psr\EventDispatcherInterface::class => Phly\EventDispatcher::class,
|
||||
],
|
||||
'delegators' => [
|
||||
// The listener provider has to be lazy, because it uses the Swoole server to generate AsyncEventListeners
|
||||
// Without making this lazy, CLI commands which depend on the EventDispatcher fail
|
||||
Psr\ListenerProviderInterface::class => [
|
||||
LazyServiceFactory::class,
|
||||
],
|
||||
|
21
module/IpGeolocation/LICENSE
Normal file
21
module/IpGeolocation/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
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.
|
30
module/IpGeolocation/README.md
Normal file
30
module/IpGeolocation/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Shlink IP Address Geolocation module
|
||||
|
||||
Shlink module with tools to geolocate an IP address using different strategies.
|
||||
|
||||
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-ip-geolocation
|
||||
|
||||
> This library is also an expressive module which provides its own `ConfigProvider`. Add it to your configuration to get everything automatically set up.
|
||||
|
||||
## *TODO*
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'geolite2' => [
|
||||
'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb',
|
||||
'temp_dir' => sys_get_temp_dir(),
|
||||
// 'download_from' => 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz',
|
||||
],
|
||||
|
||||
];
|
||||
```
|
35
module/IpGeolocation/config/geolite2.config.php
Normal file
35
module/IpGeolocation/config/geolite2.config.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\IpGeolocation;
|
||||
|
||||
use GeoIp2\Database\Reader;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Zend\ServiceManager\Proxy\LazyServiceFactory;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Reader::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
'delegators' => [
|
||||
// The GeoLite2 db reader has to be lazy so that it does not try to load the DB file at app bootstrapping.
|
||||
// By doing so, it would fail the first time shlink tries to download it.
|
||||
Reader::class => [
|
||||
LazyServiceFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
'lazy_services' => [
|
||||
'class_map' => [
|
||||
Reader::class => Reader::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Reader::class => ['config.geolite2.db_location'],
|
||||
],
|
||||
|
||||
];
|
10
module/IpGeolocation/src/Exception/ExceptionInterface.php
Normal file
10
module/IpGeolocation/src/Exception/ExceptionInterface.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\IpGeolocation\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface ExceptionInterface extends Throwable
|
||||
{
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
namespace Shlinkio\Shlink\IpGeolocation\Exception;
|
||||
|
||||
use RuntimeException as SplRuntimeException;
|
||||
|
@ -1,13 +1,14 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
namespace Shlinkio\Shlink\IpGeolocation\Exception;
|
||||
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
class WrongIpException extends RuntimeException
|
||||
class WrongIpException extends RuntimeException implements ExceptionInterface
|
||||
{
|
||||
public static function fromIpAddress($ipAddress, ?Throwable $prev = null): self
|
||||
{
|
@ -8,7 +8,7 @@ use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use PharData;
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
||||
use Symfony\Component\Filesystem\Exception as FilesystemException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Throwable;
|
||||
|
@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\IpGeolocation\GeoLite2;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
||||
|
||||
interface DbUpdaterInterface
|
||||
{
|
||||
|
@ -3,9 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\IpGeolocation\Resolver;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
|
||||
|
||||
class ChainIpLocationResolver implements IpLocationResolverInterface
|
||||
{
|
||||
|
@ -3,9 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\IpGeolocation\Resolver;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
|
||||
|
||||
class EmptyIpLocationResolver implements IpLocationResolverInterface
|
||||
{
|
||||
|
@ -8,9 +8,8 @@ use GeoIp2\Exception\AddressNotFoundException;
|
||||
use GeoIp2\Model\City;
|
||||
use GeoIp2\Record\Subdivision;
|
||||
use MaxMind\Db\Reader\InvalidDatabaseException;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
|
||||
|
||||
use function Functional\first;
|
||||
|
||||
|
@ -6,9 +6,8 @@ namespace Shlinkio\Shlink\IpGeolocation\Resolver;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
|
||||
|
||||
use function Shlinkio\Shlink\Common\json_decode;
|
||||
use function sprintf;
|
||||
|
@ -3,7 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\IpGeolocation\Resolver;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model;
|
||||
|
||||
interface IpLocationResolverInterface
|
||||
|
@ -1,16 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Exception;
|
||||
namespace ShlinkioTest\Shlink\IpGeolocation\Exception;
|
||||
|
||||
use Exception;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
|
||||
class WrongIpExceptionTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function fromIpAddressProperlyCreatesExceptionWithoutPrev()
|
||||
public function fromIpAddressProperlyCreatesExceptionWithoutPrev(): void
|
||||
{
|
||||
$e = WrongIpException::fromIpAddress('1.2.3.4');
|
||||
|
||||
@ -18,8 +18,9 @@ class WrongIpExceptionTest extends TestCase
|
||||
$this->assertEquals(0, $e->getCode());
|
||||
$this->assertNull($e->getPrevious());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function fromIpAddressProperlyCreatesExceptionWithPrev()
|
||||
public function fromIpAddressProperlyCreatesExceptionWithPrev(): void
|
||||
{
|
||||
$prev = new Exception('Previous error');
|
||||
$e = WrongIpException::fromIpAddress('1.2.3.4', $prev);
|
@ -8,7 +8,7 @@ use GuzzleHttp\Exception\ClientException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater;
|
||||
use Shlinkio\Shlink\IpGeolocation\GeoLite2\GeoLite2Options;
|
||||
use Symfony\Component\Filesystem\Exception as FilesystemException;
|
||||
|
@ -5,7 +5,7 @@ namespace ShlinkioTest\Shlink\IpGeolocation\Resolver;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\ChainIpLocationResolver;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
|
||||
|
@ -9,7 +9,7 @@ use GeoIp2\Model\City;
|
||||
use MaxMind\Db\Reader\InvalidDatabaseException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\GeoLite2LocationResolver;
|
||||
|
||||
|
@ -8,7 +8,7 @@ use GuzzleHttp\Exception\TransferException;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use Shlinkio\Shlink\IpGeolocation\Resolver\IpApiLocationResolver;
|
||||
|
||||
|
@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
@ -32,6 +33,7 @@ return [
|
||||
Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class,
|
||||
Action\Tag\UpdateTagAction::class => ConfigAbstractFactory::class,
|
||||
|
||||
ImplicitOptionsMiddleware::class => Middleware\EmptyResponseImplicitOptionsMiddlewareFactory::class,
|
||||
Middleware\BodyParserMiddleware::class => InvokableFactory::class,
|
||||
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
||||
Middleware\PathVersionMiddleware::class => InvokableFactory::class,
|
||||
|
@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\Rest;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
|
||||
use Shlinkio\Shlink\Common\Type\ChronosDateTimeType;
|
||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||
|
||||
/** @var $metadata ClassMetadata */
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Middleware;
|
||||
|
||||
use Zend\Diactoros\Response\EmptyResponse;
|
||||
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||
|
||||
class EmptyResponseImplicitOptionsMiddlewareFactory
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
return new ImplicitOptionsMiddleware(function () {
|
||||
return new EmptyResponse();
|
||||
});
|
||||
}
|
||||
}
|
@ -10,9 +10,10 @@ use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
use function str_replace;
|
||||
|
||||
/** @deprecated */
|
||||
class ShortCodePathMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private const OLD_PATH_PREFIX = '/short-codes';
|
||||
private const OLD_PATH_PREFIX = '/short-codes'; // Old path is deprecated. Remove this middleware on v2
|
||||
private const NEW_PATH_PREFIX = '/short-urls';
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,7 @@ use ShlinkioTest\Shlink\Common\ApiTest\ApiTestCase;
|
||||
class ListShortUrlsTest extends ApiTestCase
|
||||
{
|
||||
/** @test */
|
||||
public function shortUrlsAreProperlyListed()
|
||||
public function shortUrlsAreProperlyListed(): void
|
||||
{
|
||||
$resp = $this->callApiWithKey(self::METHOD_GET, '/short-urls');
|
||||
$respPayload = $this->getJsonResponsePayload($resp);
|
||||
|
32
module/Rest/test-api/Action/OptionsRequestTest.php
Normal file
32
module/Rest/test-api/Action/OptionsRequestTest.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioApiTest\Shlink\Rest\Action;
|
||||
|
||||
use ShlinkioTest\Shlink\Common\ApiTest\ApiTestCase;
|
||||
|
||||
use function explode;
|
||||
|
||||
class OptionsRequestTest extends ApiTestCase
|
||||
{
|
||||
/** @test */
|
||||
public function optionsRequestsReturnEmptyResponse(): void
|
||||
{
|
||||
$resp = $this->callApi(self::METHOD_OPTIONS, '/short-urls');
|
||||
|
||||
$this->assertEquals(self::STATUS_NO_CONTENT, $resp->getStatusCode());
|
||||
$this->assertEmpty((string) $resp->getBody());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function optionsRequestsReturnAllowedMethodsForEndpoint(): void
|
||||
{
|
||||
$resp = $this->callApi(self::METHOD_OPTIONS, '/short-urls');
|
||||
$allowedMethods = $resp->getHeaderLine('Allow');
|
||||
|
||||
$this->assertEquals([
|
||||
self::METHOD_GET,
|
||||
self::METHOD_POST,
|
||||
], explode(',', $allowedMethods));
|
||||
}
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
namespace ShlinkioTest\Shlink\Rest\Middleware;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionObject;
|
||||
use Shlinkio\Shlink\Common\Factory\EmptyResponseImplicitOptionsMiddlewareFactory;
|
||||
use Shlinkio\Shlink\Rest\Middleware\EmptyResponseImplicitOptionsMiddlewareFactory;
|
||||
use Zend\Diactoros\Response\EmptyResponse;
|
||||
use Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase
|
||||
{
|
||||
@ -21,16 +20,16 @@ class EmptyResponseImplicitOptionsMiddlewareFactoryTest extends TestCase
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function serviceIsCreated()
|
||||
public function serviceIsCreated(): void
|
||||
{
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), '');
|
||||
$instance = ($this->factory)();
|
||||
$this->assertInstanceOf(ImplicitOptionsMiddleware::class, $instance);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function responsePrototypeIsEmptyResponse()
|
||||
public function responsePrototypeIsEmptyResponse(): void
|
||||
{
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), '');
|
||||
$instance = ($this->factory)();
|
||||
|
||||
$ref = new ReflectionObject($instance);
|
||||
$prop = $ref->getProperty('responseFactory');
|
@ -5,9 +5,9 @@ namespace ShlinkioTest\Shlink\Rest\Util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user