vrok / symfony-addons
Symfony helper classes
Installs: 3 839
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 3
Forks: 1
Open Issues: 0
Type:symfony-bundle
Requires
- php: ^8.2
- symfony/framework-bundle: ^7.0.0
- symfony/yaml: ^7.0.0
Requires (Dev)
- api-platform/core: ^3.4.3|^4.0.0
- doctrine/data-fixtures: ^1.5.3|^2.0.1
- doctrine/doctrine-bundle: ^2.12.0|^3
- doctrine/doctrine-fixtures-bundle: ^3.5.1|^4.0.0
- doctrine/orm: ^3.0.0
- doctrine/persistence: ^3.1.0
- friendsofphp/php-cs-fixer: ^3.65.0
- monolog/monolog: ^3.0.0
- phpunit/phpunit: ^11.5.1
- rector/rector: ^2.0.0
- roave/security-advisories: dev-latest
- symfony/browser-kit: ^7.0.0
- symfony/doctrine-messenger: ^7.0.0
- symfony/http-client: ^7.0.0
- symfony/mailer: ^7.0.0
- symfony/monolog-bundle: ^3.8.0
- symfony/string: ^7.0.0
- symfony/twig-bundle: ^7.0.0
- symfony/validator: ^7.0.0
- symfony/workflow: ^7.0.0
- vrok/doctrine-addons: ^2.13.0
- dev-main
- 2.14.0
- 2.13.1
- 2.13.0
- 2.12.0
- 2.11.0
- 2.10.0
- 2.9.0
- 2.8.1
- 2.8.0
- 2.7.0
- 2.6.0
- 2.5.0
- 2.4.0
- 2.3.0
- 2.2.0
- 2.1.0
- 2.0.1
- 2.0.0
- v1.11.0
- v1.9.10
- v1.9.1
- v1.9.0
- v1.8.0
- v1.6.1
- v1.6.0
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.1
- v1.3.0
- v1.2.0
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.0
- dev-develop
- dev-renovate/all
- dev-release-1.x
This package is auto-updated.
Last update: 2024-12-21 21:49:25 UTC
README
This is a library with additional classes for usage in combination with the Symfony framework.
Mailer helpers
Automatically set a sender address
We want to replace setting the sender via mailer.yaml as envelope (@see https://symfonycasts.com/screencast/mailer/event-global-recipients) as this would still require each mail to have a FROM address set and also doesn't allow us to set a sender name.
config/services.yaml:
Vrok\SymfonyAddons\EventSubscriber\AutoSenderSubscriber: arguments: $sender: "%env(MAILER_SENDER)%"
.env[.local]:
MAILER_SENDER="Change Me <your@email>"
Messenger helpers
Resetting the logger before/after a message
We want to group all log entries belonging to a single message to be grouped with a distinct UID and to flush a buffer logger after a message was processed (successfully or failed), to immediately see the entries in the log:
config/services.yaml:
# add a UID to the context, same UID for each HTTP request or console command # and with the event subscriber also for each message Monolog\Processor\UidProcessor: tags: - { name: monolog.processor, handler: logstash } # resets the UID when a message is received, flushed a buffer after a # message was handled. Add this multiple times if you want to flush more # channels, e.g. messenger app.event.reset_app_logger: class: Vrok\SymfonyAddons\EventSubscriber\ResetLoggerSubscriber tags: - { name: monolog.logger, channel: app }
Validators
AtLeastOneOf
Works like Symfony's own AtLeastOneOf constraint, but instead of returning a message like
This value should satisfy at least ...
it returns the message of the last failed validation.
Can be used for obviously optional form fields where only simple messages should be
displayed when AtLeastOne
is used with Blank
as first constraint.
See AtLeastOneOfValidatorTest
for examples.
NoHtml
This validator tries to detect if a string contains HTML, to allow only plain text.
See NoHtmlValidatorTest
for examples of allowed / forbidden values.
NoLineBreak
This validator raises a violation if it detects one or more linebreak characters in
the validated string.
Detects unicode linebreaks, see NoLineBreaksValidatorTest
for details.
NoSurroundingWhitespace
This validator raises a violation if it detects trailing or leading whitespace or
newline characters in the validated string. Linebreaks and spaces are valid within the string.
Uses a regex looking for \s
and \R
, see NoSurroundingWhitespaceValidatorTest
for details on detected characters.
PasswordStrength
This validator evaluates the strength of a given password string by determining its entropy
instead of requireing something like "must contain at least one uppercase & one digit
& one special char".
Allows to set a minStrength
to vary the requirements.
See Vrok\SymfonyAddons\Helper\PasswordStrength
for details on the calculation.
PHPUnit helpers
Using the ApiPlatformTestCase
This class is used to test ApiPlatform endpoints by specifying input data and verifying the response data. It combines the traits documented below to refresh the database before each test, optionally create authenticated requests and check for created logs / sent emails / dispatched messages. It allows to easily check for expected response content, allowed or forbidden keys in the data or to verify against a given schema.
Requires "symfony/browser-kit" & "symfony/http-client" to be installed (and of cause ApiPlatform).
<?php use Vrok\SymfonyAddons\PHPUnit\ApiPlatformTestCase; class AuthApiTest extends ApiPlatformTestCase { public function testAuthRequiresPassword(): void { $this->testOperation([ 'uri' => '/authentication_token', 'method' => 'POST', 'requestOptions' => ['json' => ['username' => 'fakeuser']], 'responseCode' => 400, 'contentType' => 'application/json', 'json' => [ 'type' => 'https://tools.ietf.org/html/rfc2616#section-10', 'title' => 'An error occurred', 'detail' => 'The key "password" must be provided.', ], ]); } }
Using the RefreshDatabaseTrait
(Re-)Creates the DB schema for each test, removes existing data and fills the tables
with predefined fixtures.
Install doctrine/doctrine-fixtures-bundle
and create fixtures,
the trait uses the test group per default.
Just include the trait in your testcase and call bootKernel()
or
createClient()
, e.g. in the setUp method:
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Vrok\SymfonyAddons\PHPUnit\RefreshDatabaseTrait; class DatabaseTest extends KernelTestCase { use RefreshDatabaseTrait; /** * @var \Doctrine\ORM\EntityManager */ private $entityManager; protected function setUp(): void { $kernel = self::bootKernel(); $this->entityManager = $kernel->getContainer() ->get('doctrine') ->getManager(); } }
Optionally define which fixtures to use for this test class:
protected static $fixtureGroups = ['test', 'other'];
Supports setting the cleanup method after tests via DB_CLEANUP_METHOD
. Allowed values
are purge and dropSchema, for more details see RefreshDatabaseTrait::$cleanupMethod
.
Using the AuthenticatedClientTrait
For use with an APIPlatform project with lexik/jwt-authentication-bundle
.
Creates a JWT for the user given by its unique email, username etc. and adds it
to the test client's headers.
Include the trait in your testcase and call createAuthenticatedClient
:
use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase; use Vrok\SymfonyAddons\PHPUnit\AuthenticatedClientTrait; class ApiTest extends ApiTestCase { use AuthenticatedClientTrait; public function testAccess(): void { $client = static::createAuthenticatedClient([ 'email' => TestFixtures::ADMIN['email'] ]); $iri = $this->findIriBy(User::class, ['id' => 1]); $client->request('GET', $iri); self::assertResponseIsSuccessful(); } }
Using the MonologAssertsTrait
For use with an Symfony project using the monolog-bundle.
Requires monolog/monolog
of v3.0 or higher.
Include the trait in your testcase and call prepareLogger
before triggering the
action that should create logs and use assertLoggerHasMessage
afterwards to check
if a log record was created with the given message & severity:
use Monolog\Level; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Vrok\SymfonyAddons\PHPUnit\MonologAssertsTrait; class LoggerTest extends KernelTestCase { use MonologAssertsTrait; public function testLog(): void { self::prepareLogger(); $logger = static::getContainer()->get(LoggerInterface::class); $logger->error('Failed to do something'); self::assertLoggerHasMessage('Failed to do something', Level::Error); } }
Workflow helpers
Require symfony/workflow
.
PropertyMarkingStore
Can be used instead of the default MethodMarkingStore
, for entities
& properties without Setter/Getter.
workflow.yaml:
framework: workflows: application_state: type: state_machine marking_store: # We need to use a service as there is no option to register a new "type" service: workflow.application.marking_store
services.yaml:
# When using the "service" option, all other settings like "property: state" # are ignored in the workflow.yaml -> That's why we need a service definition # with the correct arguments. workflow.application.marking_store: class: Vrok\SymfonyAddons\Workflow\PropertyMarkingStore arguments: [true, 'state']
WorkflowHelper
Allows to get an array of available transitions and their blockers, can be used to show the user what transitions are possible from the current state and/or why a transition is currently blocked.
public function __invoke( Entity $data WorkflowInterface $entityStateMachine, ): array { $result = $data->toArray(); $result['transitions'] = WorkflowHelper::getTransitionList($data, $entityStateMachine); return $result; }
'publish' => [
'blockers' => [
TransitionBlocker::UNKNOWN => 'Title is empty!',
],
],
Cron events
Adding this bundle to the bundles.php
registers three new CLI commands:
Vrok\SymfonyAddons\VrokSymfonyAddonsBundle::class => ['all' => true],
bin/console cron:hourly bin/console cron:daily bin/console cron:monthly
When these are called, they trigger an event (CronHourlyEvent
, CronDailyEvent
,
CronMonthlyEvent
) that can be used by one ore more event listeners/subscribers to do
maintenance, push messages to the messenger etc.
It is your responsibility to execute these commands via crontab correctly!
use Vrok\SymfonyAddons\Event\CronDailyEvent; class MyEventSubscriber implements EventSubscriberInterface public static function getSubscribedEvents(): array { return [ CronDailyEvent::class => [ ['onCronDaily', 100], ], ]; } }
ApiPlatform Filters
SimpleSearchFilter
Selects entities where the search term is found (case insensitive) in at least
one of the specified properties. The properties can also be of relations, e.g.
child.name
. All specified properties must be string types (varchar, text etc.)
or JSON fields (Postgres only), in that case the JSON is cast to string first.
#[ApiFilter( filterClass: SimpleSearchFilter::class, properties: [ 'description', 'name', 'slug', 'parent.title', 'children.content', ], arguments: ['searchParameterName' => 'pattern'] )]
Requires CAST as defined Doctrine function, e.g. by vrok/doctrine-addons
:
doctrine: orm: dql: string_functions: CAST: Vrok\DoctrineAddons\ORM\Query\AST\CastFunction
ContainsFilter
Postgres-only: Filters entities by their jsonb fields, if they contain the search parameter,
using the @>
operator. For example for filtering for numbers in an array.
#[ApiFilter(filterClass: ContainsFilter::class, properties: ['numbers'])]
Requires CONTAINS as defined Doctrine function, provided by vrok/doctrine-addons
:
doctrine: orm: dql: string_functions: CONTAINS: Vrok\DoctrineAddons\ORM\Query\AST\ContainsFunction
JsonExistsFilter
Postgres-only: Filters entities by their jsonb fields, if they contain the search parameter,
using the ?
operator. For example for filtering Users by their role, to prevent accidental
matching with overlapping role names (e.g. ROLE_ADMIN and ROLE_ADMIN_BLOG) when searching as
text with WHERE roles LIKE '%ROLE_ADMIN%'
.
#[ApiFilter(filterClass: JsonExistsFilter::class, properties: ['roles'])]
Requires JSON_CONTAINS_TEXT as defined Doctrine function, provided by vrok/doctrine-addons
:
doctrine: orm: dql: string_functions: JSON_CONTAINS_TEXT: Vrok\DoctrineAddons\ORM\Query\AST\JsonContainsTextFunction
MultipartDecoder
Adding this bundle to the bundles.php
registers the MultipartDecoder
to allow handling of file uploads with additional data (e.g. in ApiPlatform):
Vrok\SymfonyAddons\VrokSymfonyAddonsBundle::class => ['all' => true],
The decoder is automatically called for multipart
requests and
simply returns all POST parameters and uploaded files together. To enable
this add the multipart
format to your config\api_platform.yaml
:
api_platform: formats: multipart: ['multipart/form-data']
FormDecoder
Adding this bundle to the bundles.php
registers the FormDecoder
to allow handling HTML form data in ApiPlatform:
Vrok\SymfonyAddons\VrokSymfonyAddonsBundle::class => ['all' => true],
The decoder is automatically called for form
requests and
simply returns all POST parameters. To enable this add the form
format to your
config\api_platform.yaml
:
api_platform: formats: form: ['application/x-www-form-urlencoded']
Twig Extensions
Adding this bundle to the bundles.php
together with the symfony/twig-bundle
registers the new extension:
Vrok\SymfonyAddons\VrokSymfonyAddonsBundle::class => ['all' => true],
FormatBytes
Converts bytes to human-readable notation (supports up to TiB).
This extension is auto-registered.
In your Twig template:
{{ attachment.filesize|formatBytes }}
Outputs: 9.34 MiB
Developer Doc
composer.json require
- symfony/yaml is required for loading the bundle & test config
composer.json dev
- doctrine/data-fixtures is automatically installed by the doctrine-fixtures bundle, but we need to pin the minimal version as the versions before 1.5.2 are not compatible with DBAL < 3 (@see doctrine/data-fixtures#370)
- doctrine/doctrine-fixtures-bundle is required for tests of the ApiPlatformTestCase
- symfony/browser-kit is required for tests of the MultipartDecoder
- symfony/mailer is required for tests of the AutoSenderSubscriber
- symfony/doctrine-messenger is required for tests of the ResetLoggerSubscriber
- symfony/monolog-bundle is required for tests of the MonologAssertsTrait and ResetLoggerSubscriber
- symfony/phpunit-bridge must be at least v6.2.3 to prevent"Call to undefined method Doctrine\Common\Annotations\AnnotationRegistry::registerLoader()"
- symfony/string is required for API Platform's inflector
- symfony/twig-bundle is required for tests of the FormatBytesExtension
- symfony/workflow is required for tests of the WorkflowHelper and PropertyMarkingStore
- monolog/monolog must be at least v3 for
Monolog\Level
- api-platform/core and vrok/doctrine-addons are required for testing the ApiPlatform filters
Open ToDos
- tests for
AuthenticatedClientTrait
,RefreshDatabaseTrait
ApiPlatformTestCase
should no longer useAuthenticatedClientTrait
but use its own getJWT() and make the User class configurable like the fixtures.- tests for QueryBuilderHelper
- compare code to ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper