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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions .claude/skills/migrate-to-1.0/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -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"
45 changes: 45 additions & 0 deletions .github/workflows/coding-standards.yml
Original file line number Diff line number Diff line change
@@ -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 | cs2pr
57 changes: 57 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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'
- '8.5'
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
Loading
Loading