Osumi Framework
es en eu v9.10.0 GitHub

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

Changed

Removed from the 9.9 runtime API

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:

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

{
	"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:

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:

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:

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

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

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

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

Forced migration safety checks

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:

1
true
yes
y
on

and:

0
false
no
n
off

5. Manual Migration CLI

The framework package exposes:

php vendor/bin/ofw-migrate --help

Normal pending migration execution:

php vendor/bin/ofw-migrate

Recommended inspection command:

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

Supported options:

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

--from

Overrides the source framework version instead of using migration state.

Example:

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:

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:

src/Middleware/

Example:

src/Filter/LoginFilter.php
↓ preserved

src/Middleware/LoginMiddleware.php
↓ generated compatibility adapter

Generated adapters execute the original Filter only during:

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:

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

becomes Middleware context:

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

It can then be read with:

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

or:

$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:

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:

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

After:

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

DTO Fields

Before:

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

After:

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

Routes

Before:

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

After:

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:

src/Middleware/Middlewares.php

the migration creates:

<?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:

$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:

$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:

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:

ofw/tmp/state.json

After a successful 9.9.0 migration:

{
	"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:

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:

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:

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

and should explicitly return Middleware results such as:

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

or:

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

Recommended conversion:

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:

before
afterRender
afterResponse

They can publish:

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

global
↓
outer group
↓
inner group
↓
route

See:

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.