shlinkio/shlink-common

Shlink 常用工具

v6.2.0 2024-08-11 16:29 UTC

README

Build Status Code Coverage Latest Stable Version License Paypal donate

此库提供了一些用于 Web 应用的工具和约定。其主要目的是在 Shlink 项目中使用,但任何 PHP 项目都可以从中受益。

它提供的多数元素都需要一个 PSR-11 容器,并且由于它包含 ConfigProvider,在 mezzio 应用程序中易于集成。

安装

使用 composer 安装此库

composer require shlinkio/shlink-common

此库也是一个 mezzio 模块,它提供了自己的 ConfigProvider。将其添加到您的配置中,以自动设置一切。

缓存

此库提供了通过 symfony/cache 提供的 PSR-6 和 PSR-16 缓存适配器。

它们可以通过 Psr\Cache\CacheItemPoolInterfacePsr\SimpleCache\CacheInterface 获取。

它们返回的具体实现取决于您的配置。

  • debug 配置设置为 true 或未安装 APCu 扩展且未定义 cache.redis 配置时,将返回一个 ArrayAdapter 实例。
  • 当未定义 cache.redis 且已安装 APCu 扩展时,将返回一个 ApcuAdapter 实例。
  • 当定义了 cache.redis 配置时,将返回一个 RedisAdapter 实例。

最后两个适配器将使用 cache.namespace 配置条目中定义的命名空间。

这三个适配器将允许为未明确定义生命周期的条目设置默认生命周期,从 cache.default_lifetime 中获取。

<?php

declare(strict_types=1);

return [

    'debug' => false,

    'cache' => [
        'namespace' => 'my_namespace',
        'default_lifetime' => 86400, // Optional. Defaults to "never expire"

        'redis' => [
            'servers' => [
                // These should be valid URIs. Make sure credentials are URL-encoded
                'tcp://1.1.1.1:6379',
                'tcp://2.2.2.2:6379',
                'tcp://3.3.3.3:6379/3', // Define a database index to use (https://redis.ac.cn/docs/commands/select/)
                'tcp://user:pass%40word@4.4.4.4:6379', // Redis ACL (https://redis.ac.cn/docs/management/security/acl/)
                'tcp://:password@5.5.5.5:6379', // Redis security (https://redis.ac.cn/docs/management/security/)
                'tls://server_with_encryption:6379',
            ],
            'sentinel_service' => 'the_service', // Optional.
        ],
    ],

];

Redis 支持

您可以通过在 cache.redis 配置下定义一些选项来允许在 redis 实例、redis 集群或 redis sentinels 上执行缓存。

  • servers: 一系列 redis 服务器。如果提供,它将被视为单个实例;否则,将假定是一个集群。
  • sentinel_service: 允许您启用 sentinel 模式。当提供时,服务器将被视为 sentinel 实例。

注意 servers 中的条目支持以下形式的凭据:tcp://password@my-server:6379tcp://username:password@my-server:6379

Redis 发布辅助工具

此外,为了支持 redis pub/sub 的发布,还提供了一个 RedisPublishingHelper 服务,该服务将使用上述配置连接到 redis 实例/集群。

<?php

declare(strict_types=1);

use Shlinkio\Shlink\Common\Cache\RedisPublishingHelper;
use Shlinkio\Shlink\Common\UpdatePublishing\Update;

$helper = $container->get(RedisPublishingHelper::class);

$helper->publishUpdate(Update::forTopicAndPayload('some_queue', ['foo' => 'bar']));

中间件

此模块提供了一套有用的中间件,所有中间件都注册为容器中的服务。

  • CloseDbConnectionMiddleware:

    应该是管道中的早期中间件。它使用 EntityManager 确保在请求结束时关闭数据库连接。

    当使用非阻塞 IO 服务器(如 RoadRunner 或 FrankenPHP)提供服务且在请求之间持久化服务时,应使用它。

  • IpAddress (来自 akrabat/ip-address-middleware 包)

    改进了对远程 IP 地址的检测。

    可以使用此配置自定义用于搜索地址的头部集合。

    <?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',
            ],
        ],
    
    ];
  • RequestIdMiddleware:通过读取 X-Request-Id 头或回退到自动生成的 UUID v4 来设置当前请求的 request-id 属性。

    它还实现了monolog的ProcessorInterface,将请求ID设置为extra.request-id

Doctrine集成

提供了一些与doctrine相关的服务,可以通过配置进行自定义。

EntityManager

可以使用名称emDoctrine\ORM\EntityManager获取EntityManager服务。

无论如何,它都会被装饰,以便在关闭后自动重新打开。

可以使用此配置自定义EntityManager

<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Common;

use Doctrine\ORM\Events;

return [

    'entity_manager' => [
        'orm' => [
            'proxies_dir' => 'data/proxies', // Directory in which proxies will be persisted
            'default_repository_classname' => '', // A FQCN for the class used as repository by default
            'entities_mappings' => [ // List of directories from which entities mappings should be read
                __DIR__ . '/../foo/entities-mappings',
                __DIR__ . '/../bar/entities-mappings',
            ],
            'types' => [ // List of custom database types to map
                Doctrine\Type\ChronosDateTimeType::CHRONOS_DATETIME => Doctrine\Type\ChronosDateTimeType::class,
            ],
            'load_mappings_using_functional_style' => true, // Makes loader assume mappings return a function which should be invoked. Defaults to false
            'listeners' => [ // Map telling which service listeners to invoke for every ORM event
                Events::postFlush => ['some_service'],
                Events::preUpdate => ['foo', 'bar'],
            ]
        ],
        'connection' => [ // Database connection params
            'driver' => 'pdo_mysql',
            'host' => 'shlink_db',
            'user' => 'DB_USER',
            'password' => 'DB_PASSWORD',
            'dbname' => 'DB_NAME',
            'charset' => 'utf8',
        ],
    ],

];

连接

除了EntityManager,还有两个连接对象可以被获取。

  • Doctrine\DBAL\Connection:返回EntityManager使用的连接,无需修改。
  • Shlinkio\Shlink\Common\Doctrine\NoDbNameConnection:返回一个连接,与EntityManager使用的相同,但不设置数据库名。这在执行如创建数据库等操作时很有用(否则会失败,因为数据库尚未存在)。

实体仓库工厂

为了允许每个实体有多个仓库,并避免使用$this->em->getRepository(MyEntity::class)模式,而是“提升”注入仓库,此库提供了一个可以像这样使用的EntityRepositoryFactory辅助类。

<?php

declare(strict_types=1);

use Shlinkio\Shlink\Common\Doctrine\EntityRepositoryFactory;

return [

    'dependencies' => [
        MyEntityRepository::class => [EntityRepositoryFactory::class, MyEntity::class],
    ],

];

记录器

此库提供了一些与记录器相关的辅助类。

记录器工厂

LoggerFactory类能够创建Monolog\Logger实例,该实例包含流处理器或轮换文件处理器,这些处理器应定义在logger配置条目下。

<?php

declare(strict_types=1);

use Monolog\Level;
use Shlinkio\Shlink\Common\Middleware\RequestIdMiddleware;
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
use Shlinkio\Shlink\Common\Logger\LoggerType;

return [

    'logger' => [
        'Shlink' => [
            'type' => LoggerType::FILE->value,
            'level' => Level::Info->value,
            'processors' => [RequestIdMiddleware::class],
            'line_format' => '[%datetime%] [%extra.request_id%] %channel%.%level_name% - %message%',
        ],
        'Access' => [
            'type' => LoggerType::STREAM->value,
            'level' => Level::Alert->value,
            'line_format' => '[%datetime%] %level_name% - %message%',
            'add_new_line' => false,
        ],
    ],

    'dependencies' => [
        'factories' => [
            'ShlinkLogger' => [LoggerFactory::class, 'Shlink'],
            'AccessLogger' => [LoggerFactory::class, 'Access'],
        ],
    ],

];

每个记录器都可以有这些配置选项

  • type:来自LoggerType枚举的任何值,这将使不同的处理器被注入到记录器实例中。
  • level:来自monolog的Level枚举的任何值,它决定了生成的日志的最小级别。如果没有提供,默认为Level::Info
  • line_format:要生成的行日志的格式。
  • add_new_line:是否在每条日志后添加额外的空行。默认为true
  • processors:一个可选的额外处理器列表,用于注入生成的记录器。数组中的值必须是服务名称。
  • destination:日志发送的位置。对于流日志,默认为php:stdout,对于文件日志,默认为data/log/shlink_log.log

其他记录器工具

此模块提供了一些其他与记录器相关的实用工具。

  • ExceptionWithNewLineProcessor:一个monolog处理器,它捕获日志消息中的{e}模式,并在它之前添加一个新行,假设你将用异常跟踪替换它。
  • LoggerAwareDelegatorFactory:一个服务管理器代理工厂,它会检查前一个工厂返回的服务是否是Psr\Log\LoggerAwareInterface实例。如果是,它会设置Psr\Log\LoggerInterface服务(如果已注册)。
  • ErrorLogger:一个可调用对象,它期望注入一个Psr\Log\LoggerInterface并使用它在被调用时记录一个Throwable。它将5xx错误记录为错误级别,将4xx错误记录为调试级别。
  • ErrorHandlerListenerAttachingDelegator:一个服务管理器代理工厂,它将配置在error_handler.listeners下的所有服务注册为策略ErrorHandlerProblemDetailsMiddleware的监听器。
  • BackwardsCompatibleMonologProcessor:它允许你将monolog 2处理器包装为callable(array): array签名,以便与monolog 3及其新的callable(LogRecord): LogRecord签名兼容。
  • AccessLogMiddleware:一个PSR-15中间件,用于记录请求。它期望在AccessLogMiddleware::LOGGER_SERVICE_NAME下注册PSR-3记录器服务。

HTTP客户端

一个Guzzle HTTP客户端已预先注册,服务名称为GuzzleHttp\Client,别名为httpClient

可以通过添加请求和响应中间件来对其进行自定义,如下所示

<?php

declare(strict_types=1);

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

return [

    'http_client' => [
        'request_middlewares' => [
            'some_service_middleware',
            fn (RequestInterface $req): RequestInterface => $req->withHeader('X-Foo', 'bar'),
        ],
        'response_middlewares' => [
            'some_service_middleware',
            fn (ResponseInterface $res): ResponseInterface => $res->withHeader('X-Foo', 'bar'),
        ],
    ],

];

中间件可以注册为具有类似示例中签名的静态回调,或者作为解析到具有相同签名的服务的服务名称。

Mercure

用于在Mercure中心发布更新的辅助工具已预先注册。您需要提供如下配置

<?php

declare(strict_types=1);

return [

    'mercure' => [
        // A URL publicly available in which the mercure hub can be reached.
        'public_hub_url' => null,

        // Optional. An internal URL in which the mercure hub can be reached. Will fall back to public_hub_url if not provided.
        'internal_hub_url' => null,

        // The JWT secret you provided to the mercure hub as JWT_KEY, so that valid JWTs can be generated.
        'jwt_secret' => null,

        // Optional. The issuer for generated JWTs. Will fall back to "Shlink".
        'jwt_issuer' => 'Shlink',
    ],

];

之后,您可以从容器中获取发布者,并调用它来为特定主题发布更新

<?php

declare(strict_types=1);

use Symfony\Component\Mercure\Publisher;
use Symfony\Component\Mercure\Update;

$publisher = $container->get(Publisher::class);

$publisher(new Update('some_topic', json_encode([
    'foo' => 'bar',
])));

有关symfony/mercure组件的更多信息,请参阅此处:https://symfony.ac.cn/blog/symfony-gets-real-time-push-capabilities

RabbitMQ

用于在RabbitMQ上发布更新的辅助工具已预先注册。您需要提供如下配置

<?php

declare(strict_types=1);

return [

    'rabbitmq' => [
        // The RabbitMQ server name
        'host' => 'my-rabbitmq-server.com',

        // The RabbitMQ server port
        'port' => '5672',

        // The username credential
        'user' => 'username',

        // The password credential
        'password' => 'password',

        // The vHost
        'vhost' => '/',

        // Tells if connection should be encrypted. Defaults to false if not provided
        'use_ssl' => true,
    ],

];

之后,您可以从容器中获取辅助工具,并调用它来为特定队列发布更新

<?php

declare(strict_types=1);

use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelper;
use Shlinkio\Shlink\Common\UpdatePublishing\Update;

$helper = $container->get(RabbitMqPublishingHelper::class);

$helper->publishUpdate(Update::forTopicAndPayload('some_queue', ['foo' => 'bar']));

实用工具

  • PagerfantaUtilsTrait:一个特质,提供从Pagerfanta\Pagerfanta对象获取有用信息的方法。它要求您安装pagerfanta/core
  • Paginator:一个扩展Pagerfanta的对象,使其在设置-1作为最大结果并在此情况下获取所有结果方面表现得像laminas的Paginator对象。它要求您安装pagerfanta/core
  • DateRange:一个不可变值对象,封装两个Chronos日期对象,用于表示两个日期之间的时间范围。
  • IpAddress:一个不可变值对象,表示一个IP地址,可以复制到删除最后一个八位的匿名实例中。
  • NamespacedStore:一个symfony/lock存储,可以包装另一个存储实例,但确保键以命名空间和命名空间分隔符为前缀。