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:
beforeafterRenderafterResponse
- Global, group and route Middleware scopes.
src/Middleware/Middlewares.phpfor project-wide Middleware configuration.ORequest::getMiddleware().ORequest::getMiddlewareValue().ODTOFieldMiddleware sources:middlewaremiddlewareProperty
- 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: ...)becomesmiddleware/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
ORequestAPIs.
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:
{
"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:
- Commit or stash application changes.
- Make sure the project is in a known working state.
- Back up production data if the application uses persistent data.
- 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:
- 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:
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:
- HTTP 302
Locationheader- 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:
$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:
- changed files are restored;
- files created by the failed migration are removed;
- migration state is rolled back with the project;
- 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:
- 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.phpconfiguration 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:
- Review generated files under
src/Middleware/. - Review migrated route definitions.
- Review DTOs using
middleware/middlewareProperty. - Search the application for legacy runtime API usage.
- Run the complete test suite.
- Perform functional authentication/authorization tests.
- 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:
- 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:
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:
- Ensure PHP 8.5+.
- Allow
osumionline/plugin-updaterin root Composer configuration. - Commit or stash local source changes.
- Update Osumi Framework with Composer.
- Review the automatic migration output.
- Resolve manually any unsupported legacy constructs reported by the migrator.
- Run the complete test suite and functional checks.
- Commit the migrated application.
- 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.