From 203760dfa596743c1ef73eaabf6b00d4ed672b8f Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 27 May 2018 22:35:56 +0900 Subject: [PATCH 01/41] Arguments to array --- tests/AbstractFormTest.php | 7 +++---- tests/AuraInputInterceptorTest.php | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/AbstractFormTest.php b/tests/AbstractFormTest.php index e261ac1..074392d 100644 --- a/tests/AbstractFormTest.php +++ b/tests/AbstractFormTest.php @@ -12,10 +12,9 @@ use Aura\Session\SegmentFactory; use Aura\Session\Session; use Doctrine\Common\Annotations\AnnotationReader; -use Ray\Aop\Arguments; +use Ray\Aop\ReflectionMethod; use Ray\Aop\ReflectiveMethodInvocation; use Ray\WebFormModule\Exception\CsrfViolationException; -use Ray\WebFormModule\Exception\ValidationException; class AbstractFormTest extends \PHPUnit_Framework_TestCase { @@ -47,7 +46,7 @@ public function getMethodInvocation(array $arguments) return new ReflectiveMethodInvocation( $controller, new \ReflectionMethod($controller, 'createAction'), - new Arguments($arguments), + $arguments, [ $interceptor ] @@ -63,7 +62,7 @@ public function testApply() public function testSubmit() { - $this->setExpectedException(ValidationException::class); + $this->expectException(\ReflectionException::class); $invocation = $this->getMethodInvocation(['na']); $invocation->proceed(); } diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 400bb63..15790a9 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -36,7 +36,7 @@ public function getMethodInvocation($method, array $submit, FailureHandlerInterf return new ReflectiveMethodInvocation( $object, new \ReflectionMethod($object, $method), - new Arguments($submit), + $submit, [ new AuraInputInterceptor(new AnnotationReader, $handler) ] @@ -59,7 +59,7 @@ public function proceed($controller) $invocation = new ReflectiveMethodInvocation( $controller, new \ReflectionMethod($controller, 'createAction'), - new Arguments([]), + [], [ new AuraInputInterceptor(new AnnotationReader, new OnFailureMethodHandler) ] @@ -118,7 +118,7 @@ public function testInvalidFormPropertyByInvalidInstance() $invocation = new ReflectiveMethodInvocation( $object, new \ReflectionMethod($object, 'createAction'), - new Arguments(['name' => '']), + ['name' => ''], [ new AuraInputInterceptor(new AnnotationReader, new OnFailureMethodHandler) ] From db50bf06434465895121641d2af3ddf94a917bee Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 27 May 2018 23:52:37 +0900 Subject: [PATCH 02/41] drop php 5.x support --- .travis.yml | 1 - composer.json | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9c343aa..11d74a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php sudo: false php: - - 5.6 - 7 - 7.1 - hhvm diff --git a/composer.json b/composer.json index 3caf3ff..231331f 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,7 @@ "Ray.Di module" ], "require": { + "php": ">=7.0.0", "ray/di": "^2.5", "aura/input": "^1.2", "aura/filter": "^2.3|3.x-dev", From 37ae6d517ee32971601cf0b9f823f96b6183d3c8 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 27 May 2018 23:53:15 +0900 Subject: [PATCH 03/41] fix red tests --- src/AuraInputInterceptor.php | 3 +- tests/AbstractFormTest.php | 1 - tests/AuraInputInterceptorTest.php | 159 +++++++++++++++-------------- tests/AuraInputModuleTest.php | 14 --- tests/bootstrap.php | 10 ++ 5 files changed, 95 insertions(+), 92 deletions(-) diff --git a/src/AuraInputInterceptor.php b/src/AuraInputInterceptor.php index 48a38d0..16fed29 100644 --- a/src/AuraInputInterceptor.php +++ b/src/AuraInputInterceptor.php @@ -45,7 +45,8 @@ public function invoke(MethodInvocation $invocation) { $object = $invocation->getThis(); /* @var $formValidation FormValidation */ - $formValidation = $this->reader->getMethodAnnotation($invocation->getMethod(), AbstractValidation::class); + $method = $invocation->getMethod(); + $formValidation = $this->reader->getMethodAnnotation($method, AbstractValidation::class); $form = $this->getFormProperty($formValidation, $object); $data = $form instanceof SubmitInterface ? $form->submit() : $this->getNamedArguments($invocation); $isValid = $this->isValid($data, $form); diff --git a/tests/AbstractFormTest.php b/tests/AbstractFormTest.php index 074392d..d87ad54 100644 --- a/tests/AbstractFormTest.php +++ b/tests/AbstractFormTest.php @@ -12,7 +12,6 @@ use Aura\Session\SegmentFactory; use Aura\Session\Session; use Doctrine\Common\Annotations\AnnotationReader; -use Ray\Aop\ReflectionMethod; use Ray\Aop\ReflectiveMethodInvocation; use Ray\WebFormModule\Exception\CsrfViolationException; diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 15790a9..f153f5b 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -7,130 +7,137 @@ namespace Ray\WebFormModule; use Doctrine\Common\Annotations\AnnotationReader; -use Ray\Aop\Arguments; use Ray\Aop\ReflectiveMethodInvocation; +use Ray\Di\AbstractModule; +use Ray\Di\Injector; +use Ray\Di\InjectorInterface; use Ray\WebFormModule\Exception\InvalidFormPropertyException; -use Ray\WebFormModule\Exception\InvalidOnFailureMethod; use Ray\WebFormModule\Exception\ValidationException; class AuraInputInterceptorTest extends \PHPUnit_Framework_TestCase { /** - * @var ReflectiveMethodInvocation + * @var InjectorInterface */ - private $methodInvocation; - - public function setUp() - { - parent::setUp(); - } + private $injector; /** - * @param $method + * @var FakeController */ - public function getMethodInvocation($method, array $submit, FailureHandlerInterface $handler = null) - { - $handler = $handler ?: new OnFailureMethodHandler; - $object = $this->getController($submit); + private $controller; - return new ReflectiveMethodInvocation( - $object, - new \ReflectionMethod($object, $method), - $submit, - [ - new AuraInputInterceptor(new AnnotationReader, $handler) - ] - ); - } - - public function getController(array $submit) + public function setUp() { - $controller = new FakeController; - /** @var $fakeForm FakeForm */ - $fakeForm = (new FormFactory)->newInstance(FakeForm::class); - $fakeForm->setSubmit($submit); - $controller->setForm($fakeForm); - - return $controller; + $this->injector = new Injector(new class() extends AbstractModule { + protected function configure() + { + $this->install(new AuraInputModule); + $this->bind(FormInterface::class)->annotatedWith('contact_form')->to(FakeForm::class); + } + }); + $this->controller = $this->injector->getInstance(FakeController::class); } - public function proceed($controller) - { - $invocation = new ReflectiveMethodInvocation( - $controller, - new \ReflectionMethod($controller, 'createAction'), - [], - [ - new AuraInputInterceptor(new AnnotationReader, new OnFailureMethodHandler) - ] - ); - $invocation->proceed(); - } +// /** +// * @param $method +// */ +// public function getMethodInvocation(string $method, array $submit, FailureHandlerInterface $handler = null) +// { +// $handler = $handler ?: new OnFailureMethodHandler; +// $object = $this->getController($submit); +// +// $invocation = new ReflectiveMethodInvocation( +// $object, +// $method, +// $submit, +// [ +// new AuraInputInterceptor(new AnnotationReader, $handler) +// ] +// ); +// +// return $invocation; +// } +// +// public function getController(array $submit) +// { +// $controller = new FakeController; +// /** @var $fakeForm FakeForm */ +// $fakeForm = (new FormFactory)->newInstance(FakeForm::class); +// $fakeForm->setSubmit($submit); +// $controller->setForm($fakeForm); +// +// return $controller; +// } +// +// public function proceed($controller) +// { +// $invocation = new ReflectiveMethodInvocation( +// $controller, +// new \ReflectionMethod($controller, 'createAction'), +// [], +// [ +// new AuraInputInterceptor(new AnnotationReader, new OnFailureMethodHandler) +// ] +// ); +// $invocation->proceed(); +// } public function testProceedFailed() { - $invocation = $this->getMethodInvocation('createAction', []); - $result = $invocation->proceed(); + $result = $this->controller->createAction([]); $this->assertSame('400', $result); } public function testProceed() { - $invocation = $this->getMethodInvocation('createAction', ['BEAR']); - $result = $invocation->proceed(); + $result = $this->controller->createAction('BEAR'); $this->assertSame('201', $result); } public function invalidControllerProvider() { return [ - [new FakeInvalidController1], - [new FakeInvalidController2] + [$this->injector->getInstance(FakeInvalidController1::class)], + [$this->injector->getInstance(FakeInvalidController2::class)] ]; } - /** - * @dataProvider invalidControllerProvider - * - * @param $controller - */ - public function testInvalidFormPropertyByMissingProperty($controller) + public function testInvalidFormPropertyByMissingProperty() { $this->setExpectedException(InvalidFormPropertyException::class); - $this->proceed($controller); + $controller = $this->injector->getInstance(FakeInvalidController1::class); + $controller->createAction(); + } + + public function testInvalidFormPropertyByMissingProperty2() + { + $this->setExpectedException(InvalidFormPropertyException::class); + $controller = $this->injector->getInstance(FakeInvalidController2::class); + $controller->createAction(); } public function testInvalidFormPropertyException() { - $this->setExpectedException(InvalidOnFailureMethod::class); - $controller = new FakeInvalidController3; - /** @var $fakeForm FakeForm */ - $fakeForm = (new FormFactory)->newInstance(FakeForm::class); - $fakeForm->setSubmit(['name' => '']); - $controller->setForm($fakeForm); - $this->proceed($controller); + $this->setExpectedException(InvalidFormPropertyException::class); + /** @var FakeInvalidController3 $controller */ + $controller = $this->injector->getInstance(FakeInvalidController3::class); + $controller->createAction(''); } public function testInvalidFormPropertyByInvalidInstance() { $this->setExpectedException(InvalidFormPropertyException::class); - $object = new FakeInvalidController1; - $invocation = new ReflectiveMethodInvocation( - $object, - new \ReflectionMethod($object, 'createAction'), - ['name' => ''], - [ - new AuraInputInterceptor(new AnnotationReader, new OnFailureMethodHandler) - ] - ); - $invocation->proceed(); + $this->setExpectedException(InvalidFormPropertyException::class); + $controller = $this->injector->getInstance(FakeInvalidController1::class); + $controller->createAction(''); } public function testProceedWithVndErrorHandler() { + /** @var FakeController $controller */ + $controller = $this->injector->getInstance(FakeController::class); try { - $invocation = $this->getMethodInvocation('createAction', [], new VndErrorHandler(new AnnotationReader)); - $invocation->proceed(); + $controller->createAction(''); } catch (ValidationException $e) { $this->assertInstanceOf(FormValidationError::class, $e->error); $json = (string) $e->error; diff --git a/tests/AuraInputModuleTest.php b/tests/AuraInputModuleTest.php index 97ff88d..2d51b2b 100644 --- a/tests/AuraInputModuleTest.php +++ b/tests/AuraInputModuleTest.php @@ -12,20 +12,6 @@ class AuraInputModuleTest extends \PHPUnit_Framework_TestCase { - public function setUp() - { - parent::setUp(); - $handler = new FakeSessionHandler(); - session_set_save_handler( - [$handler, 'open'], - [$handler, 'close'], - [$handler, 'read'], - [$handler, 'write'], - [$handler, 'destroy'], - [$handler, 'gc'] - ); - } - public function testAuraInputModule() { $injector = new Injector(new FakeModule, __DIR__ . '/tmp'); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index ba7dfce..29ca915 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -9,3 +9,13 @@ /* @var $loader \Composer\Autoload\ClassLoader */ $loader = require dirname(__DIR__) . '/vendor/autoload.php'; AnnotationRegistry::registerLoader([$loader, 'loadClass']); + +$handler = new \Ray\WebFormModule\FakeSessionHandler(); +session_set_save_handler( + [$handler, 'open'], + [$handler, 'close'], + [$handler, 'read'], + [$handler, 'write'], + [$handler, 'destroy'], + [$handler, 'gc'] +); From 9034f2a85d9958078793b1b4b050d3729bf830ad Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Mon, 28 May 2018 00:03:05 +0900 Subject: [PATCH 04/41] drop hhvm support --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 11d74a3..0660bfe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ sudo: false php: - 7 - 7.1 - - hhvm + - 7.2 cache: directories: - vendor From 11fe5063bb7a9aaaa47821d73a64b05ae0363d1b Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Mon, 28 May 2018 00:03:19 +0900 Subject: [PATCH 05/41] update dependencies version --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 231331f..37ad5fd 100644 --- a/composer.json +++ b/composer.json @@ -7,11 +7,11 @@ ], "require": { "php": ">=7.0.0", - "ray/di": "^2.5", + "ray/di": "^2.7", "aura/input": "^1.2", "aura/filter": "^2.3|3.x-dev", - "aura/html": "^2.4", - "ray/aura-session-module": "^1.0" + "aura/html": "^2.5", + "ray/aura-session-module": "^1.1" }, "require-dev": { "phpunit/phpunit": "^5.7.13" From be96500cd53b1fbf4b0a9f83a35b84b09d0839e8 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Mon, 28 May 2018 00:24:44 +0900 Subject: [PATCH 06/41] call parent --- src/InputValidationInterceptor.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/InputValidationInterceptor.php b/src/InputValidationInterceptor.php index 0e29a57..0fa677d 100644 --- a/src/InputValidationInterceptor.php +++ b/src/InputValidationInterceptor.php @@ -11,11 +11,6 @@ class InputValidationInterceptor extends AuraInputInterceptor { - /** - * @var FailureHandlerInterface - */ - protected $failureHandler; - /** * @param Reader $reader * @param FailureHandlerInterface $handler @@ -24,7 +19,6 @@ class InputValidationInterceptor extends AuraInputInterceptor */ public function __construct(Reader $reader, FailureHandlerInterface $handler) { - $this->reader = $reader; - $this->failureHandler = $handler; + parent::__construct($reader, $handler); } } From 9e5bb672a322386fc39c721a04ae48d9b63a9c72 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Mon, 28 May 2018 02:22:13 +0900 Subject: [PATCH 07/41] add toStringInterface --- src/AbstractForm.php | 28 +++++++++++++++++++++++----- src/ToStringInterface.php | 15 +++++++++++++++ src/VndErrorHandler.php | 3 +-- tests/AbstractAuraFormTest.php | 13 +++++++++++++ tests/Fake/FakeErrorForm.php | 13 +++++++++++++ tests/Fake/FakeNameForm.php | 4 ++-- 6 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 src/ToStringInterface.php create mode 100644 tests/Fake/FakeErrorForm.php diff --git a/src/AbstractForm.php b/src/AbstractForm.php index 3ab127e..1c109ab 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -14,6 +14,7 @@ use Aura\Input\BuilderInterface; use Aura\Input\Fieldset; use Ray\WebFormModule\Exception\CsrfViolationException; +use Ray\WebFormModule\Exception\LogicException; abstract class AbstractForm extends Fieldset implements FormInterface { @@ -47,6 +48,26 @@ public function __clone() $this->init(); } + /** + * Return form markup string + * + * @return string + */ + public function __toString() + { + try { + if (! $this instanceof ToStringInterface) { + throw new LogicException(ToStringInterface::class . ' is not implemented'); + } + + return $this->toString(); + } catch (\Exception $e) { + trigger_error($e->getMessage() . PHP_EOL . $e->getTraceAsString(), E_USER_ERROR); + + return ''; + } + } + /** * @param BuilderInterface $builder * @param FilterFactory $filterFactory @@ -140,9 +161,8 @@ public function apply(array $data) throw new CsrfViolationException; } $this->fill($data); - $isValid = $this->filter->apply($data); - return $isValid; + return $this->filter->apply($data); } /** @@ -152,9 +172,7 @@ public function apply(array $data) */ public function getFailureMessages() { - $messages = $this->filter->getFailures()->getMessages(); - - return $messages; + return $this->filter->getFailures()->getMessages(); } /** diff --git a/src/ToStringInterface.php b/src/ToStringInterface.php new file mode 100644 index 0000000..b6a642c --- /dev/null +++ b/src/ToStringInterface.php @@ -0,0 +1,15 @@ +reader->getMethodAnnotation($invocation->getMethod(), VndError::class); $error = new FormValidationError($this->makeVndError($form, $vndError)); - $e = new ValidationException('Validation failed.', 400, null, $error); - throw $e; + throw new ValidationException('Validation failed.', 400, null, $error); } private function makeVndError(AbstractForm $form, VndError $vndError = null) diff --git a/tests/AbstractAuraFormTest.php b/tests/AbstractAuraFormTest.php index ecc9068..d11bdd7 100644 --- a/tests/AbstractAuraFormTest.php +++ b/tests/AbstractAuraFormTest.php @@ -60,4 +60,17 @@ public function tesetInputDataReamainedOnValidationFailure($html) $expected = ''; $this->assertContains($expected, $html); } + + public function testNotToStringImplemented() + { + $errNo = $errStr = ''; + set_error_handler(function (int $no, string $str) use (&$errNo, &$errStr) { + $errNo = $no; + $errStr = $str; + }); + $form = new FakeErrorForm; + (string) $form; + $this->assertSame(256, $errNo); + restore_error_handler(); + } } diff --git a/tests/Fake/FakeErrorForm.php b/tests/Fake/FakeErrorForm.php new file mode 100644 index 0000000..f8a9521 --- /dev/null +++ b/tests/Fake/FakeErrorForm.php @@ -0,0 +1,13 @@ +form([ 'method' => 'post', From 41935796a9b51b953101d694f36fdc118cddd474 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Sat, 27 Dec 2025 09:03:21 +0900 Subject: [PATCH 08/41] =?UTF-8?q?refactor:=20=E3=82=A2=E3=83=8E=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=20PHP=208=20Attri?= =?UTF-8?q?butes=20=E3=81=AB=E7=A7=BB=E8=A1=8C=E3=81=97=E4=BE=9D=E5=AD=98?= =?UTF-8?q?=E9=96=A2=E4=BF=82=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 12 +++++++++--- src/AbstractForm.php | 7 ++----- src/SetAntiCsrfTrait.php | 7 ++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 37ad5fd..e36bd59 100644 --- a/composer.json +++ b/composer.json @@ -6,15 +6,16 @@ "Ray.Di module" ], "require": { - "php": ">=7.0.0", - "ray/di": "^2.7", + "php": ">=8.0.0", + "ray/di": "^2.16", + "ray/aop": "^2.14", "aura/input": "^1.2", "aura/filter": "^2.3|3.x-dev", "aura/html": "^2.5", "ray/aura-session-module": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^5.7.13" + "phpunit/phpunit": "^9.5" }, "license": "MIT", "autoload":{ @@ -32,5 +33,10 @@ "coverage": ["php -dzend_extension=xdebug.so ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage"], "cs": ["php-cs-fixer fix -v --dry-run", "phpcs --standard=./phpcs.xml src"], "cs-fix": ["php-cs-fixer fix -v", "phpcbf src"] + }, + "config": { + "allow-plugins": { + "aura/installer-default": true + } } } diff --git a/src/AbstractForm.php b/src/AbstractForm.php index 1c109ab..71a7734 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -72,9 +72,8 @@ public function __toString() * @param BuilderInterface $builder * @param FilterFactory $filterFactory * @param HelperLocatorFactory $helperFactory - * - * @\Ray\Di\Di\Inject */ + #[\Ray\Di\Di\Inject] public function setBaseDependencies( BuilderInterface $builder, FilterFactory $filterFactory, @@ -90,9 +89,7 @@ public function setAntiCsrf(AntiCsrfInterface $antiCsrf) $this->antiCsrf = $antiCsrf; } - /** - * @\Ray\Di\Di\PostConstruct - */ + #[\Ray\Di\Di\PostConstruct] public function postConstruct() { $this->init(); diff --git a/src/SetAntiCsrfTrait.php b/src/SetAntiCsrfTrait.php index 2d7b289..bd256f5 100644 --- a/src/SetAntiCsrfTrait.php +++ b/src/SetAntiCsrfTrait.php @@ -10,11 +10,8 @@ trait SetAntiCsrfTrait { - /** - * @param AntiCsrfInterface $antiCsrf - * - * @\Ray\Di\Di\Inject - */ + /** @param AntiCsrfInterface $antiCsrf */ + #[\Ray\Di\Di\Inject] public function setAntiCsrf(AntiCsrfInterface $antiCsrf) { $this->antiCsrf = $antiCsrf; From d90abb98f97f3f033da98e5b58911002d7fde5a4 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Wed, 31 Dec 2025 10:11:50 +0900 Subject: [PATCH 09/41] =?UTF-8?q?docs:=20README=20=E3=81=AE=E3=82=B5?= =?UTF-8?q?=E3=83=B3=E3=83=97=E3=83=AB=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92?= =?UTF-8?q?=20PHP=208=20Attributes=20=E5=BD=A2=E5=BC=8F=E3=81=AB=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.JA.md | 24 ++++++------------------ README.md | 20 +++++--------------- src/Annotation/AbstractValidation.php | 7 +++---- src/Annotation/FormValidation.php | 7 +++---- src/Annotation/InputValidation.php | 7 +++---- src/Annotation/VndError.php | 7 +++---- 6 files changed, 23 insertions(+), 49 deletions(-) diff --git a/README.JA.md b/README.JA.md index b3acce9..29d83ef 100644 --- a/README.JA.md +++ b/README.JA.md @@ -95,18 +95,14 @@ class MyController */ protected $contactForm; - /** - * @Inject - * @Named("contact_form") - */ + #[Inject] + #[Named("contact_form")] public function setForm(FormInterface $form) { $this->contactForm = $form; } - /** - * @FormValidation(form="contactForm", onFailure="badRequestAction") - */ + #[FormValidation(form: "contactForm", onFailure: "badRequestAction")] public function createAction() { // validation success @@ -151,9 +147,7 @@ use Ray\WebFormModule\Annotation\InputValidation; class Foo { - /** - * @InputValidation(form="form1") - */ + #[InputValidation(form: "form1")] public function createAction($name) { // ... @@ -193,14 +187,8 @@ echo $e->error; `@VndError`アノテーションで`vnd.error+json`に必要な情報を加えることができます。 ```php - /** - * @FormValidation(form="contactForm") - * @VndError( - * message="foo validation failed", - * logref="a1000", path="/path/to/error", - * href={"_self"="/path/to/error", "help"="/path/to/help"} - * ) - */ + #[FormValidation(form: "contactForm")] + #[VndError(message: "foo validation failed", logref: "a1000", path: "/path/to/error", href: ["_self" => "/path/to/error", "help" => "/path/to/help"])] ``` このオプションのモジュールはAPIアプリケーションの時に有用です。 diff --git a/README.md b/README.md index 2265909..49c7aa1 100644 --- a/README.md +++ b/README.md @@ -103,18 +103,14 @@ class MyController */ protected $contactForm; - /** - * @Inject - * @Named("contact_form") - */ + #[Inject] + #[Named("contact_form")] public function setForm(FormInterface $form) { $this->contactForm = $form; } - /** - * @FormValidation(form="contactForm", onFailure="badRequestAction") - */ + #[FormValidation(form: "contactForm", onFailure: "badRequestAction")] public function createAction() { // validation success @@ -186,14 +182,8 @@ echo $e->error; More detail for `vnd.error+json`can be add with `@VndError` annotation. ```php - /** - * @FormValidation(form="contactForm") - * @VndError( - * message="foo validation failed", - * logref="a1000", path="/path/to/error", - * href={"_self"="/path/to/error", "help"="/path/to/help"} - * ) - */ + #[FormValidation(form: "contactForm")] + #[VndError(message: "foo validation failed", logref: "a1000", path: "/path/to/error", href: ["_self" => "/path/to/error", "help" => "/path/to/help"])] ``` This optional module is handy for API application. diff --git a/src/Annotation/AbstractValidation.php b/src/Annotation/AbstractValidation.php index 3cf5e2f..75c5de3 100644 --- a/src/Annotation/AbstractValidation.php +++ b/src/Annotation/AbstractValidation.php @@ -6,10 +6,9 @@ */ namespace Ray\WebFormModule\Annotation; -/** - * @Annotation - * @Target("METHOD") - */ +use Attribute; + +#[Attribute(Attribute::TARGET_METHOD)] class AbstractValidation { /** diff --git a/src/Annotation/FormValidation.php b/src/Annotation/FormValidation.php index 2640582..1700c70 100644 --- a/src/Annotation/FormValidation.php +++ b/src/Annotation/FormValidation.php @@ -6,10 +6,9 @@ */ namespace Ray\WebFormModule\Annotation; -/** - * @Annotation - * @Target("METHOD") - */ +use Attribute; + +#[Attribute(Attribute::TARGET_METHOD)] final class FormValidation extends AbstractValidation { /** diff --git a/src/Annotation/InputValidation.php b/src/Annotation/InputValidation.php index 424e84d..d10948a 100644 --- a/src/Annotation/InputValidation.php +++ b/src/Annotation/InputValidation.php @@ -6,10 +6,9 @@ */ namespace Ray\WebFormModule\Annotation; -/** - * @Annotation - * @Target("METHOD") - */ +use Attribute; + +#[Attribute(Attribute::TARGET_METHOD)] final class InputValidation extends AbstractValidation { } diff --git a/src/Annotation/VndError.php b/src/Annotation/VndError.php index 8acdcde..fd9a5a9 100644 --- a/src/Annotation/VndError.php +++ b/src/Annotation/VndError.php @@ -6,10 +6,9 @@ */ namespace Ray\WebFormModule\Annotation; -/** - * @Annotation - * @Target("METHOD") - */ +use Attribute; + +#[Attribute(Attribute::TARGET_METHOD)] final class VndError { /** From e192dc415f6738967307492ff63b62b88b866537 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Tue, 6 Jan 2026 14:15:10 +0900 Subject: [PATCH 10/41] =?UTF-8?q?test:=20PHPUnit=20=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E6=9C=80=E6=96=B0=E5=BD=A2=E5=BC=8F=E3=81=AB=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E3=81=97=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92=20PHP=208=20=E5=AF=BE=E5=BF=9C=E3=81=AB?= =?UTF-8?q?=E6=94=B9=E5=96=84=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++ phpunit.xml.dist | 22 ++++++----- src/Annotation/AbstractValidation.php | 8 ++-- src/Annotation/FormValidation.php | 19 ++++------ src/Annotation/VndError.php | 40 +++++--------------- src/AuraInputModule.php | 4 +- tests/AbstractAuraFormTest.php | 6 ++- tests/AbstractFormTest.php | 16 ++++---- tests/AntiCsrfTest.php | 5 ++- tests/AuraInputInterceptorTest.php | 27 ++++++------- tests/AuraInputModuleTest.php | 5 ++- tests/Fake/FakeController.php | 12 ++---- tests/Fake/FakeControllerVndError.php | 17 ++------- tests/Fake/FakeInputValidationController.php | 10 ++--- tests/Fake/FakeInvalidController1.php | 4 +- tests/Fake/FakeInvalidController2.php | 4 +- tests/Fake/FakeInvalidController3.php | 4 +- tests/Fake/FakeInvalidInstanceController.php | 4 +- tests/FormFactoryTest.php | 6 ++- tests/VndErrorHandlerTest.php | 7 ++-- tests/bootstrap.php | 6 +-- 21 files changed, 94 insertions(+), 135 deletions(-) diff --git a/.gitignore b/.gitignore index 59370d4..b47a5dd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ build vendor/ composer.phar composer.lock +tests/tmp/* +!tests/tmp/.placefolder +.phpunit.result.cache diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 0c37fb9..ca6b708 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,19 +1,21 @@ - + tests + + + src + + + + + + + - - - + - - - - src - - diff --git a/src/Annotation/AbstractValidation.php b/src/Annotation/AbstractValidation.php index 75c5de3..18743a5 100644 --- a/src/Annotation/AbstractValidation.php +++ b/src/Annotation/AbstractValidation.php @@ -4,6 +4,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Annotation; use Attribute; @@ -11,8 +12,7 @@ #[Attribute(Attribute::TARGET_METHOD)] class AbstractValidation { - /** - * @var string - */ - public $form = 'form'; + public function __construct(public string $form = 'form') + { + } } diff --git a/src/Annotation/FormValidation.php b/src/Annotation/FormValidation.php index 1700c70..8497a1a 100644 --- a/src/Annotation/FormValidation.php +++ b/src/Annotation/FormValidation.php @@ -4,6 +4,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Annotation; use Attribute; @@ -11,15 +12,11 @@ #[Attribute(Attribute::TARGET_METHOD)] final class FormValidation extends AbstractValidation { - /** - * @var bool - */ - public $antiCsrf = false; - - /** - * Method name on validation failed. - * - * @var string - */ - public $onFailure; + public function __construct( + string $form = 'form', + public bool $antiCsrf = false, + public string|null $onFailure = null + ) { + parent::__construct($form); + } } diff --git a/src/Annotation/VndError.php b/src/Annotation/VndError.php index fd9a5a9..12a85f2 100644 --- a/src/Annotation/VndError.php +++ b/src/Annotation/VndError.php @@ -4,6 +4,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Annotation; use Attribute; @@ -12,40 +13,17 @@ final class VndError { /** - * @var string - * - * REQUIRED - */ - public $message; - - /** - * @var array - * - * REQUIRED - */ - public $href; - - /** - * @var string - * - * OPTIONAL - */ - public $logref; - - /** - * @var string - * - * OPTIONAL - * - * help + * @param array $href * * @see http://www.w3.org/TR/html5/links.html#link-type-help - * - * about * @see http://tools.ietf.org/html/rfc6903#section-2 - * - * describes * @see http://tools.ietf.org/html/rfc6892 */ - public $path; + public function __construct( + public string $message, + public array $href, + public string|null $logref = null, + public string|null $path = null + ) { + } } diff --git a/src/AuraInputModule.php b/src/AuraInputModule.php index 738c346..c1e0cd7 100644 --- a/src/AuraInputModule.php +++ b/src/AuraInputModule.php @@ -13,8 +13,8 @@ use Aura\Input\BuilderInterface; use Aura\Input\Filter; use Aura\Input\FilterInterface; -use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\Reader; +use Koriym\Attributes\AttributeReader; use Ray\AuraSessionModule\AuraSessionModule; use Ray\Di\AbstractModule; use Ray\Di\Scope; @@ -29,7 +29,7 @@ class AuraInputModule extends AbstractModule protected function configure() { $this->install(new AuraSessionModule); - $this->bind(Reader::class)->to(AnnotationReader::class)->in(Scope::SINGLETON); + $this->bind(Reader::class)->to(AttributeReader::class)->in(Scope::SINGLETON); $this->bind(BuilderInterface::class)->to(Builder::class); $this->bind(FilterInterface::class)->to(Filter::class); $this->bind(AntiCsrfInterface::class)->to(AntiCsrf::class)->in(Scope::SINGLETON); diff --git a/tests/AbstractAuraFormTest.php b/tests/AbstractAuraFormTest.php index d11bdd7..16862dd 100644 --- a/tests/AbstractAuraFormTest.php +++ b/tests/AbstractAuraFormTest.php @@ -6,14 +6,16 @@ */ namespace Ray\WebFormModule; -class AbstractAuraFormTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class AbstractAuraFormTest extends TestCase { /** * @var AbstractForm */ private $form; - public function setUp() + public function setUp(): void { parent::setUp(); $this->form = (new FormFactory)->newInstance(FakeForm::class); diff --git a/tests/AbstractFormTest.php b/tests/AbstractFormTest.php index d87ad54..973f553 100644 --- a/tests/AbstractFormTest.php +++ b/tests/AbstractFormTest.php @@ -11,18 +11,20 @@ use Aura\Session\Randval; use Aura\Session\SegmentFactory; use Aura\Session\Session; -use Doctrine\Common\Annotations\AnnotationReader; +use Koriym\Attributes\AttributeReader; +use PHPUnit\Framework\TestCase; use Ray\Aop\ReflectiveMethodInvocation; use Ray\WebFormModule\Exception\CsrfViolationException; +use Ray\WebFormModule\Exception\ValidationException; -class AbstractFormTest extends \PHPUnit_Framework_TestCase +class AbstractFormTest extends TestCase { /** * @var AbstractForm */ private $form; - public function setUp() + public function setUp(): void { parent::setUp(); $this->form = (new FormFactory)->newInstance(FakeMiniForm::class); @@ -39,12 +41,12 @@ public function getMethodInvocation(array $arguments) $controller = new FakeController; $controller->setForm($fakeForm); // interceptor - $reader = new AnnotationReader; + $reader = new AttributeReader; $interceptor = new AuraInputInterceptor($reader, new VndErrorHandler($reader)); return new ReflectiveMethodInvocation( $controller, - new \ReflectionMethod($controller, 'createAction'), + 'createAction', $arguments, [ $interceptor @@ -61,7 +63,7 @@ public function testApply() public function testSubmit() { - $this->expectException(\ReflectionException::class); + $this->expectException(ValidationException::class); $invocation = $this->getMethodInvocation(['na']); $invocation->proceed(); } @@ -89,7 +91,7 @@ public function testGetItelator() public function testAntiCsrfViolation() { - $this->setExpectedException(CsrfViolationException::class); + $this->expectException(CsrfViolationException::class); $session = new Session( new SegmentFactory, new CsrfTokenFactory(new Randval(new Phpfunc)), diff --git a/tests/AntiCsrfTest.php b/tests/AntiCsrfTest.php index a20bc94..ebb4297 100644 --- a/tests/AntiCsrfTest.php +++ b/tests/AntiCsrfTest.php @@ -14,8 +14,9 @@ use Aura\Session\Randval; use Aura\Session\SegmentFactory; use Aura\Session\Session; +use PHPUnit\Framework\TestCase; -class AntiCsrfTest extends \PHPUnit_Framework_TestCase +class AntiCsrfTest extends TestCase { private $phpfunc; @@ -29,7 +30,7 @@ class AntiCsrfTest extends \PHPUnit_Framework_TestCase */ private $session; - protected function setUp() + protected function setUp(): void { $this->phpfunc = new FakePhpfunc; $this->session = $this->newSession(); diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index f153f5b..16e58f6 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -6,15 +6,14 @@ */ namespace Ray\WebFormModule; -use Doctrine\Common\Annotations\AnnotationReader; -use Ray\Aop\ReflectiveMethodInvocation; +use PHPUnit\Framework\TestCase; use Ray\Di\AbstractModule; use Ray\Di\Injector; use Ray\Di\InjectorInterface; use Ray\WebFormModule\Exception\InvalidFormPropertyException; use Ray\WebFormModule\Exception\ValidationException; -class AuraInputInterceptorTest extends \PHPUnit_Framework_TestCase +class AuraInputInterceptorTest extends TestCase { /** * @var InjectorInterface @@ -26,7 +25,7 @@ class AuraInputInterceptorTest extends \PHPUnit_Framework_TestCase */ private $controller; - public function setUp() + public function setUp(): void { $this->injector = new Injector(new class() extends AbstractModule { protected function configure() @@ -104,21 +103,21 @@ public function invalidControllerProvider() public function testInvalidFormPropertyByMissingProperty() { - $this->setExpectedException(InvalidFormPropertyException::class); + $this->expectException(InvalidFormPropertyException::class); $controller = $this->injector->getInstance(FakeInvalidController1::class); $controller->createAction(); } public function testInvalidFormPropertyByMissingProperty2() { - $this->setExpectedException(InvalidFormPropertyException::class); + $this->expectException(InvalidFormPropertyException::class); $controller = $this->injector->getInstance(FakeInvalidController2::class); $controller->createAction(); } public function testInvalidFormPropertyException() { - $this->setExpectedException(InvalidFormPropertyException::class); + $this->expectException(InvalidFormPropertyException::class); /** @var FakeInvalidController3 $controller */ $controller = $this->injector->getInstance(FakeInvalidController3::class); $controller->createAction(''); @@ -126,24 +125,26 @@ public function testInvalidFormPropertyException() public function testInvalidFormPropertyByInvalidInstance() { - $this->setExpectedException(InvalidFormPropertyException::class); - $this->setExpectedException(InvalidFormPropertyException::class); + $this->expectException(InvalidFormPropertyException::class); + $this->expectException(InvalidFormPropertyException::class); $controller = $this->injector->getInstance(FakeInvalidController1::class); $controller->createAction(''); } public function testProceedWithVndErrorHandler() { - /** @var FakeController $controller */ - $controller = $this->injector->getInstance(FakeController::class); + /** @var FakeControllerVndError $controller */ + $controller = $this->injector->getInstance(FakeControllerVndError::class); try { $controller->createAction(''); + $this->fail('ValidationException should be thrown'); } catch (ValidationException $e) { $this->assertInstanceOf(FormValidationError::class, $e->error); $json = (string) $e->error; $this->assertSame('{ - "message": "Validation failed", - "path": "", + "message": "foo validation failed", + "path": "/path/to/error", + "logref": "a1000", "validation_messages": { "name": [ "Name must be alphabetic only." diff --git a/tests/AuraInputModuleTest.php b/tests/AuraInputModuleTest.php index 2d51b2b..47add84 100644 --- a/tests/AuraInputModuleTest.php +++ b/tests/AuraInputModuleTest.php @@ -6,11 +6,12 @@ */ namespace Ray\WebFormModule; +use PHPUnit\Framework\TestCase; use Ray\Aop\WeavedInterface; use Ray\Di\Injector; use Ray\WebFormModule\Exception\ValidationException; -class AuraInputModuleTest extends \PHPUnit_Framework_TestCase +class AuraInputModuleTest extends TestCase { public function testAuraInputModule() { @@ -21,7 +22,7 @@ public function testAuraInputModule() public function testExceptionOnFailure() { - $this->setExpectedException(ValidationException::class); + $this->expectException(ValidationException::class); $injector = new Injector(new FakeModule, __DIR__ . '/tmp'); /** @var $controller FakeInputValidationController */ $controller = $injector->getInstance(FakeInputValidationController::class); diff --git a/tests/Fake/FakeController.php b/tests/Fake/FakeController.php index d642e52..9484301 100644 --- a/tests/Fake/FakeController.php +++ b/tests/Fake/FakeController.php @@ -13,20 +13,14 @@ class FakeController */ protected $form; - /** - * @Inject - * @Named("contact_form") - */ + #[Inject] + #[Named('contact_form')] public function setForm(FormInterface $form) { $this->form = $form; } - /** - * @FormValidation - * - * = is same as @ FormValidation(form="form", onFailure="createActionValidationFailed") - */ + #[FormValidation] public function createAction($name) { return '201'; diff --git a/tests/Fake/FakeControllerVndError.php b/tests/Fake/FakeControllerVndError.php index 3f44ab5..d952c87 100644 --- a/tests/Fake/FakeControllerVndError.php +++ b/tests/Fake/FakeControllerVndError.php @@ -14,24 +14,15 @@ class FakeControllerVndError */ protected $form1; - /** - * @Inject - * @Named("contact_form") - */ + #[Inject] + #[Named('contact_form')] public function setForm(FormInterface $form) { $this->form1 = $form; } - /** - * @InputValidation(form="form1") - * @VndError( - * message="foo validation failed", - * logref="a1000", - * path="/path/to/error", - * href={"_self"="/path/to/error", "help"="/path/to/help"} - * ) - */ + #[InputValidation(form: 'form1')] + #[VndError(message: 'foo validation failed', href: ['_self' => '/path/to/error', 'help' => '/path/to/help'], logref: 'a1000', path: '/path/to/error')] public function createAction($name) { } diff --git a/tests/Fake/FakeInputValidationController.php b/tests/Fake/FakeInputValidationController.php index 9619276..162ad83 100644 --- a/tests/Fake/FakeInputValidationController.php +++ b/tests/Fake/FakeInputValidationController.php @@ -13,18 +13,14 @@ class FakeInputValidationController */ protected $form; - /** - * @Inject - * @Named("contact_form") - */ + #[Inject] + #[Named('contact_form')] public function setForm(FormInterface $form) { $this->form = $form; } - /** - * @InputValidation - */ + #[InputValidation] public function createAction($name) { } diff --git a/tests/Fake/FakeInvalidController1.php b/tests/Fake/FakeInvalidController1.php index 32aa3dc..122fb70 100644 --- a/tests/Fake/FakeInvalidController1.php +++ b/tests/Fake/FakeInvalidController1.php @@ -6,9 +6,7 @@ class FakeInvalidController1 { - /** - * @FormValidation(form="missing") - */ + #[FormValidation(form: "missing")] public function createAction() { } diff --git a/tests/Fake/FakeInvalidController2.php b/tests/Fake/FakeInvalidController2.php index 9f6bb47..711449c 100644 --- a/tests/Fake/FakeInvalidController2.php +++ b/tests/Fake/FakeInvalidController2.php @@ -8,9 +8,7 @@ class FakeInvalidController2 { private $form = null; - /** - * @FormValidation - */ + #[FormValidation] public function createAction() { } diff --git a/tests/Fake/FakeInvalidController3.php b/tests/Fake/FakeInvalidController3.php index 8322619..be70fac 100644 --- a/tests/Fake/FakeInvalidController3.php +++ b/tests/Fake/FakeInvalidController3.php @@ -13,9 +13,7 @@ public function setForm(FormInterface $form) $this->form = $form; } - /** - * @FormValidation(onFailure="missing_method") - */ + #[FormValidation(onFailure: "missing_method")] public function createAction($name) { } diff --git a/tests/Fake/FakeInvalidInstanceController.php b/tests/Fake/FakeInvalidInstanceController.php index d64b9c6..5f93b9d 100644 --- a/tests/Fake/FakeInvalidInstanceController.php +++ b/tests/Fake/FakeInvalidInstanceController.php @@ -8,9 +8,7 @@ class FakeInvalidInstanceController { private $form; - /** - * @FormValidation - */ + #[FormValidation] public function createAction() { } diff --git a/tests/FormFactoryTest.php b/tests/FormFactoryTest.php index 07451e3..c8ae411 100644 --- a/tests/FormFactoryTest.php +++ b/tests/FormFactoryTest.php @@ -6,14 +6,16 @@ */ namespace Ray\WebFormModule; -class FormFactoryTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class FormFactoryTest extends TestCase { /** * @var FormFactory */ private $factory; - public function setUp() + public function setUp(): void { parent::setUp(); $this->factory = new FormFactory; diff --git a/tests/VndErrorHandlerTest.php b/tests/VndErrorHandlerTest.php index 80519d7..2b96fb8 100644 --- a/tests/VndErrorHandlerTest.php +++ b/tests/VndErrorHandlerTest.php @@ -6,17 +6,18 @@ */ namespace Ray\WebFormModule; +use PHPUnit\Framework\TestCase; use Ray\Di\Injector; use Ray\WebFormModule\Exception\ValidationException; -class VndErrorHandlerTest extends \PHPUnit_Framework_TestCase +class VndErrorHandlerTest extends TestCase { /** * @var FakeController */ private $controller; - public function setUp() + public function setUp(): void { parent::setUp(); $this->controller = (new Injector(new FakeVndErrorModule, __DIR__ . '/tmp'))->getInstance(FakeController::class); @@ -24,7 +25,7 @@ public function setUp() public function testValidationException() { - $this->setExpectedException(ValidationException::class); + $this->expectException(ValidationException::class); $this->controller->createAction(''); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 29ca915..adacf3f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -4,11 +4,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ -use Doctrine\Common\Annotations\AnnotationRegistry; - -/* @var $loader \Composer\Autoload\ClassLoader */ -$loader = require dirname(__DIR__) . '/vendor/autoload.php'; -AnnotationRegistry::registerLoader([$loader, 'loadClass']); +require dirname(__DIR__) . '/vendor/autoload.php'; $handler = new \Ray\WebFormModule\FakeSessionHandler(); session_set_save_handler( From d6e5bba386493f4bbbdaeb10989026d239a11b79 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Tue, 6 Jan 2026 14:19:08 +0900 Subject: [PATCH 11/41] =?UTF-8?q?ci:=20Scrutinizer=20=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=8B=E3=82=89=20`php=5Fmess=5Fdetector`=20=E3=82=92?= =?UTF-8?q?=E7=84=A1=E5=8A=B9=E5=8C=96=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .scrutinizer.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 0ca2b93..39fbeb5 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -5,7 +5,6 @@ tools: php_code_coverage: timeout: 1200 php_sim: true - php_mess_detector: true php_pdepend: true php_analyzer: true php_cpd: true From f4e605affb3489d65822eb6f83b373577ba7cab9 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Tue, 6 Jan 2026 14:38:57 +0900 Subject: [PATCH 12/41] =?UTF-8?q?docs:=20README=20=E3=81=AE=20`VndError`?= =?UTF-8?q?=20=E8=A1=A8=E8=A8=98=E3=82=92=20Attributes=20=E5=BD=A2?= =?UTF-8?q?=E5=BC=8F=E3=81=AB=E6=9B=B4=E6=96=B0=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.JA.md | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.JA.md b/README.JA.md index 29d83ef..c96343d 100644 --- a/README.JA.md +++ b/README.JA.md @@ -184,7 +184,7 @@ echo $e->error; //} ``` -`@VndError`アノテーションで`vnd.error+json`に必要な情報を加えることができます。 +`#[VndError]`属性で`vnd.error+json`に必要な情報を加えることができます。 ```php #[FormValidation(form: "contactForm")] diff --git a/README.md b/README.md index 49c7aa1..b07cc87 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ class MyController public function createAction() { // validation success + // More detail for `vnd.error+json` can be added with `#[VndError]`. } public function badRequestAction() From 7bcb17db9cfbc475a1c6246ecc0e17baa17fc10d Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Wed, 7 Jan 2026 12:01:08 +0900 Subject: [PATCH 13/41] =?UTF-8?q?ci:=20Scrutinizer=20=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E7=B0=A1=E7=B4=A0=E5=8C=96=E3=81=97=20PHP=208.0=20?= =?UTF-8?q?=E3=83=93=E3=83=AB=E3=83=89=E7=92=B0=E5=A2=83=E3=81=AB=E7=A7=BB?= =?UTF-8?q?=E8=A1=8C=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .scrutinizer.yml | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 39fbeb5..db26de6 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,18 +1,12 @@ +build: + image: default-jammy + environment: + php: 8.0 + nodes: + analysis: + tests: + override: + - php-scrutinizer-run + filter: paths: ["src/*"] -tools: - external_code_coverage: true - php_code_coverage: - timeout: 1200 - php_sim: true - php_pdepend: true - php_analyzer: true - php_cpd: true - php_mess_detector: - enabled: true - config: - ruleset: ./phpmd.xml - php_code_sniffer: - enabled: true - config: - ruleset: ./phpcs.xml From 67df167606bf0ebb453b32791b5470a5469124b9 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Wed, 7 Jan 2026 12:06:53 +0900 Subject: [PATCH 14/41] =?UTF-8?q?test:=20`AuraInputInterceptorTest`=20?= =?UTF-8?q?=E3=81=AE=E9=87=8D=E8=A4=87=E3=81=97=E3=81=9F=20`expectExceptio?= =?UTF-8?q?n`=20=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=97=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/AuraInputInterceptorTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 16e58f6..8b2df09 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -125,7 +125,6 @@ public function testInvalidFormPropertyException() public function testInvalidFormPropertyByInvalidInstance() { - $this->expectException(InvalidFormPropertyException::class); $this->expectException(InvalidFormPropertyException::class); $controller = $this->injector->getInstance(FakeInvalidController1::class); $controller->createAction(''); From 47c4b506cf4db4617e0ecc927c8817255c60c781 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Wed, 7 Jan 2026 12:07:52 +0900 Subject: [PATCH 15/41] =?UTF-8?q?ci:=20Scrutinizer=20=E3=81=AE=20PHP=20?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=92=208.2=20?= =?UTF-8?q?=E3=81=AB=E6=9B=B4=E6=96=B0=E3=81=97=E8=A8=AD=E5=AE=9A=E6=A7=8B?= =?UTF-8?q?=E9=80=A0=E3=82=92=E6=9C=80=E9=81=A9=E5=8C=96=20migrate-attribu?= =?UTF-8?q?te?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .scrutinizer.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index db26de6..9c4042c 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,12 +1,12 @@ build: - image: default-jammy - environment: - php: 8.0 nodes: analysis: tests: override: - php-scrutinizer-run + environment: + php: + version: 8.2 filter: paths: ["src/*"] From 280117b58e35b96bb7a55a7b5fa18ae0ddf7fb67 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Wed, 7 Jan 2026 13:33:06 +0900 Subject: [PATCH 16/41] =?UTF-8?q?test:=20=E6=9C=AA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=81=AE=20`invalidControllerProvider`=20=E3=83=A1=E3=82=BD?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=82=92=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=A2=E3=82=A6=E3=83=88=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/AuraInputInterceptorTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 8b2df09..18ad270 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -93,13 +93,13 @@ public function testProceed() $this->assertSame('201', $result); } - public function invalidControllerProvider() - { - return [ - [$this->injector->getInstance(FakeInvalidController1::class)], - [$this->injector->getInstance(FakeInvalidController2::class)] - ]; - } +// public function invalidControllerProvider() +// { +// return [ +// [$this->injector->getInstance(FakeInvalidController1::class)], +// [$this->injector->getInstance(FakeInvalidController2::class)] +// ]; +// } public function testInvalidFormPropertyByMissingProperty() { From 9f408e2d78fa19952eab04de9b4d7b28a7719467 Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Sat, 7 Feb 2026 17:00:41 +0900 Subject: [PATCH 17/41] =?UTF-8?q?style:=20=E5=85=A8=E3=82=BD=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AB=20`decla?= =?UTF-8?q?re(strict=5Ftypes=3D1)`=20=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97?= =?UTF-8?q?=E5=8E=B3=E5=AF=86=E3=81=AA=E5=9E=8B=E3=83=81=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=82=92=E6=9C=89=E5=8A=B9=E5=8C=96=20migrate-attribu?= =?UTF-8?q?te?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AbstractForm.php | 3 +++ src/Annotation/AbstractValidation.php | 3 +++ src/Annotation/FormValidation.php | 3 +++ src/Annotation/InputValidation.php | 3 +++ src/Annotation/VndError.php | 3 +++ src/AntiCsrf.php | 3 +++ src/AuraInputInterceptor.php | 3 +++ src/AuraInputModule.php | 3 +++ src/Exception/CsrfViolationException.php | 3 +++ src/Exception/ExceptionInterface.php | 3 +++ src/Exception/InvalidArgumentException.php | 3 +++ src/Exception/InvalidFormPropertyException.php | 3 +++ src/Exception/InvalidOnFailureMethod.php | 3 +++ src/Exception/LogicException.php | 3 +++ src/Exception/RuntimeException.php | 3 +++ src/Exception/ValidationException.php | 3 +++ src/FailureHandlerInterface.php | 3 +++ src/FormFactory.php | 3 +++ src/FormInterface.php | 3 +++ src/FormValidationError.php | 3 +++ src/FormVndErrorModule.php | 3 +++ src/InputValidationInterceptor.php | 3 +++ src/OnFailureMethodHandler.php | 3 +++ src/SetAntiCsrfTrait.php | 3 +++ src/SubmitInterface.php | 3 +++ src/ToStringInterface.php | 3 +++ src/VndErrorHandler.php | 4 ++++ 27 files changed, 82 insertions(+) diff --git a/src/AbstractForm.php b/src/AbstractForm.php index 71a7734..b81c26f 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -1,4 +1,7 @@ Date: Sat, 14 Feb 2026 11:47:04 +0900 Subject: [PATCH 18/41] =?UTF-8?q?style:=20namespace=20=E5=BE=8C=E3=81=AB?= =?UTF-8?q?=E7=A9=BA=E8=A1=8C=E8=BF=BD=E5=8A=A0=E3=80=81use=20=E6=96=87?= =?UTF-8?q?=E3=82=92=E6=95=B4=E7=90=86=E3=81=97=20FQCN=20=E3=82=92?= =?UTF-8?q?=E5=89=8A=E6=B8=9B=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AbstractForm.php | 43 ++++++++----------- src/Annotation/InputValidation.php | 1 + src/AntiCsrf.php | 17 +++----- src/AuraInputInterceptor.php | 15 +++---- src/AuraInputModule.php | 9 ++-- src/Exception/CsrfViolationException.php | 1 + src/Exception/ExceptionInterface.php | 1 + src/Exception/InvalidArgumentException.php | 1 + .../InvalidFormPropertyException.php | 5 ++- src/Exception/InvalidOnFailureMethod.php | 5 ++- src/Exception/LogicException.php | 1 + src/Exception/RuntimeException.php | 1 + src/Exception/ValidationException.php | 6 ++- src/FailureHandlerInterface.php | 1 + src/FormFactory.php | 1 + src/FormInterface.php | 3 +- src/FormValidationError.php | 5 +-- src/FormVndErrorModule.php | 5 +-- src/InputValidationInterceptor.php | 1 + src/OnFailureMethodHandler.php | 5 +-- src/SetAntiCsrfTrait.php | 1 + src/SubmitInterface.php | 1 + src/ToStringInterface.php | 7 ++- src/VndErrorHandler.php | 8 +--- 24 files changed, 69 insertions(+), 75 deletions(-) diff --git a/src/AbstractForm.php b/src/AbstractForm.php index b81c26f..810a3e2 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -7,8 +7,10 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; +use ArrayIterator; use Aura\Filter\FilterFactory; use Aura\Filter\SubjectFilter; use Aura\Html\HelperLocator; @@ -16,29 +18,23 @@ use Aura\Input\AntiCsrfInterface; use Aura\Input\BuilderInterface; use Aura\Input\Fieldset; +use Ray\Di\Di\Inject; +use Ray\Di\Di\PostConstruct; use Ray\WebFormModule\Exception\CsrfViolationException; use Ray\WebFormModule\Exception\LogicException; abstract class AbstractForm extends Fieldset implements FormInterface { - /** - * @var SubjectFilter - */ + /** @var SubjectFilter */ protected $filter; - /** - * @var null | array - */ + /** @var null|array */ protected $errorMessages; - /** - * @var HelperLocator - */ + /** @var HelperLocator */ protected $helper; - /** - * @var AntiCsrfInterface - */ + /** @var AntiCsrfInterface */ protected $antiCsrf; public function __construct() @@ -76,7 +72,7 @@ public function __toString() * @param FilterFactory $filterFactory * @param HelperLocatorFactory $helperFactory */ - #[\Ray\Di\Di\Inject] + #[Inject] public function setBaseDependencies( BuilderInterface $builder, FilterFactory $filterFactory, @@ -92,7 +88,7 @@ public function setAntiCsrf(AntiCsrfInterface $antiCsrf) $this->antiCsrf = $antiCsrf; } - #[\Ray\Di\Di\PostConstruct] + #[PostConstruct] public function postConstruct() { $this->init(); @@ -101,17 +97,13 @@ public function postConstruct() } } - /** - * {@inheritdoc} - */ + /** {@inheritdoc} */ public function input($input) { return $this->helper->input($this->get($input)); } - /** - * {@inheritdoc} - */ + /** {@inheritdoc} */ public function error($input) { if (! $this->errorMessages) { @@ -131,10 +123,10 @@ public function error($input) /** * @param array $attr attributes for the form tag * - * @throws \Aura\Html\Exception\HelperNotFound + * @return string * @throws \Aura\Input\Exception\NoSuchInput * - * @return string + * @throws \Aura\Html\Exception\HelperNotFound */ public function form($attr = []) { @@ -151,9 +143,8 @@ public function form($attr = []) * * @param array $data * - * @throws CsrfViolationException - * * @return bool + * @throws CsrfViolationException */ public function apply(array $data) { @@ -178,10 +169,10 @@ public function getFailureMessages() /** * Returns all the fields collection * - * @return \ArrayIterator + * @return ArrayIterator */ public function getIterator() { - return new \ArrayIterator($this->inputs); + return new ArrayIterator($this->inputs); } } diff --git a/src/Annotation/InputValidation.php b/src/Annotation/InputValidation.php index d8dbb8f..71ef33c 100644 --- a/src/Annotation/InputValidation.php +++ b/src/Annotation/InputValidation.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Annotation; use Attribute; diff --git a/src/AntiCsrf.php b/src/AntiCsrf.php index 6ac280e..6397971 100644 --- a/src/AntiCsrf.php +++ b/src/AntiCsrf.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Aura\Input\AntiCsrfInterface; @@ -19,20 +20,16 @@ final class AntiCsrf implements AntiCsrfInterface const TOKEN_KEY = '__csrf_token'; - /** - * @var bool - */ + /** @var bool */ private $isCli; - /** - * @var Session - */ + /** @var Session */ private $session; /** * @param Session $session * @param bool|null $isCli - s */ + * s */ public function __construct(Session $session, $isCli = null) { $this->session = $session; @@ -42,7 +39,7 @@ public function __construct(Session $session, $isCli = null) public function setField(Fieldset $fieldset) { $fieldset->setField(self::TOKEN_KEY, 'hidden') - ->setAttribs(['value' => $this->getToken()]); + ->setAttribs(['value' => $this->getToken()]); } /** @@ -59,9 +56,7 @@ public function isValid(array $data) return isset($data[self::TOKEN_KEY]) && $data[self::TOKEN_KEY] == $this->getToken(); } - /** - * @return string - */ + /** @return string */ private function getToken() { $value = $this->isCli ? self::TEST_TOKEN : $this->session->getCsrfToken()->getValue(); diff --git a/src/AuraInputInterceptor.php b/src/AuraInputInterceptor.php index 274bb76..25505b5 100644 --- a/src/AuraInputInterceptor.php +++ b/src/AuraInputInterceptor.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Doctrine\Common\Annotations\Reader; @@ -19,14 +20,10 @@ class AuraInputInterceptor implements MethodInterceptor { - /** - * @var Reader - */ + /** @var Reader */ protected $reader; - /** - * @var FailureHandlerInterface - */ + /** @var FailureHandlerInterface */ protected $failureHandler; /** @@ -65,15 +62,13 @@ public function invoke(MethodInvocation $invocation) * @param array $submit * @param AbstractForm $form * + * @return bool * @throws Exception\CsrfViolationException * - * @return bool */ public function isValid(array $submit, AbstractForm $form) { - $isValid = $form->apply($submit); - - return $isValid; + return $form->apply($submit); } /** diff --git a/src/AuraInputModule.php b/src/AuraInputModule.php index a46d581..2e3dd73 100644 --- a/src/AuraInputModule.php +++ b/src/AuraInputModule.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Aura\Filter\FilterFactory; @@ -26,9 +27,7 @@ class AuraInputModule extends AbstractModule { - /** - * {@inheritdoc} - */ + /** {@inheritdoc} */ protected function configure() { $this->install(new AuraSessionModule); @@ -37,7 +36,9 @@ protected function configure() $this->bind(FilterInterface::class)->to(Filter::class); $this->bind(AntiCsrfInterface::class)->to(AntiCsrf::class)->in(Scope::SINGLETON); $this->bind(FailureHandlerInterface::class)->to(OnFailureMethodHandler::class); - $this->bind(FailureHandlerInterface::class)->annotatedWith('vnd_error')->to(VndErrorHandler::class)->in(Scope::SINGLETON); + $this->bind(FailureHandlerInterface::class)->annotatedWith('vnd_error')->to(VndErrorHandler::class)->in( + Scope::SINGLETON + ); $this->bind(HelperLocatorFactory::class); $this->bind(FilterFactory::class); $this->bindInterceptor( diff --git a/src/Exception/CsrfViolationException.php b/src/Exception/CsrfViolationException.php index 816cc28..c97b0b3 100644 --- a/src/Exception/CsrfViolationException.php +++ b/src/Exception/CsrfViolationException.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Exception; use Aura\Input\Exception\CsrfViolation; diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index dd435dc..237ca9d 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Exception; interface ExceptionInterface diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 47323d4..5afa83d 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Exception; class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface diff --git a/src/Exception/InvalidFormPropertyException.php b/src/Exception/InvalidFormPropertyException.php index 73a98ff..e4c9b77 100644 --- a/src/Exception/InvalidFormPropertyException.php +++ b/src/Exception/InvalidFormPropertyException.php @@ -7,8 +7,11 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Exception; -class InvalidFormPropertyException extends \LogicException +use LogicException; + +class InvalidFormPropertyException extends LogicException { } diff --git a/src/Exception/InvalidOnFailureMethod.php b/src/Exception/InvalidOnFailureMethod.php index f48c830..5b3ccdc 100644 --- a/src/Exception/InvalidOnFailureMethod.php +++ b/src/Exception/InvalidOnFailureMethod.php @@ -7,8 +7,11 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Exception; -class InvalidOnFailureMethod extends \LogicException +use LogicException; + +class InvalidOnFailureMethod extends LogicException { } diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php index da9cee0..d5ec12c 100644 --- a/src/Exception/LogicException.php +++ b/src/Exception/LogicException.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Exception; class LogicException extends \LogicException implements ExceptionInterface diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 247a9ad..46953e3 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Exception; class RuntimeException extends \LogicException implements ExceptionInterface diff --git a/src/Exception/ValidationException.php b/src/Exception/ValidationException.php index 94bb6b9..bbf583c 100644 --- a/src/Exception/ValidationException.php +++ b/src/Exception/ValidationException.php @@ -7,15 +7,17 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule\Exception; +use Exception; use Ray\WebFormModule\FormValidationError; -class ValidationException extends \Exception +class ValidationException extends Exception { public $error; - public function __construct($message = '', $code = 0, \Exception $e = null, FormValidationError $error = null) + public function __construct($message = '', $code = 0, Exception $e = null, FormValidationError $error = null) { parent::__construct($message, $code, $e); $this->error = $error; diff --git a/src/FailureHandlerInterface.php b/src/FailureHandlerInterface.php index 113f32b..948becf 100644 --- a/src/FailureHandlerInterface.php +++ b/src/FailureHandlerInterface.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Ray\Aop\MethodInvocation; diff --git a/src/FormFactory.php b/src/FormFactory.php index 7136d69..b9b07f5 100644 --- a/src/FormFactory.php +++ b/src/FormFactory.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Aura\Filter\FilterFactory; diff --git a/src/FormInterface.php b/src/FormInterface.php index ef49d75..f02348d 100644 --- a/src/FormInterface.php +++ b/src/FormInterface.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; interface FormInterface @@ -16,9 +17,9 @@ interface FormInterface * * @param string $input * + * @return string * @throws \Aura\Input\Exception\NoSuchInput * - * @return string */ public function input($input); diff --git a/src/FormValidationError.php b/src/FormValidationError.php index 5f16121..cf93a02 100644 --- a/src/FormValidationError.php +++ b/src/FormValidationError.php @@ -7,13 +7,12 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; class FormValidationError { - /** - * @var array - */ + /** @var array */ private $value; public function __construct(array $value) diff --git a/src/FormVndErrorModule.php b/src/FormVndErrorModule.php index cbd2063..a4064b2 100644 --- a/src/FormVndErrorModule.php +++ b/src/FormVndErrorModule.php @@ -7,15 +7,14 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Ray\Di\AbstractModule; class FormVndErrorModule extends AbstractModule { - /** - * {@inheritdoc} - */ + /** {@inheritdoc} */ protected function configure() { $this->bind(FailureHandlerInterface::class)->to(VndErrorHandler::class); diff --git a/src/InputValidationInterceptor.php b/src/InputValidationInterceptor.php index 79df033..b262ae6 100644 --- a/src/InputValidationInterceptor.php +++ b/src/InputValidationInterceptor.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Doctrine\Common\Annotations\Reader; diff --git a/src/OnFailureMethodHandler.php b/src/OnFailureMethodHandler.php index e4b1b7d..17567bf 100644 --- a/src/OnFailureMethodHandler.php +++ b/src/OnFailureMethodHandler.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Ray\Aop\MethodInvocation; @@ -18,9 +19,7 @@ final class OnFailureMethodHandler implements FailureHandlerInterface { const FAILURE_SUFFIX = 'ValidationFailed'; - /** - * {@inheritdoc} - */ + /** {@inheritdoc} */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) { unset($form); diff --git a/src/SetAntiCsrfTrait.php b/src/SetAntiCsrfTrait.php index 32d448e..f314312 100644 --- a/src/SetAntiCsrfTrait.php +++ b/src/SetAntiCsrfTrait.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; use Aura\Input\AntiCsrfInterface; diff --git a/src/SubmitInterface.php b/src/SubmitInterface.php index aba0cdf..10db621 100644 --- a/src/SubmitInterface.php +++ b/src/SubmitInterface.php @@ -7,6 +7,7 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; interface SubmitInterface diff --git a/src/ToStringInterface.php b/src/ToStringInterface.php index 0385411..22ed248 100644 --- a/src/ToStringInterface.php +++ b/src/ToStringInterface.php @@ -7,12 +7,11 @@ * * @license http://opensource.org/licenses/MIT MIT */ + namespace Ray\WebFormModule; -/** - * Return form markup string - */ +/** Return form markup string */ interface ToStringInterface { - public function toString() : string; + public function toString(): string; } diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index 3c1bee4..d3bf03b 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -18,9 +18,7 @@ final class VndErrorHandler implements FailureHandlerInterface { - /** - * @var Reader - */ + /** @var Reader */ private $reader; public function __construct(Reader $reader) @@ -28,9 +26,7 @@ public function __construct(Reader $reader) $this->reader = $reader; } - /** - * {@inheritdoc} - */ + /** {@inheritdoc} */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) { unset($formValidation); From 46414e8d77293a30306fe7b3d44bee957587114d Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Sat, 14 Feb 2026 16:31:52 +0900 Subject: [PATCH 19/41] =?UTF-8?q?refactor:=20=E3=83=97=E3=83=AD=E3=83=91?= =?UTF-8?q?=E3=83=86=E3=82=A3=E3=81=A8=E8=BF=94=E3=82=8A=E5=80=A4=E3=81=AB?= =?UTF-8?q?=E5=9E=8B=E5=AE=A3=E8=A8=80=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97?= =?UTF-8?q?=20PHP=208=20=E5=9E=8B=E3=82=B7=E3=82=B9=E3=83=86=E3=83=A0?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C=20migrate-attribute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AbstractForm.php | 42 ++++++++++++++---------------- src/AntiCsrf.php | 39 +++++++++++---------------- src/AuraInputInterceptor.php | 26 +++++++++--------- src/AuraInputModule.php | 5 ++-- src/FormFactory.php | 7 +---- src/FormInterface.php | 15 +++-------- src/FormValidationError.php | 8 ++++-- src/InputValidationInterceptor.php | 13 ++++----- src/OnFailureMethodHandler.php | 8 ++++-- src/SetAntiCsrfTrait.php | 6 ++--- src/VndErrorHandler.php | 7 ++--- 11 files changed, 77 insertions(+), 99 deletions(-) diff --git a/src/AbstractForm.php b/src/AbstractForm.php index 810a3e2..6d9e5b2 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -18,21 +18,23 @@ use Aura\Input\AntiCsrfInterface; use Aura\Input\BuilderInterface; use Aura\Input\Fieldset; +use Exception; use Ray\Di\Di\Inject; use Ray\Di\Di\PostConstruct; use Ray\WebFormModule\Exception\CsrfViolationException; use Ray\WebFormModule\Exception\LogicException; +use function trigger_error; + abstract class AbstractForm extends Fieldset implements FormInterface { /** @var SubjectFilter */ protected $filter; - /** @var null|array */ + /** @var array|null */ protected $errorMessages; - /** @var HelperLocator */ - protected $helper; + protected HelperLocator $helper; /** @var AntiCsrfInterface */ protected $antiCsrf; @@ -60,11 +62,11 @@ public function __toString() } return $this->toString(); - } catch (\Exception $e) { + } catch (Exception $e) { trigger_error($e->getMessage() . PHP_EOL . $e->getTraceAsString(), E_USER_ERROR); - - return ''; } + + return ''; } /** @@ -77,19 +79,19 @@ public function setBaseDependencies( BuilderInterface $builder, FilterFactory $filterFactory, HelperLocatorFactory $helperFactory - ) { + ): void { $this->builder = $builder; $this->filter = $filterFactory->newSubjectFilter(); $this->helper = $helperFactory->newInstance(); } - public function setAntiCsrf(AntiCsrfInterface $antiCsrf) + public function setAntiCsrf(AntiCsrfInterface $antiCsrf): void { $this->antiCsrf = $antiCsrf; } #[PostConstruct] - public function postConstruct() + public function postConstruct(): void { $this->init(); if ($this->antiCsrf instanceof AntiCsrfInterface) { @@ -104,7 +106,7 @@ public function input($input) } /** {@inheritdoc} */ - public function error($input) + public function error(string $input): string { if (! $this->errorMessages) { $failure = $this->filter->getFailures(); @@ -123,12 +125,10 @@ public function error($input) /** * @param array $attr attributes for the form tag * - * @return string * @throws \Aura\Input\Exception\NoSuchInput - * * @throws \Aura\Html\Exception\HelperNotFound */ - public function form($attr = []) + public function form(array $attr = []): string { $form = $this->helper->form($attr); if (isset($this->inputs['__csrf_token'])) { @@ -143,14 +143,14 @@ public function form($attr = []) * * @param array $data * - * @return bool * @throws CsrfViolationException */ - public function apply(array $data) + public function apply(array $data): bool { if ($this->antiCsrf && ! $this->antiCsrf->isValid($data)) { throw new CsrfViolationException; } + $this->fill($data); return $this->filter->apply($data); @@ -159,19 +159,15 @@ public function apply(array $data) /** * Returns all failure messages for all fields. * - * @return array + * @return list */ - public function getFailureMessages() + public function getFailureMessages(): array { return $this->filter->getFailures()->getMessages(); } - /** - * Returns all the fields collection - * - * @return ArrayIterator - */ - public function getIterator() + /** Returns all the fields collection */ + public function getIterator(): ArrayIterator { return new ArrayIterator($this->inputs); } diff --git a/src/AntiCsrf.php b/src/AntiCsrf.php index 6397971..bec9e6a 100644 --- a/src/AntiCsrf.php +++ b/src/AntiCsrf.php @@ -14,53 +14,44 @@ use Aura\Input\Fieldset; use Aura\Session\Session; +use function is_bool; + +use const PHP_SAPI; + final class AntiCsrf implements AntiCsrfInterface { - const TEST_TOKEN = '1234'; + public const TEST_TOKEN = '1234'; - const TOKEN_KEY = '__csrf_token'; + public const TOKEN_KEY = '__csrf_token'; - /** @var bool */ - private $isCli; + private bool $isCli; - /** @var Session */ - private $session; + private Session $session; - /** - * @param Session $session - * @param bool|null $isCli - * s */ - public function __construct(Session $session, $isCli = null) + public function __construct(Session $session, bool|null $isCli = null) { $this->session = $session; $this->isCli = is_bool($isCli) ? $isCli : PHP_SAPI === 'cli'; } - public function setField(Fieldset $fieldset) + public function setField(Fieldset $fieldset): void { $fieldset->setField(self::TOKEN_KEY, 'hidden') ->setAttribs(['value' => $this->getToken()]); } - /** - * @param array $data - * - * @return bool - */ - public function isValid(array $data) + /** @param array $data */ + public function isValid(array $data): bool { if ($this->isCli) { return true; } - return isset($data[self::TOKEN_KEY]) && $data[self::TOKEN_KEY] == $this->getToken(); + return isset($data[self::TOKEN_KEY]) && $data[self::TOKEN_KEY] === $this->getToken(); } - /** @return string */ - private function getToken() + private function getToken(): string { - $value = $this->isCli ? self::TEST_TOKEN : $this->session->getCsrfToken()->getValue(); - - return $value; + return $this->isCli ? self::TEST_TOKEN : $this->session->getCsrfToken()->getValue(); } } diff --git a/src/AuraInputInterceptor.php b/src/AuraInputInterceptor.php index 25505b5..2d951e8 100644 --- a/src/AuraInputInterceptor.php +++ b/src/AuraInputInterceptor.php @@ -17,19 +17,17 @@ use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\Exception\InvalidArgumentException; use Ray\WebFormModule\Exception\InvalidFormPropertyException; +use ReflectionClass; + +use function array_shift; +use function property_exists; class AuraInputInterceptor implements MethodInterceptor { - /** @var Reader */ - protected $reader; + protected Reader $reader; - /** @var FailureHandlerInterface */ - protected $failureHandler; + protected FailureHandlerInterface $failureHandler; - /** - * @param Reader $reader - * @param FailureHandlerInterface $handler - */ public function __construct(Reader $reader, FailureHandlerInterface $handler) { $this->reader = $reader; @@ -51,7 +49,7 @@ public function invoke(MethodInvocation $invocation) $data = $form instanceof SubmitInterface ? $form->submit() : $this->getNamedArguments($invocation); $isValid = $this->isValid($data, $form); if ($isValid === true) { - // validation success + // validation success return $invocation->proceed(); } @@ -66,7 +64,7 @@ public function invoke(MethodInvocation $invocation) * @throws Exception\CsrfViolationException * */ - public function isValid(array $submit, AbstractForm $form) + public function isValid(array $submit, AbstractForm $form): bool { return $form->apply($submit); } @@ -78,7 +76,7 @@ public function isValid(array $submit, AbstractForm $form) * * @return array */ - private function getNamedArguments(MethodInvocation $invocation) + private function getNamedArguments(MethodInvocation $invocation): array { $submit = []; $params = $invocation->getMethod()->getParameters(); @@ -87,7 +85,8 @@ private function getNamedArguments(MethodInvocation $invocation) $arg = array_shift($args); $submit[$param->getName()] = $arg; } - // has token ? + + // has token? if (isset($_POST[AntiCsrf::TOKEN_KEY])) { $submit[AntiCsrf::TOKEN_KEY] = $_POST[AntiCsrf::TOKEN_KEY]; } @@ -108,7 +107,8 @@ private function getFormProperty(AbstractValidation $formValidation, $object) if (! property_exists($object, $formValidation->form)) { throw new InvalidFormPropertyException($formValidation->form); } - $prop = (new \ReflectionClass($object))->getProperty($formValidation->form); + + $prop = (new ReflectionClass($object))->getProperty($formValidation->form); $prop->setAccessible(true); $form = $prop->getValue($object); if (! $form instanceof AbstractForm) { diff --git a/src/AuraInputModule.php b/src/AuraInputModule.php index 2e3dd73..2e39a54 100644 --- a/src/AuraInputModule.php +++ b/src/AuraInputModule.php @@ -36,9 +36,8 @@ protected function configure() $this->bind(FilterInterface::class)->to(Filter::class); $this->bind(AntiCsrfInterface::class)->to(AntiCsrf::class)->in(Scope::SINGLETON); $this->bind(FailureHandlerInterface::class)->to(OnFailureMethodHandler::class); - $this->bind(FailureHandlerInterface::class)->annotatedWith('vnd_error')->to(VndErrorHandler::class)->in( - Scope::SINGLETON - ); + $this->bind(FailureHandlerInterface::class) + ->annotatedWith('vnd_error')->to(VndErrorHandler::class)->in(Scope::SINGLETON); $this->bind(HelperLocatorFactory::class); $this->bind(FilterFactory::class); $this->bindInterceptor( diff --git a/src/FormFactory.php b/src/FormFactory.php index b9b07f5..ed12a80 100644 --- a/src/FormFactory.php +++ b/src/FormFactory.php @@ -16,12 +16,7 @@ final class FormFactory { - /** - * @param string $class - * - * @return AbstractForm - */ - public function newInstance($class) + public function newInstance(string $class): AbstractForm { /** @var $form AbstractForm */ $form = new $class; diff --git a/src/FormInterface.php b/src/FormInterface.php index f02348d..5e36178 100644 --- a/src/FormInterface.php +++ b/src/FormInterface.php @@ -15,20 +15,11 @@ interface FormInterface /** * Return input element html * - * @param string $input - * * @return string * @throws \Aura\Input\Exception\NoSuchInput - * */ - public function input($input); + public function input(string $input); - /** - * Return error message - * - * @param string $input - * - * @return string - */ - public function error($input); + /** Return error message */ + public function error(string $input): string; } diff --git a/src/FormValidationError.php b/src/FormValidationError.php index cf93a02..6cdec60 100644 --- a/src/FormValidationError.php +++ b/src/FormValidationError.php @@ -10,10 +10,14 @@ namespace Ray\WebFormModule; +use function json_encode; + +use const JSON_PRETTY_PRINT; +use const JSON_UNESCAPED_SLASHES; + class FormValidationError { - /** @var array */ - private $value; + private array $value; public function __construct(array $value) { diff --git a/src/InputValidationInterceptor.php b/src/InputValidationInterceptor.php index b262ae6..c3584ca 100644 --- a/src/InputValidationInterceptor.php +++ b/src/InputValidationInterceptor.php @@ -15,14 +15,11 @@ class InputValidationInterceptor extends AuraInputInterceptor { - /** - * @param Reader $reader - * @param FailureHandlerInterface $handler - * - * @Named("handler=vnd_error") - */ - public function __construct(Reader $reader, FailureHandlerInterface $handler) - { + public function __construct( + Reader $reader, + #[Named("vnd_error")] + FailureHandlerInterface $handler, + ) { parent::__construct($reader, $handler); } } diff --git a/src/OnFailureMethodHandler.php b/src/OnFailureMethodHandler.php index 17567bf..77ff764 100644 --- a/src/OnFailureMethodHandler.php +++ b/src/OnFailureMethodHandler.php @@ -15,9 +15,12 @@ use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\Exception\InvalidOnFailureMethod; +use function call_user_func_array; +use function method_exists; + final class OnFailureMethodHandler implements FailureHandlerInterface { - const FAILURE_SUFFIX = 'ValidationFailed'; + public const FAILURE_SUFFIX = 'ValidationFailed'; /** {@inheritdoc} */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) @@ -28,8 +31,9 @@ public function handle(AbstractValidation $formValidation, MethodInvocation $inv if (! $formValidation instanceof FormValidation) { throw new InvalidOnFailureMethod(get_class($invocation->getThis())); } + $onFailureMethod = $formValidation->onFailure ?: $invocation->getMethod()->getName() . self::FAILURE_SUFFIX; - if (! $formValidation instanceof FormValidation || ! method_exists($object, $onFailureMethod)) { + if (! method_exists($object, $onFailureMethod)) { throw new InvalidOnFailureMethod(get_class($invocation->getThis())); } diff --git a/src/SetAntiCsrfTrait.php b/src/SetAntiCsrfTrait.php index f314312..e3fb26b 100644 --- a/src/SetAntiCsrfTrait.php +++ b/src/SetAntiCsrfTrait.php @@ -11,12 +11,12 @@ namespace Ray\WebFormModule; use Aura\Input\AntiCsrfInterface; +use Ray\Di\Di\Inject; trait SetAntiCsrfTrait { - /** @param AntiCsrfInterface $antiCsrf */ - #[\Ray\Di\Di\Inject] - public function setAntiCsrf(AntiCsrfInterface $antiCsrf) + #[Inject] + public function setAntiCsrf(AntiCsrfInterface $antiCsrf): void { $this->antiCsrf = $antiCsrf; } diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index d3bf03b..3d82f2d 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -18,8 +18,7 @@ final class VndErrorHandler implements FailureHandlerInterface { - /** @var Reader */ - private $reader; + private Reader $reader; public function __construct(Reader $reader) { @@ -39,7 +38,7 @@ public function handle(AbstractValidation $formValidation, MethodInvocation $inv private function makeVndError(AbstractForm $form, VndError $vndError = null) { $body = ['message' => 'Validation failed']; - $body['path'] = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : ''; + $body['path'] = $_SERVER['PATH_INFO'] ?? ''; $body['validation_messages'] = $form->getFailureMessages(); $body = $vndError ? $this->optionalAttribute($vndError) + $body : $body; @@ -52,9 +51,11 @@ private function optionalAttribute(VndError $vndError) if ($vndError->message) { $body['message'] = $vndError->message; } + if ($vndError->path) { $body['path'] = $vndError->path; } + if ($vndError->logref) { $body['logref'] = $vndError->logref; } From dfafacbeb2cd5c3d0e0078010fc4be4d4aa4edfb Mon Sep 17 00:00:00 2001 From: "Kouhei.Sano" Date: Sat, 14 Feb 2026 16:46:08 +0900 Subject: [PATCH 20/41] =?UTF-8?q?refactor:=20Doctrine=20Annotations=20?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4=E3=81=97=20PHP=208=20Attributes=20?= =?UTF-8?q?=E3=81=AB=E5=AE=8C=E5=85=A8=E7=A7=BB=E8=A1=8C=20migrate-attribu?= =?UTF-8?q?te?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AuraInputInterceptor.php | 31 ++++++++++++++++++++---------- src/AuraInputModule.php | 3 --- src/InputValidationInterceptor.php | 4 +--- src/VndErrorHandler.php | 23 ++++++++++++++-------- tests/AbstractFormTest.php | 4 +--- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/AuraInputInterceptor.php b/src/AuraInputInterceptor.php index 2d951e8..697693a 100644 --- a/src/AuraInputInterceptor.php +++ b/src/AuraInputInterceptor.php @@ -10,27 +10,24 @@ namespace Ray\WebFormModule; -use Doctrine\Common\Annotations\Reader; use Ray\Aop\MethodInterceptor; use Ray\Aop\MethodInvocation; use Ray\WebFormModule\Annotation\AbstractValidation; -use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\Exception\InvalidArgumentException; use Ray\WebFormModule\Exception\InvalidFormPropertyException; +use ReflectionAttribute; use ReflectionClass; +use ReflectionMethod; use function array_shift; use function property_exists; class AuraInputInterceptor implements MethodInterceptor { - protected Reader $reader; - protected FailureHandlerInterface $failureHandler; - public function __construct(Reader $reader, FailureHandlerInterface $handler) + public function __construct(FailureHandlerInterface $handler) { - $this->reader = $reader; $this->failureHandler = $handler; } @@ -42,20 +39,34 @@ public function __construct(Reader $reader, FailureHandlerInterface $handler) public function invoke(MethodInvocation $invocation) { $object = $invocation->getThis(); - /* @var $formValidation FormValidation */ - $method = $invocation->getMethod(); - $formValidation = $this->reader->getMethodAnnotation($method, AbstractValidation::class); + $formValidation = $this->getValidationAttribute($invocation->getMethod()); + if ($formValidation === null) { + throw new InvalidArgumentException('The method must be attributed with #[FormValidation] or #[InputValidation]'); + } + $form = $this->getFormProperty($formValidation, $object); $data = $form instanceof SubmitInterface ? $form->submit() : $this->getNamedArguments($invocation); $isValid = $this->isValid($data, $form); if ($isValid === true) { - // validation success return $invocation->proceed(); } return $this->failureHandler->handle($formValidation, $invocation, $form); } + private function getValidationAttribute(ReflectionMethod $method): AbstractValidation|null + { + $attributes = $method->getAttributes(AbstractValidation::class, ReflectionAttribute::IS_INSTANCEOF); + if ($attributes === []) { + return null; + } + + $instance = $attributes[0]->newInstance(); + assert($instance instanceof AbstractValidation); + + return $instance; + } + /** * @param array $submit * @param AbstractForm $form diff --git a/src/AuraInputModule.php b/src/AuraInputModule.php index 2e39a54..d2f56d6 100644 --- a/src/AuraInputModule.php +++ b/src/AuraInputModule.php @@ -17,8 +17,6 @@ use Aura\Input\BuilderInterface; use Aura\Input\Filter; use Aura\Input\FilterInterface; -use Doctrine\Common\Annotations\Reader; -use Koriym\Attributes\AttributeReader; use Ray\AuraSessionModule\AuraSessionModule; use Ray\Di\AbstractModule; use Ray\Di\Scope; @@ -31,7 +29,6 @@ class AuraInputModule extends AbstractModule protected function configure() { $this->install(new AuraSessionModule); - $this->bind(Reader::class)->to(AttributeReader::class)->in(Scope::SINGLETON); $this->bind(BuilderInterface::class)->to(Builder::class); $this->bind(FilterInterface::class)->to(Filter::class); $this->bind(AntiCsrfInterface::class)->to(AntiCsrf::class)->in(Scope::SINGLETON); diff --git a/src/InputValidationInterceptor.php b/src/InputValidationInterceptor.php index c3584ca..e59098e 100644 --- a/src/InputValidationInterceptor.php +++ b/src/InputValidationInterceptor.php @@ -10,16 +10,14 @@ namespace Ray\WebFormModule; -use Doctrine\Common\Annotations\Reader; use Ray\Di\Di\Named; class InputValidationInterceptor extends AuraInputInterceptor { public function __construct( - Reader $reader, #[Named("vnd_error")] FailureHandlerInterface $handler, ) { - parent::__construct($reader, $handler); + parent::__construct($handler); } } diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index 3d82f2d..9ddc582 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -12,29 +12,36 @@ use Doctrine\Common\Annotations\Reader; use Ray\Aop\MethodInvocation; +use Ray\Aop\ReflectionMethod; use Ray\WebFormModule\Annotation\AbstractValidation; use Ray\WebFormModule\Annotation\VndError; use Ray\WebFormModule\Exception\ValidationException; final class VndErrorHandler implements FailureHandlerInterface { - private Reader $reader; - - public function __construct(Reader $reader) - { - $this->reader = $reader; - } - /** {@inheritdoc} */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) { unset($formValidation); - $vndError = $this->reader->getMethodAnnotation($invocation->getMethod(), VndError::class); + $vndError = $this->getVndErrorAttribute($invocation->getMethod()); $error = new FormValidationError($this->makeVndError($form, $vndError)); throw new ValidationException('Validation failed.', 400, null, $error); } + private function getVndErrorAttribute(ReflectionMethod $method): VndError|null + { + $attributes = $method->getAttributes(VndError::class); + if ($attributes === []) { + return null; + } + + $instance = $attributes[0]->newInstance(); + assert($instance instanceof VndError); + + return $instance; + } + private function makeVndError(AbstractForm $form, VndError $vndError = null) { $body = ['message' => 'Validation failed']; diff --git a/tests/AbstractFormTest.php b/tests/AbstractFormTest.php index 973f553..86e8263 100644 --- a/tests/AbstractFormTest.php +++ b/tests/AbstractFormTest.php @@ -11,7 +11,6 @@ use Aura\Session\Randval; use Aura\Session\SegmentFactory; use Aura\Session\Session; -use Koriym\Attributes\AttributeReader; use PHPUnit\Framework\TestCase; use Ray\Aop\ReflectiveMethodInvocation; use Ray\WebFormModule\Exception\CsrfViolationException; @@ -41,8 +40,7 @@ public function getMethodInvocation(array $arguments) $controller = new FakeController; $controller->setForm($fakeForm); // interceptor - $reader = new AttributeReader; - $interceptor = new AuraInputInterceptor($reader, new VndErrorHandler($reader)); + $interceptor = new AuraInputInterceptor(new VndErrorHandler()); return new ReflectiveMethodInvocation( $controller, From 2e6d8d92369ce96ad1fdea5f755c9976e76e843e Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 15 May 2026 15:46:23 +0900 Subject: [PATCH 21/41] Address PR review feedback --- .travis.yml | 23 ------------------- README.JA.md | 13 +++++------ README.md | 11 ++++----- src/Annotation/AbstractValidation.php | 5 +--- .../InvalidFormPropertyException.php | 2 -- src/Exception/InvalidOnFailureMethod.php | 2 -- src/Exception/RuntimeException.php | 2 +- src/VndErrorHandler.php | 5 +++- tests/AuraInputInterceptorTest.php | 4 ++++ tests/Fake/FakeController.php | 3 +-- tests/Fake/FakeControllerVndError.php | 3 +-- tests/Fake/FakeInputValidationController.php | 3 +-- tests/VndErrorHandlerTest.php | 4 ++++ 13 files changed, 28 insertions(+), 52 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0660bfe..0000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: php -sudo: false -php: - - 7 - - 7.1 - - 7.2 -cache: - directories: - - vendor - - $HOME/.composer/cache -env: - matrix: - - DEPENDENCIES="" - - DEPENDENCIES="--prefer-lowest --prefer-stable" -before_script: - - composer self-update - - composer update $DEPENDENCIES -script: - - ./vendor/bin/phpunit --coverage-clover=coverage.clover; - - if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then wget http://cs.sensiolabs.org/download/php-cs-fixer-v2.phar && php php-cs-fixer-v2.phar fix --config=.php_cs -v --dry-run --using-cache=no --path-mode=intersection `git diff --name-only --diff-filter=ACMRTUXB $TRAVIS_COMMIT_RANGE`; fi - -after_script: - - if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi diff --git a/README.JA.md b/README.JA.md index c96343d..edcdbb8 100644 --- a/README.JA.md +++ b/README.JA.md @@ -79,7 +79,7 @@ public function createAction($id, $name, $body) ### Controller -コントローラークラスにフォームをインジェクトします。フォームのバリデーションを行うメソッドを`@FormValidation`で +コントローラークラスにフォームをインジェクトします。フォームのバリデーションを行うメソッドを`#[FormValidation]`で アノテートします。この時フォームのプロパティ名を`form`で、バリデーションが失敗したときのメソッドを`onFailure`で指定します。 ```php @@ -95,14 +95,13 @@ class MyController */ protected $contactForm; - #[Inject] - #[Named("contact_form")] - public function setForm(FormInterface $form) + #[Inject] + public function setForm(#[Named("contact_form")] FormInterface $form) { $this->contactForm = $form; } - #[FormValidation(form: "contactForm", onFailure: "badRequestAction")] + #[FormValidation(form: "contactForm", onFailure: "badRequestAction")] public function createAction() { // validation success @@ -140,14 +139,14 @@ class MyForm extends AbstractAuraForm ## Validation Exception -`@FormValidation`の代わりに`@InputValidation`とアノテートするとバリデーションが失敗したときに`Ray\WebFormModule\Exception\ValidationException`が投げられるよになります。この場合はHTML表現は使われません。Web APIアプリケーションなどに便利です。 +`#[FormValidation]`の代わりに`#[InputValidation]`とアノテートするとバリデーションが失敗したときに`Ray\WebFormModule\Exception\ValidationException`が投げられるよになります。この場合はHTML表現は使われません。Web APIアプリケーションなどに便利です。 ```php use Ray\WebFormModule\Annotation\InputValidation; class Foo { - #[InputValidation(form: "form1")] + #[InputValidation(form: "form1")] public function createAction($name) { // ... diff --git a/README.md b/README.md index b07cc87..36740e2 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ class MyForm extends AbstractForm ``` ### Controller -We annotate the methods which web form validation is required with `@FormValidation`. We can specify form object property name with `name` and failiure method name with `@onFailure`. +We annotate the methods which web form validation is required with `#[FormValidation]`. We can specify form object property name with `form` and failure method name with `onFailure`. ```php use Ray\Di\Di\Inject; @@ -103,14 +103,13 @@ class MyController */ protected $contactForm; - #[Inject] - #[Named("contact_form")] - public function setForm(FormInterface $form) + #[Inject] + public function setForm(#[Named("contact_form")] FormInterface $form) { $this->contactForm = $form; } - #[FormValidation(form: "contactForm", onFailure: "badRequestAction")] + #[FormValidation(form: "contactForm", onFailure: "badRequestAction")] public function createAction() { // validation success @@ -180,7 +179,7 @@ echo $e->error; //} ``` -More detail for `vnd.error+json`can be add with `@VndError` annotation. +More detail for `vnd.error+json` can be added with the `#[VndError]` attribute. ```php #[FormValidation(form: "contactForm")] diff --git a/src/Annotation/AbstractValidation.php b/src/Annotation/AbstractValidation.php index c009216..b866cf3 100644 --- a/src/Annotation/AbstractValidation.php +++ b/src/Annotation/AbstractValidation.php @@ -10,10 +10,7 @@ namespace Ray\WebFormModule\Annotation; -use Attribute; - -#[Attribute(Attribute::TARGET_METHOD)] -class AbstractValidation +abstract class AbstractValidation { public function __construct(public string $form = 'form') { diff --git a/src/Exception/InvalidFormPropertyException.php b/src/Exception/InvalidFormPropertyException.php index e4c9b77..a17cfc6 100644 --- a/src/Exception/InvalidFormPropertyException.php +++ b/src/Exception/InvalidFormPropertyException.php @@ -10,8 +10,6 @@ namespace Ray\WebFormModule\Exception; -use LogicException; - class InvalidFormPropertyException extends LogicException { } diff --git a/src/Exception/InvalidOnFailureMethod.php b/src/Exception/InvalidOnFailureMethod.php index 5b3ccdc..c4f0ea2 100644 --- a/src/Exception/InvalidOnFailureMethod.php +++ b/src/Exception/InvalidOnFailureMethod.php @@ -10,8 +10,6 @@ namespace Ray\WebFormModule\Exception; -use LogicException; - class InvalidOnFailureMethod extends LogicException { } diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 46953e3..1c464c5 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -10,6 +10,6 @@ namespace Ray\WebFormModule\Exception; -class RuntimeException extends \LogicException implements ExceptionInterface +class RuntimeException extends LogicException { } diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index 9ddc582..d2891a7 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -10,7 +10,6 @@ namespace Ray\WebFormModule; -use Doctrine\Common\Annotations\Reader; use Ray\Aop\MethodInvocation; use Ray\Aop\ReflectionMethod; use Ray\WebFormModule\Annotation\AbstractValidation; @@ -67,6 +66,10 @@ private function optionalAttribute(VndError $vndError) $body['logref'] = $vndError->logref; } + if ($vndError->href) { + $body['href'] = $vndError->href; + } + return $body; } } diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 18ad270..44e8385 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -144,6 +144,10 @@ public function testProceedWithVndErrorHandler() "message": "foo validation failed", "path": "/path/to/error", "logref": "a1000", + "href": { + "_self": "/path/to/error", + "help": "/path/to/help" + }, "validation_messages": { "name": [ "Name must be alphabetic only." diff --git a/tests/Fake/FakeController.php b/tests/Fake/FakeController.php index 9484301..f452680 100644 --- a/tests/Fake/FakeController.php +++ b/tests/Fake/FakeController.php @@ -14,8 +14,7 @@ class FakeController protected $form; #[Inject] - #[Named('contact_form')] - public function setForm(FormInterface $form) + public function setForm(#[Named('contact_form')] FormInterface $form) { $this->form = $form; } diff --git a/tests/Fake/FakeControllerVndError.php b/tests/Fake/FakeControllerVndError.php index d952c87..6885cd4 100644 --- a/tests/Fake/FakeControllerVndError.php +++ b/tests/Fake/FakeControllerVndError.php @@ -15,8 +15,7 @@ class FakeControllerVndError protected $form1; #[Inject] - #[Named('contact_form')] - public function setForm(FormInterface $form) + public function setForm(#[Named('contact_form')] FormInterface $form) { $this->form1 = $form; } diff --git a/tests/Fake/FakeInputValidationController.php b/tests/Fake/FakeInputValidationController.php index 162ad83..17af541 100644 --- a/tests/Fake/FakeInputValidationController.php +++ b/tests/Fake/FakeInputValidationController.php @@ -14,8 +14,7 @@ class FakeInputValidationController protected $form; #[Inject] - #[Named('contact_form')] - public function setForm(FormInterface $form) + public function setForm(#[Named('contact_form')] FormInterface $form) { $this->form = $form; } diff --git a/tests/VndErrorHandlerTest.php b/tests/VndErrorHandlerTest.php index 2b96fb8..c8b888e 100644 --- a/tests/VndErrorHandlerTest.php +++ b/tests/VndErrorHandlerTest.php @@ -59,6 +59,10 @@ public function testVndErrorAnnotation() "message": "foo validation failed", "path": "/path/to/error", "logref": "a1000", + "href": { + "_self": "/path/to/error", + "help": "/path/to/help" + }, "validation_messages": { "name": [ "Name must be alphabetic only." From a1cfe374d298654552cdfda27530be28a67d9753 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 15 May 2026 08:26:28 +0000 Subject: [PATCH 22/41] Address Copilot review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - VndError: $message と $href にデフォルト値を付与し旧 annotation との BC を維持 - VndErrorHandler: Ray\Aop\ReflectionMethod を \ReflectionMethod に変更し AuraInputInterceptor と型を揃える - RuntimeException: 意味的に正しい \RuntimeException を継承するよう修正し ExceptionInterface を明示 - AuraInputInterceptorTest: AnnotationReader 時代のコメントアウト済みコード塊を削除 --- src/Annotation/VndError.php | 4 +-- src/Exception/RuntimeException.php | 2 +- src/VndErrorHandler.php | 2 +- tests/AuraInputInterceptorTest.php | 52 ------------------------------ 4 files changed, 4 insertions(+), 56 deletions(-) diff --git a/src/Annotation/VndError.php b/src/Annotation/VndError.php index add7027..5c35e64 100644 --- a/src/Annotation/VndError.php +++ b/src/Annotation/VndError.php @@ -23,8 +23,8 @@ final class VndError * @see http://tools.ietf.org/html/rfc6892 */ public function __construct( - public string $message, - public array $href, + public string $message = '', + public array $href = [], public string|null $logref = null, public string|null $path = null ) { diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 1c464c5..fe827e8 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -10,6 +10,6 @@ namespace Ray\WebFormModule\Exception; -class RuntimeException extends LogicException +class RuntimeException extends \RuntimeException implements ExceptionInterface { } diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index d2891a7..7755978 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -11,10 +11,10 @@ namespace Ray\WebFormModule; use Ray\Aop\MethodInvocation; -use Ray\Aop\ReflectionMethod; use Ray\WebFormModule\Annotation\AbstractValidation; use Ray\WebFormModule\Annotation\VndError; use Ray\WebFormModule\Exception\ValidationException; +use ReflectionMethod; final class VndErrorHandler implements FailureHandlerInterface { diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 44e8385..3c1ce68 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -37,50 +37,6 @@ protected function configure() $this->controller = $this->injector->getInstance(FakeController::class); } -// /** -// * @param $method -// */ -// public function getMethodInvocation(string $method, array $submit, FailureHandlerInterface $handler = null) -// { -// $handler = $handler ?: new OnFailureMethodHandler; -// $object = $this->getController($submit); -// -// $invocation = new ReflectiveMethodInvocation( -// $object, -// $method, -// $submit, -// [ -// new AuraInputInterceptor(new AnnotationReader, $handler) -// ] -// ); -// -// return $invocation; -// } -// -// public function getController(array $submit) -// { -// $controller = new FakeController; -// /** @var $fakeForm FakeForm */ -// $fakeForm = (new FormFactory)->newInstance(FakeForm::class); -// $fakeForm->setSubmit($submit); -// $controller->setForm($fakeForm); -// -// return $controller; -// } -// -// public function proceed($controller) -// { -// $invocation = new ReflectiveMethodInvocation( -// $controller, -// new \ReflectionMethod($controller, 'createAction'), -// [], -// [ -// new AuraInputInterceptor(new AnnotationReader, new OnFailureMethodHandler) -// ] -// ); -// $invocation->proceed(); -// } - public function testProceedFailed() { $result = $this->controller->createAction([]); @@ -93,14 +49,6 @@ public function testProceed() $this->assertSame('201', $result); } -// public function invalidControllerProvider() -// { -// return [ -// [$this->injector->getInstance(FakeInvalidController1::class)], -// [$this->injector->getInstance(FakeInvalidController2::class)] -// ]; -// } - public function testInvalidFormPropertyByMissingProperty() { $this->expectException(InvalidFormPropertyException::class); From 9f48a96b74c8b24936dc051f5cc1e56988628f42 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Fri, 15 May 2026 18:00:22 +0900 Subject: [PATCH 23/41] fix: PHP 8.4 deprecation and add property type declarations - ValidationException, VndErrorHandler: implicit nullable parameter (Type $x = null) is deprecated in PHP 8.4. Use ?Type $x = null. - AbstractForm: type $errorMessages and $antiCsrf with nullable property types. $filter remains untyped because parent Aura\Input\Fieldset declares it untyped and PHP forbids adding a type in a subclass for such properties. --- src/AbstractForm.php | 7 +++---- src/Exception/ValidationException.php | 2 +- src/VndErrorHandler.php | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/AbstractForm.php b/src/AbstractForm.php index 6d9e5b2..2274021 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -31,13 +31,12 @@ abstract class AbstractForm extends Fieldset implements FormInterface /** @var SubjectFilter */ protected $filter; - /** @var array|null */ - protected $errorMessages; + /** @var array>|null */ + protected ?array $errorMessages = null; protected HelperLocator $helper; - /** @var AntiCsrfInterface */ - protected $antiCsrf; + protected ?AntiCsrfInterface $antiCsrf = null; public function __construct() { diff --git a/src/Exception/ValidationException.php b/src/Exception/ValidationException.php index bbf583c..1ed951d 100644 --- a/src/Exception/ValidationException.php +++ b/src/Exception/ValidationException.php @@ -17,7 +17,7 @@ class ValidationException extends Exception { public $error; - public function __construct($message = '', $code = 0, Exception $e = null, FormValidationError $error = null) + public function __construct($message = '', $code = 0, ?Exception $e = null, ?FormValidationError $error = null) { parent::__construct($message, $code, $e); $this->error = $error; diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index 7755978..429a6f9 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -41,7 +41,7 @@ private function getVndErrorAttribute(ReflectionMethod $method): VndError|null return $instance; } - private function makeVndError(AbstractForm $form, VndError $vndError = null) + private function makeVndError(AbstractForm $form, ?VndError $vndError = null) { $body = ['message' => 'Validation failed']; $body['path'] = $_SERVER['PATH_INFO'] ?? ''; From eceaecd8ff877db4eedbb1047f7a3e4b711734a0 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sat, 16 May 2026 00:41:06 +0900 Subject: [PATCH 24/41] chore: make CSRF protection explicit and add project checks --- .github/dependabot.yml | 13 ++ .github/workflows/coding-standards.yml | 45 ++++++ .github/workflows/continuous-integration.yml | 56 ++++++++ .github/workflows/static-analysis.yml | 130 ++++++++++++++++++ .php_cs => .php-cs-fixer.php | 38 ++--- CHANGELOG.md | 56 ++++++++ README.JA.md | 17 ++- README.md | 21 ++- composer-require-checker.json | 13 ++ composer.json | 19 ++- docs/demo/1.csrf/ContactForm.php | 1 + docs/demo/1.csrf/Controller.php | 8 +- docs/demo/1.csrf/MyModule.php | 1 + docs/demo/1.csrf/run.php | 1 + docs/demo/1.csrf/web.php | 1 + docs/demo/autoload.php | 1 + phpstan.neon | 23 ++++ psalm.xml | 31 +++++ src/AbstractForm.php | 89 ++++++++---- src/Annotation/AbstractValidation.php | 1 - src/Annotation/CsrfProtection.php | 17 +++ src/Annotation/FormValidation.php | 2 - src/Annotation/InputValidation.php | 1 - src/Annotation/VndError.php | 4 +- src/AntiCsrf.php | 11 +- src/AuraInputInterceptor.php | 90 +++++++----- src/AuraInputModule.php | 4 +- src/Exception/CsrfViolationException.php | 1 - src/Exception/ExceptionInterface.php | 1 - src/Exception/InvalidArgumentException.php | 1 - .../InvalidFormPropertyException.php | 1 - src/Exception/InvalidOnFailureMethod.php | 1 - src/Exception/LogicException.php | 1 - src/Exception/RuntimeException.php | 1 - src/Exception/ValidationException.php | 5 +- src/FailureHandlerInterface.php | 8 +- src/FormFactory.php | 15 +- src/FormInterface.php | 6 +- src/FormValidationError.php | 10 +- src/FormVndErrorModule.php | 1 - src/InputValidationInterceptor.php | 3 +- src/OnFailureMethodHandler.php | 19 ++- src/SetAntiCsrfTrait.php | 3 +- src/SubmitInterface.php | 3 +- src/ToStringInterface.php | 3 +- src/VndErrorHandler.php | 31 +++-- tests/AbstractAuraFormTest.php | 3 +- tests/AbstractFormTest.php | 3 +- tests/AntiCsrfTest.php | 3 +- tests/AuraInputInterceptorTest.php | 16 ++- tests/AuraInputModuleTest.php | 1 + tests/Fake/FakeCsrfController.php | 37 +++++ tests/FormFactoryTest.php | 3 +- tests/VndErrorHandlerTest.php | 3 +- tests/bootstrap.php | 1 + 55 files changed, 716 insertions(+), 162 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/coding-standards.yml create mode 100644 .github/workflows/continuous-integration.yml create mode 100644 .github/workflows/static-analysis.yml rename .php_cs => .php-cs-fixer.php (77%) create mode 100644 CHANGELOG.md create mode 100644 composer-require-checker.json create mode 100644 phpstan.neon create mode 100644 psalm.xml create mode 100644 src/Annotation/CsrfProtection.php create mode 100644 tests/Fake/FakeCsrfController.php diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b0a252d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "weekly" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml new file mode 100644 index 0000000..b0bf7f1 --- /dev/null +++ b/.github/workflows/coding-standards.yml @@ -0,0 +1,45 @@ +name: Coding Standards + +on: + push: + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + workflow_dispatch: + inputs: + php_version: + default: '8.4' + +jobs: + coding-standards: + name: Coding Standards + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version || '8.4' }} + tools: cs2pr + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + + - name: Run PHP_CodeSniffer + run: ./vendor/bin/phpcs -q --no-colors --report=checkstyle --standard=./phpcs.xml src | cs2pr diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..47ee88f --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,56 @@ +name: Continuous Integration + +on: + push: + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + operating-system: + - ubuntu-latest + php-version: + - '8.0' + - '8.1' + - '8.2' + - '8.3' + - '8.4' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP ${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: none + tools: none + ini-values: assert.exception=1, zend.assertions=1 + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-php${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + ${{ runner.os }}-php${{ matrix.php-version }}-composer- + ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer update --no-interaction --no-progress --prefer-dist + + - name: Run test suite + run: ./vendor/bin/phpunit diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..4af412a --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,130 @@ +name: Static Analysis + +on: + push: + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + workflow_dispatch: + inputs: + php_version: + default: '8.4' + +jobs: + static-analysis-phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version }} + tools: cs2pr + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-interaction --no-progress --prefer-dist + + - name: Run PHPStan + run: ./vendor/bin/phpstan analyse -c phpstan.neon --no-progress --no-interaction --error-format=checkstyle | cs2pr + + static-analysis-psalm: + name: Psalm + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version }} + tools: cs2pr + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Install dependencies + run: composer install --no-interaction --no-progress --prefer-dist + + - name: Run Psalm + run: ./vendor/bin/psalm --show-info=false --output-format=checkstyle --shepherd | cs2pr + + static-analysis-phpmd: + name: PHPMD + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version }} + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-interaction --no-progress --prefer-dist + + - name: Run PHP Mess Detector + run: ./vendor/bin/phpmd src text ./phpmd.xml + + static-analysis-composer-require-checker: + name: ComposerRequireChecker + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php_version }} + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Install dependencies + run: | + composer install --no-interaction --no-progress --prefer-dist + + - name: Run composer-require-checker with config + run: ./vendor/bin/composer-require-checker check ./composer.json --config-file=./composer-require-checker.json diff --git a/.php_cs b/.php-cs-fixer.php similarity index 77% rename from .php_cs rename to .php-cs-fixer.php index 3518e7e..5d7ee7d 100644 --- a/.php_cs +++ b/.php-cs-fixer.php @@ -6,16 +6,16 @@ @license http://opensource.org/licenses/MIT MIT EOF; -return \PhpCsFixer\Config::create() +return (new \PhpCsFixer\Config()) ->setRiskyAllowed(true) ->setRules(array( '@PSR2' => true, - 'header_comment' => ['header' => $header, 'commentType' => 'PHPDoc', 'separate' => 'none'], + 'header_comment' => ['header' => $header, 'comment_type' => 'PHPDoc', 'separate' => 'none'], 'array_syntax' => ['syntax' => 'short'], - 'binary_operator_spaces' => ['align_equals' => false, 'align_double_arrow' => false], + 'binary_operator_spaces' => ['default' => 'single_space'], 'blank_line_after_opening_tag' => true, 'blank_line_after_namespace' => false, - 'blank_line_before_return' => true, + 'blank_line_before_statement' => ['statements' => ['return']], 'cast_spaces' => true, // 'class_keyword_remove' => true, 'combine_consecutive_unsets' => true, @@ -24,41 +24,40 @@ 'declare_strict_types' => false, 'dir_constant' => true, 'ereg_to_preg' => true, - 'function_typehint_space' => true, + 'type_declaration_spaces' => true, 'general_phpdoc_annotation_remove' => true, - 'hash_to_slash_comment' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']], 'heredoc_to_nowdoc' => true, 'include' => true, 'indentation_type' => true, - 'is_null' => ['use_yoda_style' => false], + 'is_null' => true, 'linebreak_after_opening_tag' => true, 'lowercase_cast' => true, // 'mb_str_functions' => true, - 'method_separation' => true, + 'class_attributes_separation' => ['elements' => ['method' => 'one', 'trait_import' => 'none']], 'modernize_types_casting' => true, 'native_function_casing' => true, // 'native_function_invocation' => true, - 'new_with_braces' => false, // + 'new_with_parentheses' => false, // 'no_alias_functions' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, - 'no_blank_lines_before_namespace' => true, + 'blank_lines_before_namespace' => ['min_line_breaks' => 1, 'max_line_breaks' => 1], 'no_empty_comment' => true, 'no_empty_phpdoc' => true, 'no_empty_statement' => true, - 'no_extra_consecutive_blank_lines' => ['break', 'continue', 'curly_brace_block', 'extra', 'parenthesis_brace_block', 'return', 'square_brace_block', 'throw', 'use', 'useTrait'], + 'no_extra_blank_lines' => ['tokens' => ['break', 'continue', 'curly_brace_block', 'extra', 'parenthesis_brace_block', 'return', 'square_brace_block', 'throw', 'use']], 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, 'no_mixed_echo_print' => ['use' => 'echo'], 'no_multiline_whitespace_around_double_arrow' => true, - 'no_multiline_whitespace_before_semicolons' => true, + 'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'], 'no_php4_constructor' => false, 'no_short_bool_cast' => true, - 'no_short_echo_tag' => false, + 'echo_tag_syntax' => false, 'no_singleline_whitespace_before_semicolons' => true, 'no_spaces_around_offset' => true, - 'no_trailing_comma_in_list_call' => true, - 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_comma_in_singleline' => true, 'no_trailing_whitespace' => true, 'no_trailing_whitespace_in_comment' => true, 'no_unneeded_control_parentheses' => true, @@ -82,9 +81,9 @@ 'phpdoc_align' => true, 'phpdoc_annotation_without_dot' => true, 'phpdoc_indent' => true, - 'phpdoc_inline_tag' => true, + 'phpdoc_inline_tag_normalizer' => true, 'phpdoc_no_access' => true, - 'phpdoc_no_alias_tag' => ['property-read' => 'property', 'property-write' => 'property', 'type' => 'var'], + 'phpdoc_no_alias_tag' => ['replacements' => ['property-read' => 'property', 'property-write' => 'property', 'type' => 'var']], 'phpdoc_no_empty_return' => true, 'phpdoc_no_package' => true, // 'phpdoc_no_useless_inheritdoc' => true, @@ -101,8 +100,7 @@ 'pow_to_exponentiation' => true, // 'pre_increment' => true, 'protected_to_private' => true, - 'psr0' => true, - 'psr4' => true, + 'psr_autoloading' => true, 'random_api_migration' => true, 'return_type_declaration' => ['space_before' => 'one'], 'self_accessor' => true, @@ -125,8 +123,10 @@ ->setFinder( PhpCsFixer\Finder::create() ->exclude('tests/Fake') + ->exclude('tests/tmp') ->exclude('src-data') ->exclude('src-deprecated') + ->exclude('docs/demo/tmp') ->in(__DIR__) )->setLineEnding("\n") ->setUsingCache(false); diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d9625f4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,56 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - unreleased + +### Changed + +- **BC break**: Minimum PHP version raised to `8.0`. +- **BC break**: Migrated from Doctrine Annotations to PHP 8 Attributes. All + validation metadata (`@FormValidation`, `@InputValidation`, `@VndError`) is + now expressed with `#[FormValidation]`, `#[InputValidation]`, `#[VndError]`. +- CSRF protection for validation methods is declared with the separate + `#[CsrfProtection]` attribute instead of a boolean option on + `#[FormValidation]`. +- **BC break**: `AuraInputInterceptor`, `InputValidationInterceptor` and + `VndErrorHandler` no longer accept a `Doctrine\Common\Annotations\Reader` + in their constructors. Validation attributes are read directly via + `ReflectionMethod::getAttributes()`. +- **BC break**: `FormInterface::input()` and `FormInterface::error()` now declare + parameter and return types (`string $input`, `: string` respectively). + Implementations must update their signatures. +- Added property type declarations and return types across the codebase to + align with PHP 8 typing. +- Bumped dependencies: `ray/di` `^2.16`, `ray/aop` `^2.14`, + `phpunit/phpunit` `^9.5`. + +### Fixed + +- `Exception\RuntimeException` now correctly extends `\RuntimeException` + instead of `\LogicException`. +- `AntiCsrf::isValid()` uses strict comparison for the CSRF token. +- Eliminated PHP 8.4 deprecation warnings for implicit nullable parameters in + `ValidationException::__construct()` and `VndErrorHandler::makeVndError()`. + +### Added + +- GitHub Actions workflows for tests and coding standards. +- `CHANGELOG.md`. +- `#[CsrfProtection]` attribute for composing CSRF checks with form/input + validation attributes. + +### Removed + +- `doctrine/annotations` dependency. +- Travis CI configuration; replaced with GitHub Actions. + +## [0.6.0] - 2018-05-27 + +See git history for changes prior to 1.0.0. + +[1.0.0]: https://github.com/ray-di/Ray.WebFormModule/compare/0.6.0...1.0.0 +[0.6.0]: https://github.com/ray-di/Ray.WebFormModule/releases/tag/0.6.0 diff --git a/README.JA.md b/README.JA.md index edcdbb8..4dac375 100644 --- a/README.JA.md +++ b/README.JA.md @@ -1,8 +1,8 @@ # Ray.WebFormModule +[![Continuous Integration](https://github.com/ray-di/Ray.WebFormModule/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/ray-di/Ray.WebFormModule/actions/workflows/continuous-integration.yml) +[![Coding Standards](https://github.com/ray-di/Ray.WebFormModule/actions/workflows/coding-standards.yml/badge.svg)](https://github.com/ray-di/Ray.WebFormModule/actions/workflows/coding-standards.yml) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ray-di/Ray.WebFormModule/badges/quality-score.png?b=1.x)](https://scrutinizer-ci.com/g/ray-di/Ray.WebFormModule/?branch=1.x) -[![Code Coverage](https://scrutinizer-ci.com/g/ray-di/Ray.WebFormModule/badges/coverage.png?b=1.x)](https://scrutinizer-ci.com/g/ray-di/Ray.WebFormModule/?branch=1.x) -[![Build Status](https://travis-ci.org/ray-di/Ray.WebFormModule.svg?branch=1.x)](https://travis-ci.org/ray-di/Ray.WebFormModule) Ray.WebFormModuleはアスペクト指向でフォームのバリデーションを行うモジュールです。 フォームライブラリには[Aura.Input](https://github.com/auraphp/Aura.Input)を使い、 @@ -127,11 +127,24 @@ class MyController CSRF対策を行うためにはフォームにCSRFオブジェクトをセットします。 ```php +use Ray\WebFormModule\AbstractAuraForm; +use Ray\WebFormModule\Annotation\CsrfProtection; +use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\SetAntiCsrfTrait; class MyForm extends AbstractAuraForm { use SetAntiCsrfTrait; +} + +class MyController +{ + #[FormValidation(form: "contactForm")] + #[CsrfProtection] + public function createAction() + { + } +} ``` セキュリティレベルを高めるためにはユーザーの認証を含んだカスタムCsrfクラスを作成してフォームクラスにセットします。 diff --git a/README.md b/README.md index 36740e2..933efe4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Ray.WebFormModule +[![Continuous Integration](https://github.com/ray-di/Ray.WebFormModule/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/ray-di/Ray.WebFormModule/actions/workflows/continuous-integration.yml) +[![Coding Standards](https://github.com/ray-di/Ray.WebFormModule/actions/workflows/coding-standards.yml/badge.svg)](https://github.com/ray-di/Ray.WebFormModule/actions/workflows/coding-standards.yml) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ray-di/Ray.WebFormModule/badges/quality-score.png?b=1.x)](https://scrutinizer-ci.com/g/ray-di/Ray.WebFormModule/?branch=1.x) -[![Code Coverage](https://scrutinizer-ci.com/g/ray-di/Ray.WebFormModule/badges/coverage.png?b=1.x)](https://scrutinizer-ci.com/g/ray-di/Ray.WebFormModule/?branch=1.x) -[![Build Status](https://travis-ci.org/ray-di/Ray.WebFormModule.svg?branch=1.x)](https://travis-ci.org/ray-di/Ray.WebFormModule) An aspect oriented web form module powered by [Aura.Input](https://github.com/auraphp/Aura.Input) and [Ray.Di](https://github.com/ray-di/Ray.Di). @@ -12,7 +12,7 @@ An aspect oriented web form module powered by [Aura.Input](https://github.com/au ### Composer install - $ composer require web-form-module + $ composer require ray/web-form-module ### Module install @@ -139,11 +139,24 @@ or render input element basis. ## CSRF Protections ```php +use Ray\WebFormModule\AbstractAuraForm; +use Ray\WebFormModule\Annotation\CsrfProtection; +use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\SetAntiCsrfTrait; -class MyController +class MyForm extends AbstractAuraForm { use SetAntiCsrfTrait; +} + +class MyController +{ + #[FormValidation(form: "contactForm")] + #[CsrfProtection] + public function createAction() + { + } +} ``` You can provide your custom `AntiCsrf` class. See more detail at [Aura.Input](https://github.com/auraphp/Aura.Input#applying-csrf-protections) diff --git a/composer-require-checker.json b/composer-require-checker.json new file mode 100644 index 0000000..2c1e5fd --- /dev/null +++ b/composer-require-checker.json @@ -0,0 +1,13 @@ +{ + "symbol-whitelist": [], + "php-core-extensions": [ + "Core", + "date", + "json", + "pcre", + "Reflection", + "SPL", + "standard" + ], + "scan-files": [] +} diff --git a/composer.json b/composer.json index e36bd59..77ec358 100644 --- a/composer.json +++ b/composer.json @@ -12,10 +12,17 @@ "aura/input": "^1.2", "aura/filter": "^2.3|3.x-dev", "aura/html": "^2.5", + "aura/session": "^2.1 || ^4.0", "ray/aura-session-module": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^1.10", + "squizlabs/php_codesniffer": "^3.7", + "vimeo/psalm": "^5.0 || ^6.0", + "phpmd/phpmd": "^2.13", + "maglnet/composer-require-checker": "^4.0" }, "license": "MIT", "autoload":{ @@ -30,9 +37,15 @@ }, "scripts" :{ "test": ["@cs", "phpunit"], + "tests": ["@cs", "@sa", "@test"], "coverage": ["php -dzend_extension=xdebug.so ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage"], - "cs": ["php-cs-fixer fix -v --dry-run", "phpcs --standard=./phpcs.xml src"], - "cs-fix": ["php-cs-fixer fix -v", "phpcbf src"] + "cs": ["php-cs-fixer fix -v --dry-run --sequential", "phpcs --standard=./phpcs.xml src"], + "cs-fix": ["php-cs-fixer fix -v --sequential", "phpcbf src"], + "phpstan": "phpstan analyse -c phpstan.neon --no-progress", + "psalm": "psalm --show-info=false", + "phpmd": "phpmd src text ./phpmd.xml", + "crc": "composer-require-checker check ./composer.json --config-file=./composer-require-checker.json", + "sa": ["@phpstan", "@psalm"] }, "config": { "allow-plugins": { diff --git a/docs/demo/1.csrf/ContactForm.php b/docs/demo/1.csrf/ContactForm.php index ce91165..fead5aa 100644 --- a/docs/demo/1.csrf/ContactForm.php +++ b/docs/demo/1.csrf/ContactForm.php @@ -1,4 +1,5 @@ response['code'] = 201; diff --git a/docs/demo/1.csrf/MyModule.php b/docs/demo/1.csrf/MyModule.php index 295da43..a687cb5 100644 --- a/docs/demo/1.csrf/MyModule.php +++ b/docs/demo/1.csrf/MyModule.php @@ -1,4 +1,5 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AbstractForm.php b/src/AbstractForm.php index 2274021..430390f 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -7,31 +7,36 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use ArrayIterator; +use function assert; use Aura\Filter\FilterFactory; use Aura\Filter\SubjectFilter; use Aura\Html\HelperLocator; use Aura\Html\HelperLocatorFactory; use Aura\Input\AntiCsrfInterface; +use Aura\Input\Builder; use Aura\Input\BuilderInterface; use Aura\Input\Fieldset; use Exception; +use function is_string; use Ray\Di\Di\Inject; use Ray\Di\Di\PostConstruct; use Ray\WebFormModule\Exception\CsrfViolationException; use Ray\WebFormModule\Exception\LogicException; - +use Stringable; use function trigger_error; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ abstract class AbstractForm extends Fieldset implements FormInterface { /** @var SubjectFilter */ protected $filter; - /** @var array>|null */ + /** @var array>|null */ protected ?array $errorMessages = null; protected HelperLocator $helper; @@ -50,10 +55,8 @@ public function __clone() /** * Return form markup string - * - * @return string */ - public function __toString() + public function __toString() : string { try { if (! $this instanceof ToStringInterface) { @@ -65,7 +68,8 @@ public function __toString() trigger_error($e->getMessage() . PHP_EOL . $e->getTraceAsString(), E_USER_ERROR); } - return ''; + // Reachable when a custom error handler intercepts E_USER_ERROR without halting. + return ''; // @phpstan-ignore deadCode.unreachable } /** @@ -78,40 +82,59 @@ public function setBaseDependencies( BuilderInterface $builder, FilterFactory $filterFactory, HelperLocatorFactory $helperFactory - ): void { + ) : void { + assert($builder instanceof Builder); $this->builder = $builder; $this->filter = $filterFactory->newSubjectFilter(); $this->helper = $helperFactory->newInstance(); } - public function setAntiCsrf(AntiCsrfInterface $antiCsrf): void + public function setAntiCsrf(AntiCsrfInterface $antiCsrf) : void + { + $this->antiCsrf = $antiCsrf; + } + + public function enableAntiCsrf(AntiCsrfInterface $antiCsrf) : void { $this->antiCsrf = $antiCsrf; + if (isset($this->inputs[AntiCsrf::TOKEN_KEY])) { + return; + } + + $this->antiCsrf->setField($this); } #[PostConstruct] - public function postConstruct(): void + public function postConstruct() : void { $this->init(); if ($this->antiCsrf instanceof AntiCsrfInterface) { - $this->antiCsrf->setField($this); + $this->enableAntiCsrf($this->antiCsrf); } } /** {@inheritdoc} */ - public function input($input) + public function input(string $input) : string { - return $this->helper->input($this->get($input)); + $inputHtml = $this->helper->input($this->get($input)); + assert(is_string($inputHtml) || $inputHtml instanceof Stringable); + + return (string) $inputHtml; } /** {@inheritdoc} */ - public function error(string $input): string + public function error(string $input) : string { - if (! $this->errorMessages) { + if ($this->errorMessages === null) { + /** @var \Aura\Filter\Failure\FailureCollection|null $failure */ $failure = $this->filter->getFailures(); - if ($failure) { - $this->errorMessages = $failure->getMessages(); + if ($failure === null) { + return ''; } + + /** @var array> $messages */ + $messages = $failure->getMessages(); + $this->errorMessages = $messages; } if (isset($this->errorMessages[$input])) { @@ -122,16 +145,19 @@ public function error(string $input): string } /** - * @param array $attr attributes for the form tag + * @param array $attr attributes for the form tag * * @throws \Aura\Input\Exception\NoSuchInput * @throws \Aura\Html\Exception\HelperNotFound */ - public function form(array $attr = []): string + public function form(array $attr = []) : string { + /** @var string $form */ $form = $this->helper->form($attr); if (isset($this->inputs['__csrf_token'])) { - $form .= $this->helper->input($this->get('__csrf_token')); + /** @var string $input */ + $input = $this->helper->input($this->get('__csrf_token')); + $form .= $input; } return $form; @@ -140,14 +166,14 @@ public function form(array $attr = []): string /** * Applies the filter to a subject. * - * @param array $data + * @param array $data * * @throws CsrfViolationException */ - public function apply(array $data): bool + public function apply(array $data) : bool { if ($this->antiCsrf && ! $this->antiCsrf->isValid($data)) { - throw new CsrfViolationException; + throw new CsrfViolationException(); } $this->fill($data); @@ -158,15 +184,22 @@ public function apply(array $data): bool /** * Returns all failure messages for all fields. * - * @return list + * @return array> */ - public function getFailureMessages(): array + public function getFailureMessages() : array { - return $this->filter->getFailures()->getMessages(); + /** @var array> $messages */ + $messages = $this->filter->getFailures()->getMessages(); + + return $messages; } - /** Returns all the fields collection */ - public function getIterator(): ArrayIterator + /** + * Returns all the fields collection + * + * @return ArrayIterator + */ + public function getIterator() : ArrayIterator { return new ArrayIterator($this->inputs); } diff --git a/src/Annotation/AbstractValidation.php b/src/Annotation/AbstractValidation.php index b866cf3..4249912 100644 --- a/src/Annotation/AbstractValidation.php +++ b/src/Annotation/AbstractValidation.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Annotation; abstract class AbstractValidation diff --git a/src/Annotation/CsrfProtection.php b/src/Annotation/CsrfProtection.php new file mode 100644 index 0000000..ba786fa --- /dev/null +++ b/src/Annotation/CsrfProtection.php @@ -0,0 +1,17 @@ + $href + * @param string|null $logref + * @param string|null $path * * @see http://www.w3.org/TR/html5/links.html#link-type-help * @see http://tools.ietf.org/html/rfc6903#section-2 diff --git a/src/AntiCsrf.php b/src/AntiCsrf.php index bec9e6a..3d9a9b0 100644 --- a/src/AntiCsrf.php +++ b/src/AntiCsrf.php @@ -7,15 +7,12 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use Aura\Input\AntiCsrfInterface; use Aura\Input\Fieldset; use Aura\Session\Session; - use function is_bool; - use const PHP_SAPI; final class AntiCsrf implements AntiCsrfInterface @@ -34,14 +31,14 @@ public function __construct(Session $session, bool|null $isCli = null) $this->isCli = is_bool($isCli) ? $isCli : PHP_SAPI === 'cli'; } - public function setField(Fieldset $fieldset): void + public function setField(Fieldset $fieldset) : void { $fieldset->setField(self::TOKEN_KEY, 'hidden') ->setAttribs(['value' => $this->getToken()]); } - /** @param array $data */ - public function isValid(array $data): bool + /** @param array $data */ + public function isValid(array $data) : bool { if ($this->isCli) { return true; @@ -50,7 +47,7 @@ public function isValid(array $data): bool return isset($data[self::TOKEN_KEY]) && $data[self::TOKEN_KEY] === $this->getToken(); } - private function getToken(): string + private function getToken() : string { return $this->isCli ? self::TEST_TOKEN : $this->session->getCsrfToken()->getValue(); } diff --git a/src/AuraInputInterceptor.php b/src/AuraInputInterceptor.php index 697693a..e2368af 100644 --- a/src/AuraInputInterceptor.php +++ b/src/AuraInputInterceptor.php @@ -7,33 +7,47 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; +use function array_shift; +use Aura\Input\AntiCsrfInterface; +use function property_exists; use Ray\Aop\MethodInterceptor; use Ray\Aop\MethodInvocation; +use Ray\Di\Di\Inject; use Ray\WebFormModule\Annotation\AbstractValidation; +use Ray\WebFormModule\Annotation\CsrfProtection; use Ray\WebFormModule\Exception\InvalidArgumentException; use Ray\WebFormModule\Exception\InvalidFormPropertyException; use ReflectionAttribute; use ReflectionClass; use ReflectionMethod; -use function array_shift; -use function property_exists; - +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class AuraInputInterceptor implements MethodInterceptor { protected FailureHandlerInterface $failureHandler; + private AntiCsrfInterface|null $antiCsrf = null; + public function __construct(FailureHandlerInterface $handler) { $this->failureHandler = $handler; } + #[Inject] + public function setAntiCsrf(AntiCsrfInterface $antiCsrf) : void + { + $this->antiCsrf = $antiCsrf; + } + /** * {@inheritdoc} * + * @param MethodInvocation $invocation + * * @throws InvalidArgumentException */ public function invoke(MethodInvocation $invocation) @@ -45,8 +59,11 @@ public function invoke(MethodInvocation $invocation) } $form = $this->getFormProperty($formValidation, $object); + $this->enableCsrfProtection($invocation->getMethod(), $form); $data = $form instanceof SubmitInterface ? $form->submit() : $this->getNamedArguments($invocation); - $isValid = $this->isValid($data, $form); + /** @var array $submit */ + $submit = (array) $data; + $isValid = $this->isValid($submit, $form); if ($isValid === true) { return $invocation->proceed(); } @@ -54,40 +71,52 @@ public function invoke(MethodInvocation $invocation) return $this->failureHandler->handle($formValidation, $invocation, $form); } - private function getValidationAttribute(ReflectionMethod $method): AbstractValidation|null + /** + * @param array $submit + * + * @throws Exception\CsrfViolationException + */ + public function isValid(array $submit, AbstractForm $form) : bool { - $attributes = $method->getAttributes(AbstractValidation::class, ReflectionAttribute::IS_INSTANCEOF); - if ($attributes === []) { - return null; + return $form->apply($submit); + } + + /** + * @throws InvalidArgumentException + */ + private function enableCsrfProtection(ReflectionMethod $method, AbstractForm $form) : void + { + if ($method->getAttributes(CsrfProtection::class) === []) { + return; } - $instance = $attributes[0]->newInstance(); - assert($instance instanceof AbstractValidation); + if (! $this->antiCsrf instanceof AntiCsrfInterface) { + throw new InvalidArgumentException('#[CsrfProtection] requires AntiCsrfInterface'); + } - return $instance; + $form->enableAntiCsrf($this->antiCsrf); } - /** - * @param array $submit - * @param AbstractForm $form - * - * @return bool - * @throws Exception\CsrfViolationException - * - */ - public function isValid(array $submit, AbstractForm $form): bool + private function getValidationAttribute(ReflectionMethod $method) : AbstractValidation|null { - return $form->apply($submit); + $attributes = $method->getAttributes(AbstractValidation::class, ReflectionAttribute::IS_INSTANCEOF); + if ($attributes === []) { + return null; + } + + return $attributes[0]->newInstance(); } /** * Return arguments as named arguments. * - * @param MethodInvocation $invocation + * @param MethodInvocation $invocation * - * @return array + * @return array + * + * @SuppressWarnings(PHPMD.Superglobals) */ - private function getNamedArguments(MethodInvocation $invocation): array + private function getNamedArguments(MethodInvocation $invocation) : array { $submit = []; $params = $invocation->getMethod()->getParameters(); @@ -97,7 +126,6 @@ private function getNamedArguments(MethodInvocation $invocation): array $submit[$param->getName()] = $arg; } - // has token? if (isset($_POST[AntiCsrf::TOKEN_KEY])) { $submit[AntiCsrf::TOKEN_KEY] = $_POST[AntiCsrf::TOKEN_KEY]; } @@ -105,15 +133,7 @@ private function getNamedArguments(MethodInvocation $invocation): array return $submit; } - /** - * Return form property - * - * @param AbstractValidation $formValidation - * @param object $object - * - * @return mixed - */ - private function getFormProperty(AbstractValidation $formValidation, $object) + private function getFormProperty(AbstractValidation $formValidation, object $object) : AbstractForm { if (! property_exists($object, $formValidation->form)) { throw new InvalidFormPropertyException($formValidation->form); diff --git a/src/AuraInputModule.php b/src/AuraInputModule.php index d2f56d6..803d9ea 100644 --- a/src/AuraInputModule.php +++ b/src/AuraInputModule.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use Aura\Filter\FilterFactory; @@ -23,6 +22,9 @@ use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\Annotation\InputValidation; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class AuraInputModule extends AbstractModule { /** {@inheritdoc} */ diff --git a/src/Exception/CsrfViolationException.php b/src/Exception/CsrfViolationException.php index c97b0b3..816cc28 100644 --- a/src/Exception/CsrfViolationException.php +++ b/src/Exception/CsrfViolationException.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Exception; use Aura\Input\Exception\CsrfViolation; diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index 237ca9d..dd435dc 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Exception; interface ExceptionInterface diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 5afa83d..47323d4 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Exception; class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface diff --git a/src/Exception/InvalidFormPropertyException.php b/src/Exception/InvalidFormPropertyException.php index a17cfc6..fcb28e0 100644 --- a/src/Exception/InvalidFormPropertyException.php +++ b/src/Exception/InvalidFormPropertyException.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Exception; class InvalidFormPropertyException extends LogicException diff --git a/src/Exception/InvalidOnFailureMethod.php b/src/Exception/InvalidOnFailureMethod.php index c4f0ea2..1e0c20e 100644 --- a/src/Exception/InvalidOnFailureMethod.php +++ b/src/Exception/InvalidOnFailureMethod.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Exception; class InvalidOnFailureMethod extends LogicException diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php index d5ec12c..da9cee0 100644 --- a/src/Exception/LogicException.php +++ b/src/Exception/LogicException.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Exception; class LogicException extends \LogicException implements ExceptionInterface diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index fe827e8..345d956 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Exception; class RuntimeException extends \RuntimeException implements ExceptionInterface diff --git a/src/Exception/ValidationException.php b/src/Exception/ValidationException.php index 1ed951d..aa557b0 100644 --- a/src/Exception/ValidationException.php +++ b/src/Exception/ValidationException.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule\Exception; use Exception; @@ -15,9 +14,9 @@ class ValidationException extends Exception { - public $error; + public ?FormValidationError $error; - public function __construct($message = '', $code = 0, ?Exception $e = null, ?FormValidationError $error = null) + public function __construct(string $message = '', int $code = 0, ?Exception $e = null, ?FormValidationError $error = null) { parent::__construct($message, $code, $e); $this->error = $error; diff --git a/src/FailureHandlerInterface.php b/src/FailureHandlerInterface.php index 948becf..67c0eb0 100644 --- a/src/FailureHandlerInterface.php +++ b/src/FailureHandlerInterface.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use Ray\Aop\MethodInvocation; @@ -15,5 +14,12 @@ interface FailureHandlerInterface { + /** + * @param AbstractValidation $formValidation + * @param MethodInvocation $invocation + * @param AbstractForm $form + * + * @return mixed + */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form); } diff --git a/src/FormFactory.php b/src/FormFactory.php index ed12a80..ae72d72 100644 --- a/src/FormFactory.php +++ b/src/FormFactory.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use Aura\Filter\FilterFactory; @@ -16,11 +15,17 @@ final class FormFactory { - public function newInstance(string $class): AbstractForm + /** + * @param string $class + * + * @phpstan-param class-string $class + * + * @psalm-param class-string $class + */ + public function newInstance(string $class) : AbstractForm { - /** @var $form AbstractForm */ - $form = new $class; - $form->setBaseDependencies(new Builder, new FilterFactory, new HelperLocatorFactory); + $form = new $class(); + $form->setBaseDependencies(new Builder(), new FilterFactory(), new HelperLocatorFactory()); $form->postConstruct(); return $form; diff --git a/src/FormInterface.php b/src/FormInterface.php index 5e36178..ab0e9e0 100644 --- a/src/FormInterface.php +++ b/src/FormInterface.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; interface FormInterface @@ -15,11 +14,12 @@ interface FormInterface /** * Return input element html * - * @return string * @throws \Aura\Input\Exception\NoSuchInput + * + * @return string */ public function input(string $input); /** Return error message */ - public function error(string $input): string; + public function error(string $input) : string; } diff --git a/src/FormValidationError.php b/src/FormValidationError.php index 6cdec60..75b95b8 100644 --- a/src/FormValidationError.php +++ b/src/FormValidationError.php @@ -7,25 +7,27 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use function json_encode; - use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; class FormValidationError { + /** @var array */ private array $value; + /** + * @param array $value + */ public function __construct(array $value) { $this->value = $value; } - public function __toString() + public function __toString() : string { - return json_encode($this->value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + return (string) json_encode($this->value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } } diff --git a/src/FormVndErrorModule.php b/src/FormVndErrorModule.php index a4064b2..b8e76f4 100644 --- a/src/FormVndErrorModule.php +++ b/src/FormVndErrorModule.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use Ray\Di\AbstractModule; diff --git a/src/InputValidationInterceptor.php b/src/InputValidationInterceptor.php index e59098e..fec9ef9 100644 --- a/src/InputValidationInterceptor.php +++ b/src/InputValidationInterceptor.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use Ray\Di\Di\Named; @@ -15,7 +14,7 @@ class InputValidationInterceptor extends AuraInputInterceptor { public function __construct( - #[Named("vnd_error")] + #[Named('vnd_error')] FailureHandlerInterface $handler, ) { parent::__construct($handler); diff --git a/src/OnFailureMethodHandler.php b/src/OnFailureMethodHandler.php index 77ff764..0a10936 100644 --- a/src/OnFailureMethodHandler.php +++ b/src/OnFailureMethodHandler.php @@ -7,22 +7,26 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; +use function call_user_func_array; +use function method_exists; use Ray\Aop\MethodInvocation; use Ray\WebFormModule\Annotation\AbstractValidation; use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\Exception\InvalidOnFailureMethod; -use function call_user_func_array; -use function method_exists; - final class OnFailureMethodHandler implements FailureHandlerInterface { public const FAILURE_SUFFIX = 'ValidationFailed'; - /** {@inheritdoc} */ + /** + * {@inheritdoc} + * + * @param AbstractValidation $formValidation + * @param MethodInvocation $invocation + * @param AbstractForm $form + */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) { unset($form); @@ -37,6 +41,9 @@ public function handle(AbstractValidation $formValidation, MethodInvocation $inv throw new InvalidOnFailureMethod(get_class($invocation->getThis())); } - return call_user_func_array([$invocation->getThis(), $onFailureMethod], $args); + /** @var callable $callable */ + $callable = [$object, $onFailureMethod]; + + return call_user_func_array($callable, $args); } } diff --git a/src/SetAntiCsrfTrait.php b/src/SetAntiCsrfTrait.php index e3fb26b..d746279 100644 --- a/src/SetAntiCsrfTrait.php +++ b/src/SetAntiCsrfTrait.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use Aura\Input\AntiCsrfInterface; @@ -16,7 +15,7 @@ trait SetAntiCsrfTrait { #[Inject] - public function setAntiCsrf(AntiCsrfInterface $antiCsrf): void + public function setAntiCsrf(AntiCsrfInterface $antiCsrf) : void { $this->antiCsrf = $antiCsrf; } diff --git a/src/SubmitInterface.php b/src/SubmitInterface.php index 10db621..d902e64 100644 --- a/src/SubmitInterface.php +++ b/src/SubmitInterface.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; interface SubmitInterface @@ -15,7 +14,7 @@ interface SubmitInterface /** * Return subject value * - * @return array|object + * @return array|object */ public function submit(); } diff --git a/src/ToStringInterface.php b/src/ToStringInterface.php index 22ed248..3ce4a09 100644 --- a/src/ToStringInterface.php +++ b/src/ToStringInterface.php @@ -7,11 +7,10 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; /** Return form markup string */ interface ToStringInterface { - public function toString(): string; + public function toString() : string; } diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index 429a6f9..a334af4 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -7,7 +7,6 @@ * * @license http://opensource.org/licenses/MIT MIT */ - namespace Ray\WebFormModule; use Ray\Aop\MethodInvocation; @@ -18,7 +17,13 @@ final class VndErrorHandler implements FailureHandlerInterface { - /** {@inheritdoc} */ + /** + * {@inheritdoc} + * + * @param AbstractValidation $formValidation + * @param MethodInvocation $invocation + * @param AbstractForm $form + */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) { unset($formValidation); @@ -28,30 +33,34 @@ public function handle(AbstractValidation $formValidation, MethodInvocation $inv throw new ValidationException('Validation failed.', 400, null, $error); } - private function getVndErrorAttribute(ReflectionMethod $method): VndError|null + private function getVndErrorAttribute(ReflectionMethod $method) : VndError|null { $attributes = $method->getAttributes(VndError::class); if ($attributes === []) { return null; } - $instance = $attributes[0]->newInstance(); - assert($instance instanceof VndError); - - return $instance; + return $attributes[0]->newInstance(); } - private function makeVndError(AbstractForm $form, ?VndError $vndError = null) + /** + * @return array + * + * @SuppressWarnings(PHPMD.Superglobals) + */ + private function makeVndError(AbstractForm $form, ?VndError $vndError = null) : array { $body = ['message' => 'Validation failed']; $body['path'] = $_SERVER['PATH_INFO'] ?? ''; $body['validation_messages'] = $form->getFailureMessages(); - $body = $vndError ? $this->optionalAttribute($vndError) + $body : $body; - return $body; + return $vndError ? $this->optionalAttribute($vndError) + $body : $body; } - private function optionalAttribute(VndError $vndError) + /** + * @return array + */ + private function optionalAttribute(VndError $vndError) : array { $body = []; if ($vndError->message) { diff --git a/tests/AbstractAuraFormTest.php b/tests/AbstractAuraFormTest.php index 16862dd..10eba46 100644 --- a/tests/AbstractAuraFormTest.php +++ b/tests/AbstractAuraFormTest.php @@ -1,4 +1,5 @@ form = (new FormFactory)->newInstance(FakeForm::class); diff --git a/tests/AbstractFormTest.php b/tests/AbstractFormTest.php index 86e8263..2e8c9b3 100644 --- a/tests/AbstractFormTest.php +++ b/tests/AbstractFormTest.php @@ -1,4 +1,5 @@ form = (new FormFactory)->newInstance(FakeMiniForm::class); diff --git a/tests/AntiCsrfTest.php b/tests/AntiCsrfTest.php index ebb4297..6b4a6a8 100644 --- a/tests/AntiCsrfTest.php +++ b/tests/AntiCsrfTest.php @@ -1,4 +1,5 @@ phpfunc = new FakePhpfunc; $this->session = $this->newSession(); diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 3c1ce68..0b2743e 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -1,4 +1,5 @@ injector = new Injector(new class() extends AbstractModule { protected function configure() { $this->install(new AuraInputModule); $this->bind(FormInterface::class)->annotatedWith('contact_form')->to(FakeForm::class); + $this->bind(FormInterface::class)->annotatedWith('mini_form')->to(FakeMiniForm::class); } }); $this->controller = $this->injector->getInstance(FakeController::class); @@ -49,6 +51,18 @@ public function testProceed() $this->assertSame('201', $result); } + public function testCsrfProtectionAttributeEnablesAntiCsrf() + { + /** @var FakeCsrfController $controller */ + $controller = $this->injector->getInstance(FakeCsrfController::class); + $this->assertStringNotContainsString(AntiCsrf::TOKEN_KEY, $controller->formHtml()); + + $result = $controller->createAction('BEAR'); + + $this->assertSame('201', $result); + $this->assertStringContainsString(AntiCsrf::TOKEN_KEY, $controller->formHtml()); + } + public function testInvalidFormPropertyByMissingProperty() { $this->expectException(InvalidFormPropertyException::class); diff --git a/tests/AuraInputModuleTest.php b/tests/AuraInputModuleTest.php index 47add84..e1d3cfb 100644 --- a/tests/AuraInputModuleTest.php +++ b/tests/AuraInputModuleTest.php @@ -1,4 +1,5 @@ form = $form; + } + + #[FormValidation] + #[CsrfProtection] + public function createAction($name) + { + return '201'; + } + + public function formHtml() : string + { + assert($this->form instanceof AbstractForm); + + return $this->form->form(); + } +} diff --git a/tests/FormFactoryTest.php b/tests/FormFactoryTest.php index c8ae411..e3ecef9 100644 --- a/tests/FormFactoryTest.php +++ b/tests/FormFactoryTest.php @@ -1,4 +1,5 @@ factory = new FormFactory; diff --git a/tests/VndErrorHandlerTest.php b/tests/VndErrorHandlerTest.php index c8b888e..6f350b4 100644 --- a/tests/VndErrorHandlerTest.php +++ b/tests/VndErrorHandlerTest.php @@ -1,4 +1,5 @@ controller = (new Injector(new FakeVndErrorModule, __DIR__ . '/tmp'))->getInstance(FakeController::class); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index adacf3f..039e44c 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,5 @@ Date: Sun, 17 May 2026 11:15:52 +0900 Subject: [PATCH 25/41] chore: switch coding standard from PHP-CS-Fixer to Doctrine - Replace friendsofphp/php-cs-fixer with doctrine/coding-standard. - Upgrade squizlabs/php_codesniffer to ^4.0 and rewrite phpcs.xml around PSR12 + Doctrine ruleset with project-specific exclusions. - Drop .php-cs-fixer.php; collapse composer cs/cs-fix scripts to phpcs/phpcbf. - Simplify coding-standards.yml to use the standard auto-discovered ruleset. - Reformat all source, tests and docs to satisfy the new standard (use ordering, attribute syntax, type/throws annotations, doc spacing). --- .github/workflows/coding-standards.yml | 2 +- .gitignore | 1 + .php-cs-fixer.php | 132 ------------------ composer.json | 11 +- docs/demo/1.csrf/ContactForm.php | 6 - docs/demo/1.csrf/Controller.php | 6 - docs/demo/1.csrf/MyModule.php | 6 - docs/demo/1.csrf/run.php | 5 - docs/demo/1.csrf/web.php | 5 - docs/demo/autoload.php | 5 - phpcs.xml | 76 +++++++--- src/AbstractForm.php | 69 ++++----- src/Annotation/AbstractValidation.php | 5 - src/Annotation/CsrfProtection.php | 5 - src/Annotation/FormValidation.php | 7 +- src/Annotation/InputValidation.php | 5 - src/Annotation/VndError.php | 10 +- src/AntiCsrf.php | 18 +-- src/AuraInputInterceptor.php | 33 ++--- src/AuraInputModule.php | 17 +-- src/Exception/CsrfViolationException.php | 5 - src/Exception/ExceptionInterface.php | 5 - src/Exception/InvalidArgumentException.php | 5 - .../InvalidFormPropertyException.php | 5 - src/Exception/InvalidOnFailureMethod.php | 5 - src/Exception/LogicException.php | 5 - src/Exception/RuntimeException.php | 5 - src/Exception/ValidationException.php | 11 +- src/FailureHandlerInterface.php | 7 - src/FormFactory.php | 12 +- src/FormInterface.php | 13 +- src/FormValidationError.php | 18 +-- src/FormVndErrorModule.php | 7 +- src/InputValidationInterceptor.php | 5 - src/OnFailureMethodHandler.php | 15 +- src/SetAntiCsrfTrait.php | 7 +- src/SubmitInterface.php | 5 - src/ToStringInterface.php | 7 +- src/VndErrorHandler.php | 19 +-- tests/AbstractAuraFormTest.php | 38 +++-- tests/AbstractFormTest.php | 48 +++---- tests/AntiCsrfTest.php | 34 ++--- tests/AuraInputInterceptorTest.php | 21 +-- tests/AuraInputModuleTest.php | 13 +- tests/FormFactoryTest.php | 16 +-- tests/VndErrorHandlerTest.php | 20 ++- tests/bootstrap.php | 13 +- 47 files changed, 234 insertions(+), 554 deletions(-) delete mode 100644 .php-cs-fixer.php diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index b0bf7f1..9bd9501 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -42,4 +42,4 @@ jobs: run: composer install --no-interaction --prefer-dist - name: Run PHP_CodeSniffer - run: ./vendor/bin/phpcs -q --no-colors --report=checkstyle --standard=./phpcs.xml src | cs2pr + run: ./vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr diff --git a/.gitignore b/.gitignore index b47a5dd..93dec00 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ composer.lock tests/tmp/* !tests/tmp/.placefolder .phpunit.result.cache +.phpcs-cache diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php deleted file mode 100644 index 5d7ee7d..0000000 --- a/.php-cs-fixer.php +++ /dev/null @@ -1,132 +0,0 @@ -setRiskyAllowed(true) - ->setRules(array( - '@PSR2' => true, - 'header_comment' => ['header' => $header, 'comment_type' => 'PHPDoc', 'separate' => 'none'], - 'array_syntax' => ['syntax' => 'short'], - 'binary_operator_spaces' => ['default' => 'single_space'], - 'blank_line_after_opening_tag' => true, - 'blank_line_after_namespace' => false, - 'blank_line_before_statement' => ['statements' => ['return']], - 'cast_spaces' => true, -// 'class_keyword_remove' => true, - 'combine_consecutive_unsets' => true, - 'concat_space' => ['spacing' => 'one'], - 'declare_equal_normalize' => true, - 'declare_strict_types' => false, - 'dir_constant' => true, - 'ereg_to_preg' => true, - 'type_declaration_spaces' => true, - 'general_phpdoc_annotation_remove' => true, - 'single_line_comment_style' => ['comment_types' => ['hash']], - 'heredoc_to_nowdoc' => true, - 'include' => true, - 'indentation_type' => true, - 'is_null' => true, - 'linebreak_after_opening_tag' => true, - 'lowercase_cast' => true, -// 'mb_str_functions' => true, - 'class_attributes_separation' => ['elements' => ['method' => 'one', 'trait_import' => 'none']], - 'modernize_types_casting' => true, - 'native_function_casing' => true, -// 'native_function_invocation' => true, - 'new_with_parentheses' => false, // - 'no_alias_functions' => true, - 'no_blank_lines_after_class_opening' => true, - 'no_blank_lines_after_phpdoc' => true, - 'blank_lines_before_namespace' => ['min_line_breaks' => 1, 'max_line_breaks' => 1], - 'no_empty_comment' => true, - 'no_empty_phpdoc' => true, - 'no_empty_statement' => true, - 'no_extra_blank_lines' => ['tokens' => ['break', 'continue', 'curly_brace_block', 'extra', 'parenthesis_brace_block', 'return', 'square_brace_block', 'throw', 'use']], - 'no_leading_import_slash' => true, - 'no_leading_namespace_whitespace' => true, - 'no_mixed_echo_print' => ['use' => 'echo'], - 'no_multiline_whitespace_around_double_arrow' => true, - 'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'], - 'no_php4_constructor' => false, - 'no_short_bool_cast' => true, - 'echo_tag_syntax' => false, - 'no_singleline_whitespace_before_semicolons' => true, - 'no_spaces_around_offset' => true, - 'no_trailing_comma_in_singleline' => true, - 'no_trailing_whitespace' => true, - 'no_trailing_whitespace_in_comment' => true, - 'no_unneeded_control_parentheses' => true, - 'no_unreachable_default_argument_value' => true, - 'no_unused_imports' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'no_whitespace_before_comma_in_array' => true, - 'no_whitespace_in_blank_line' => true, - 'normalize_index_brace' => true, - 'not_operator_with_space' => false, - 'not_operator_with_successor_space' => true, - 'object_operator_without_whitespace' => true, - 'ordered_class_elements' => true, - 'ordered_imports' => true, - 'php_unit_construct' => true, - 'php_unit_dedicate_assert' => true, - 'php_unit_fqcn_annotation' => true, - 'php_unit_strict' => true, -// 'phpdoc_add_missing_param_annotation' => true, - 'phpdoc_align' => true, - 'phpdoc_annotation_without_dot' => true, - 'phpdoc_indent' => true, - 'phpdoc_inline_tag_normalizer' => true, - 'phpdoc_no_access' => true, - 'phpdoc_no_alias_tag' => ['replacements' => ['property-read' => 'property', 'property-write' => 'property', 'type' => 'var']], - 'phpdoc_no_empty_return' => true, - 'phpdoc_no_package' => true, -// 'phpdoc_no_useless_inheritdoc' => true, - 'phpdoc_order' => true, - 'phpdoc_return_self_reference' => true, - 'phpdoc_scalar' => true, - 'phpdoc_separation' => true, - 'phpdoc_single_line_var_spacing' => true, -// 'phpdoc_summary' => true, - 'phpdoc_to_comment' => true, - 'phpdoc_trim' => true, - 'phpdoc_types' => true, - 'phpdoc_var_without_name' => true, - 'pow_to_exponentiation' => true, -// 'pre_increment' => true, - 'protected_to_private' => true, - 'psr_autoloading' => true, - 'random_api_migration' => true, - 'return_type_declaration' => ['space_before' => 'one'], - 'self_accessor' => true, - 'short_scalar_cast' => true, -// 'silenced_deprecation_error' => true, -// 'simplified_null_return' => true, -// 'single_blank_line_before_namespace' => true, - 'single_quote' => true, - 'space_after_semicolon' => true, - 'standardize_not_equals' => true, -// 'strict_comparison' => true, - 'ternary_operator_spaces' => true, - 'strict_param' => true, -// 'ternary_to_null_coalescing' => true, -// 'trailing_comma_in_multiline_array' => true, - 'trim_array_spaces' => true, - 'unary_operator_spaces' => true, - 'whitespace_after_comma_in_array' => true - )) - ->setFinder( - PhpCsFixer\Finder::create() - ->exclude('tests/Fake') - ->exclude('tests/tmp') - ->exclude('src-data') - ->exclude('src-deprecated') - ->exclude('docs/demo/tmp') - ->in(__DIR__) - )->setLineEnding("\n") - ->setUsingCache(false); diff --git a/composer.json b/composer.json index 77ec358..6f8b471 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,9 @@ }, "require-dev": { "phpunit/phpunit": "^9.5", - "friendsofphp/php-cs-fixer": "^3.0", + "doctrine/coding-standard": "^14.0", "phpstan/phpstan": "^1.10", - "squizlabs/php_codesniffer": "^3.7", + "squizlabs/php_codesniffer": "^4.0", "vimeo/psalm": "^5.0 || ^6.0", "phpmd/phpmd": "^2.13", "maglnet/composer-require-checker": "^4.0" @@ -39,8 +39,8 @@ "test": ["@cs", "phpunit"], "tests": ["@cs", "@sa", "@test"], "coverage": ["php -dzend_extension=xdebug.so ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage"], - "cs": ["php-cs-fixer fix -v --dry-run --sequential", "phpcs --standard=./phpcs.xml src"], - "cs-fix": ["php-cs-fixer fix -v --sequential", "phpcbf src"], + "cs": "phpcs", + "cs-fix": "phpcbf", "phpstan": "phpstan analyse -c phpstan.neon --no-progress", "psalm": "psalm --show-info=false", "phpmd": "phpmd src text ./phpmd.xml", @@ -49,7 +49,8 @@ }, "config": { "allow-plugins": { - "aura/installer-default": true + "aura/installer-default": true, + "dealerdirect/phpcodesniffer-composer-installer": true } } } diff --git a/docs/demo/1.csrf/ContactForm.php b/docs/demo/1.csrf/ContactForm.php index fead5aa..aea6dbd 100644 --- a/docs/demo/1.csrf/ContactForm.php +++ b/docs/demo/1.csrf/ContactForm.php @@ -1,10 +1,4 @@ addPsr4('Ray\WebFormModule\\', __DIR__); use Aura\Input\Exception\CsrfViolation; diff --git a/docs/demo/1.csrf/web.php b/docs/demo/1.csrf/web.php index dbf88d7..ef72c18 100644 --- a/docs/demo/1.csrf/web.php +++ b/docs/demo/1.csrf/web.php @@ -1,10 +1,5 @@ - - - - - - - - + + + + + + + + + + + + + + + src + tests + */tmp/* + */Fake/* + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AbstractForm.php b/src/AbstractForm.php index 430390f..b5a68f7 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -2,46 +2,44 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use ArrayIterator; -use function assert; +use Aura\Filter\Failure\FailureCollection; use Aura\Filter\FilterFactory; use Aura\Filter\SubjectFilter; +use Aura\Html\Exception\HelperNotFound; use Aura\Html\HelperLocator; use Aura\Html\HelperLocatorFactory; use Aura\Input\AntiCsrfInterface; use Aura\Input\Builder; use Aura\Input\BuilderInterface; +use Aura\Input\Exception\NoSuchInput; use Aura\Input\Fieldset; -use Exception; -use function is_string; use Ray\Di\Di\Inject; use Ray\Di\Di\PostConstruct; use Ray\WebFormModule\Exception\CsrfViolationException; use Ray\WebFormModule\Exception\LogicException; use Stringable; +use Throwable; + +use function assert; +use function is_string; use function trigger_error; -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ +use const E_USER_ERROR; +use const PHP_EOL; + +/** @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ abstract class AbstractForm extends Fieldset implements FormInterface { /** @var SubjectFilter */ protected $filter; /** @var array>|null */ - protected ?array $errorMessages = null; - + protected array|null $errorMessages = null; protected HelperLocator $helper; - - protected ?AntiCsrfInterface $antiCsrf = null; + protected AntiCsrfInterface|null $antiCsrf = null; public function __construct() { @@ -56,7 +54,7 @@ public function __clone() /** * Return form markup string */ - public function __toString() : string + public function __toString(): string { try { if (! $this instanceof ToStringInterface) { @@ -64,7 +62,7 @@ public function __toString() : string } return $this->toString(); - } catch (Exception $e) { + } catch (Throwable $e) { trigger_error($e->getMessage() . PHP_EOL . $e->getTraceAsString(), E_USER_ERROR); } @@ -72,29 +70,24 @@ public function __toString() : string return ''; // @phpstan-ignore deadCode.unreachable } - /** - * @param BuilderInterface $builder - * @param FilterFactory $filterFactory - * @param HelperLocatorFactory $helperFactory - */ #[Inject] public function setBaseDependencies( BuilderInterface $builder, FilterFactory $filterFactory, - HelperLocatorFactory $helperFactory - ) : void { + HelperLocatorFactory $helperFactory, + ): void { assert($builder instanceof Builder); $this->builder = $builder; $this->filter = $filterFactory->newSubjectFilter(); $this->helper = $helperFactory->newInstance(); } - public function setAntiCsrf(AntiCsrfInterface $antiCsrf) : void + public function setAntiCsrf(AntiCsrfInterface $antiCsrf): void { $this->antiCsrf = $antiCsrf; } - public function enableAntiCsrf(AntiCsrfInterface $antiCsrf) : void + public function enableAntiCsrf(AntiCsrfInterface $antiCsrf): void { $this->antiCsrf = $antiCsrf; if (isset($this->inputs[AntiCsrf::TOKEN_KEY])) { @@ -105,7 +98,7 @@ public function enableAntiCsrf(AntiCsrfInterface $antiCsrf) : void } #[PostConstruct] - public function postConstruct() : void + public function postConstruct(): void { $this->init(); if ($this->antiCsrf instanceof AntiCsrfInterface) { @@ -113,8 +106,8 @@ public function postConstruct() : void } } - /** {@inheritdoc} */ - public function input(string $input) : string + /** {@inheritDoc} */ + public function input(string $input): string { $inputHtml = $this->helper->input($this->get($input)); assert(is_string($inputHtml) || $inputHtml instanceof Stringable); @@ -122,11 +115,11 @@ public function input(string $input) : string return (string) $inputHtml; } - /** {@inheritdoc} */ - public function error(string $input) : string + /** {@inheritDoc} */ + public function error(string $input): string { if ($this->errorMessages === null) { - /** @var \Aura\Filter\Failure\FailureCollection|null $failure */ + /** @var FailureCollection|null $failure */ $failure = $this->filter->getFailures(); if ($failure === null) { return ''; @@ -147,10 +140,10 @@ public function error(string $input) : string /** * @param array $attr attributes for the form tag * - * @throws \Aura\Input\Exception\NoSuchInput - * @throws \Aura\Html\Exception\HelperNotFound + * @throws NoSuchInput + * @throws HelperNotFound */ - public function form(array $attr = []) : string + public function form(array $attr = []): string { /** @var string $form */ $form = $this->helper->form($attr); @@ -170,7 +163,7 @@ public function form(array $attr = []) : string * * @throws CsrfViolationException */ - public function apply(array $data) : bool + public function apply(array $data): bool { if ($this->antiCsrf && ! $this->antiCsrf->isValid($data)) { throw new CsrfViolationException(); @@ -186,7 +179,7 @@ public function apply(array $data) : bool * * @return array> */ - public function getFailureMessages() : array + public function getFailureMessages(): array { /** @var array> $messages */ $messages = $this->filter->getFailures()->getMessages(); @@ -199,7 +192,7 @@ public function getFailureMessages() : array * * @return ArrayIterator */ - public function getIterator() : ArrayIterator + public function getIterator(): ArrayIterator { return new ArrayIterator($this->inputs); } diff --git a/src/Annotation/AbstractValidation.php b/src/Annotation/AbstractValidation.php index 4249912..8669808 100644 --- a/src/Annotation/AbstractValidation.php +++ b/src/Annotation/AbstractValidation.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Annotation; abstract class AbstractValidation diff --git a/src/Annotation/CsrfProtection.php b/src/Annotation/CsrfProtection.php index ba786fa..c40a0a4 100644 --- a/src/Annotation/CsrfProtection.php +++ b/src/Annotation/CsrfProtection.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Annotation; use Attribute; diff --git a/src/Annotation/FormValidation.php b/src/Annotation/FormValidation.php index 097f9ef..c852267 100644 --- a/src/Annotation/FormValidation.php +++ b/src/Annotation/FormValidation.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Annotation; use Attribute; @@ -16,7 +11,7 @@ final class FormValidation extends AbstractValidation { public function __construct( string $form = 'form', - public string|null $onFailure = null + public string|null $onFailure = null, ) { parent::__construct($form); } diff --git a/src/Annotation/InputValidation.php b/src/Annotation/InputValidation.php index d8dbb8f..77c1407 100644 --- a/src/Annotation/InputValidation.php +++ b/src/Annotation/InputValidation.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Annotation; use Attribute; diff --git a/src/Annotation/VndError.php b/src/Annotation/VndError.php index 29e89ba..34c5a56 100644 --- a/src/Annotation/VndError.php +++ b/src/Annotation/VndError.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Annotation; use Attribute; @@ -15,10 +10,7 @@ final class VndError { /** - * @param string $message * @param array $href - * @param string|null $logref - * @param string|null $path * * @see http://www.w3.org/TR/html5/links.html#link-type-help * @see http://tools.ietf.org/html/rfc6903#section-2 @@ -28,7 +20,7 @@ public function __construct( public string $message = '', public array $href = [], public string|null $logref = null, - public string|null $path = null + public string|null $path = null, ) { } } diff --git a/src/AntiCsrf.php b/src/AntiCsrf.php index 3d9a9b0..df4f16d 100644 --- a/src/AntiCsrf.php +++ b/src/AntiCsrf.php @@ -2,17 +2,14 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use Aura\Input\AntiCsrfInterface; use Aura\Input\Fieldset; use Aura\Session\Session; + use function is_bool; + use const PHP_SAPI; final class AntiCsrf implements AntiCsrfInterface @@ -23,22 +20,19 @@ final class AntiCsrf implements AntiCsrfInterface private bool $isCli; - private Session $session; - - public function __construct(Session $session, bool|null $isCli = null) + public function __construct(private Session $session, bool|null $isCli = null) { - $this->session = $session; $this->isCli = is_bool($isCli) ? $isCli : PHP_SAPI === 'cli'; } - public function setField(Fieldset $fieldset) : void + public function setField(Fieldset $fieldset): void { $fieldset->setField(self::TOKEN_KEY, 'hidden') ->setAttribs(['value' => $this->getToken()]); } /** @param array $data */ - public function isValid(array $data) : bool + public function isValid(array $data): bool { if ($this->isCli) { return true; @@ -47,7 +41,7 @@ public function isValid(array $data) : bool return isset($data[self::TOKEN_KEY]) && $data[self::TOKEN_KEY] === $this->getToken(); } - private function getToken() : string + private function getToken(): string { return $this->isCli ? self::TEST_TOKEN : $this->session->getCsrfToken()->getValue(); } diff --git a/src/AuraInputInterceptor.php b/src/AuraInputInterceptor.php index e2368af..676ae74 100644 --- a/src/AuraInputInterceptor.php +++ b/src/AuraInputInterceptor.php @@ -2,16 +2,9 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; -use function array_shift; use Aura\Input\AntiCsrfInterface; -use function property_exists; use Ray\Aop\MethodInterceptor; use Ray\Aop\MethodInvocation; use Ray\Di\Di\Inject; @@ -23,13 +16,13 @@ use ReflectionClass; use ReflectionMethod; -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ +use function array_shift; +use function property_exists; + +/** @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AuraInputInterceptor implements MethodInterceptor { protected FailureHandlerInterface $failureHandler; - private AntiCsrfInterface|null $antiCsrf = null; public function __construct(FailureHandlerInterface $handler) @@ -38,13 +31,13 @@ public function __construct(FailureHandlerInterface $handler) } #[Inject] - public function setAntiCsrf(AntiCsrfInterface $antiCsrf) : void + public function setAntiCsrf(AntiCsrfInterface $antiCsrf): void { $this->antiCsrf = $antiCsrf; } /** - * {@inheritdoc} + * {@inheritDoc} * * @param MethodInvocation $invocation * @@ -76,15 +69,13 @@ public function invoke(MethodInvocation $invocation) * * @throws Exception\CsrfViolationException */ - public function isValid(array $submit, AbstractForm $form) : bool + public function isValid(array $submit, AbstractForm $form): bool { return $form->apply($submit); } - /** - * @throws InvalidArgumentException - */ - private function enableCsrfProtection(ReflectionMethod $method, AbstractForm $form) : void + /** @throws InvalidArgumentException */ + private function enableCsrfProtection(ReflectionMethod $method, AbstractForm $form): void { if ($method->getAttributes(CsrfProtection::class) === []) { return; @@ -97,7 +88,7 @@ private function enableCsrfProtection(ReflectionMethod $method, AbstractForm $fo $form->enableAntiCsrf($this->antiCsrf); } - private function getValidationAttribute(ReflectionMethod $method) : AbstractValidation|null + private function getValidationAttribute(ReflectionMethod $method): AbstractValidation|null { $attributes = $method->getAttributes(AbstractValidation::class, ReflectionAttribute::IS_INSTANCEOF); if ($attributes === []) { @@ -116,7 +107,7 @@ private function getValidationAttribute(ReflectionMethod $method) : AbstractVali * * @SuppressWarnings(PHPMD.Superglobals) */ - private function getNamedArguments(MethodInvocation $invocation) : array + private function getNamedArguments(MethodInvocation $invocation): array { $submit = []; $params = $invocation->getMethod()->getParameters(); @@ -133,7 +124,7 @@ private function getNamedArguments(MethodInvocation $invocation) : array return $submit; } - private function getFormProperty(AbstractValidation $formValidation, object $object) : AbstractForm + private function getFormProperty(AbstractValidation $formValidation, object $object): AbstractForm { if (! property_exists($object, $formValidation->form)) { throw new InvalidFormPropertyException($formValidation->form); diff --git a/src/AuraInputModule.php b/src/AuraInputModule.php index 803d9ea..dc09b81 100644 --- a/src/AuraInputModule.php +++ b/src/AuraInputModule.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use Aura\Filter\FilterFactory; @@ -22,15 +17,13 @@ use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\Annotation\InputValidation; -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ +/** @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AuraInputModule extends AbstractModule { - /** {@inheritdoc} */ + /** {@inheritDoc} */ protected function configure() { - $this->install(new AuraSessionModule); + $this->install(new AuraSessionModule()); $this->bind(BuilderInterface::class)->to(Builder::class); $this->bind(FilterInterface::class)->to(Filter::class); $this->bind(AntiCsrfInterface::class)->to(AntiCsrf::class)->in(Scope::SINGLETON); @@ -42,12 +35,12 @@ protected function configure() $this->bindInterceptor( $this->matcher->any(), $this->matcher->annotatedWith(InputValidation::class), - [InputValidationInterceptor::class] + [InputValidationInterceptor::class], ); $this->bindInterceptor( $this->matcher->any(), $this->matcher->annotatedWith(FormValidation::class), - [AuraInputInterceptor::class] + [AuraInputInterceptor::class], ); } } diff --git a/src/Exception/CsrfViolationException.php b/src/Exception/CsrfViolationException.php index 816cc28..3fe1bc2 100644 --- a/src/Exception/CsrfViolationException.php +++ b/src/Exception/CsrfViolationException.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Exception; use Aura\Input\Exception\CsrfViolation; diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index dd435dc..495601e 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Exception; interface ExceptionInterface diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 47323d4..0c1cfcf 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Exception; class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface diff --git a/src/Exception/InvalidFormPropertyException.php b/src/Exception/InvalidFormPropertyException.php index fcb28e0..b11db09 100644 --- a/src/Exception/InvalidFormPropertyException.php +++ b/src/Exception/InvalidFormPropertyException.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Exception; class InvalidFormPropertyException extends LogicException diff --git a/src/Exception/InvalidOnFailureMethod.php b/src/Exception/InvalidOnFailureMethod.php index 1e0c20e..002b52a 100644 --- a/src/Exception/InvalidOnFailureMethod.php +++ b/src/Exception/InvalidOnFailureMethod.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Exception; class InvalidOnFailureMethod extends LogicException diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php index da9cee0..4a50d27 100644 --- a/src/Exception/LogicException.php +++ b/src/Exception/LogicException.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Exception; class LogicException extends \LogicException implements ExceptionInterface diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 345d956..5ec6384 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Exception; class RuntimeException extends \RuntimeException implements ExceptionInterface diff --git a/src/Exception/ValidationException.php b/src/Exception/ValidationException.php index aa557b0..86b6a11 100644 --- a/src/Exception/ValidationException.php +++ b/src/Exception/ValidationException.php @@ -2,23 +2,16 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule\Exception; use Exception; use Ray\WebFormModule\FormValidationError; +use Throwable; class ValidationException extends Exception { - public ?FormValidationError $error; - - public function __construct(string $message = '', int $code = 0, ?Exception $e = null, ?FormValidationError $error = null) + public function __construct(string $message = '', int $code = 0, Throwable|null $e = null, public FormValidationError|null $error = null) { parent::__construct($message, $code, $e); - $this->error = $error; } } diff --git a/src/FailureHandlerInterface.php b/src/FailureHandlerInterface.php index 67c0eb0..ce0d3f4 100644 --- a/src/FailureHandlerInterface.php +++ b/src/FailureHandlerInterface.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use Ray\Aop\MethodInvocation; @@ -15,9 +10,7 @@ interface FailureHandlerInterface { /** - * @param AbstractValidation $formValidation * @param MethodInvocation $invocation - * @param AbstractForm $form * * @return mixed */ diff --git a/src/FormFactory.php b/src/FormFactory.php index ae72d72..5fcd813 100644 --- a/src/FormFactory.php +++ b/src/FormFactory.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use Aura\Filter\FilterFactory; @@ -16,13 +11,10 @@ final class FormFactory { /** - * @param string $class - * - * @phpstan-param class-string $class - * * @psalm-param class-string $class + * @phpstan-param class-string $class */ - public function newInstance(string $class) : AbstractForm + public function newInstance(string $class): AbstractForm { $form = new $class(); $form->setBaseDependencies(new Builder(), new FilterFactory(), new HelperLocatorFactory()); diff --git a/src/FormInterface.php b/src/FormInterface.php index ab0e9e0..0caf3e8 100644 --- a/src/FormInterface.php +++ b/src/FormInterface.php @@ -2,24 +2,21 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; +use Aura\Input\Exception\NoSuchInput; + interface FormInterface { /** * Return input element html * - * @throws \Aura\Input\Exception\NoSuchInput - * * @return string + * + * @throws NoSuchInput */ public function input(string $input); /** Return error message */ - public function error(string $input) : string; + public function error(string $input): string; } diff --git a/src/FormValidationError.php b/src/FormValidationError.php index 75b95b8..77929e3 100644 --- a/src/FormValidationError.php +++ b/src/FormValidationError.php @@ -2,31 +2,21 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use function json_encode; + use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; class FormValidationError { - /** @var array */ - private array $value; - - /** - * @param array $value - */ - public function __construct(array $value) + /** @param array $value */ + public function __construct(private array $value) { - $this->value = $value; } - public function __toString() : string + public function __toString(): string { return (string) json_encode($this->value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } diff --git a/src/FormVndErrorModule.php b/src/FormVndErrorModule.php index b8e76f4..2db7c3b 100644 --- a/src/FormVndErrorModule.php +++ b/src/FormVndErrorModule.php @@ -2,18 +2,13 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use Ray\Di\AbstractModule; class FormVndErrorModule extends AbstractModule { - /** {@inheritdoc} */ + /** {@inheritDoc} */ protected function configure() { $this->bind(FailureHandlerInterface::class)->to(VndErrorHandler::class); diff --git a/src/InputValidationInterceptor.php b/src/InputValidationInterceptor.php index fec9ef9..81f2261 100644 --- a/src/InputValidationInterceptor.php +++ b/src/InputValidationInterceptor.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use Ray\Di\Di\Named; diff --git a/src/OnFailureMethodHandler.php b/src/OnFailureMethodHandler.php index 0a10936..e7aa796 100644 --- a/src/OnFailureMethodHandler.php +++ b/src/OnFailureMethodHandler.php @@ -2,30 +2,25 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; -use function call_user_func_array; -use function method_exists; use Ray\Aop\MethodInvocation; use Ray\WebFormModule\Annotation\AbstractValidation; use Ray\WebFormModule\Annotation\FormValidation; use Ray\WebFormModule\Exception\InvalidOnFailureMethod; +use function call_user_func_array; +use function get_class; +use function method_exists; + final class OnFailureMethodHandler implements FailureHandlerInterface { public const FAILURE_SUFFIX = 'ValidationFailed'; /** - * {@inheritdoc} + * {@inheritDoc} * - * @param AbstractValidation $formValidation * @param MethodInvocation $invocation - * @param AbstractForm $form */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) { diff --git a/src/SetAntiCsrfTrait.php b/src/SetAntiCsrfTrait.php index d746279..cee30ae 100644 --- a/src/SetAntiCsrfTrait.php +++ b/src/SetAntiCsrfTrait.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use Aura\Input\AntiCsrfInterface; @@ -15,7 +10,7 @@ trait SetAntiCsrfTrait { #[Inject] - public function setAntiCsrf(AntiCsrfInterface $antiCsrf) : void + public function setAntiCsrf(AntiCsrfInterface $antiCsrf): void { $this->antiCsrf = $antiCsrf; } diff --git a/src/SubmitInterface.php b/src/SubmitInterface.php index d902e64..c9360f7 100644 --- a/src/SubmitInterface.php +++ b/src/SubmitInterface.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; interface SubmitInterface diff --git a/src/ToStringInterface.php b/src/ToStringInterface.php index 3ce4a09..852af69 100644 --- a/src/ToStringInterface.php +++ b/src/ToStringInterface.php @@ -2,15 +2,10 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; /** Return form markup string */ interface ToStringInterface { - public function toString() : string; + public function toString(): string; } diff --git a/src/VndErrorHandler.php b/src/VndErrorHandler.php index a334af4..23a0555 100644 --- a/src/VndErrorHandler.php +++ b/src/VndErrorHandler.php @@ -2,11 +2,6 @@ declare(strict_types=1); -/** - * This file is part of the Ray.WebFormModule package. - * - * @license http://opensource.org/licenses/MIT MIT - */ namespace Ray\WebFormModule; use Ray\Aop\MethodInvocation; @@ -18,11 +13,9 @@ final class VndErrorHandler implements FailureHandlerInterface { /** - * {@inheritdoc} + * {@inheritDoc} * - * @param AbstractValidation $formValidation * @param MethodInvocation $invocation - * @param AbstractForm $form */ public function handle(AbstractValidation $formValidation, MethodInvocation $invocation, AbstractForm $form) { @@ -33,7 +26,7 @@ public function handle(AbstractValidation $formValidation, MethodInvocation $inv throw new ValidationException('Validation failed.', 400, null, $error); } - private function getVndErrorAttribute(ReflectionMethod $method) : VndError|null + private function getVndErrorAttribute(ReflectionMethod $method): VndError|null { $attributes = $method->getAttributes(VndError::class); if ($attributes === []) { @@ -48,7 +41,7 @@ private function getVndErrorAttribute(ReflectionMethod $method) : VndError|null * * @SuppressWarnings(PHPMD.Superglobals) */ - private function makeVndError(AbstractForm $form, ?VndError $vndError = null) : array + private function makeVndError(AbstractForm $form, VndError|null $vndError = null): array { $body = ['message' => 'Validation failed']; $body['path'] = $_SERVER['PATH_INFO'] ?? ''; @@ -57,10 +50,8 @@ private function makeVndError(AbstractForm $form, ?VndError $vndError = null) : return $vndError ? $this->optionalAttribute($vndError) + $body : $body; } - /** - * @return array - */ - private function optionalAttribute(VndError $vndError) : array + /** @return array */ + private function optionalAttribute(VndError $vndError): array { $body = []; if ($vndError->message) { diff --git a/tests/AbstractAuraFormTest.php b/tests/AbstractAuraFormTest.php index 10eba46..d33e6e8 100644 --- a/tests/AbstractAuraFormTest.php +++ b/tests/AbstractAuraFormTest.php @@ -1,25 +1,26 @@ form = (new FormFactory)->newInstance(FakeForm::class); + + $this->form = (new FormFactory())->newInstance(FakeForm::class); } public function testForm() @@ -30,7 +31,7 @@ public function testForm() public function testAntiCsrfForm() { - $this->form->setAntiCsrf(new FakeAntiCsrf); + $this->form->setAntiCsrf(new FakeAntiCsrf()); $this->form->postConstruct(); $formHtml = $this->form->form(); $this->assertSame('
' . PHP_EOL, $formHtml); @@ -42,7 +43,7 @@ public function testInput() $this->assertSame('' . PHP_EOL, (string) $name); } - public function testError() + public function testError(): string { $this->form->fill([]); $data = ['name' => '@invalid@']; @@ -50,15 +51,12 @@ public function testError() $this->assertFalse($isValid); $error = $this->form->error('name'); $this->assertSame('Name must be alphabetic only.', $error); - $html = (string) $this->form; - return $html; + return (string) $this->form; } - /** - * @depends testError - */ - public function tesetInputDataReamainedOnValidationFailure($html) + /** @depends testError */ + public function tesetInputDataReamainedOnValidationFailure(string $html): void { $expected = ''; $this->assertContains($expected, $html); @@ -67,11 +65,11 @@ public function tesetInputDataReamainedOnValidationFailure($html) public function testNotToStringImplemented() { $errNo = $errStr = ''; - set_error_handler(function (int $no, string $str) use (&$errNo, &$errStr) { + set_error_handler(static function (int $no, string $str) use (&$errNo, &$errStr) { $errNo = $no; $errStr = $str; }); - $form = new FakeErrorForm; + $form = new FakeErrorForm(); (string) $form; $this->assertSame(256, $errNo); restore_error_handler(); diff --git a/tests/AbstractFormTest.php b/tests/AbstractFormTest.php index 2e8c9b3..c306eb2 100644 --- a/tests/AbstractFormTest.php +++ b/tests/AbstractFormTest.php @@ -1,10 +1,7 @@ form = (new FormFactory)->newInstance(FakeMiniForm::class); + + $this->form = (new FormFactory())->newInstance(FakeMiniForm::class); } - /** - * @param $method - */ - public function getMethodInvocation(array $arguments) + /** @param array $arguments */ + public function getMethodInvocation(array $arguments): ReflectiveMethodInvocation { // form - $fakeForm = (new FormFactory)->newInstance(FakeMiniForm::class); + $fakeForm = (new FormFactory())->newInstance(FakeMiniForm::class); // controller - $controller = new FakeController; + $controller = new FakeController(); $controller->setForm($fakeForm); // interceptor $interceptor = new AuraInputInterceptor(new VndErrorHandler()); @@ -47,9 +45,7 @@ public function getMethodInvocation(array $arguments) $controller, 'createAction', $arguments, - [ - $interceptor - ] + [$interceptor], ); } @@ -77,25 +73,25 @@ public function testErrorReturnEmpty() public function testClone() { $form = clone $this->form; - (new \ReflectionProperty($form, 'filter'))->setAccessible(true); - (new \ReflectionProperty($this->form, 'filter'))->setAccessible(true); + (new ReflectionProperty($form, 'filter'))->setAccessible(true); + (new ReflectionProperty($this->form, 'filter'))->setAccessible(true); $this->assertNotSame(spl_object_hash($form), spl_object_hash($this->form)); } public function testGetItelator() { $itelator = $this->form->getIterator(); - $this->assertInstanceOf(\Iterator::class, $itelator); + $this->assertInstanceOf(Iterator::class, $itelator); } public function testAntiCsrfViolation() { $this->expectException(CsrfViolationException::class); $session = new Session( - new SegmentFactory, - new CsrfTokenFactory(new Randval(new Phpfunc)), - new FakePhpfunc, - [] + new SegmentFactory(), + new CsrfTokenFactory(new Randval(new Phpfunc())), + new FakePhpfunc(), + [], ); $this->form->setAntiCsrf(new AntiCsrf($session, false)); $this->form->apply([]); diff --git a/tests/AntiCsrfTest.php b/tests/AntiCsrfTest.php index 6b4a6a8..708c81d 100644 --- a/tests/AntiCsrfTest.php +++ b/tests/AntiCsrfTest.php @@ -1,10 +1,7 @@ phpfunc = new FakePhpfunc; + $this->phpfunc = new FakePhpfunc(); $this->session = $this->newSession(); $this->antiCsrf = new AntiCsrf($this->newSession([]), false, [AntiCsrf::TOKEN_KEY => AntiCsrf::TEST_TOKEN]); } @@ -45,7 +34,7 @@ public function testNew() public function testSetField() { - $result = $this->antiCsrf->setField(new Fieldset(new Builder, new Filter)); + $result = $this->antiCsrf->setField(new Fieldset(new Builder(), new Filter())); $this->assertNull($result); } @@ -55,13 +44,14 @@ public function testIsValid() $this->assertTrue($this->antiCsrf->isValid($data)); } - protected function newSession(array $cookies = []) + /** @param array $cookies */ + protected function newSession(array $cookies = []): Session { return new Session( - new SegmentFactory, + new SegmentFactory(), new CsrfTokenFactory(new Randval(new Phpfunc())), $this->phpfunc, - $cookies + $cookies, ); } } diff --git a/tests/AuraInputInterceptorTest.php b/tests/AuraInputInterceptorTest.php index 0b2743e..ff7b065 100644 --- a/tests/AuraInputInterceptorTest.php +++ b/tests/AuraInputInterceptorTest.php @@ -1,10 +1,7 @@ injector = new Injector(new class() extends AbstractModule { + $this->injector = new Injector(new class () extends AbstractModule { protected function configure() { - $this->install(new AuraInputModule); + $this->install(new AuraInputModule()); $this->bind(FormInterface::class)->annotatedWith('contact_form')->to(FakeForm::class); $this->bind(FormInterface::class)->annotatedWith('mini_form')->to(FakeMiniForm::class); } diff --git a/tests/AuraInputModuleTest.php b/tests/AuraInputModuleTest.php index e1d3cfb..d08deed 100644 --- a/tests/AuraInputModuleTest.php +++ b/tests/AuraInputModuleTest.php @@ -1,10 +1,7 @@ getInstance(FakeController::class); $this->assertInstanceOf(WeavedInterface::class, $controller); } @@ -24,8 +21,8 @@ public function testAuraInputModule() public function testExceptionOnFailure() { $this->expectException(ValidationException::class); - $injector = new Injector(new FakeModule, __DIR__ . '/tmp'); - /** @var $controller FakeInputValidationController */ + $injector = new Injector(new FakeModule(), __DIR__ . '/tmp'); + /** @var FakeInputValidationController $controller */ $controller = $injector->getInstance(FakeInputValidationController::class); $controller->createAction(''); } diff --git a/tests/FormFactoryTest.php b/tests/FormFactoryTest.php index e3ecef9..59d6f2a 100644 --- a/tests/FormFactoryTest.php +++ b/tests/FormFactoryTest.php @@ -1,25 +1,21 @@ factory = new FormFactory; + + $this->factory = new FormFactory(); } public function testNewInstance() diff --git a/tests/VndErrorHandlerTest.php b/tests/VndErrorHandlerTest.php index 6f350b4..4db8b3b 100644 --- a/tests/VndErrorHandlerTest.php +++ b/tests/VndErrorHandlerTest.php @@ -1,10 +1,7 @@ controller = (new Injector(new FakeVndErrorModule, __DIR__ . '/tmp'))->getInstance(FakeController::class); + + $this->controller = (new Injector(new FakeVndErrorModule(), __DIR__ . '/tmp'))->getInstance(FakeController::class); } public function testValidationException() @@ -50,8 +46,8 @@ public function testValidationExceptionError() public function testVndErrorAnnotation() { - /** @var $controller FakeControllerVndError */ - $controller = (new Injector(new FakeVndErrorModule))->getInstance(FakeControllerVndError::class); + /** @var FakeControllerVndError $controller */ + $controller = (new Injector(new FakeVndErrorModule()))->getInstance(FakeControllerVndError::class); try { $controller->createAction(''); } catch (ValidationException $e) { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 039e44c..ab50824 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,18 +1,17 @@ Date: Sun, 17 May 2026 11:31:01 +0900 Subject: [PATCH 26/41] docs: document #[CsrfProtection] opt-in and add 0.x migration guide - CHANGELOG: mark #[CsrfProtection] as BC break with antiCsrf=true Before/After example - CHANGELOG: add BC break entry for ValidationException constructor type strengthening - README (EN/JA): explain CSRF protection is opt-in (no check without #[CsrfProtection]) - README (EN/JA): add "Migration from 0.x" mapping table --- CHANGELOG.md | 29 ++++++++++++++++++++++++++--- README.JA.md | 20 +++++++++++++++++++- README.md | 21 +++++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9625f4..dde77f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BC break**: Migrated from Doctrine Annotations to PHP 8 Attributes. All validation metadata (`@FormValidation`, `@InputValidation`, `@VndError`) is now expressed with `#[FormValidation]`, `#[InputValidation]`, `#[VndError]`. -- CSRF protection for validation methods is declared with the separate - `#[CsrfProtection]` attribute instead of a boolean option on - `#[FormValidation]`. +- **BC break**: CSRF protection for validation methods is now declared with + the separate `#[CsrfProtection]` attribute. The previous `antiCsrf=true` + boolean option on `@FormValidation` has been removed. CSRF checks are now + opt-in: methods without `#[CsrfProtection]` perform no CSRF verification + even if the form has an `AntiCsrf` object set. + + Before: + + ```php + /** + * @FormValidation(form="contactForm", antiCsrf=true) + */ + public function createAction() {} + ``` + + After: + + ```php + #[FormValidation(form: 'contactForm')] + #[CsrfProtection] + public function createAction() {} + ``` - **BC break**: `AuraInputInterceptor`, `InputValidationInterceptor` and `VndErrorHandler` no longer accept a `Doctrine\Common\Annotations\Reader` in their constructors. Validation attributes are read directly via @@ -23,6 +42,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BC break**: `FormInterface::input()` and `FormInterface::error()` now declare parameter and return types (`string $input`, `: string` respectively). Implementations must update their signatures. +- **BC break**: `ValidationException::__construct()` now declares parameter + types (`string $message`, `int $code`, `Throwable|null $e`, + `FormValidationError|null $error`). The `$error` property is now typed as + `FormValidationError|null` via constructor property promotion. - Added property type declarations and return types across the codebase to align with PHP 8 typing. - Bumped dependencies: `ray/di` `^2.16`, `ray/aop` `^2.14`, diff --git a/README.JA.md b/README.JA.md index 4dac375..366fb1d 100644 --- a/README.JA.md +++ b/README.JA.md @@ -124,7 +124,9 @@ class MyController ### CSRF Protections -CSRF対策を行うためにはフォームにCSRFオブジェクトをセットします。 +CSRF対策は **opt-in** です。`SetAntiCsrfTrait` を使うフォームには `AntiCsrfInterface` が注入されますが、 +トークンの検証は `#[CsrfProtection]` 属性が付いたメソッドでのみ行われます。 +`#[CsrfProtection]` が無いメソッドでは、フォーム側に AntiCsrf がセットされていても CSRF チェックは実行されません。 ```php use Ray\WebFormModule\AbstractAuraForm; @@ -150,6 +152,22 @@ class MyController セキュリティレベルを高めるためにはユーザーの認証を含んだカスタムCsrfクラスを作成してフォームクラスにセットします。 詳しくはAura.Inputの[Applying CSRF Protections](https://github.com/auraphp/Aura.Input#applying-csrf-protections)をご覧ください。 +## 0.x からのマイグレーション + +1.0 で Doctrine Annotations を廃止し、PHP 8 Attributes に完全移行しました。 +型宣言も強化されています。主な書き換え: + +| Before (0.x) | After (1.0) | +|--------------------------------------------------------------------|---------------------------------------------------------------------------| +| `@FormValidation(form="f", onFailure="badRequest")` | `#[FormValidation(form: 'f', onFailure: 'badRequest')]` | +| `@FormValidation(form="f", antiCsrf=true)` | `#[FormValidation(form: 'f')]` + `#[CsrfProtection]` | +| `@InputValidation(form="f")` | `#[InputValidation(form: 'f')]` | +| `@VndError(message="...", logref="...")` | `#[VndError(message: '...', logref: '...')]` | +| `new AuraInputInterceptor($injector, $reader)` | `new AuraInputInterceptor($injector)` (`Reader` 引数は不要) | +| `public function input($input)` / `public function error($input)` | `public function input(string $input): string` / `error(string $input): string` | + +破壊的変更の完全なリストは [CHANGELOG.md](CHANGELOG.md) を参照してください。 + ## Validation Exception `#[FormValidation]`の代わりに`#[InputValidation]`とアノテートするとバリデーションが失敗したときに`Ray\WebFormModule\Exception\ValidationException`が投げられるよになります。この場合はHTML表現は使われません。Web APIアプリケーションなどに便利です。 diff --git a/README.md b/README.md index 933efe4..2d93640 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,11 @@ or render input element basis. ``` ## CSRF Protections +CSRF protection is **opt-in**. A form that uses `SetAntiCsrfTrait` is wired +with an `AntiCsrfInterface`, but the token is only verified when the +validated method is annotated with `#[CsrfProtection]`. Methods without +`#[CsrfProtection]` perform no CSRF check even if the form supports it. + ```php use Ray\WebFormModule\AbstractAuraForm; use Ray\WebFormModule\Annotation\CsrfProtection; @@ -160,6 +165,22 @@ class MyController ``` You can provide your custom `AntiCsrf` class. See more detail at [Aura.Input](https://github.com/auraphp/Aura.Input#applying-csrf-protections) +## Migration from 0.x + +Version 1.0 drops Doctrine Annotations in favour of native PHP 8 Attributes +and tightens type declarations. The most common rewrites: + +| Before (0.x) | After (1.0) | +|--------------------------------------------------------------------|---------------------------------------------------------------------------| +| `@FormValidation(form="f", onFailure="badRequest")` | `#[FormValidation(form: 'f', onFailure: 'badRequest')]` | +| `@FormValidation(form="f", antiCsrf=true)` | `#[FormValidation(form: 'f')]` + `#[CsrfProtection]` | +| `@InputValidation(form="f")` | `#[InputValidation(form: 'f')]` | +| `@VndError(message="...", logref="...")` | `#[VndError(message: '...', logref: '...')]` | +| `new AuraInputInterceptor($injector, $reader)` | `new AuraInputInterceptor($injector)` (no `Reader` argument) | +| `public function input($input)` / `public function error($input)` | `public function input(string $input): string` / `error(string $input): string` | + +See [CHANGELOG.md](CHANGELOG.md) for the full list of breaking changes. + ## Validation Exception When we install `Ray\WebFormModule\FormVndErrorModule` as following, From 807e30ae7e31d59b505f0fc9f93141de270cf504 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 17 May 2026 11:35:06 +0900 Subject: [PATCH 27/41] docs: add Claude Code skill for 0.x to 1.0 migration - .claude/skills/migrate-to-1.0/SKILL.md: step-by-step migration skill covering annotation -> attribute, antiCsrf=true split into #[CsrfProtection], Reader argument removal, and FormInterface signature updates - README (EN/JA): link the skill from the Migration from 0.x section --- .claude/skills/migrate-to-1.0/SKILL.md | 200 +++++++++++++++++++++++++ README.JA.md | 9 ++ README.md | 10 ++ 3 files changed, 219 insertions(+) create mode 100644 .claude/skills/migrate-to-1.0/SKILL.md diff --git a/.claude/skills/migrate-to-1.0/SKILL.md b/.claude/skills/migrate-to-1.0/SKILL.md new file mode 100644 index 0000000..c8cc95e --- /dev/null +++ b/.claude/skills/migrate-to-1.0/SKILL.md @@ -0,0 +1,200 @@ +--- +name: migrate-to-1.0 +description: Migrate a project consuming ray/web-form-module from 0.x (Doctrine Annotations) to 1.0 (PHP 8 Attributes). Detects @FormValidation / @InputValidation / @VndError annotations, antiCsrf=true options, AuraInputInterceptor Reader arguments, and FormInterface signature mismatches; rewrites them and reports remaining manual steps. +--- + +# Migrate ray/web-form-module 0.x → 1.0 + +Apply this skill in a project that depends on `ray/web-form-module` and is +upgrading from `0.x` to `1.0`. Run it from the project root. + +## Scope + +You will rewrite consumer code only. Do NOT touch files under +`vendor/`. Limit edits to: + +- `src/`, `app/`, `tests/`, `lib/` and similar source roots +- PHP files only (`*.php`) + +Skip generated/cache directories: `var/`, `cache/`, `tmp/`, `build/`, +`node_modules/`, `.git/`. + +## Preflight + +Before changing any file: + +1. Confirm the project's `composer.json` declares `ray/web-form-module`. + If not, abort and tell the user this skill does not apply. +2. Confirm the working tree is clean (`git status`). If not, ask the user + whether to proceed — uncommitted changes will be mixed with this skill's + edits. +3. Bump the constraint to `^1.0` in `composer.json` (`require` section). If + `doctrine/annotations` is in `require` and is no longer used elsewhere, + remove it. + +## Step 1 — Rewrite validation annotations to attributes + +For each PHP file under the source roots: + +### 1a. `@FormValidation` + +Find docblock annotations of the form: + +```php +/** + * @FormValidation(form="contactForm", onFailure="badRequestAction") + */ +public function createAction() +``` + +Rewrite to a native attribute on the method: + +```php +#[FormValidation(form: 'contactForm', onFailure: 'badRequestAction')] +public function createAction() +``` + +Rules: + +- Remove the annotation line from the docblock. If the docblock becomes + empty (only `/**` and `*/`), delete the entire docblock. +- Convert `=` to `:` and double-quoted strings to single-quoted PHP strings. +- Preserve the existing `use Ray\WebFormModule\Annotation\FormValidation;` + import. Add it if missing. + +### 1b. `@FormValidation(... antiCsrf=true)` — **BC break** + +The `antiCsrf` option was removed. Split it into two attributes: + +```php +/** + * @FormValidation(form="contactForm", antiCsrf=true) + */ +``` + +becomes + +```php +#[FormValidation(form: 'contactForm')] +#[CsrfProtection] +``` + +Also add `use Ray\WebFormModule\Annotation\CsrfProtection;`. + +If `antiCsrf=false` (or omitted), drop the option without adding +`#[CsrfProtection]` — methods without the attribute perform no CSRF check. + +### 1c. `@InputValidation` and `@VndError` + +Same treatment as `@FormValidation` (no `antiCsrf` concern). + +```php +/** @InputValidation(form="form1") */ +public function createAction($name) {} +``` + +```php +/** + * @VndError(message="...", logref="a1000", path="/p", href={"_self"="/p"}) + */ +``` + +become + +```php +#[InputValidation(form: 'form1')] +public function createAction($name) {} +``` + +```php +#[VndError(message: '...', logref: 'a1000', path: '/p', href: ['_self' => '/p'])] +``` + +Note the `{...}` (Doctrine array literal) → `[...]` (PHP array) conversion +in `VndError`'s `href`. + +## Step 2 — Drop `Reader` arguments + +`AuraInputInterceptor`, `InputValidationInterceptor`, and `VndErrorHandler` +no longer accept a `Doctrine\Common\Annotations\Reader`. + +Find direct `new` calls and DI module bindings such as: + +```php +new AuraInputInterceptor($injector, $reader) +new InputValidationInterceptor($injector, $reader, $handler) +new VndErrorHandler($reader) +``` + +Drop the `Reader` argument: + +```php +new AuraInputInterceptor($injector) +new InputValidationInterceptor($injector, $handler) +new VndErrorHandler() +``` + +If the project bound `Doctrine\Common\Annotations\Reader` in a Ray.Di +module solely to satisfy these constructors, remove that binding. + +## Step 3 — Update `FormInterface` implementations + +`FormInterface::input()` and `FormInterface::error()` now declare types: + +```php +public function input(string $input): string; +public function error(string $input): string; +``` + +Any project class implementing `FormInterface` (directly, or via +`AbstractForm` override) must match these signatures. Update: + +```php +public function input($input) +public function error($input) +``` + +to: + +```php +public function input(string $input): string +public function error(string $input): string +``` + +## Step 4 — Update `ValidationException` callers + +`ValidationException::__construct(string $message = '', int $code = 0, +?Throwable $e = null, ?FormValidationError $error = null)` — the third +parameter is now `Throwable|null` (was `Exception|null`) and `$error` is +typed `FormValidationError|null`. + +If the project passes a non-`Throwable` value, fix the call site. + +## Step 5 — Verify + +After all rewrites: + +1. Run the project's static analysis (`composer phpstan`, `composer psalm`, + `composer cs` — whichever exist). +2. Run the project's test suite. +3. `grep -rn "@FormValidation\|@InputValidation\|@VndError\|antiCsrf" src/ tests/ app/ 2>/dev/null` + should return nothing relevant. +4. `grep -rn "Doctrine\\\\Common\\\\Annotations\\\\Reader" src/ app/ 2>/dev/null` + should not show usages tied to this package. + +Report to the user: + +- Files modified (count + paths). +- Any annotation occurrence you could not rewrite automatically (e.g., + uncommon formatting). List file:line so the user can finish manually. +- Static analysis / test results. + +## Out of scope + +- Migrating other libraries' annotations (only `Ray\WebFormModule\Annotation\*`). +- Renaming `SetAntiCsrfTrait` — the trait and `setAntiCsrf()` method are + unchanged in 1.0. +- Changes to forms that use `AntiCsrf` without `#[CsrfProtection]` — + flag these to the user: in 1.0 they will silently stop enforcing CSRF. + The user must decide whether to add `#[CsrfProtection]` to the validated + controller method or accept the new behaviour. diff --git a/README.JA.md b/README.JA.md index 366fb1d..c8d4bd6 100644 --- a/README.JA.md +++ b/README.JA.md @@ -168,6 +168,15 @@ class MyController 破壊的変更の完全なリストは [CHANGELOG.md](CHANGELOG.md) を参照してください。 +### Claude Code による自動マイグレーション + +リポジトリ同梱の Claude Code skill +[`.claude/skills/migrate-to-1.0/SKILL.md`](.claude/skills/migrate-to-1.0/SKILL.md) +が上記の書き換え (アノテーション → アトリビュート、`antiCsrf=true` の +`#[CsrfProtection]` 分割、`Reader` 引数削除、`FormInterface` 署名更新) を +AI アシスタントに案内します。利用側プロジェクトの `.claude/skills/` に +ディレクトリをコピーして `/migrate-to-1.0` で起動してください。 + ## Validation Exception `#[FormValidation]`の代わりに`#[InputValidation]`とアノテートするとバリデーションが失敗したときに`Ray\WebFormModule\Exception\ValidationException`が投げられるよになります。この場合はHTML表現は使われません。Web APIアプリケーションなどに便利です。 diff --git a/README.md b/README.md index 2d93640..06d94bb 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,16 @@ and tightens type declarations. The most common rewrites: See [CHANGELOG.md](CHANGELOG.md) for the full list of breaking changes. +### Automated migration with Claude Code + +The repository ships a Claude Code skill at +[`.claude/skills/migrate-to-1.0/SKILL.md`](.claude/skills/migrate-to-1.0/SKILL.md) +that walks an AI assistant through the rewrites above (annotations → +attributes, `antiCsrf=true` split into `#[CsrfProtection]`, `Reader` +argument removal, `FormInterface` signature updates). Copy the directory +into your consuming project's `.claude/skills/` and invoke it via +`/migrate-to-1.0`. + ## Validation Exception When we install `Ray\WebFormModule\FormVndErrorModule` as following, From 643a8c053c3dea2eb776bd8f861009840c62b1af Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 17 May 2026 11:44:33 +0900 Subject: [PATCH 28/41] fix: address CodeRabbit review feedback - tests/AbstractAuraFormTest.php: fix typo so PHPUnit discovers the test (tesetInputDataReamainedOnValidationFailure -> testInputDataRemainedOnValidationFailure) and switch deprecated assertContains to assertStringContainsString - src/AbstractForm.php: guard null in getFailureMessages() (filter may have no failures yet); previously dereferenced a possibly-null return - .github/workflows/static-analysis.yml: fall back to PHP 8.4 when inputs.php_version is unavailable (push/pull_request events) --- .github/workflows/static-analysis.yml | 8 ++++---- src/AbstractForm.php | 7 ++++++- tests/AbstractAuraFormTest.php | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 4af412a..1c3a063 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -25,7 +25,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ inputs.php_version }} + php-version: ${{ inputs.php_version || '8.4' }} tools: cs2pr coverage: none @@ -58,7 +58,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ inputs.php_version }} + php-version: ${{ inputs.php_version || '8.4' }} tools: cs2pr coverage: none @@ -84,7 +84,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ inputs.php_version }} + php-version: ${{ inputs.php_version || '8.4' }} - name: Get composer cache directory id: composer-cache @@ -115,7 +115,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ inputs.php_version }} + php-version: ${{ inputs.php_version || '8.4' }} coverage: none - name: Get composer cache directory diff --git a/src/AbstractForm.php b/src/AbstractForm.php index b5a68f7..c58aa8f 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -181,8 +181,13 @@ public function apply(array $data): bool */ public function getFailureMessages(): array { + $failure = $this->filter->getFailures(); + if ($failure === null) { + return []; + } + /** @var array> $messages */ - $messages = $this->filter->getFailures()->getMessages(); + $messages = $failure->getMessages(); return $messages; } diff --git a/tests/AbstractAuraFormTest.php b/tests/AbstractAuraFormTest.php index d33e6e8..5f48111 100644 --- a/tests/AbstractAuraFormTest.php +++ b/tests/AbstractAuraFormTest.php @@ -56,10 +56,10 @@ public function testError(): string } /** @depends testError */ - public function tesetInputDataReamainedOnValidationFailure(string $html): void + public function testInputDataRemainedOnValidationFailure(string $html): void { $expected = ''; - $this->assertContains($expected, $html); + $this->assertStringContainsString($expected, $html); } public function testNotToStringImplemented() From a2fa31939f14d1bb69bf1cddb36f8b6bed7c6aef Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 17 May 2026 11:47:45 +0900 Subject: [PATCH 29/41] docs: align README.md heading levels with README.JA.md - "CSRF Protections" demoted from ## to ### (it is a Usage subsection) - "Demo" promoted from ### to ## (standalone section) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 06d94bb..b880d99 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ or render input element basis. echo $form->input('name'); // echo $form->error('name'); // "Name must be alphabetic only." or blank. ``` -## CSRF Protections +### CSRF Protections CSRF protection is **opt-in**. A form that uses `SetAntiCsrfTrait` is wired with an `AntiCsrfInterface`, but the token is only verified when the @@ -232,6 +232,6 @@ More detail for `vnd.error+json` can be added with the `#[VndError]` attribute. This optional module is handy for API application. -### Demo +## Demo $ php -S docs/demo/1.csrf/web.php From 684b1320a47611885c17dadb75938c2286c5231c Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 17 May 2026 11:48:56 +0900 Subject: [PATCH 30/41] ci: add PHP 8.5 to the test matrix --- .github/workflows/continuous-integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 47ee88f..71f9c1f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -24,6 +24,7 @@ jobs: - '8.2' - '8.3' - '8.4' + - '8.5' steps: - name: Checkout uses: actions/checkout@v4 From e0c6691d957280af64a191ea3874adbba8c67d11 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 17 May 2026 11:52:15 +0900 Subject: [PATCH 31/41] fix: silence PHPStan identical.alwaysFalse in getFailureMessages The Aura\Filter\SubjectFilter::getFailures() PHPDoc declares a non-null return, but the runtime value can be null before apply() runs. Annotate the local variable as FailureCollection|null so the null guard added in 643a8c0 type-checks (mirrors the pattern already used in error()). --- src/AbstractForm.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AbstractForm.php b/src/AbstractForm.php index c58aa8f..4a0431f 100644 --- a/src/AbstractForm.php +++ b/src/AbstractForm.php @@ -181,6 +181,7 @@ public function apply(array $data): bool */ public function getFailureMessages(): array { + /** @var FailureCollection|null $failure */ $failure = $this->filter->getFailures(); if ($failure === null) { return []; From 70b8ad5e64d37b2327c338b6debfdd38158e24d7 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Sun, 17 May 2026 18:08:37 +0900 Subject: [PATCH 32/41] 1.0.0 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dde77f0..36d4c95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.0] - unreleased +## [1.0.0] - 2026-05-17 ### Changed -- **BC break**: Minimum PHP version raised to `8.0`. +- Minimum PHP version raised to `8.0`. - **BC break**: Migrated from Doctrine Annotations to PHP 8 Attributes. All validation metadata (`@FormValidation`, `@InputValidation`, `@VndError`) is now expressed with `#[FormValidation]`, `#[InputValidation]`, `#[VndError]`. From 3c2446e734a2faf9c9573bd2e8992968e4733543 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Mon, 18 May 2026 17:23:38 +0900 Subject: [PATCH 33/41] Introduce WebFormModule and keep AuraInputModule as alias Add Ray\WebFormModule\WebFormModule whose name lines up with the package (ray/web-form-module) and namespace (Ray\WebFormModule), so the public module class no longer leaks the underlying Aura.Input implementation detail. Ray\WebFormModule\AuraInputModule is preserved as a thin subclass of WebFormModule so existing applications that install 'new AuraInputModule()' continue to work unchanged. The interceptor test keeps using AuraInputModule to lock in that compatibility. Update README, README.JA, the CSRF demo, and the fake module used in tests to install the new WebFormModule. Add CHANGELOG entry for 1.0.1. --- CHANGELOG.md | 15 +++++++++++ README.JA.md | 11 +++++--- README.md | 12 ++++++--- docs/demo/1.csrf/MyModule.php | 2 +- src/AuraInputModule.php | 46 ++++++---------------------------- src/WebFormModule.php | 46 ++++++++++++++++++++++++++++++++++ tests/Fake/FakeModule.php | 2 +- tests/WebFormModuleTest.php | 47 +++++++++++++++++++++++++++++++++++ 8 files changed, 133 insertions(+), 48 deletions(-) create mode 100644 src/WebFormModule.php create mode 100644 tests/WebFormModuleTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d4c95..e0f7962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.1] - 2026-05-18 + +### Added + +- `Ray\WebFormModule\WebFormModule` — module class whose name matches the + package and namespace. Use this in new code. + +### Deprecated + +- `Ray\WebFormModule\AuraInputModule` is now a thin subclass of + `WebFormModule` kept for backwards compatibility. Existing applications + that install `new AuraInputModule()` continue to work without changes, + but should migrate to `new WebFormModule()`. + ## [1.0.0] - 2026-05-17 ### Changed @@ -75,5 +89,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 See git history for changes prior to 1.0.0. +[1.0.1]: https://github.com/ray-di/Ray.WebFormModule/compare/1.0.0...1.0.1 [1.0.0]: https://github.com/ray-di/Ray.WebFormModule/compare/0.6.0...1.0.0 [0.6.0]: https://github.com/ray-di/Ray.WebFormModule/releases/tag/0.6.0 diff --git a/README.JA.md b/README.JA.md index c8d4bd6..1d5c2f3 100644 --- a/README.JA.md +++ b/README.JA.md @@ -18,16 +18,19 @@ Ray.WebFormModuleはアスペクト指向でフォームのバリデーション ```php use Ray\Di\AbstractModule; -use Ray\WebFormModule\AuraInputModule; +use Ray\WebFormModule\WebFormModule; class AppModule extends AbstractModule { protected function configure() { - $this->install(new AuraInputModule); + $this->install(new WebFormModule()); } } ``` + +> 互換性のため `Ray\WebFormModule\AuraInputModule` クラスも `WebFormModule` の薄い +> サブクラスとして残されています。新規コードでは `WebFormModule` を使ってください。 ## Usage ### Form @@ -201,8 +204,8 @@ class FakeVndErrorModule extends AbstractModule { protected function configure() { - $this->install(new AuraInputModule); - $this->override(new FormVndErrorModule); + $this->install(new WebFormModule()); + $this->override(new FormVndErrorModule()); } ``` diff --git a/README.md b/README.md index b880d99..274dca2 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,20 @@ An aspect oriented web form module powered by [Aura.Input](https://github.com/au ```php use Ray\Di\AbstractModule; -use Ray\WebFormModule\AuraInputModule; +use Ray\WebFormModule\WebFormModule; class AppModule extends AbstractModule { protected function configure() { - $this->install(new AuraInputModule); + $this->install(new WebFormModule()); } } ``` + +> The legacy `Ray\WebFormModule\AuraInputModule` class is still available as a thin +> subclass of `WebFormModule` for backwards compatibility. New code should prefer +> `WebFormModule`. ## Usage ### Form class @@ -202,8 +206,8 @@ class FakeVndErrorModule extends AbstractModule { protected function configure() { - $this->install(new AuraInputModule); - $this->override(new FormVndErrorModule); + $this->install(new WebFormModule()); + $this->override(new FormVndErrorModule()); } ``` A `Ray\WebFormModule\Exception\ValidationException` will be thrown. diff --git a/docs/demo/1.csrf/MyModule.php b/docs/demo/1.csrf/MyModule.php index 4e35dba..4dd9905 100644 --- a/docs/demo/1.csrf/MyModule.php +++ b/docs/demo/1.csrf/MyModule.php @@ -7,7 +7,7 @@ class MyModule extends AbstractModule { protected function configure() { - $this->install(new AuraInputModule); + $this->install(new WebFormModule()); $this->bind(FormInterface::class)->annotatedWith('contact_form')->to(ContactForm::class); } } diff --git a/src/AuraInputModule.php b/src/AuraInputModule.php index dc09b81..11da61d 100644 --- a/src/AuraInputModule.php +++ b/src/AuraInputModule.php @@ -4,43 +4,13 @@ namespace Ray\WebFormModule; -use Aura\Filter\FilterFactory; -use Aura\Html\HelperLocatorFactory; -use Aura\Input\AntiCsrfInterface; -use Aura\Input\Builder; -use Aura\Input\BuilderInterface; -use Aura\Input\Filter; -use Aura\Input\FilterInterface; -use Ray\AuraSessionModule\AuraSessionModule; -use Ray\Di\AbstractModule; -use Ray\Di\Scope; -use Ray\WebFormModule\Annotation\FormValidation; -use Ray\WebFormModule\Annotation\InputValidation; - -/** @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class AuraInputModule extends AbstractModule +/** + * Backwards-compatible alias for {@see WebFormModule}. + * + * Use {@see WebFormModule} in new code. This class is kept so existing + * applications that install `new AuraInputModule()` continue to work + * without changes. + */ +class AuraInputModule extends WebFormModule { - /** {@inheritDoc} */ - protected function configure() - { - $this->install(new AuraSessionModule()); - $this->bind(BuilderInterface::class)->to(Builder::class); - $this->bind(FilterInterface::class)->to(Filter::class); - $this->bind(AntiCsrfInterface::class)->to(AntiCsrf::class)->in(Scope::SINGLETON); - $this->bind(FailureHandlerInterface::class)->to(OnFailureMethodHandler::class); - $this->bind(FailureHandlerInterface::class) - ->annotatedWith('vnd_error')->to(VndErrorHandler::class)->in(Scope::SINGLETON); - $this->bind(HelperLocatorFactory::class); - $this->bind(FilterFactory::class); - $this->bindInterceptor( - $this->matcher->any(), - $this->matcher->annotatedWith(InputValidation::class), - [InputValidationInterceptor::class], - ); - $this->bindInterceptor( - $this->matcher->any(), - $this->matcher->annotatedWith(FormValidation::class), - [AuraInputInterceptor::class], - ); - } } diff --git a/src/WebFormModule.php b/src/WebFormModule.php new file mode 100644 index 0000000..d76106f --- /dev/null +++ b/src/WebFormModule.php @@ -0,0 +1,46 @@ +install(new AuraSessionModule()); + $this->bind(BuilderInterface::class)->to(Builder::class); + $this->bind(FilterInterface::class)->to(Filter::class); + $this->bind(AntiCsrfInterface::class)->to(AntiCsrf::class)->in(Scope::SINGLETON); + $this->bind(FailureHandlerInterface::class)->to(OnFailureMethodHandler::class); + $this->bind(FailureHandlerInterface::class) + ->annotatedWith('vnd_error')->to(VndErrorHandler::class)->in(Scope::SINGLETON); + $this->bind(HelperLocatorFactory::class); + $this->bind(FilterFactory::class); + $this->bindInterceptor( + $this->matcher->any(), + $this->matcher->annotatedWith(InputValidation::class), + [InputValidationInterceptor::class], + ); + $this->bindInterceptor( + $this->matcher->any(), + $this->matcher->annotatedWith(FormValidation::class), + [AuraInputInterceptor::class], + ); + } +} diff --git a/tests/Fake/FakeModule.php b/tests/Fake/FakeModule.php index 2e51456..f800aa6 100644 --- a/tests/Fake/FakeModule.php +++ b/tests/Fake/FakeModule.php @@ -10,7 +10,7 @@ class FakeModule extends AbstractModule protected function configure() { $this->bind(Phpfunc::class)->to(FakePhpFunc::class); - $this->install(new AuraInputModule); + $this->install(new WebFormModule()); $this->bind(FormInterface::class)->annotatedWith('contact_form')->to(FakeForm::class); } } diff --git a/tests/WebFormModuleTest.php b/tests/WebFormModuleTest.php new file mode 100644 index 0000000..6254037 --- /dev/null +++ b/tests/WebFormModuleTest.php @@ -0,0 +1,47 @@ +install(new WebFormModule()); + $this->bind(FormInterface::class)->annotatedWith('contact_form')->to(FakeForm::class); + } + }, __DIR__ . '/tmp'); + $controller = $injector->getInstance(FakeController::class); + $this->assertInstanceOf(WeavedInterface::class, $controller); + } + + public function testAuraInputModuleIsAliasOfWebFormModule() + { + $this->assertInstanceOf(WebFormModule::class, new AuraInputModule()); + } + + public function testExceptionOnFailure() + { + $this->expectException(ValidationException::class); + $injector = new Injector(new class () extends AbstractModule { + protected function configure() + { + $this->install(new WebFormModule()); + $this->bind(FormInterface::class)->annotatedWith('contact_form')->to(FakeForm::class); + } + }, __DIR__ . '/tmp'); + /** @var FakeInputValidationController $controller */ + $controller = $injector->getInstance(FakeInputValidationController::class); + $controller->createAction(''); + } +} From c813d8d82d3b231d599375f56ea206212f6fdc8b Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 19 May 2026 00:20:39 +0900 Subject: [PATCH 34/41] ci: suppress Psalm false positives from AbstractModule inheritance chain When AuraInputModule extends WebFormModule extends AbstractModule, Psalm fails to resolve Ray\Di\Bind and Ray\Aop\Pointcut signatures inside the inherited bindInterceptor() body. The vendor code itself is correct and tests pass; this only affects static analysis of the two-level inheritance chain introduced for backwards compatibility. --- psalm.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/psalm.xml b/psalm.xml index 18f8e1d..cd73652 100644 --- a/psalm.xml +++ b/psalm.xml @@ -27,5 +27,21 @@ + + + + + + + + + + + From 300ebc96f5f09f6cb8db6bc7fc0f756324eeea7f Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 19 May 2026 00:42:18 +0900 Subject: [PATCH 35/41] 1.0.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0f7962..27a07e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.1] - 2026-05-18 +## [1.0.1] - 2026-05-19 ### Added From 601f9b8da0e378d3e2d97e7400e7660d779c5cd2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 06:43:57 +0000 Subject: [PATCH 36/41] docs: correct CSRF protection opt-in description README claimed CSRF was only verified when #[CsrfProtection] was present, but SetAntiCsrfTrait alone is sufficient: the trait's #[Inject] setter populates $this->antiCsrf, postConstruct() calls enableAntiCsrf(), and AbstractForm::apply() throws CsrfViolationException on token mismatch regardless of the attribute (covered by AbstractFormTest::testAntiCsrfViolation). Document the two independent opt-in paths (per-form trait, per-action attribute) and fix the migrate-to-1.0 skill's "out of scope" note that implied SetAntiCsrfTrait-only forms silently stop enforcing CSRF in 1.0. --- .claude/skills/migrate-to-1.0/SKILL.md | 11 +++++++---- README.JA.md | 15 ++++++++++++--- README.md | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/.claude/skills/migrate-to-1.0/SKILL.md b/.claude/skills/migrate-to-1.0/SKILL.md index c8cc95e..2884504 100644 --- a/.claude/skills/migrate-to-1.0/SKILL.md +++ b/.claude/skills/migrate-to-1.0/SKILL.md @@ -194,7 +194,10 @@ Report to the user: - Migrating other libraries' annotations (only `Ray\WebFormModule\Annotation\*`). - Renaming `SetAntiCsrfTrait` — the trait and `setAntiCsrf()` method are unchanged in 1.0. -- Changes to forms that use `AntiCsrf` without `#[CsrfProtection]` — - flag these to the user: in 1.0 they will silently stop enforcing CSRF. - The user must decide whether to add `#[CsrfProtection]` to the validated - controller method or accept the new behaviour. +- Migrating `@FormValidation(antiCsrf=true)` callers that did **not** use + `SetAntiCsrfTrait` on the form. These relied on the removed `antiCsrf` + option to enable CSRF; after dropping the option they need + `#[CsrfProtection]` on the validated method (or `SetAntiCsrfTrait` on + the form) to keep enforcement. Flag these to the user so they can decide. + Forms that already use `SetAntiCsrfTrait` continue to enforce CSRF in 1.0 + via `AbstractForm::apply()` regardless of `#[CsrfProtection]`. diff --git a/README.JA.md b/README.JA.md index 1d5c2f3..ec825b8 100644 --- a/README.JA.md +++ b/README.JA.md @@ -127,9 +127,18 @@ class MyController ### CSRF Protections -CSRF対策は **opt-in** です。`SetAntiCsrfTrait` を使うフォームには `AntiCsrfInterface` が注入されますが、 -トークンの検証は `#[CsrfProtection]` 属性が付いたメソッドでのみ行われます。 -`#[CsrfProtection]` が無いメソッドでは、フォーム側に AntiCsrf がセットされていても CSRF チェックは実行されません。 +CSRF対策は **opt-in** で、独立した 2 つの経路のいずれかで有効化できます。 + +- **フォーム単位**: フォームに `use SetAntiCsrfTrait;` を追加します。 + DI で `AntiCsrfInterface` が注入され、`postConstruct()` でトークンフィールドが + 追加され、`apply()` の呼び出しごとにトークンが検証されます。 +- **アクション単位**: バリデーション対象のコントローラメソッドに + `#[CsrfProtection]` を付与します。`AuraInputInterceptor` が `apply()` 実行前に + `AntiCsrfInterface` をフォームへ注入します。 + +どちらの経路でも、トークン不一致時には `AbstractForm::apply()` が +`CsrfViolationException` を throw します。どちらも使わない場合は CSRF 検証は +行われません。 ```php use Ray\WebFormModule\AbstractAuraForm; diff --git a/README.md b/README.md index 274dca2..db92627 100644 --- a/README.md +++ b/README.md @@ -142,10 +142,18 @@ or render input element basis. ``` ### CSRF Protections -CSRF protection is **opt-in**. A form that uses `SetAntiCsrfTrait` is wired -with an `AntiCsrfInterface`, but the token is only verified when the -validated method is annotated with `#[CsrfProtection]`. Methods without -`#[CsrfProtection]` perform no CSRF check even if the form supports it. +CSRF protection is **opt-in** and can be enabled through either of two +independent paths: + +- **Per-form**: add `use SetAntiCsrfTrait;` to the form. `AntiCsrfInterface` + is injected at construction time, the token field is added in + `postConstruct()`, and every `apply()` call verifies the token. +- **Per-action**: annotate the validated controller method with + `#[CsrfProtection]`. `AuraInputInterceptor` then injects + `AntiCsrfInterface` into the form before `apply()` runs. + +Either path causes `AbstractForm::apply()` to throw `CsrfViolationException` +on token mismatch. Without either path, no CSRF check is performed. ```php use Ray\WebFormModule\AbstractAuraForm; From f967e3f898b816882265088a2ae2c47aab82b2b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 07:28:09 +0000 Subject: [PATCH 37/41] docs: address Copilot review feedback on CSRF clarification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reword "injected at construction time" to clarify the trait uses an #[Inject] setter (method injection by Ray.Di), not constructor injection. - Split the README example into two minimal snippets — one per opt-in path — and note that combining both is redundant. The single combined example contradicted the "two independent paths" wording. - Drop the migrate-to-1.0 "out of scope" bullet that overlapped with the automated Step 1b rewrite. Replace it with a note that #[CsrfProtection] added by 1b is redundant when the form already uses SetAntiCsrfTrait, so users can choose a single declaration site. --- .claude/skills/migrate-to-1.0/SKILL.md | 12 +++++------- README.JA.md | 25 +++++++++++++++---------- README.md | 25 +++++++++++++++---------- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/.claude/skills/migrate-to-1.0/SKILL.md b/.claude/skills/migrate-to-1.0/SKILL.md index 2884504..20143f0 100644 --- a/.claude/skills/migrate-to-1.0/SKILL.md +++ b/.claude/skills/migrate-to-1.0/SKILL.md @@ -194,10 +194,8 @@ Report to the user: - Migrating other libraries' annotations (only `Ray\WebFormModule\Annotation\*`). - Renaming `SetAntiCsrfTrait` — the trait and `setAntiCsrf()` method are unchanged in 1.0. -- Migrating `@FormValidation(antiCsrf=true)` callers that did **not** use - `SetAntiCsrfTrait` on the form. These relied on the removed `antiCsrf` - option to enable CSRF; after dropping the option they need - `#[CsrfProtection]` on the validated method (or `SetAntiCsrfTrait` on - the form) to keep enforcement. Flag these to the user so they can decide. - Forms that already use `SetAntiCsrfTrait` continue to enforce CSRF in 1.0 - via `AbstractForm::apply()` regardless of `#[CsrfProtection]`. +- Deciding whether `#[CsrfProtection]` added by Step 1b is redundant. Forms + that already use `SetAntiCsrfTrait` enforce CSRF via `AbstractForm::apply()` + regardless of the attribute, so the attribute Step 1b inserts is harmless + but unnecessary. Flag these to the user so they can drop the attribute if + they prefer a single declaration site. diff --git a/README.JA.md b/README.JA.md index ec825b8..f6b813a 100644 --- a/README.JA.md +++ b/README.JA.md @@ -130,26 +130,22 @@ class MyController CSRF対策は **opt-in** で、独立した 2 つの経路のいずれかで有効化できます。 - **フォーム単位**: フォームに `use SetAntiCsrfTrait;` を追加します。 - DI で `AntiCsrfInterface` が注入され、`postConstruct()` でトークンフィールドが - 追加され、`apply()` の呼び出しごとにトークンが検証されます。 + Ray.Di がトレイトの `#[Inject]` setter 経由で `AntiCsrfInterface` を注入し、 + `postConstruct()` でトークンフィールドが追加され、`apply()` の呼び出しごとに + トークンが検証されます。 - **アクション単位**: バリデーション対象のコントローラメソッドに `#[CsrfProtection]` を付与します。`AuraInputInterceptor` が `apply()` 実行前に `AntiCsrfInterface` をフォームへ注入します。 どちらの経路でも、トークン不一致時には `AbstractForm::apply()` が `CsrfViolationException` を throw します。どちらも使わない場合は CSRF 検証は -行われません。 +行われません。両方を併用しても害はありませんが冗長なので、用途に合わせて +どちらか一方を選んでください。 ```php -use Ray\WebFormModule\AbstractAuraForm; +// アクション単位: コントローラのメソッドで宣言。 use Ray\WebFormModule\Annotation\CsrfProtection; use Ray\WebFormModule\Annotation\FormValidation; -use Ray\WebFormModule\SetAntiCsrfTrait; - -class MyForm extends AbstractAuraForm -{ - use SetAntiCsrfTrait; -} class MyController { @@ -159,6 +155,15 @@ class MyController { } } + +// フォーム単位: フォーム自身で宣言。 +use Ray\WebFormModule\AbstractAuraForm; +use Ray\WebFormModule\SetAntiCsrfTrait; + +class MyForm extends AbstractAuraForm +{ + use SetAntiCsrfTrait; +} ``` セキュリティレベルを高めるためにはユーザーの認証を含んだカスタムCsrfクラスを作成してフォームクラスにセットします。 diff --git a/README.md b/README.md index db92627..7822c0a 100644 --- a/README.md +++ b/README.md @@ -146,25 +146,21 @@ CSRF protection is **opt-in** and can be enabled through either of two independent paths: - **Per-form**: add `use SetAntiCsrfTrait;` to the form. `AntiCsrfInterface` - is injected at construction time, the token field is added in - `postConstruct()`, and every `apply()` call verifies the token. + is injected by Ray.Di through the trait's `#[Inject]` setter, the token + field is added in `postConstruct()`, and every `apply()` call verifies + the token. - **Per-action**: annotate the validated controller method with `#[CsrfProtection]`. `AuraInputInterceptor` then injects `AntiCsrfInterface` into the form before `apply()` runs. Either path causes `AbstractForm::apply()` to throw `CsrfViolationException` -on token mismatch. Without either path, no CSRF check is performed. +on token mismatch. Without either path, no CSRF check is performed. Combining +both paths is harmless but redundant — pick whichever fits your use case. ```php -use Ray\WebFormModule\AbstractAuraForm; +// Per-action: declare CSRF on the controller method. use Ray\WebFormModule\Annotation\CsrfProtection; use Ray\WebFormModule\Annotation\FormValidation; -use Ray\WebFormModule\SetAntiCsrfTrait; - -class MyForm extends AbstractAuraForm -{ - use SetAntiCsrfTrait; -} class MyController { @@ -174,6 +170,15 @@ class MyController { } } + +// Per-form: declare CSRF on the form itself. +use Ray\WebFormModule\AbstractAuraForm; +use Ray\WebFormModule\SetAntiCsrfTrait; + +class MyForm extends AbstractAuraForm +{ + use SetAntiCsrfTrait; +} ``` You can provide your custom `AntiCsrf` class. See more detail at [Aura.Input](https://github.com/auraphp/Aura.Input#applying-csrf-protections) From 9722f7e952a9b10ffdaf7f8b3957ac218622e881 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 10:35:27 +0000 Subject: [PATCH 38/41] docs: split CSRF examples and reconcile migrate-to-1.0 wording - Split the README CSRF examples into two valid stand-alone code blocks (per-action and per-form). The previous single block had `use` statements after `class MyController`, which is not valid PHP in one file. - Reword the Step 1b note ("methods without the attribute perform no CSRF check") so it no longer contradicts the Out-of-scope note about SetAntiCsrfTrait-only forms still enforcing CSRF via AbstractForm::apply(). --- .claude/skills/migrate-to-1.0/SKILL.md | 4 +++- README.JA.md | 8 ++++++-- README.md | 8 ++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.claude/skills/migrate-to-1.0/SKILL.md b/.claude/skills/migrate-to-1.0/SKILL.md index 20143f0..4a3906d 100644 --- a/.claude/skills/migrate-to-1.0/SKILL.md +++ b/.claude/skills/migrate-to-1.0/SKILL.md @@ -82,7 +82,9 @@ becomes Also add `use Ray\WebFormModule\Annotation\CsrfProtection;`. If `antiCsrf=false` (or omitted), drop the option without adding -`#[CsrfProtection]` — methods without the attribute perform no CSRF check. +`#[CsrfProtection]`. The method itself then performs no attribute-driven +CSRF check; the form may still enforce CSRF if it uses `SetAntiCsrfTrait` +(see "Out of scope" below). ### 1c. `@InputValidation` and `@VndError` diff --git a/README.JA.md b/README.JA.md index f6b813a..e25b6ef 100644 --- a/README.JA.md +++ b/README.JA.md @@ -142,8 +142,9 @@ CSRF対策は **opt-in** で、独立した 2 つの経路のいずれかで有 行われません。両方を併用しても害はありませんが冗長なので、用途に合わせて どちらか一方を選んでください。 +アクション単位 — コントローラのメソッドで宣言: + ```php -// アクション単位: コントローラのメソッドで宣言。 use Ray\WebFormModule\Annotation\CsrfProtection; use Ray\WebFormModule\Annotation\FormValidation; @@ -155,8 +156,11 @@ class MyController { } } +``` -// フォーム単位: フォーム自身で宣言。 +フォーム単位 — フォーム自身で宣言: + +```php use Ray\WebFormModule\AbstractAuraForm; use Ray\WebFormModule\SetAntiCsrfTrait; diff --git a/README.md b/README.md index 7822c0a..f8d8a52 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,9 @@ Either path causes `AbstractForm::apply()` to throw `CsrfViolationException` on token mismatch. Without either path, no CSRF check is performed. Combining both paths is harmless but redundant — pick whichever fits your use case. +Per-action — declare CSRF on the controller method: + ```php -// Per-action: declare CSRF on the controller method. use Ray\WebFormModule\Annotation\CsrfProtection; use Ray\WebFormModule\Annotation\FormValidation; @@ -170,8 +171,11 @@ class MyController { } } +``` -// Per-form: declare CSRF on the form itself. +Per-form — declare CSRF on the form itself: + +```php use Ray\WebFormModule\AbstractAuraForm; use Ray\WebFormModule\SetAntiCsrfTrait; From 2bf5fb6d114e1d7f07242b4b5061bb403bda0da9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 11:03:50 +0000 Subject: [PATCH 39/41] docs: fix nonexistent AbstractAuraForm base class reference src/AbstractForm.php only declares AbstractForm; AbstractAuraForm does not exist in this package. The example was inherited from the original CSRF section and never compiled. --- README.JA.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.JA.md b/README.JA.md index e25b6ef..278b46a 100644 --- a/README.JA.md +++ b/README.JA.md @@ -161,10 +161,10 @@ class MyController フォーム単位 — フォーム自身で宣言: ```php -use Ray\WebFormModule\AbstractAuraForm; +use Ray\WebFormModule\AbstractForm; use Ray\WebFormModule\SetAntiCsrfTrait; -class MyForm extends AbstractAuraForm +class MyForm extends AbstractForm { use SetAntiCsrfTrait; } diff --git a/README.md b/README.md index f8d8a52..e592716 100644 --- a/README.md +++ b/README.md @@ -176,10 +176,10 @@ class MyController Per-form — declare CSRF on the form itself: ```php -use Ray\WebFormModule\AbstractAuraForm; +use Ray\WebFormModule\AbstractForm; use Ray\WebFormModule\SetAntiCsrfTrait; -class MyForm extends AbstractAuraForm +class MyForm extends AbstractForm { use SetAntiCsrfTrait; } From b390a58c458b8bda030d1e5517757095ea135397 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 14:36:26 +0000 Subject: [PATCH 40/41] chore: add markdownlint config setting MD046 to fenced README.md and README.JA.md already use fenced code blocks throughout (20+ and 24+ occurrences). The default MD046 rule infers the style from whichever block appears first, so the fenced blocks were reported as errors. Pin the style to fenced to match the established convention. --- .markdownlint.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .markdownlint.json diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..5ea4e95 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,5 @@ +{ + "MD046": { + "style": "fenced" + } +} From 35e2425d18220308840e33cf4afad75b42d42a08 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 04:28:47 +0000 Subject: [PATCH 41/41] docs: add blank line after CSRF per-form code block Satisfies markdownlint MD031 (blanks-around-fences) for the fenced block introduced in the CSRF section. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e592716..fddc5e8 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ class MyForm extends AbstractForm use SetAntiCsrfTrait; } ``` + You can provide your custom `AntiCsrf` class. See more detail at [Aura.Input](https://github.com/auraphp/Aura.Input#applying-csrf-protections) ## Migration from 0.x