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
8 changes: 4 additions & 4 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Setup PHP 8.4
- name: Setup PHP 8.5
uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
php-version: "8.5"
tools: composer:v2
coverage: none

Expand All @@ -32,9 +32,9 @@ jobs:
path: |
vendor
~/.composer/cache/files
key: ${{ runner.os }}-php-8.4-composer-${{ hashFiles('**/composer.lock') }}
key: ${{ runner.os }}-php-8.5-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-8.4-composer-
${{ runner.os }}-php-8.5-composer-

- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-interaction
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Tag Release

on:
pull_request:
types: [closed]
branches: [main]

permissions:
contents: write

jobs:
tag:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
fetch-tags: true

- name: Get latest tag and compute next patch version
id: version
run: |
latest=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
if [ -z "$latest" ]; then
echo "next=v0.0.1" >> "$GITHUB_OUTPUT"
else
major=$(echo "$latest" | cut -d. -f1)
minor=$(echo "$latest" | cut -d. -f2)
patch=$(echo "$latest" | cut -d. -f3)
next_patch=$((patch + 1))
echo "next=${major}.${minor}.${next_patch}" >> "$GITHUB_OUTPUT"
fi
echo "Latest tag: ${latest:-none}, next: $(cat "$GITHUB_OUTPUT" | grep next | cut -d= -f2)"

- name: Create and push tag
run: |
git tag "${{ steps.version.outputs.next }}"
git push origin "${{ steps.version.outputs.next }}"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
/vendor/
/var/
composer.lock
.claude/settings.local.json
.claude/agent-memory/
.phpunit.cache/
121 changes: 101 additions & 20 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,105 @@
- `composer update` refreshes dependency versions per `composer.json`.
- `./vendor/bin/phpunit` runs the test suite using `phpunit.xml.dist`.

## Coding Style & Naming Conventions
- PHP 8.4 with `declare(strict_types=1);` at the top of PHP files.
- PSR-4 autoloading: `ChamberOrchestra\FormBundle\` maps to `src/`.

## Code Conventions

- PSR-12 style, `declare(strict_types=1)` in every file, 4-space indent
- View classes end with `View` suffix; utilities use verb naming (`BindUtils`, `ReflectionService`)
- Typed properties and return types; favor `readonly` where appropriate
- JSON structures should be explicit — avoid leaking nulls
- Namespace: `ChamberOrchestra\FormBundle\*` (PSR-4 from `src/`)
- Class names use `PascalCase` (e.g., `UniqueFieldValidator`), methods and variables use `camelCase`.
- Keep one class/interface/trait per file, matching the filename.
- No formatter or linter is configured; follow existing code style (PSR-12 conventions).

## Testing Guidelines
- PHPUnit is the test framework; config lives in `phpunit.xml.dist`.
- Tests live under `tests/` with namespaces rooted at `Tests\`.
- Prefer descriptive test class names aligned with the subject under `src/` (e.g., `FooTypeTest`).
- Run tests locally before submitting changes: `./vendor/bin/phpunit`.

## Commit & Pull Request Guidelines
- Git history is minimal and does not define a commit message convention. Use clear, imperative summaries (e.g., "Add form transformer for JSON input").
- PRs should include a short description, test results, and any relevant context or screenshots for UI changes.
- Link related issues if they exist and call out any backward-compatibility concerns.

## Configuration Notes
- Runtime requirements are in `composer.json` (PHP 8.4, Symfony 8 components, Doctrine ORM).
- Test kernel is configured via `KERNEL_CLASS=Tests\Integrational\TestKernel` in `phpunit.xml.dist`.
- Follow a consistent formatting style.
- Use clear, descriptive names for variables, functions, and classes.
- Avoid non-standard abbreviations.
- Each function should have a single, well-defined responsibility.

## Testing

- PHPUnit 12.x; tests in `tests/` autoloaded as `Tests\`
- **Unit tests** (`tests/Unit/`) extend `TestCase`; mirror source structure
- **Integration tests** (`tests/Integrational/`) extend `KernelTestCase`; use `Tests\Integrational\TestKernel` (minimal kernel with FrameworkBundle + ChamberOrchestraViewBundle)
- Tests reset `BindUtils` and `ReflectionService` static state between runs
- Use data providers for mapping scenarios and cache behavior
- Write code that is easy to test.
- Avoid hard dependencies; use dependency injection where appropriate.
- Do not hardcode time, randomness, UUIDs, or global state.

## Commit Style

Short, action-oriented messages with optional bracketed scope: `[fix] ensure nulls are stripped`, `[master] bump version`. Keep commits focused; avoid unrelated formatting churn.

## General Coding Principles

- Write production-quality code, not illustrative examples.
- Prefer simple, readable solutions over clever ones.
- Avoid premature optimization.
- Do not introduce architectural complexity without clear justification.
- Follow Symfony bundle and directory conventions.
- Use Dependency Injection; never fetch services from the container.
- Do not use static service locators.
- Prefer configuration via services.yaml over hardcoding.
- Use autowiring and autoconfiguration where possible.
- Follow PSR-12 coding standards.
- Use strict types.
- Prefer typed properties and return types everywhere.
- Avoid magic methods unless explicitly required.
- Do not rely on global state or superglobals.

## Structure and Architecture

- Separate business logic, infrastructure, and presentation layers.
- Do not mix side effects with pure logic.
- Minimize coupling between modules.
- Prefer composition to inheritance.
- Services must be small and focused.
- One class — one responsibility.
- Constructor injection only.
- Do not inject the container itself.
- Prefer interfaces for public-facing services.


## Error Handling and Edge Cases

- Handle errors explicitly.
- Never silently swallow exceptions.
- Validate all inputs.
- Consider edge cases and empty or null values.
- Use domain-specific exceptions.
- Do not catch exceptions unless you can handle them meaningfully.
- Fail fast on invalid state.
- Write code that is unit-testable by default.
- Avoid hard dependencies on time, randomness, or static state.
- Use interfaces or abstractions for external services.

## Performance and Resources

- Avoid unnecessary allocations and calls.
- Prevent N+1 queries.
- Assume the code may run on large datasets.

## Documentation and Comments

- Do not comment obvious code.
- Explain *why*, not *what*.
- Add comments when logic is non-trivial.
- Document public services and extension points.
- Comment non-obvious decisions, not implementation details.

## Working with Existing Code

- Preserve the existing codebase style and conventions.
- Do not refactor unrelated code.
- Make the smallest change necessary.

## Assistant Behavior

- Ask clarifying questions if requirements are ambiguous.
- If multiple solutions exist, choose the best one and briefly justify it.
- Avoid deprecated or experimental APIs unless explicitly requested.

## Backward Compatibility

- Do not introduce BC breaks without explicit instruction.
- Follow Symfony bundle versioning and deprecation practices.
91 changes: 91 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# CLAUDE.md

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

## Project Overview

ChamberOrchestra Form Bundle is a Symfony bundle that streamlines JSON-first form handling for APIs. It provides controller helpers, specialized form types, data transformers, and RFC 7807-style error views for consistent API responses.

## Build and Test Commands

```bash
# Install dependencies
composer install

# Run all tests
./vendor/bin/phpunit

# Run specific test file
./vendor/bin/phpunit tests/Unit/FormTraitTest.php

# Run tests in specific directory
./vendor/bin/phpunit tests/Unit/Transformer/
```

## Architecture

### Core Traits

**FormTrait** (`src/FormTrait.php`): Base trait for form handling in controllers. Provides methods for creating responses (`createSuccessResponse()`, `createValidationFailedResponse()`, `createRedirectResponse()`), handling form submission flow (`handleFormCall()`, `onFormSubmitted()`), and serializing form errors into structured violations.

**ApiFormTrait** (`src/ApiFormTrait.php`): Extends `FormTrait` for API controllers. Key method is `handleApiCall()` which automatically handles JSON payloads for `MutationForm` types and merges file uploads. Uses `convertRequestToArray()` to parse JSON content and merge with uploaded files.

### Form Type Hierarchy

**API Base Types** (`src/Type/Api/`):
- `GetForm`: For GET requests, sets method to GET
- `PostForm`: For POST requests, sets method to POST
- `QueryForm`: Extends `GetForm`, disables CSRF
- `MutationForm`: Extends `PostForm`, disables CSRF for JSON mutations

All API form types use empty `block_prefix` to avoid HTML name prefixes in JSON responses.

**Custom Types** (`src/Type/`):
- `BooleanType`: Text input that transforms to boolean via `TextToBoolTransformer`
- `TimestampType`: Integer input that transforms to DateTime via `DateTimeToNumberTransformer`
- `HiddenEntityType`: Hidden field that loads entities by ID from Doctrine repositories

### Data Transformers

Located in `src/Transformer/`:
- `TextToBoolTransformer`: Converts string values ("true", "1", "yes") to boolean
- `DateTimeToNumberTransformer`: Converts Unix timestamps to DateTime objects
- `ArrayToStringTransformer`: Converts arrays to comma-separated strings
- `JsonStringToArrayTransformer`: Parses JSON strings to arrays

### View Types

All views extend `ChamberOrchestra\ViewBundle\View\ViewInterface` (from chamber-orchestra/view-bundle):
- `FailureView`: Generic error response with HTTP status
- `ValidationFailedView`: Form validation errors (422 status) with structured violations
- `ViolationView`: Individual field violation with id, message, parameters, and path
- `RedirectView`: Redirect response for AJAX requests
- `SuccessHtmlView`: HTML fragment response for AJAX requests

### Validation

**UniqueField** constraint (`src/Validator/Constraints/`): Validates field uniqueness against Doctrine repositories. Use `repositoryMethod` to specify custom query method, `fields` to check multiple columns, and `errorPath` to target specific form field.

### Service Configuration

Services are autowired and autoconfigured via `src/Resources/config/services.php`. The config excludes `DependencyInjection`, `Resources`, `Exception`, `Transformer`, and `View` directories from auto-loading.

## Testing

- **Unit tests**: `tests/Unit/` - Test individual classes in isolation
- **Integration tests**: `tests/Integrational/` - Test bundle integration with Symfony and Doctrine
- **Test kernel**: `tests/Integrational/TestKernel.php` boots minimal Symfony application with FrameworkBundle, ChamberOrchestraViewBundle, ChamberOrchestraFormBundle, and optionally DoctrineBundle with in-memory SQLite

When writing tests, follow the existing pattern: Unit tests under `tests/Unit/` mirroring the `src/` structure, integration tests under `tests/Integrational/` for service wiring and Doctrine integration.

## Code Style

- PHP 8.5+ with strict types (`declare(strict_types=1);`)
- PSR-4 autoloading: `ChamberOrchestra\FormBundle\` → `src/`
- One class/interface/trait per file matching the filename
- Follow existing code formatting (PSR-12 conventions)

## Dependencies

- Requires Symfony 8.0 components, PHP 8.5, Doctrine ORM 3.6, and chamber-orchestra/view-bundle 8.0
- Main branch is `8.0` for Symfony 8 compatibility
Loading