# Migration Guide: 9.8.5 → 9.9.0

Version **9.9.0** replaces the legacy Filter runtime with a phased Middleware system and introduces the first versioned application migration engine for Osumi Framework.

This is a **breaking release** for applications that use Filters.

The framework includes an automatic migration step that converts supported legacy Filter usage to Middleware-compatible code while preserving the original Filter business logic.

---

## Summary

### Added

- Native Middleware pipeline with three phases:
  - `before`
  - `afterRender`
  - `afterResponse`
- Global, group and route Middleware scopes.
- `src/Middleware/Middlewares.php` for project-wide Middleware configuration.
- `ORequest::getMiddleware()`.
- `ORequest::getMiddlewareValue()`.
- `ODTOField` Middleware sources:
  - `middleware`
  - `middlewareProperty`
- Typed Middleware stop/error responses.
- Framework migration engine.
- `vendor/bin/ofw-migrate`.
- Transactional migration backups.
- Migration state in `ofw/tmp/state.json`.
- Composer integration through `osumionline/plugin-updater`.

### Changed

- Route Filters are replaced by phase-based Middleware definitions.
- Legacy Filter output is exposed as Middleware context after automatic migration.
- `ODTOField(filter: ..., filterProperty: ...)` becomes `middleware` / `middlewareProperty`.
- `$req->getFilter('Name')` becomes `$req->getMiddleware('Name')`.
- Authentication, authorization and request interception should now be implemented with native Middlewares.

### Removed from the 9.9 runtime API

- Filter execution pipeline.
- New Filter creation through the CLI.
- Legacy Filter-oriented `ORequest` APIs.

Legacy Filter classes may remain temporarily in migrated applications because the automatic migration generates compatibility Middleware adapters around them.

---

# 1. Requirements Before Updating

Osumi Framework 9.9 requires:

- PHP 8.5 or newer.
- Composer 2.x.
- `osumionline/plugin-updater`.
- Composer permission to execute the updater plugin.

Before updating an existing project, add this to the root `composer.json` if it is not already present:

```json
{
	"config": {
		"allow-plugins": {
			"osumionline/plugin-updater": true
		}
	}
}
```

Composer may ask interactively whether the plugin should be trusted when it is installed for the first time.

---

# 2. Recommended Update Procedure

Before updating:

1. Commit or stash application changes.
2. Make sure the project is in a known working state.
3. Back up production data if the application uses persistent data.
4. Update the framework with Composer.

Example:

```bash
composer update osumionline/framework --with-dependencies
```

When Composer has rebuilt the autoloader, `osumionline/plugin-updater` checks the installed framework version and runs all pending Osumi Framework migrations.

For the 9.8.5 → 9.9.0 update, this executes migration step `9.9.0`.

---

# 3. Automatic Composer Migration

The Composer integration performs the following flow:

```text
Composer update
↓
osumionline/plugin-updater
↓
post-autoload-dump
↓
Runner::runPending()
↓
9.9.0 migration preflight
↓
transactional project changes
↓
state.json update
```

The migration state is authoritative. This allows the migration to run correctly even when:

- several framework versions are skipped;
- the updater plugin is installed during the same Composer operation;
- Composer emits more than one autoload event.

During Composer-triggered migrations, the Git cleanliness check ignores only:

```text
composer.json
composer.lock
```

Changes to application source files and other project files still cause the migration to abort unless forced execution is explicitly enabled.

---

# 4. Previewing Composer-triggered Migrations

The updater supports environment variables.

### Dry-run

```bash
OFW_DRY_RUN=1 composer update osumionline/framework --with-dependencies
```

This makes the **framework migration** run in dry-run mode.

Important: Composer itself still performs its package operation. `OFW_DRY_RUN` only prevents the Osumi Framework migration from persisting project migration changes.

### Verbose output

```bash
OFW_VERBOSE=1 composer update osumionline/framework --with-dependencies
```

### Forced migration safety checks

```bash
OFW_FORCE=1 composer update osumionline/framework --with-dependencies
```

Use forced execution carefully. It bypasses migration safety checks that support forced execution.

Accepted boolean environment values include:

```text
1
true
yes
y
on
```

and:

```text
0
false
no
n
off
```

---

# 5. Manual Migration CLI

The framework package exposes:

```bash
php vendor/bin/ofw-migrate --help
```

Normal pending migration execution:

```bash
php vendor/bin/ofw-migrate
```

Recommended inspection command:

```bash
php vendor/bin/ofw-migrate --dry-run --verbose
```

Supported options:

```text
--from=VERSION
--to=VERSION
--dry-run
--force
--verbose
--no-interaction
--help
```

### `--from`

Overrides the source framework version instead of using migration state.

Example:

```bash
php vendor/bin/ofw-migrate --from=9.8.5 --to=9.9.0
```

Use this only when you intentionally need an explicit version range.

### `--to`

Overrides the target framework version. Without it, the installed framework version is detected automatically.

### `--dry-run`

Shows planned migration operations without modifying project files.

### `--force`

Bypasses safety checks that support forced execution, including the clean Git working tree check.

### `--verbose`

Shows migration details and file operations.

### `--no-interaction`

Disables interactive migration behavior.

---

# 6. Git Working Tree Protection

For a normal manual migration, a Git-backed project must have a clean working tree.

If there are unexpected changes, the migration aborts with an error instead of modifying the project.

A project without `.git` is accepted; transactional migration backups are still used.

Dry-run mode does not require a clean working tree because no project changes are persisted.

During automatic Composer migration, only `composer.json` and `composer.lock` are excluded from the cleanliness check.

Prefer committing or stashing changes instead of using `--force`.

---

# 7. What the 9.9.0 Migration Does

The migration performs a complete preflight before writing any application files.

It scans legacy Filters under:

```text
src/Filter/
```

Supported Filters must follow the normal Osumi Framework PSR-4 namespace and file conventions.

The original Filter files are **not deleted or rewritten**.

For each supported Filter, the migration generates a compatibility Middleware under:

```text
src/Middleware/
```

Example:

```text
src/Filter/LoginFilter.php
↓ preserved

src/Middleware/LoginMiddleware.php
↓ generated compatibility adapter
```

Generated adapters execute the original Filter only during:

```php
OMiddleware::PHASE_BEFORE
```

---

# 8. Legacy Filter Result Mapping

A generated adapter converts the old Filter result to the 9.9 Middleware contract.

### Successful Filter

Legacy result:

```php
[
	'status' => 'ok',
	'id' => 42
]
```

becomes Middleware context:

```php
[
	'context' => [
		'status' => 'ok',
		'id' => 42
	]
]
```

It can then be read with:

```php
$login = $req->getMiddleware(
	'Login'
);
```

or:

```php
$id = $req->getMiddlewareValue(
	'Login',
	'id'
);
```

### Failed Filter

A legacy Filter result that is not successful becomes a Middleware stop with HTTP 403.

### Legacy Redirect

If the Filter returns a valid `return` URL, the adapter produces:

- HTTP 302
- `Location` header
- Middleware stop

This preserves the legacy Filter behavior during migration.

---

# 9. Source Transformations

The migration automatically transforms supported application code outside `src/Filter/` and `src/Middleware/`.

## `ORequest`

Before:

```php
$login = $req->getFilter(
	'Login'
);
```

After:

```php
$login = $req->getMiddleware(
	'Login'
);
```

## DTO Fields

Before:

```php
#[ODTOField(
	filter: 'Login',
	filterProperty: 'id'
)]
public ?int $idUser = null;
```

After:

```php
#[ODTOField(
	middleware: 'Login',
	middlewareProperty: 'id'
)]
public ?int $idUser = null;
```

## Routes

Before:

```php
ORoute::get(
	'/profile',
	ProfileComponent::class,
	[
		LoginFilter::class
	]
);
```

After:

```php
ORoute::get(
	'/profile',
	ProfileComponent::class,
	[
		'before' => [
			\Osumi\OsumiFramework\App\Middleware\LoginMiddleware::class
		]
	]
);
```

Literal Filter arrays in supported `ORoute` calls are migrated to `before` Middleware lists.

---

# 10. Global Middleware Configuration

If this file does not exist:

```text
src/Middleware/Middlewares.php
```

the migration creates:

```php
<?php

declare(strict_types=1);

namespace Osumi\OsumiFramework\App\Middleware;

use Osumi\OsumiFramework\Core\OMiddleware;

OMiddleware::setGlobal([
	OMiddleware::PHASE_BEFORE => [],
	OMiddleware::PHASE_AFTER_RENDER => [],
	OMiddleware::PHASE_AFTER_RESPONSE => []
]);
```

If the file already exists, it is preserved exactly.

---

# 11. Constructs That Are Not Migrated Automatically

The migration intentionally aborts when it cannot transform legacy code safely.

Examples include:

### Dynamic route Filter expressions

Not supported:

```php
$filters = [
	LoginFilter::class
];

ORoute::get(
	'/profile',
	ProfileComponent::class,
	$filters
);
```

Route Filter expressions must be literal class arrays for automatic migration.

### Unsupported `ORequest` legacy methods

The following require manual changes:

```php
$req->getFilters()
$req->setFilter(...)
$req->setFilters(...)
```

The migration aborts when these calls are found.

### Unresolved Filter references

A route referencing a Filter that cannot be matched to a migratable Filter definition is rejected.

### Existing Middleware conflicts

If the generated Middleware destination already exists with different contents, the migration aborts rather than overwriting it.

### Unsafe filesystem structures

Symbolic-link traversal or unsafe project paths are rejected.

The migration is intentionally conservative: ambiguous code must be migrated manually instead of being guessed.

---

# 12. Transactional Safety and Rollback

Project changes are written through a transactional file patcher.

Migration backups are stored under:

```text
ofw/tmp/migrations/
```

Each transaction uses its own timestamp/random directory and keeps a manifest of changed files.

The migration state is written through the same transaction as application changes.

If any migration operation fails:

1. changed files are restored;
2. files created by the failed migration are removed;
3. migration state is rolled back with the project;
4. the migration exits with an error.

Successful transaction backups are retained on disk for inspection.

---

# 13. Migration State

State is stored in:

```text
ofw/tmp/state.json
```

After a successful 9.9.0 migration:

```json
{
	"last_migrated": "9.9.0"
}
```

If no state file exists, the migration runner treats the source migration version as `0.0.0` and selects all applicable pending migration steps up to the installed target version.

This makes upgrades from projects created before the migration engine possible.

Running the normal pending migration command again after success produces no 9.9.0 changes.

---

# 14. Idempotence

The migration system is designed to be idempotent.

Examples:

- Migration state prevents completed steps from running again during normal pending execution.
- An already generated compatibility adapter is accepted if its contents match exactly.
- Existing `Middlewares.php` configuration is preserved.
- A second normal migration run finds no pending 9.9.0 migration.

Do not use `--from` to deliberately reselect an already completed migration unless you understand the consequences.

---

# 15. After Migration: Verify the Project

After migration:

1. Review generated files under `src/Middleware/`.
2. Review migrated route definitions.
3. Review DTOs using `middleware` / `middlewareProperty`.
4. Search the application for legacy runtime API usage.
5. Run the complete test suite.
6. Perform functional authentication/authorization tests.
7. Commit the migrated application code.

Useful searches include:

```text
getFilter(
getFilters(
setFilter(
setFilters(
filterProperty
```

Legacy classes under `src/Filter/` are expected to remain while compatibility adapters are in use.

---

# 16. Converting Generated Adapters to Native Middlewares

Generated adapters are a compatibility bridge, not the preferred final architecture.

After verifying the migrated project, you can progressively move the old Filter logic into the generated Middleware class.

A native Middleware uses:

```php
public static function handle(
	string $phase,
	array $data
): array
```

and should explicitly return Middleware results such as:

```php
return [
	'context' => [
		'id' => 42
	]
];
```

or:

```php
return [
	'stop' => true,
	'status_code' => 401,
	'message' => 'Unauthorized'
];
```

Recommended conversion:

```text
legacy Filter
↓
generated compatibility Middleware
↓
functional verification
↓
move logic into native Middleware
↓
remove unused Filter
```

Keep the generated Middleware class name when possible so already migrated routes and DTO context names do not need further changes.

After converting all adapters, `src/Filter/` can be removed if no legacy Filter code remains.

---

# 17. New Middleware Capabilities

Native Middlewares are not limited to the old Filter behavior.

They can run in:

```text
before
afterRender
afterResponse
```

They can publish:

- context
- body changes
- response headers
- HTTP status changes
- stop/error responses

Global, nested group and route Middleware definitions are accumulated for each phase in this order:

```text
global
↓
outer group
↓
inner group
↓
route
```

See:

```text
docs/en/concepts/middlewares.md
```

for the full 9.9 Middleware API.

---

# 18. Developer Action Required

For applications upgrading from 9.8.5:

1. Ensure PHP 8.5+.
2. Allow `osumionline/plugin-updater` in root Composer configuration.
3. Commit or stash local source changes.
4. Update Osumi Framework with Composer.
5. Review the automatic migration output.
6. Resolve manually any unsupported legacy constructs reported by the migrator.
7. Run the complete test suite and functional checks.
8. Commit the migrated application.
9. Optionally replace generated compatibility adapters with native Middlewares.

For projects that do not use Filters, the 9.9 migration still creates `src/Middleware/Middlewares.php` when needed and records migration state.

---

# 19. Backward Compatibility

9.9.0 intentionally removes the Filter runtime architecture.

Automatic adapters provide an upgrade path for supported legacy projects, but new 9.9 code must use Middlewares.

Do not add new Filter code after migration.
