mmo / sf-utils
A collection of extensions for Symfony Components
Installs: 5 325
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 3
Forks: 0
Open Issues: 0
Requires
- php: >=7.2
Requires (Dev)
- ext-intl: *
- aws/aws-sdk-php: ^3.132.4
- doctrine/orm: ^2.8
- lexik/jwt-authentication-bundle: ^2.11
- liip/imagine-bundle: ^2.6
- moneyphp/money: ^3.3
- myclabs/php-enum: ^1.7
- phpunit/phpunit: ^8.5
- ramsey/uuid: ^4.1
- symfony/console: ^4.4
- symfony/form: ^4.4
- symfony/http-kernel: ^4.4
- symfony/security-core: ^4.4
- symfony/serializer: ^4.4
- symfony/validator: ^4.4
Suggests
- ext-intl: *
This package is auto-updated.
Last update: 2024-11-06 18:18:41 UTC
README
Validators
ITIN validator
ITIN - Individual Taxpayer Identification Number
<?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
Birthday
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
BankRoutingNumber
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
Only UTF-8 letters and dashes.
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
Only UTF-8 letters, dashes, and spaces are allowed. Useful to validate the full name of a person.
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
Only digits are allowed.
<?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
Validators which don't follow a convention of naming a Constraint and ConstraintValidator, will not be found by the default implementation of ConstraintValidatorFactory.
The class ArrayConstraintValidatorFactory
resolve this problem, you can map ConstraintValidator to object.
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(); // ...
Translator
FakeTranslator
The FakeTranslator
class can be used in a unit tests instead of using a stub.
At the moment only id
and locale
arguments are supported.
<?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
Security
Encrypter
Encrypter
is used to encrypt string value.
All encrypted values are encrypted using OpenSSL and the AES-256-CBC cipher (as default).
<?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 and 5.4)
AlwaysTheSameEncoderFactory
is useful in integration tests with combination of UserPasswordEncoder
. No matter which implementation of UserInterface you pass,
will always be used the same password encoder injected via constructor.
<?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 (only Symfony 4.4 nad 5.4)
For Symfony 6.4+ use build-in provider \Symfony\Component\Security\Core\User\InMemoryUserProvider
.
MemoryUserProvider
is a simple non persistent user provider for tests.
This provider compares to InMemoryUserProvider allows for store any user objects, which implement the UserInterface interface instead of only the internal Symfony User class.
<?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');
Form
RamseyUuidToStringTransformer
Transforms between a UUID string, and a UUID object.
Symfony 5.3 include an own UuidToStringTransformer
transformer, but you need also use a symfony/uuid component.
This transformer works with a ramsey/uuid
library.
PrimaryKeyToEntityTransformer
Transforms between a primary key(composite primary key is not supported), and an entity.
StringInsteadNullTransformer
The goal of this transformer is to fix an error when you have a form with ChoiceType and pass an empty value for this field and your entity/DTO expect only string value you get error "Expected argument of type "string", "NULL" given at property path ...".
The Symfony changes this empty string to null value (due to ChoiceToValueTransformer). You can add StringInsteadNullTransformer as a model transformer, so null values will be transformed to an empty string.
//... 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
The goal of this EventSubscriber is to overwrite data of model, when no data has been sent. Imagine scenario, that you have entities:
class FormDto { /** * @var string|null */ public $text; /** * @var PersonDto|null */ public $person; } class PersonDto { /** * @var string|null */ public $firstName; /** * @var string|null */ public $lastName; }
Property $person is not null.
You want to set this value to null, because PersonDto cannot be in "split state".
Both properties of PersonDto has to be set (cannot be empty).
When the form is submitted, and Symfony parameter $clearMissing
of method submit
is set to false
,
then due to this EventSubscriber the property person
of FormDto
will be set to null value.
Without this EventSubscriber, an empty PersonDto object will be created which will be passed to FormDto
.
See also test \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
Revoke JWT token
The JWT is stateful token. We don't need to store them. This property create problem, when we need to revoke (invalidate) a token. Some resources: How to change a token to be invalid status? or Invalidating JSON Web Tokens
In a file config/packages/cache.yaml
we register a new pool cache.jwt
. In this example as adapter we use Redis.
cache.jwt: adapter: cache.adapter.redis
In a file config/routes.yaml
we add a router:
api_logout: path: /api/sessions controller: 'mmo\sf\JWTAuthenticationBundle\Controller\LogoutAction' methods: DELETE
Finally, we need to register services in config/services.yaml
file.
We create alias for interface JitGeneratorInterface
to RamseyUuid4JitGenerator
and configure listeners.
For CheckRevokeListener
we need pass correct arguments for cache pool (we create custom pool - cache.jwt
in config/packages/cache.yaml
) and router name api_logout
, which we add in a file 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 }
Util
Transliterator
Class Transliterator
contains one static method transliterate
to returns transliterated version of a string.
Based on 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
The class EntityTestHelper
helps set a value for a private field e.g. id
.
<?php require_once './vendor/autoload.php'; use mmo\sf\Util\EntityTestHelper; EntityTestHelper::setPrivateProperty($entity, 12); EntityTestHelper::setPrivateProperty($entity, 12, 'fieldName');
ObjectHelper
arrayToObject
This static method recursive converts an array to 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" // } // }
Commands
S3CreateBucketCommand
Command mmo:s3:create-bucket
creates a S3 bucket.
When the option skip-if-exists
is enabled, and the bucket exists the process will finish successful.
You can use the option --public
so everyone can get objects from a bucket.
To use this command you must register two services.
In config/services.yaml
register a service s3client
and mmo\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
This resolver always returns true whether image already exists or not.
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
The default implementation of data mapper (PropertyPathMapperTest
) also set array key when the value is null
This is a problem when fields in JsonSchema are not required.
Schema validator doesn't check whether the field value is set. This is something different from the Symfony Validator component.
In Symfony if the field value is the null or empty string, the validation is skipped.
In JsonSchemaValidator event optional field with value null must match validation rules.
Also cyve/json-schema-form-bundle not supported multiple types of field.
Doctrine
IgnoreSchemaTablesListener
This listener expects table names, which will be ignored during comparing database schema with entities. It is useful when you manually manage the table's schema for some of your entities. This is a similar solution to Manual tables from DoctrineMigrationsBundle
mmo\sf\Doctrine\IgnoreSchemaTablesListener: arguments: $ignoredTables: - schema._table1_ - schema.table2 tags: - {name: doctrine.event_listener, event: postGenerateSchema }
Serializer
MyCLabsEnumNormalizer
Normalizer and denormalizer for Enum class from package myclabs/php-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
Normalizer and denormalizer for Money class from package moneyphp/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);
EventSubscriber
PerformanceSubscriber
This listener connects to HttpKernel events (RequestEvent and TerminateEvent) to log the performance of an endpoint via LoggerInterface. The entry in the log includes duration, HTTP method, URL, and PID. Analysis of those data allows us to find the most time-consuming endpoints. We can have some suspects and start a deeper analysis of the performance of those endpoints via Xdebug or Blackfire.
Create a new channel and handler for monolog in config/packages/monolog.yaml
.
# config/packages/monolog.yaml monolog: channels: ['performance'] handlers: performancelog: type: stream path: php://stderr level: debug channels: [performance]
Next register listener in config/services.yaml
.
# config/services.yaml services: mmo\sf\EventSubscriber\PerformanceSubscriber: arguments: $logger: '@monolog.logger.performance'
When we refresh page in log (in this configuration logs are sent to stderr) we should see something like this:
[2021-08-30 15:15:19] performance.INFO: The request "GET /admin/login" took "1.041289" second. {"url":"/admin/login","method":"GET","pid":7,"status_code":200}