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()); // VALIDrequire_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()); // VALIDrequire_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()); // VALIDOnly 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()); // VALIDOnly 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()); // VALIDOnly 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()); // VALIDValidators 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();
// ...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-fooEncrypter 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 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`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');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.
Transforms between a primary key(composite primary key is not supported), and an entity.
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;
}
}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));
}
}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.redisIn a file config/routes.yaml we add a router:
api_logout:
path: /api/sessions
controller: 'mmo\sf\JWTAuthenticationBundle\Controller\LogoutAction'
methods: DELETEFinally, 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 }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, êvropaThe 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');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"
// }
// }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'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_resolverservices:
# ...
mmo\sf\ImagineBundle\ResolverAlwaysStoredDecorator:
arguments:
$resolver: '@liip_imagine.cache.resolver.offers'
tags:
- { name: "liip_imagine.cache.resolver", resolver: always_stored_resolver }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.
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 }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 MyEnumNormalizer 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);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}