mmo/sf-utils

Symfony组件的扩展集合

v0.2.0 2021-09-19 08:03 UTC

This package is auto-updated.

Last update: 2024-09-06 18:02:26 UTC


README

验证器

ITIN验证器

ITIN - 个人纳税人识别号

<?php

require_once './vendor/autoload.php';

use mmo\sf\Validator\Constraints\Itin;
use Symfony\Component\Validator\Validation;

$validator = Validation::createValidatorBuilder()->getValidator();
$validator->validate('foo', new Itin()); // NOT VALID
$validator->validate('918-97-5273', new Itin()); // VALID

生日

require_once './vendor/autoload.php';

use mmo\sf\Validator\Constraints\Birthday;
use Symfony\Component\Validator\Validation;

$validator = Validation::createValidatorBuilder()->getValidator();
$validator->validate((new DateTimeImmutable('now'))->modify('-5 years'), new Birthday(['minAge' => 18])); // NOT VALID
$validator->validate((new DateTimeImmutable('now'))->modify('-120 years'), new Birthday()); // NOT VALID
$validator->validate((new DateTimeImmutable('now'))->modify('-5 years'), new Birthday()); // VALID

银行路由号码

require_once './vendor/autoload.php';

use mmo\sf\Validator\Constraints\BankRoutingNumber;
use Symfony\Component\Validator\Validation;

$validator = Validation::createValidatorBuilder()->getValidator();
$validator->validate('1234567890', new BankRoutingNumber()); // NOT VALID
$validator->validate('275332587', new BankRoutingNumber()); // VALID

Utf8Letters

仅允许UTF-8字母和破折号。

require_once './vendor/autoload.php';

use mmo\sf\Validator\Constraints\Utf8Letters;
use Symfony\Component\Validator\Validation;

$validator = Validation::createValidatorBuilder()->getValidator();
$validator->validate('foo.bar', new Utf8Letters()); // NOT VALID
$validator->validate('Zażółć', new Utf8Letters()); // VALID

Utf8Words

仅允许UTF-8字母、破折号和空格。用于验证个人全名。

require_once './vendor/autoload.php';

use mmo\sf\Validator\Constraints\Utf8Words;
use Symfony\Component\Validator\Validation;

$validator = Validation::createValidatorBuilder()->getValidator();
$validator->validate('foo.bar', new Utf8Words()); // NOT VALID
$validator->validate('Zażółć gęślą', new Utf8Words()); // VALID

OnlyDigits

仅允许数字。

<?php

use mmo\sf\Validator\Constraints\OnlyDigits;
use Symfony\Component\Validator\Validation;

require_once './vendor/autoload.php';

$validator = Validation::createValidatorBuilder()->getValidator();
$violations = $validator->validate('f1234', new OnlyDigits()); // NOT VALID
$violations = $validator->validate('1234567', new OnlyDigits()); // VALID

ArrayConstraintValidatorFactory

不遵循命名约束和约束验证器命名约定的验证器,将不会在ConstraintValidatorFactory的默认实现中找到。

ArrayConstraintValidatorFactory解决了这个问题,您可以将约束验证器映射到对象。

use Symfony\Component\Validator\Validation;
use mmo\sf\Validator\ArrayConstraintValidatorFactory;
use Kiczort\PolishValidatorBundle\Validator\Constraints\NipValidator;

// ....
$validatorFactory = new ArrayConstraintValidatorFactory(['kiczort.validator.nip' => new NipValidator()]);
$validator = Validation::createValidatorBuilder()
            ->setConstraintValidatorFactory($validatorFactory)
            ->getValidator();
// ...

翻译器

FakeTranslator

FakeTranslator类可以在单元测试中使用,而不是使用存根。目前只支持idlocale参数。

<?php

require_once './vendor/autoload.php';

use mmo\sf\Translation\FakeTranslator;

$translator = new FakeTranslator('en');
$translator->trans('foo'); // en-foo
$translator->trans('foo', [], null, 'us'); // us-foo

安全性

加密器

Encrypter用于加密字符串值。所有加密值都使用OpenSSL和AES-256-CBC加密算法(默认)进行加密。

<?php

use mmo\sf\Util\Encrypter;

require_once './vendor/autoload.php';

$encrypter = new Encrypter('my-secret-key');
$secret = $encrypter->encryptString('secret message');
$plaintext = $encrypter->decryptString($secret); 

AlwaysTheSameEncoderFactory (Symofny 4.4和5.4)

AlwaysTheSameEncoderFactory在与UserPasswordEncoder结合的集成测试中非常有用。无论您传递哪种UserInterface实现,都会始终使用通过构造函数注入的相同密码加密器。

<?php

require_once './vendor/autoload.php';

use mmo\sf\Security\Test\AlwaysTheSameEncoderFactory;
use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;

$factory = new AlwaysTheSameEncoderFactory(new PlaintextPasswordEncoder());
$encoder = new UserPasswordEncoder($factory);
// now you can pass $encoder to your service, which expect `UserPasswordEncoderInterface`

MemoryUserProvider (仅限Symfony 4.4和5.4)

对于Symfony 6.4+,请使用内置提供者\Symfony\Component\Security\Core\User\InMemoryUserProvider

MemoryUserProvider是一个简单的非持久用户提供者,用于测试。

此提供程序与InMemoryUserProvider不同,它允许存储任何实现UserInterface接口的用户对象,而不仅仅是内部Symfony User类。

<?php

require_once './vendor/autoload.php';

use mmo\sf\Security\Test\MemoryUserProvider;
use Symfony\Component\Security\Core\User\User;

$provider = new MemoryUserProvider(User::class, []);
$provider->createUser(new User('test', 'foo'));
$provider->loadUserByUsername('test');

表单

RamseyUuidToStringTransformer

在UUID字符串和UUID对象之间转换。Symfony 5.3包含一个自己的UuidToStringTransformer转换器,但您还需要使用symfony/uuid组件。此转换器与ramsey/uuid库一起工作。

PrimaryKeyToEntityTransformer

在主键(不支持复合主键)和实体之间转换。

StringInsteadNullTransformer

此转换器的目的是修复当您有一个带有ChoiceType的表单,并为该字段传递空值时出现的错误。您的实体/DTO仅期望字符串值,您会收到错误“在属性路径 ... 上期望参数类型为 "string","NULL" 已给出”。

Symfony将此空字符串更改为null值(由于ChoiceToValueTransformer)。您可以将StringInsteadNullTransformer作为模型转换器添加,这样null值将被转换为空字符串。

//...

use mmo\sf\Form\DataTransformer\StringInsteadNullTransformer;

class StateType extends AbstractType
{
    private StatesProviderInterface $statesProvider;

    public function __construct(StatesProviderInterface $statesProvider)
    {
        $this->statesProvider = $statesProvider;
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->addModelTransformer(new StringInsteadNullTransformer());
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults(['choices' => $this->statesProvider->getStates()]);
    }

    public function getParent(): string
    {
        return ChoiceType::class;
    }
}

ReplaceIfNotSubmittedListener

此事件订阅者的目的是在未发送任何数据时覆盖模型的数据。想象一下这样的场景,您有实体

class FormDto
{
    /**
     * @var string|null 
    */
    public $text;
    
    /**
     * @var PersonDto|null
     */
    public $person;
}

class PersonDto
{
    /**
     * @var string|null
     */
    public $firstName;

    /**
     * @var string|null
     */
    public $lastName;
}

属性 $person 不为空。您希望将该值设置为 null,因为 PersonDto 不能处于“分割状态”。PersonDto 的两个属性都必须设置(不能为空)。当表单提交时,如果方法 submit 的 Symfony 参数 $clearMissing 设置为 false,则由于此 EventSubscriber,FormDto 的属性 person 将被设置为 null 值。如果没有此 EventSubscriber,将创建一个空的 PersonDto 对象,并将其传递给 FormDto

请参阅测试 \mmo\sf\tests\Form\ReplaceIfNotSubmittedFormTest

use mmo\sf\Form\ReplaceIfNotSubmittedListener;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class FormToTestReplaceValueType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('text', TextType::class);
        $builder->add('person', PersonType::class, [
            'required' => false,
        ]);
        $builder->get('person')->addEventSubscriber(new ReplaceIfNotSubmittedListener(null));
    }
}

lexik/jwt-authentication-bundle

撤销 JWT 令牌

JWT 是有状态的令牌。我们不需要存储它们。当需要撤销(使无效)令牌时,此属性会引发问题。一些资源:如何将令牌更改为其无效状态?使 JSON Web 令牌无效

在文件 config/packages/cache.yaml 中注册一个新的池 cache.jwt。在此示例中,我们使用 Redis 作为适配器。

cache.jwt:
    adapter: cache.adapter.redis

在文件 config/routes.yaml 中添加一个路由器

api_logout:
    path: /api/sessions
    controller: 'mmo\sf\JWTAuthenticationBundle\Controller\LogoutAction'
    methods: DELETE

最后,我们需要在 config/services.yaml 文件中注册服务。我们为接口 JitGeneratorInterface 创建到 RamseyUuid4JitGenerator 的别名,并配置监听器。对于 CheckRevokeListener,我们需要传递正确的缓存池(我们创建了一个自定义池 - cache.jwtconfig/packages/cache.yaml 中)和路由名称 api_logout,我们在文件 config/routes.yaml 中添加了它

mmo\sf\JWTAuthenticationBundle\Controller\LogoutAction:
  tags: ['controller.service_arguments']
  mmo\sf\JWTAuthenticationBundle\JitGenerator\RamseyUuid4JitGenerator: ~
  mmo\sf\JWTAuthenticationBundle\JitGenerator\JitGeneratorInterface:
    alias: mmo\sf\JWTAuthenticationBundle\JitGenerator\RamseyUuid4JitGenerator

  mmo\sf\JWTAuthenticationBundle\Listener\CheckRevokeListener:
    arguments:
      - '@request_stack'
      - '@cache.jwt'
      - 'api_logout'
      - 'key_prefix_in_cache.'
    tags:
      - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_decoded, method: onJWTDecoded }
  mmo\sf\JWTAuthenticationBundle\Listener\AddJitClaimListener:
    tags:
      - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_created, method: onJWTCreated }

工具

转写器

Transliterator 包含一个静态方法 transliterate,用于返回字符串的转写版本。基于 yii2 Inflector

<?php

require_once './vendor/autoload.php';

use mmo\sf\Util\Transliterator;

Transliterator::transliterate('¿Español?'); // Espanol?
Transliterator::transliterate('Українська: ґанок, європа', Transliterator::TRANSLITERATE_STRICT); // Ukraí̈nsʹka: g̀anok, êvropa

实体测试助手

EntityTestHelper 帮助设置私有字段的值,例如 id

<?php

require_once './vendor/autoload.php';

use mmo\sf\Util\EntityTestHelper;

EntityTestHelper::setPrivateProperty($entity, 12);
EntityTestHelper::setPrivateProperty($entity, 12, 'fieldName');

对象助手

arrayToObject

此静态方法递归地将数组转换为 stdClass。

<?php

require_once './vendor/autoload.php';

use mmo\sf\Util\ObjectHelper;

$object = ObjectHelper::arrayToObject(['foo' => 'bar', 'baz' => ['foo' => 'bar']]);

// class stdClass#3 (2) {
//   public $foo =>
//   string(3) "bar"
//   public $baz =>
//   class stdClass#2 (1) {
//     public $foo =>
//     string(3) "bar"
//   }
// }

命令

S3CreateBucketCommand

命令 mmo:s3:create-bucket 创建一个 S3 桶。如果启用选项 skip-if-exists,并且桶已存在,则过程将成功完成。您可以使用选项 --public 使每个人都可以从桶中获取对象。

要使用此命令,您必须注册两个服务。在 config/services.yaml 中注册服务 s3clientmmo\sf\Command\S3CreateBucketCommand

services:
  # ...
  s3client:
    class: Aws\S3\S3Client
    arguments:
      - version: '2006-03-01' # or 'latest'
        endpoint: '%env(AWS_S3_ENDPOINT)%'
        use_path_style_endpoint: true
        region: "us-east-1" # 'eu-central-1' for example
        credentials:
          key: '%env(AWS_S3_KEY)%'
          secret: '%env(AWS_S3_SECRET)%'
  mmo\sf\Command\S3CreateBucketCommand:
    arguments:
      $s3Client: '@s3client'

LiipImagineBundle

ResolverAlwaysStoredDecorator

此解析器无论图像是否已存在都始终返回 true。

liip_imagine:
  # ...
  resolvers:
    offers:
      flysystem:
        filesystem_service: oneup_flysystem.images_filesystem
        root_url:           "%env(AWS_S3_URL)%"
        cache_prefix:       miniatures
        visibility:         public
  cache: always_stored_resolver
services:
  # ...
  mmo\sf\ImagineBundle\ResolverAlwaysStoredDecorator:
    arguments:
      $resolver: '@liip_imagine.cache.resolver.offers'
    tags:
      - { name: "liip_imagine.cache.resolver", resolver: always_stored_resolver }

cyve/json-schema-form-bundle

CyveJsonSchemaMapper

数据映射器的默认实现(PropertyPathMapperTest)也设置当值为 null 时的数组键。当 JsonSchema 中的字段不是必需的时,这是一个问题。模式验证器不会检查字段值是否已设置。这与 Symfony 验证组件不同。在 Symfony 中,如果字段值为 null 或空字符串,则跳过验证。在 JsonSchemaValidator 事件中,具有值为 null 的可选字段必须匹配验证规则。此外,cyve/json-schema-form-bundle 不支持字段的多种类型。

Doctrine

IgnoreSchemaTablesListener

此监听器期望在比较数据库模式与实体时忽略的表名。当您手动管理某些实体的表的架构时,这很有用。这是与 DoctrineMigrationsBundle 中的手动表类似的一种解决方案。

mmo\sf\Doctrine\IgnoreSchemaTablesListener:
  arguments:
    $ignoredTables: 
      - schema._table1_
      - schema.table2
  tags:
    - {name: doctrine.event_listener, event: postGenerateSchema }

序列化器

MyCLabsEnumNormalizer

来自包 myclabs/php-enum 的 Enum 类的序列化器和反序列化器。

<?php

require_once './vendor/autoload.php';

use mmo\sf\Serializer\Normalizer\MyCLabsEnumNormalizer;
use MyCLabs\Enum\Enum;
use Symfony\Component\Serializer\Serializer;

/**
 * @method static static DRAFT()
 * @method static static PUBLISHED()
 */
class MyEnum extends Enum
{
    private const DRAFT = 'draft';
    private const PUBLISHED = 'published';
}

$serializer = new Serializer([new MyCLabsEnumNormalizer()]);
$serializer->denormalize('draft', MyEnum::class); // return instance of MyEnum

MoneyNormalizer

来自包 moneyphp/money 的 Money 类的规范化器和解规范化器。

<?php

require_once './vendor/autoload.php';

use Money\Money;
use mmo\sf\Serializer\Normalizer\MoneyNormalizer;
use Symfony\Component\Serializer\Serializer;

$serializer = new Serializer([new MoneyNormalizer()]);
$money = $serializer->denormalize($serializer->normalize(Money::EUR(100)), Money::class);

事件订阅者

性能订阅者

此监听器连接到 HttpKernel 事件(RequestEvent 和 TerminateEvent),通过 LoggerInterface 记录端点的性能。日志条目包括持续时间、HTTP 方法、URL 和 PID。对这些数据的分析可以帮助我们找到最耗时的端点。我们可以有一些嫌疑人,并通过 Xdebug 或 Blackfire 对这些端点的性能进行更深入的分析。

config/packages/monolog.yaml 中为 monolog 创建一个新的通道和处理器。

# config/packages/monolog.yaml

monolog:
  channels: ['performance']
  handlers:
    performancelog:
      type: stream
      path: php://stderr
      level: debug
      channels: [performance]

接下来在 config/services.yaml 中注册监听器。

# config/services.yaml
services:
  mmo\sf\EventSubscriber\PerformanceSubscriber:
    arguments:
      $logger: '@monolog.logger.performance'

当我们刷新日志页面(在这个配置中日志发送到 stderr)时,我们应该看到类似以下内容:[2021-08-30 15:15:19] performance.INFO: 请求 "GET /admin/login" 耗时 "1.041289" 秒。{"url":"/admin/login","method":"GET","pid":7,"status_code":200}