Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
ff85f46
feat: Work in progress
sitepark-veltrup Sep 17, 2024
748188f
refactor: method signatures
sitepark-veltrup Sep 19, 2024
a046917
feat: add limiter
sitepark-veltrup Sep 19, 2024
6f2aedd
fix: HttpApiProblem detail key
sitepark-veltrup Sep 19, 2024
aa844f0
feat: configurable default processors
sitepark-veltrup Sep 19, 2024
b38ea3a
test: initial implementation
sitepark-veltrup Sep 24, 2024
c7c4d64
style: code style
sitepark-veltrup Sep 24, 2024
ed94200
refactor: php-stan errors
sitepark-veltrup Sep 24, 2024
a9e9be5
test: fix tests
sitepark-veltrup Sep 25, 2024
04be50a
docs: update README.md
sitepark-veltrup Sep 25, 2024
c6a8040
chore: Optimize imports
sitepark-veltrup Sep 25, 2024
b9cc7c0
feat: add lang attribute to FromDefinition
sitepark-veltrup Sep 25, 2024
a71eb1b
test: add template test
sitepark-veltrup Sep 25, 2024
76ff509
docs: update README.md
sitepark-veltrup Sep 25, 2024
7932d9c
Merge branch 'main' into feature/initial-implementation
sitepark-veltrup Dec 2, 2024
1fb8b28
feat: PHP 8.4 support
sitepark-veltrup Dec 13, 2024
ba027cb
docs: update README.md
sitepark-veltrup Dec 13, 2024
fbc8acb
refactor: use stdClass instead object
sitepark-veltrup Dec 17, 2024
9c333dc
refactor: use non-empty-array
sitepark-veltrup Dec 17, 2024
6a3fcb1
docs: add TODO annotation
sitepark-veltrup Dec 17, 2024
e711b69
feat: add attachments for EmailHtmlMessageTwigMjmlRenderer
sitepark-veltrup Dec 17, 2024
42da93b
feat: remove subject from EmailHtmlMessageRendererResult
sitepark-veltrup Dec 17, 2024
e7b0f83
refactor: format date in template first
sitepark-veltrup Dec 17, 2024
0ce2861
refactor: format date in template first
sitepark-veltrup Dec 17, 2024
75674b4
docs: add TODO annotation
sitepark-veltrup Dec 17, 2024
45e7ad9
more precise array typing for buttons
sitepark-veltrup Dec 17, 2024
9d75977
docs: remove unnecessary comment
sitepark-veltrup Dec 17, 2024
c608ae0
docs: add jsonforms links for Control Dto
sitepark-veltrup Dec 17, 2024
e7140da
test: fix tests
sitepark-veltrup Dec 17, 2024
c53a769
docs: add see link
sitepark-veltrup Dec 17, 2024
b0a6ee9
refactor: Control::scope can not be null
sitepark-veltrup Dec 17, 2024
d25419d
chore: update atoolo-search-bundle dependency from dev-main to 1.3.0
sitepark-veltrup Dec 17, 2024
8125416
fix: checkbox, checkbox-group value email rendering
sitepark-veltrup Jan 29, 2025
224c537
fix: null value handling for form-data
sitepark-veltrup Jan 29, 2025
edbdede
fix: date field
sitepark-veltrup Jan 29, 2025
7a79d3c
chore: activate php 8.4 support
sitepark-veltrup Jan 30, 2025
9b97d58
fix: translate also error required messages
sitepark-veltrup Feb 24, 2025
8d76c38
feat: add email plain text messages with twig template
sitepark-veltrup Oct 20, 2025
7ef4b4b
test: fix tests
sitepark-veltrup Oct 21, 2025
b69bf96
feat: recipient field support
sitepark-veltrup Nov 4, 2025
590cbe0
fix: read undefined array key 'items'
sitepark-veltrup Nov 12, 2025
7e0fe5a
test: fix test
sitepark-veltrup Nov 12, 2025
8932864
fix: nested upload control
sitepark-veltrup Dec 8, 2025
3757e7d
fix: email-address name not required
sitepark-veltrup Jan 27, 2026
c378e16
fix: ignore multiple addresses
sitepark-veltrup Feb 25, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:
with:
botName: "sitepark-bot"
botEmail: "opensource@sitepark.com"
phpVersion: "8.4"
secrets:
# Sitepark-BOT personal access token
BOT_PAT: ${{ secrets.BOT_PAT }}
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ on:
jobs:
verify:
uses: sitepark/github-project-workflow/.github/workflows/composer-verify.yml@release/1.x
with:
phpVersions: '["8.2","8.3","8.4"]'
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/vendor
/vendor.orig
composer.lock
.idea
/var/cache/*
!var/cache/.gitkeep
Expand All @@ -8,3 +9,4 @@
/tools
.phpactor.json
*.swp
.claude/
10 changes: 5 additions & 5 deletions .phive/phars.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpstan" version="^1.10.22" location="./tools/phpstan" copy="false" installed="1.11.7"/>
<phar name="composer-normalize" version="^2.32.0" location="./tools/composer-normalize" copy="false" installed="2.43.0"/>
<phar name="phpunit" version="^10.4.0" location="./tools/phpunit.phar" copy="true" installed="10.5.26"/>
<phar name="overtrue/phplint" version="^9.0.4" location="./tools/phplint" copy="false" installed="9.4.1"/>
<phar name="php-cs-fixer" version="^3.58.1" installed="3.59.3" copy="false" location="./tools/php-cs-fixer"/>
<phar name="phpstan" version="^1.10.22" location="./tools/phpstan" copy="false" installed="1.12.12"/>
<phar name="composer-normalize" version="^2.32.0" location="./tools/composer-normalize" copy="false" installed="2.45.0"/>
<phar name="phpunit" version="^10.4.0" location="./tools/phpunit.phar" copy="true" installed="10.5.39"/>
<phar name="overtrue/phplint" version="^9.0.4" location="./tools/phplint" copy="false" installed="9.5.5"/>
<phar name="php-cs-fixer" version="^3.58.1" installed="3.65.0" copy="false" location="./tools/php-cs-fixer"/>
</phive>
344 changes: 344 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**Atoolo Form Bundle** is a Symfony bundle providing an HTTP interface for displaying and processing forms using [JSON Forms](https://jsonforms.io/). It integrates with the Atoolo Resource Bundle to load form definitions from resources and processes submissions through a configurable processor pipeline.

- **Language**: PHP 8.1+ (tested on 8.2, 8.3, 8.4)
- **Framework**: Symfony 6.3+ / 7.0+
- **License**: MIT
- **Documentation**: https://sitepark.github.io/atoolo-docs/develop/bundles/form/

## Development Commands

### Setup
```bash
# Install dependencies
composer install

# Install PHAR tools via Phive (runs automatically post-install)
phive install --force-accept-unsigned --trust-gpg-keys C00543248C87FB13,4AA394086372C20A,CF1A108D0E7AE720,51C67305FFC2E5C0,E82B2FB314E9906E
```

### Testing
```bash
# Run all tests with coverage
composer test
./tools/phpunit.phar -c phpunit.xml --coverage-text

# Run specific test file
./tools/phpunit.phar test/Service/FormDefinitionLoaderTest.php

# Run specific test method
./tools/phpunit.phar --filter testMethodName test/Path/To/TestFile.php

# Mutation testing (8 threads, covered code only)
composer test:infection
vendor/bin/infection --threads=8 --no-progress --only-covered -s
```

### Code Quality
```bash
# Run all analysis checks
composer analyse

# Individual checks
composer analyse:phplint # PHP syntax linting
composer analyse:phpstan # Static analysis (Level 9)
composer analyse:phpcsfixer # Code style checking (PER-CS)
composer analyse:compatibilitycheck # PHP compatibility check

# Fix code style issues
composer cs-fix
./vendor/bin/phpcbf
./tools/php-cs-fixer fix

# Generate PHPStan report (XML)
composer report:phpstan
```

### Build Outputs
- PHPUnit coverage: `var/log/clover/` (HTML + XML)
- PHPUnit results: `var/log/surefire-reports/surefire-report.xml`
- PHPStan report: `var/log/phpstan-report.xml`
- Cache directories: `var/cache/`

## Architecture

### Layered Architecture

```
┌─────────────────────────────────────────────────────────┐
│ HTTP/Controller Layer │
│ FormController (REST API) │
└──────────────────────────┬──────────────────────────────┘
┌──────────────────────────┴──────────────────────────────┐
│ Service/Application Layer │
│ FormDefinitionLoader SubmitHandler FormReader │
│ Email Services JsonSchemaValidator │
└──────────────────┬──────────────────────────────────────┘
┌──────────────────┴──────────────────────────────────────┐
│ Processor Pipeline Layer │
│ IpAllower → IpBlocker → IpLimiter → SubmitLimiter │
│ → SpamDetector → JsonSchemaValidator → EmailSender │
└──────────────────┬──────────────────────────────────────┘
┌──────────────────┴──────────────────────────────────────┐
│ Data Transfer Objects │
│ FormDefinition FormSubmission UISchema Elements │
└──────────────────────────────────────────────────────────┘
```

### Key Components

#### Controllers (`src/Controller/`)
- **FormController** - REST API with two endpoints:
- `GET /api/form/{locale}/{location}/{component}` - Load form definition
- `POST /api/form/{locale}/{location}/{component}` - Submit form data
- Handles multilocale support via Atoolo Resource Bundle

#### DTOs (`src/Dto/`)
- **Core DTOs**: FormDefinition, FormSubmission, UploadFile
- **UISchema DTOs** (`Dto/UISchema/`): Element, Layout, Control, Annotation, Type, Role
- Polymorphic deserialization using Symfony Serializer discriminator mapping
- **Email DTOs** (`Dto/Email/`): EmailHtmlMessageRendererResult

#### Services (`src/Service/`)
- **FormDefinitionLoader** - Loads and transforms form configurations from Atoolo resources (FormEditor model → JSON Forms format)
- **SubmitHandler** - Orchestrates processor pipeline execution
- **FormDataModelFactory** - Converts submissions to email message models
- **FormReader** - Traverses form structure to extract field data
- **JsonSchemaValidator** - Extended JSON Schema Draft 2020-12 validation with custom format constraints
- **LabelTranslator** - Handles i18n for form labels

**Email Services** (`src/Service/Email/`):
- **EmailMessageModelFactory** - Builds email data model from form submissions
- **EmailHtmlMessageRenderer** (abstract) - Base email rendering interface
- **EmailHtmlMessageTwigRenderer** - Twig-based email rendering
- **EmailHtmlMessageTwigMjmlRenderer** - MJML-based responsive email rendering
- **CsvGenerator** - Creates CSV attachments from form data
- **MjmlRenderer** - MJML compilation wrapper

**Validation Services** (`src/Service/JsonSchemaValidator/`):
- **FormatConstraint** interface - Custom JSON Schema format validators
- **PhoneConstraint**, **HtmlConstraint**, **DataUrlConstraint** - Format validators
- **Draft202012Extended** - Extended JSON Schema validator

#### Processor Pipeline (`src/Processor/`)

All processors implement `SubmitProcessor` interface:
```php
interface SubmitProcessor {
public function process(FormSubmission $submission, array $options): FormSubmission;
}
```

**Available Processors** (execution order by priority):
1. **IpLimiter** (80) - Rate limits by IP address (Symfony RateLimiter)
2. **SubmitLimiter** (70) - Global submission rate limiting
3. **SpamDetector** - Spam detection logic
4. **IpAllower** - IP whitelist validation
5. **IpBlocker** - IP blacklist validation
6. **JsonSchemaValidator** - Schema validation
7. **EmailSender** - Email delivery via Symfony Mailer

Processors can set `$submission->approved = true` to skip subsequent processors.

### Data Flow Patterns

**Form Load Flow:**
1. Request → FormController::definition()
2. FormDefinitionLoader loads from Atoolo Resource
3. Deserializes UISchema to typed objects (discriminator mapping)
4. LabelTranslator applies i18n
5. Returns FormDefinition JSON response

**Form Submit Flow:**
1. Request → FormController::submit()
2. Creates FormSubmission with client IP
3. SubmitHandler chains processors (IP checks → rate limits → validation → email)
4. Each processor can approve or reject submission
5. Returns 200 on success, API Problem on failure

**Email Generation:**
1. FormDataModelFactory extracts data from submission
2. EmailMessageModelFactory creates structured email model
3. Renderer (Twig/MJML) generates HTML body
4. CsvGenerator creates optional attachments
5. Symfony Mailer sends email

## Code Quality Standards

### PHPStan Configuration (Level 9)
- Configuration: `phpstan.neon.dist`
- Custom type aliases for complex email model structures
- Cache: `var/cache/phpstan`
- Analyzes: `src/` directory only

### PHP-CS-Fixer (PER-CS Standard)
- Configuration: `.php-cs-fixer.dist.php`
- Rules: `@PER-CS` (PSR-12 compatible modern standard)
- Scans: `src/` and `test/` directories
- Cache: `var/cache/php-cs-fixer`

### PHPUnit Configuration
- Configuration: `phpunit.xml`
- Framework: PHPUnit 10.4+
- Execution: Random order for test independence
- Coverage: Clover XML + HTML reports
- Logging: JUnit XML format
- Memory limit: 512M

### Testing Standards
- **One assertion per test** - Each test method must contain exactly one assertion
- **MANDATORY assertion messages** - Every assertion must include a descriptive message explaining what is being tested
- **Test complete objects** - Use object comparison, not field-by-field assertions
- **Static imports** - All assertion methods must use static imports
- **Test structure**: arrange-act-assert pattern

Example test structure:
```php
use PHPUnit\Framework\TestCase;
use function PHPUnit\Framework\assertEquals;

class ExampleTest extends TestCase {
#[Test]
public function loadsFormDefinitionWithAllProperties(): void {
// arrange
$loader = new FormDefinitionLoader(/* deps */);
$location = ResourceLocation::of(/* ... */);

// act
$definition = $loader->load($location);

// assert
assertEquals(
$expectedDefinition,
$definition,
"Form definition should be loaded with all properties intact including schema, UISchema, and processors"
);
}
}
```

## Project-Specific Patterns

### UISchema Polymorphic Deserialization
UISchema elements use Symfony Serializer discriminator mapping:
```php
#[DiscriminatorMap(typeProperty: 'type', mapping: [
'HorizontalLayout' => HorizontalLayout::class,
'VerticalLayout' => VerticalLayout::class,
// ...
])]
abstract class Element { /* ... */ }
```

### Processor Configuration
Default processors are configured in `config/services.yaml`:
```yaml
Atoolo\Form\Service\SubmitHandler:
arguments:
$defaultProcessors:
- '@Atoolo\Form\Processor\IpLimiter'
- '@Atoolo\Form\Processor\SubmitLimiter'
- '@Atoolo\Form\Processor\JsonSchemaValidator'
```

Individual forms can override via `FormDefinition::$processors`.

### Immutable DTOs
All DTOs use `readonly` properties with constructor injection:
```php
readonly class FormDefinition {
public function __construct(
public JsonSchema $schema,
public Element $uiSchema,
public FormMessages $messages,
// ...
) {}
}
```

### Exception to API Problem Transformation
Custom exceptions are transformed to RFC 9457 API Problems via `ExceptionTransformer`:
- `FormNotFoundException` → 404 Not Found
- `AccessDeniedException` → 403 Forbidden
- `LimitExceededException` → 429 Too Many Requests
- `SpamDetectedException` → 422 Unprocessable Entity

### Custom JSON Schema Format Constraints
Extend `FormatConstraint` interface for custom format validators:
```php
class PhoneConstraint implements FormatConstraint {
public function validate(mixed $value): ?ValidationError {
// Phone number validation logic
}
}
```

Register in service configuration with tag:
```yaml
Atoolo\Form\Service\JsonSchemaValidator\FormatConstraint\PhoneConstraint:
tags:
- { name: 'atoolo_form.format_constraint', format: 'phone' }
```

## Important File Locations

| Path | Purpose |
|------|---------|
| `src/AtooloFormBundle.php` | Bundle entry point |
| `src/Controller/FormController.php` | REST API endpoints |
| `src/Service/FormDefinitionLoader.php` | Form configuration loading |
| `src/Service/SubmitHandler.php` | Processor pipeline orchestration |
| `src/Processor/SubmitProcessor.php` | Processor interface |
| `config/services.yaml` | Service registration & default processors |
| `config/routes.yaml` | API route definitions |
| `templates/email.text.twig` | Email body template |
| `templates/email.text.summary.twig` | Email summary template |

## Dependencies

**Core Symfony Bundles:**
- symfony/framework-bundle ^7.1
- symfony/mailer ^7.1
- symfony/rate-limiter ^7.1
- symfony/serializer ^7.1
- symfony/validator ^7.1

**Domain-Specific:**
- atoolo/resource-bundle ^1.3 - Atoolo resource loading
- opis/json-schema ^2.3 - JSON Schema validation
- phpro/api-problem-bundle ^1.7 - RFC 9457 API Problems

**Email/Document:**
- twig/markdown-extra ^3.21 - Markdown support
- league/csv ^9.16 - CSV generation
- league/html-to-markdown ^5.1 - HTML to Markdown conversion

## Configuration Files

- `composer.json` - Dependencies, scripts, autoloading
- `phpunit.xml` - PHPUnit configuration
- `phpstan.neon.dist` - PHPStan Level 9 configuration with custom type aliases
- `.php-cs-fixer.dist.php` - PER-CS code style rules
- `phpcs.compatibilitycheck.xml` - PHP compatibility checking (8.1-8.4)
- `config/services.yaml` - Symfony service container configuration
- `config/routes.yaml` - API route definitions
- `config/rate_limiter.yaml` - Rate limiter configuration

## Tools (Managed via Phive)

PHAR tools are symlinked in `tools/` directory:
- `tools/phpunit.phar` - PHPUnit test runner (included directly)
- `tools/phpstan` → `~/.phive/phars/phpstan-*.phar`
- `tools/php-cs-fixer` → `~/.phive/phars/php-cs-fixer-*.phar`
- `tools/phplint` → `~/.phive/phars/overtrue/phplint-*.phar`
- `tools/composer-normalize` → `~/.phive/phars/composer-normalize-*.phar`

All tools are version-locked and installed via Phive (PHP Version Manager) for reproducible builds.
Loading