Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ jobs:

- name: Run test suite
run: composer run-script test

- name: Run PHPStan
run: composer run-script analyse

- name: Run PHP-CS-Fixer
run: composer run-script cs-check
32 changes: 32 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

$finder = (new PhpCsFixer\Finder())
->in(__DIR__ . '/src')
->in(__DIR__ . '/tests')
;

return (new PhpCsFixer\Config())
->setRules([
'@PER-CS' => true,
'@Symfony' => true,
'declare_strict_types' => true,
'strict_param' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'trailing_comma_in_multiline' => true,
'single_quote' => true,
'global_namespace_import' => [
'import_classes' => false,
'import_constants' => false,
'import_functions' => false,
],
'native_function_invocation' => [
'include' => ['@all'],
'scope' => 'all',
'strict' => true,
],
])
->setFinder($finder)
->setRiskyAllowed(true)
;
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
[![PHP Composer](https://github.com/chamber-orchestra/form-bundle/actions/workflows/php.yml/badge.svg)](https://github.com/chamber-orchestra/form-bundle/actions/workflows/php.yml)
[![Latest Stable Version](https://poser.pugx.org/chamber-orchestra/form-bundle/v)](https://packagist.org/packages/chamber-orchestra/form-bundle)
[![License](https://poser.pugx.org/chamber-orchestra/form-bundle/license)](https://packagist.org/packages/chamber-orchestra/form-bundle)
![PHP 8.5](https://img.shields.io/badge/PHP-8.5-777BB4?logo=php&logoColor=white)
![Symfony 8](https://img.shields.io/badge/Symfony-8-000000?logo=symfony&logoColor=white)
![PHPStan Max](https://img.shields.io/badge/PHPStan-max-brightgreen?logo=php&logoColor=white)
![PHP-CS-Fixer](https://img.shields.io/badge/PHP--CS--Fixer-✓-brightgreen?logo=php&logoColor=white)

# ChamberOrchestra Form Bundle

Expand Down
11 changes: 9 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@
"require-dev": {
"phpunit/phpunit": "^13.0",
"symfony/test-pack": "^1.2",
"doctrine/doctrine-bundle": "^3.2"
"doctrine/doctrine-bundle": "^3.2",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-symfony": "^2.0",
"phpstan/phpstan-doctrine": "^2.0",
"friendsofphp/php-cs-fixer": "^3.94"
},
"autoload": {
"psr-4": {
Expand Down Expand Up @@ -80,6 +84,9 @@
}
},
"scripts": {
"test": "vendor/bin/phpunit"
"test": "vendor/bin/phpunit",
"analyse": "vendor/bin/phpstan analyse",
"cs-check": "vendor/bin/php-cs-fixer fix --dry-run --diff",
"cs-fix": "vendor/bin/php-cs-fixer fix"
}
}
11 changes: 11 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
includes:
- vendor/phpstan/phpstan-symfony/extension.neon
- vendor/phpstan/phpstan-doctrine/extension.neon

parameters:
level: max
paths:
- src
treatPhpDocTypesAsCertain: false
ignoreErrors:
- identifier: trait.unused
11 changes: 8 additions & 3 deletions src/ApiFormTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,23 @@

use ChamberOrchestra\FormBundle\Type\Api\MutationForm;
use ChamberOrchestra\ViewBundle\View\ViewInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
* @phpstan-require-extends AbstractController
*/
trait ApiFormTrait
{
use FormTrait;

protected function handleApiCall(FormInterface|string $form, callable|null $callable = null): Response|ViewInterface
protected function handleApiCall(FormInterface|string $form, ?callable $callable = null): Response|ViewInterface
{
$request = $this->getCurrentRequest();
if ($request === null) {
if (null === $request) {
throw new \LogicException('Cannot handle API call without an active request.');
}

Expand All @@ -46,6 +50,7 @@ protected function handleApiCall(FormInterface|string $form, callable|null $call
return $this->onFormSubmitted($form, $callable);
}

/** @return array<string, mixed> */
private function convertRequestToArray(Request $request): array
{
$data = [];
Expand All @@ -59,4 +64,4 @@ private function convertRequestToArray(Request $request): array

return \array_replace_recursive($data, $request->files->all());
}
}
}
2 changes: 1 addition & 1 deletion src/ChamberOrchestraFormBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@

final class ChamberOrchestraFormBundle extends Bundle
{
}
}
2 changes: 1 addition & 1 deletion src/Exception/ExceptionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@

interface ExceptionInterface
{
}
}
2 changes: 1 addition & 1 deletion src/Exception/InvalidArgumentException.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@

class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
}
2 changes: 1 addition & 1 deletion src/Exception/LogicException.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@

class LogicException extends \LogicException implements ExceptionInterface
{
}
}
3 changes: 2 additions & 1 deletion src/Exception/TransformationFailedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

class TransformationFailedException extends \Symfony\Component\Form\Exception\TransformationFailedException implements ExceptionInterface
{
public static function notAllowedType($id, array $allowedTypes): TransformationFailedException
/** @param list<string> $allowedTypes */
public static function notAllowedType(mixed $id, array $allowedTypes): TransformationFailedException
{
return new self(
\sprintf(
Expand Down
7 changes: 4 additions & 3 deletions src/Extension/TelExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ public static function getExtendedTypes(): iterable
return [TelType::class];
}

/** @param array<string, mixed> $options */
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addViewTransformer(
new CallbackTransformer(
function (string|null $value): string|null {
function (?string $value): ?string {
return $value;
},
function (string|null $value): string|null {
function (?string $value): ?string {
if (null === $value || '' === $value) {
return null;
}
Expand All @@ -40,4 +41,4 @@ function (string|null $value): string|null {
)
);
}
}
}
43 changes: 22 additions & 21 deletions src/FormTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@

/**
* @mixin AbstractController
*
* @phpstan-require-extends AbstractController
*/
trait FormTrait
{
/** @param array<string, mixed> $data */
protected function createSuccessResponse(array $data = []): DataView|ResponseView
{
return $data ? new DataView($data) : new ResponseView();
Expand All @@ -44,13 +47,16 @@ protected function createRedirectResponse(
int $status = Response::HTTP_MOVED_PERMANENTLY
): Response|RedirectView {
$request = $this->getCurrentRequest();
if ($request !== null && $request->isXmlHttpRequest()) {
if (null !== $request && $request->isXmlHttpRequest()) {
return new RedirectView($url, $status);
}

return $this->redirect($url, $status);
}

/**
* @param array<string, mixed> $parameters
*/
protected function createRedirectToRouteResponse(
string $name,
array $parameters = [],
Expand All @@ -64,10 +70,11 @@ protected function createExceptionResponse(): FailureView
return $this->createFailureResponse(Response::HTTP_INTERNAL_SERVER_ERROR);
}

/** @param array<string, mixed> $parameters */
protected function createSuccessHtmlResponse(string $view, array $parameters = []): Response|SuccessHtmlView
{
$request = $this->getCurrentRequest();
if ($request !== null && $request->isXmlHttpRequest()) {
if (null !== $request && $request->isXmlHttpRequest()) {
return new SuccessHtmlView([
'html' => $this->renderView($view, $parameters),
]);
Expand All @@ -88,24 +95,18 @@ protected function createValidationFailedResponse(FormInterface $form): Validati

protected function handleFormCall(
FormInterface|string $form,
callable|null $callable = null
?callable $callable = null
): Response|ViewInterface {
if (!\is_string($form) && !$form instanceof FormInterface) {
throw new \TypeError(
\sprintf(
'Passed $form must be of type "%s", "%s" given.',
\implode(',', ['string', FormInterface::class]),
\get_debug_type($form)
)
);
throw new \TypeError(\sprintf('Passed $form must be of type "%s", "%s" given.', \implode(',', ['string', FormInterface::class]), \get_debug_type($form)));
}

if (\is_string($form)) {
$form = $this->container->get('form.factory')->create($form);
}

$request = $this->getCurrentRequest();
if ($request === null) {
if (null === $request) {
throw new \LogicException('Cannot handle form call without an active request.');
}

Expand All @@ -120,15 +121,15 @@ protected function handleFormCall(

protected function createSubmittedFormResponse(
FormInterface $form,
callable|null $callable = null
?callable $callable = null
): Response|ViewInterface {
return $this->onFormSubmitted($form, $callable);
}

/**
* @param null|callable $callable must return @see \ChamberOrchestra\ViewBundle\View\ViewInterface, array or null
* @param callable|null $callable must return @see \ChamberOrchestra\ViewBundle\View\ViewInterface, array or null
*/
protected function onFormSubmitted(FormInterface $form, callable|null $callable = null): Response|ViewInterface
protected function onFormSubmitted(FormInterface $form, ?callable $callable = null): Response|ViewInterface
{
if (!$form->isValid()) {
return $this->createValidationFailedResponse($form);
Expand All @@ -139,18 +140,13 @@ protected function onFormSubmitted(FormInterface $form, callable|null $callable
}

if (!\is_array($response) && !$response instanceof ViewInterface && !$response instanceof Response) {
throw new \TypeError(
\sprintf(
'Passed closure must return %s, returned %s',
\implode('|', [ViewInterface::class, Response::class, 'array']),
\get_debug_type($response)
)
);
throw new \TypeError(\sprintf('Passed closure must return %s, returned %s', \implode('|', [ViewInterface::class, Response::class, 'array']), \get_debug_type($response)));
}

return \is_array($response) ? $this->createSuccessResponse($response) : $response;
}

/** @return list<ViolationView> */
protected function serializeFormErrors(FormInterface $form): array
{
return $this->serializeErrors($form->getErrors(true, false));
Expand All @@ -171,6 +167,11 @@ private function getCurrentRequest(): ?Request
return $this->container->get('request_stack')->getCurrentRequest();
}

/**
* @param list<string> $paths
*
* @return list<ViolationView>
*/
private function serializeErrors(FormErrorIterator $iterator, array $paths = []): array
{
if ('' !== $name = $iterator->getForm()->getName()) {
Expand Down
6 changes: 3 additions & 3 deletions src/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@

$services->defaults()
->autowire()
->autoconfigure()
->public(false);
->autoconfigure();

$services
->load('ChamberOrchestra\\FormBundle\\', __DIR__.'/../../')
Expand All @@ -27,7 +26,8 @@

$services
->set(ProblemNormalizer::class)
->arg('$debug', '%kernel.debug%');
->arg('$debug', '%kernel.debug%')
->public();

$services
->set(TelExtension::class)
Expand Down
2 changes: 2 additions & 0 deletions src/Serializer/Normalizer/ProblemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

class ProblemNormalizer extends Normalizer
{
/** @param array<string, mixed> $defaultContext */
public function __construct(
private readonly TranslatorInterface $translator,
bool $debug = false,
Expand All @@ -25,6 +26,7 @@ public function __construct(
parent::__construct($debug, $defaultContext);
}

/** @param array<string, mixed> $context */
public function normalize(mixed $object, ?string $format = null, array $context = []): array
{
$data = parent::normalize($object, $format, $context);
Expand Down
8 changes: 5 additions & 3 deletions src/Transformer/ArrayToStringTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
use ChamberOrchestra\FormBundle\Exception\TransformationFailedException;
use Symfony\Component\Form\DataTransformerInterface;

/** @implements DataTransformerInterface<list<string>|null, string> */
readonly class ArrayToStringTransformer implements DataTransformerInterface
{
public function transform($value): string
public function transform(mixed $value): string
{
if (null !== $value && !\is_array($value)) {
throw TransformationFailedException::notAllowedType($value, ['array', 'null']);
Expand All @@ -25,7 +26,8 @@ public function transform($value): string
return null !== $value ? \implode(', ', $value) : '';
}

public function reverseTransform($value): array
/** @return list<string> */
public function reverseTransform(mixed $value): array
{
if (null !== $value && !\is_string($value)) {
throw TransformationFailedException::notAllowedType($value, ['string', 'null']);
Expand All @@ -36,7 +38,7 @@ public function reverseTransform($value): array
}

return \array_map(
fn(string $value): string => \preg_replace('/[^\d]/', '', $value),
static fn (string $value): string => \preg_replace('/[^\d]/', '', $value) ?? '',
\explode(',', $value)
);
}
Expand Down
Loading