Metadata-Version: 2.1
Name: wexample-filestate-php
Version: 6.4.8
Summary: Extends wexample-filestate with a PHP file type and a PHP-CS-Fixer option that enforces code style on .php files via Docker
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Project-URL: homepage, https://github.com/wexample/python-filestate-php
Requires-Python: >=3.10
Requires-Dist: attrs>=23.1.0
Requires-Dist: cattrs>=23.1.0
Requires-Dist: wexample-filestate>=17.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# filestate_php

Version: 6.4.8

`wexample-filestate-php` adds PHP support to `wexample-filestate`: a `PhpFile` target type that enforces the `.php` extension, and a `phpcs_fixer` option that drives PHP-CS-Fixer inside a dedicated Docker container to rewrite source files to a consistent code style. It is aimed at Python developers who use the wexample-filestate declarative framework to manage and enforce the state of PHP codebases.

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Tests](#tests)
- [Architecture](#architecture)
- [Integration in the Suite](#integration-in-the-suite)
- [Dependencies](#dependencies)
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
- [License](#license)
- [About us](#about-us)
- [Known Limitations & Roadmap](#known-limitations--roadmap)
- [Status & Compatibility](#status--compatibility)
- [Useful Links](#useful-links)
- [Migration Notes](#migration-notes)

## Installation

```bash
pip install wexample-filestate-php
```

Requires Python >=3.10.

## Quickstart

Install the package:

```bash
pip install wexample-filestate-php
```

Register `PhpOptionsProvider` when you create the manager, then set `"class": PhpFile` on any child that should be treated as a PHP file and `"php": {"phpcs_fixer": True}` to enforce code style:

```python
from pathlib import Path

from wexample_filestate.options_provider.default_options_provider import DefaultOptionsProvider
from wexample_filestate.utils.file_state_manager import FileStateManager
from wexample_filestate_php.file.php_file import PhpFile
from wexample_filestate_php.options_provider.php_options_provider import PhpOptionsProvider
from wexample_prompt.common.io_manager import IoManager

manager = FileStateManager.create_from_path(
    io=IoManager(),
    path=Path("/path/to/project"),
    options_providers=[DefaultOptionsProvider, PhpOptionsProvider],
)

manager.configure(config={
    "children": [
        {
            "name": "index.php",
            "class": PhpFile,
            "php": {"phpcs_fixer": True},
        }
    ]
})

manager.apply()
```

After `apply()`, `index.php` is passed through PHP-CS-Fixer running inside a dedicated Docker container. If the file already conforms to the configured style the call is a no-op.

`"php"` also accepts a list shorthand — `"php": ["phpcs_fixer"]` is equivalent to the dict form above. `PhpFile` enforces the `.php` extension; omitting `"class": PhpFile` treats the entry as a plain `ItemTargetFile` without extension enforcement.

## Tests

This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.

### Installation

First, install the required testing dependencies:
```bash
.venv/bin/python -m pip install pytest pytest-cov
```

### Basic Usage

Run all tests with coverage:
```bash
.venv/bin/python -m pytest --cov --cov-report=html
```

### Common Commands
```bash
# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
```

### Viewing HTML Reports

After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.

### Coverage Threshold

To enforce a minimum coverage percentage:
```bash
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
```

This will cause the test suite to fail if coverage drops below 80%.

## Architecture

`wexample-filestate-php` is a thin extension layer on top of `wexample-filestate`. It adds one file type, one config option tree, and one Docker-backed operation. Everything lives under src/wexample_filestate_php.

### Parts

**File type — `PhpFile`**

src/wexample_filestate_php/file/php_file.py extends `ItemTargetFile` with a single class variable:

```python
EXTENSION_ENV: ClassVar[str] = "php"
```

and overrides `_expected_file_name_extension` to return it. That is the entirety of the type: it enforces that any target declared with `"class": PhpFile` carries the `.php` extension. The constant `PHP_FILE_EXTENSION` in src/wexample_filestate_php/const/php_file.py mirrors this value for code that needs it without importing the class.

**Options provider — `PhpOptionsProvider`**

src/wexample_filestate_php/options_provider/php_options_provider.py is the entry point callers pass to `FileStateManager`. It extends `AbstractOptionsProvider` and returns `[PhpOption]` from `get_options()`. It also exposes `get_docker_image_name()`, which delegates to `AbstractPhpFileContentOption.DOCKER_IMAGE_NAME` (`"php-option"`), so the framework knows which Docker image is associated with this provider.

**Top-level option — `PhpOption`**

src/wexample_filestate_php/option/php_option.py is the `"php"` key in a file's config dict. It extends `AbstractNestedConfigOption` and mixes in `OptionMixin` and `WithBatchDockerOptionMixin`. Two things happen here:

- `set_value` converts the list shorthand (`["phpcs_fixer"]`) into a dict (`{"phpcs_fixer": True}`) before handing the value to the parent.
- `get_allowed_options` returns `[PhpcsFixerOption]`, which is the set of child options the framework will instantiate from the dict.
- `create_required_operation` delegates to `_create_child_required_operation`, so the actual operation is produced by whichever child option matches the dict keys.

The accepted raw value type is `Union[list[str], dict, PhpConfigValue]`, defined in src/wexample_filestate_php/config_value/php_config_value.py.

**Config value — `PhpConfigValue`**

src/wexample_filestate_php/config_value/php_config_value.py is a typed `ConfigValue` with one public field:

```python
phpcs_fixer: bool | None = public_field(default=None, ...)
```

`to_option_raw_value` converts the structured value back to the dict form the option tree expects, keyed on `PhpcsFixerConfigOption.get_name()` (`"phpcs_fixer"`).

**Config option name — `PhpcsFixerConfigOption`**

src/wexample_filestate_php/config_option/phpcs_fixer_config_option.py is a minimal `AbstractConfigOption` whose only job is to declare the string name `"phpcs_fixer"` used as the dict key.

**Abstract base for Docker-backed options — `AbstractPhpFileContentOption`**

src/wexample_filestate_php/option/php/abstract_php_file_content_option.py extends `AbstractFileContentOption` and `WithBatchDockerOptionMixin`. It sets the Docker image name to `"php-option"` and resolves the Dockerfile path at runtime:

```python
package_root = current_file.parent.parent.parent
return package_root / "resources" / "docker" / "Dockerfile.php-option"
```

This makes the Dockerfile location a property of the class, not of the caller.

**Concrete option — `PhpcsFixerOption`**

src/wexample_filestate_php/option/php/phpcs_fixer_option.py is the only concrete child option. It overrides two methods from the batch-Docker mixin:

- `_apply_content_change` — checks the in-memory batch cache for the target path and returns the cached content, or falls back to reading the file unchanged. This is called per-file after the batch run.
- `_run_batch_on_paths` — the actual work. It resolves which PHP-CS-Fixer config to use (the project's `.php-cs-fixer.dist.php` if present, otherwise the bundled one at `/home/appuser/.php-cs-fixer.dist.php`), ensures the Docker container is running, rebases all host paths to container paths via the runner, and executes:

```python
["php-cs-fixer", "fix", f"--config={config_path}", "--path-mode=override", "--using-cache=no", *container_paths]
```

**Docker image**

src/wexample_filestate_php/resources/docker/Dockerfile.php-option defines the container used by all PHP options. It starts from `php:8.3-cli`, installs Composer, then installs `friendsofphp/php-cs-fixer` globally as a non-root user (`appuser`, UID 1000). The bundled fallback config src/wexample_filestate_php/resources/docker/.php-cs-fixer.dist.php is copied into the image at `/home/appuser/.php-cs-fixer.dist.php`; it enforces PSR-12 plus a standard set of style rules. The container stays alive with `CMD ["tail", "-f", "/dev/null"]` so subsequent calls reuse it.

### Call path

When `manager.apply()` is called on a file configured with `"php": {"phpcs_fixer": True}`:

1. `PhpOptionsProvider.get_options()` has already made `PhpOption` available under the `"php"` key.
2. `PhpOption.set_value` normalises the value and stores it; `get_allowed_options` returns `[PhpcsFixerOption]`, so the framework instantiates one `PhpcsFixerOption` child.
3. `PhpOption.create_required_operation` calls `_create_child_required_operation`, which asks `PhpcsFixerOption` to produce its operation.
4. During `apply`, `PhpcsFixerOption._run_batch_on_paths` is called with the collected list of `.php` paths. It ensures the `"php-option"` container is running (building it from the Dockerfile if needed), translates host paths to container paths, and invokes `php-cs-fixer fix` inside the container.
5. The runner populates the batch cache. Each file's `_apply_content_change` then reads from the cache to return the reformatted content, which the framework writes back to disk.

If the project root contains `.php-cs-fixer.dist.php`, that file is used as the fixer config (mounted at `/var/www/html/.php-cs-fixer.dist.php`); otherwise the bundled config inside the image is used.

## Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

### Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.

## Dependencies

- attrs: >=23.1.0
- cattrs: >=23.1.0
- wexample-filestate: >=17.0.0

## Versioning & Compatibility Policy

Wexample packages follow **Semantic Versioning** (SemVer):

- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Free to use in both personal and commercial projects.

## About us

[Wexample](https://wexample.com) stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

## Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the [project roadmap](https://github.com/wexample/python-filestate_php/issues) for upcoming features and improvements.

## Status & Compatibility

**Maturity**: Production-ready

**Python Support**: >=3.10

**OS Support**: Linux, macOS, Windows

**Status**: Actively maintained

## Useful Links

- **Homepage**: https://github.com/wexample/python-filestate-php
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-filestate-php/issues
- **Discussions**: https://github.com/wexample/python-filestate-php/discussions
- **PyPI**: [pypi.org/project/wexample-filestate-php](https://pypi.org/project/wexample-filestate-php/)

## Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.
