← All case studies
Ongoing production-platform modernizationNinthroot · Oak CMS

Incremental Oak Modernization

A practical modernization program for a business-critical PHP platform where a rewrite was not the highest-value decision.

The goal was not to make the platform look new on paper. It was to reduce the cost and risk of changing a system the business already depended on, one useful boundary at a time.

  • PHP 8.4
  • ORM Architecture
  • MySQL
  • Structured Data
  • Documentation

Case study at a glance

The problem, my role, and the result.

problem
Controllers and views followed inconsistent patterns, repeated orchestration and UI logic, and relied on limited documentation.
stakes
The CMS supports production sites, so modernization had to improve maintainability without disrupting established behavior or forcing a high-risk rewrite.
My ownership
I led the PHP migration and built repeatable controller, helper, structured-data, UI, documentation, and development-instruction patterns.
outcome
All CMS instances were updated to PHP 8.4 and tested for compatibility. The modernization now includes 50 standardized controllers, 20 updated views, and 3 modules migrated to a structured ORM architecture.

Architecture

The boundaries that made the work useful.

01

Configuration over repetition

Controllers declare common request, resource, navigation, form, feedback, and activity behavior through a shared runner.

02

Reusable orchestration

Helper functions move repeated control flow into tested, understandable boundaries while keeping module-specific decisions visible.

03

Structured data

Three modules now use a structured ORM architecture, reducing direct query variation and clarifying how data reaches the view layer.

04

Documented conventions

Updated documentation and agent-ready instructions make the preferred patterns easier for developers and coding tools to follow consistently.

Approved public example

A concrete look at the implementation pattern.

Illustrative sanitized controller

php

Names and implementation details differ from the production system. This is an illustrative example, not verbatim production code.

<?php
$config = [
    'service' => $this->resolveService('Catalog:Item'),

    'request' => [
        'required_parameters' => [
            'item_id',
        ],
        'on_missing' => [
            'route' => 'catalog.items.index',
        ],
    ],

    'navigation' => [
        'source' => [
            'scope' => 'user_managed',
        ],
        'label_field' => 'display_name',
        'destination' => [
            'route' => 'catalog.items.edit',
            'parameters' => [
                'item_id' => 'id',
            ],
        ],
        'selected_parameter' => 'item_id',
    ],

    'resources' => [
        'item' => function ($controller, $service, $request) {
            return $service->find([
                'id' => $request->validatedQuery('item_id'),
                'include' => [
                    'related_options',
                ],
                'presentation' => 'form',
            ]);
        },
    ],

    'page' => [
        'configure' => function (
            $controller,
            $service,
            $request,
            $resources
        ) {
            $item = $resources['item'];

            $controller->setPageTitle($item['display_name']);
            $controller->setNavigationLabel(
                'catalog.items.edit',
                $item['display_name']
            );
        },
    ],

    'operation' => [
        'type' => 'update',
        'resource' => 'item',

        'form' => [
            'heading' => 'Edit Item',
            'identifier' => 'item',
            'submit_label' => 'Save Changes',
        ],

        'dialog' => [
            'size' => 'large',
        ],

        'feedback' => [
            'success' => [
                'message' => '{display_name} was updated successfully.',
                'activity' => [
                    'route' => 'catalog.items.edit',
                    'parameters' => [
                        'item_id' => '{id}',
                    ],
                    'message' => 'Updated item: {display_name}',
                ],
                'refresh_target' => 'catalog-item-list',
            ],
            'error' => [
                'message' => 'The item could not be updated.',
            ],
        ],
    ],
];

$result = $this->runConfiguredController($config);

$view = $result['operation']['view']
    ?? '<div class="error-state">'
        . '<h1>Unable to Load</h1>'
        . '<p>Please try again later.</p>'
        . '</div>';

$this->viewData = [
    'content' => $view,
];

Illustrative sanitized view

html

The view receives a prepared rendering result instead of repeating controller orchestration.

<div class="col-12">
  {view}
</div>

Key decisions

What I chose and why.

  1. 01

    Migrated from PHP 7.3 to PHP 8.4 while preserving production behavior and testing CMS instances for compatibility.

  2. 02

    Standardized controllers through shared configuration and helper functions instead of rewriting the application around a new framework.

  3. 03

    Improved the view contract by passing structured data and reducing repeated UI orchestration.

  4. 04

    Added documentation and development instructions alongside code changes so the new patterns could remain consistent.

Outcome & evidence

What the repository and delivered work support.

  • 01

    All CMS instances updated from PHP 7.3 to PHP 8.4 and tested for compatibility.

  • 02

    50 controllers adopted a standardized structure with reusable helper functions.

  • 03

    20 views updated with structured data and improved UI.

  • 04

    3 modules migrated to a structured ORM architecture.

Reflection

What I learned and what I would do next.

What I learned

A legacy system becomes easier to change when each improvement leaves behind a clearer pattern. Consistency, documentation, and compatibility work can create more business value than a rewrite proposal.

What I would do next

I would continue migrating modules where the repeated maintenance cost justifies it, using the same incremental approach and keeping production behavior measurable.

Senior full-stack engineering · Hands-on technical leadership

Looking for someone who can own the system around the feature?

View Résumé ↗