Zend to Laminas upgrade case study
Zend to Laminas upgrade: Breezio, a live community platform, moved from Zend Framework 2 and PHP 5.6 to Laminas and PHP 8.2 with no rewrite and no broken production. The Project Drivers delivered PHP 8.2 migration, Laminas migration, Elasticsearch upgrade, test suite, DevOps.

About the project
If your application still runs your business but sits on end-of-life PHP, an unmaintained framework and abandoned dependencies, and a rewrite is too expensive and risky, this is how we handled it for Breezio, a live community and learning platform. The Project Drivers moved it from Zend Framework 2 and PHP 5.6 to Laminas and PHP 8.2, with Elasticsearch, Redis, Ubuntu, Node and Backbone upgraded alongside, without rebuilding the product. The core upgrade took four weeks, followed by stabilisation on real data.
The upgrade in numbers
| Measure | Result |
|---|---|
| When | January 2026 |
| Core upgrade | Four weeks, followed by stabilisation on real data |
| Codebase | About 1,200 PHP files in 20 modules |
| Namespace migration | About 500 files, done by script rather than by hand |
| Automated tests | 133 PHPUnit test files covering every non-SSO controller, where there was no usable suite before |
| Frontend first load | JavaScript bundle reduced from about 6 MB to a 1.3 MB core plus lazy-loaded chunks |
| Environment | Reproducible Docker setup that a new developer can start from the README |
| Rewrite | None: same product, same behaviour, new foundation |
What changed
| Layer | Before | After |
|---|---|---|
| Framework | Zend Framework 2 | Laminas MVC 3.7 |
| Language | PHP 5.6 | PHP 8.2 |
| Search | Elasticsearch 1.4 | Elasticsearch 9.3 |
| Cache | Redis (legacy) | Redis 8.6 |
| Operating system | Ubuntu (end of life) | Ubuntu 24.04 |
| Build tooling | Old Node and Grunt | Node 24 |
| Frontend | Backbone 1.1 | Backbone 1.6 |
Why this upgrade was risky
Most companies avoid legacy upgrades because they fear breaking production. This platform had every reason for that fear: end-of-life PHP and framework, abandoned dependencies, a search engine from 2014 and about 1,200 PHP files across 20 modules. The goal was the same product on a stack that is safe to run and cheap to keep developing, not a rewrite.
How we upgraded it without breaking production
- Measure first. Static analysis across the codebase and a full dependency audit showed what would fail on PHP 8.2 and what could wait. One rule governed the work: minimal change, no refactoring, no behaviour change.
- Build the safety net. Before the framework moved, we wrote a PHPUnit suite covering every non-SSO controller (133 test files) and upgraded the test runner from PHPUnit 5 to 9.
- One jump instead of a staircase. We went directly from Zend Framework 2 to Laminas 3.7 and from PHP 5.6 to 8.2. A compatibility bridge and automated namespace migration covered about 500 files, and we skipped a dozen intermediate upgrades that would have been thrown away.
- Move the Zend ecosystem to Laminas. Framework components, user and role modules and Zend-based packages with no Laminas release were replaced, adopted in-house or rebuilt (details below).
- Rebuild what cannot be patched. The Redis cache adapter could not survive the framework change, so we rebuilt it by delegation and tested it harder than any other component, because cache failures are silent.
- Upgrade infrastructure side by side. The platform moved to a reproducible Docker environment. Elasticsearch crossed several major versions, so we ran old and new clusters together and compared results until they matched.
- Modernise the frontend without rewriting it. The Backbone application moved to 1.6 and its build was rebuilt: a 6 MB bundle became a 1.3 MB core plus seven lazy-loaded chunks, with vendor libraries served separately so releases no longer clear the browser cache.
- Stabilise on real data. Tests do not know production data. We ran the platform against realistic data and fixed what surfaced, from silent nulls to permissions and search counts.
How we moved from Zend Framework 2 to Laminas, package by package
Zend Framework 2 is no longer maintained, and the platform was built on top of it and on a layer of community packages named after it. We did not upgrade it in place. We mapped every Zend package to its Laminas successor, found the ones with no successor, and decided for each what to replace, adopt or rebuild.
Framework core
| Zend Framework 2 | Laminas |
|---|---|
| php >=5.5.0 | php ^8.2 |
| zendframework/zendframework 2.4.* | laminas-mvc ^3.7 plus only the components in use: laminas-authentication ^2.12, laminas-permissions-rbac ^3.3, laminas-session ^2.26, laminas-log ^2.17, laminas-mail ^2.25, laminas-mime ^2.10, laminas-captcha ^2.19, laminas-mvc-plugin-identity ^1.7, laminas-cache-storage-adapter-redis ^2.10 |
| (none) | laminas-zendframework-bridge ^1.4, so third-party code that still uses the old Zend names keeps working |
| zendservice-recaptcha | laminas-recaptcha ^3.4 |
| zftool | laminas-cli ^1.9 |
| zend-developer-tools | laminas-developer-tools ^2.10 |
| zendservice-twitter | removed, no longer used |
| phpunit-selenium based tests | laminas-test ^4.4 with PHPUnit 9.5 |
Community modules: Zfc to Lmc
| Zend-era module | Laminas-era successor |
|---|---|
| zf-commons/zfc-user 1.* | lm-commons/lmc-user ^3.9 |
| zf-commons/zfc-user-doctrine-orm 1.* | lm-commons/lmc-user-doctrine-orm ^3.0 |
| zf-commons/zfc-rbac 2.* | lm-commons/lmc-rbac-mvc ^4.2 |
| zfr/zfr-cors 1.* | lm-commons/lmc-cors ^1.6 |
| zf-commons/zfc-admin 0.2.* | folded into the platform’s own Admin module |
Zend-based packages with no Laminas release
These packages were written for Zend, had no Laminas version and would never get one. We brought each into the codebase as a local module and rewrote it for Laminas, so the platform owns them.
| Abandoned package | Local module |
|---|---|
| goalio/goalio-rememberme and its Doctrine ORM package | GoalioRememberMe, GoalioRememberMeDoctrineORM |
| goalio/goalio-forgotpassword and its Doctrine ORM package | GoalioForgotPassword, GoalioForgotPasswordDoctrineORM |
| goalio/goalio-mailservice | GoalioMailService |
| socalnick/scn-social-auth and its Doctrine ORM package | ScnSocialAuth, ScnSocialAuthDoctrineORM |
| webino/webino-image-thumb | WebinoImageThumb |
| soflomo/purifier | Soflomo/Purifier |
Changes inside our own code
- Namespaces. Every Zend\* reference became Laminas\* across about 500 files, using scripted replacement instead of hand edits.
- Redis cache adapter. Laminas Cache changed its storage interfaces, so the adapter that extended the framework class was rebuilt by delegation and tested harder than any other component.
- Third-party fix as a patch. One incompatibility in laminas-form became a patch file that Composer applies on every install, so vendor code is never edited by hand.
- Controllers, service factories and view helpers. Updated for the Laminas ServiceManager and View layers, and authentication, remember-me and user services adapted to the Lmc APIs, with no change to behaviour.
Which packages solved the main problem
Four decisions removed the blockers that make a Zend 2 to Laminas migration stall in most projects.
| Package or decision | Problem it solved |
|---|---|
| laminas-zendframework-bridge ^1.4 | Third-party code still referenced Zend names. The bridge let it run unchanged while we migrated our own code. |
| laminas-mvc ^3.7 (direct jump) | Skipped Zend Framework 2.5 and 3.0 and every intermediate Laminas release, so no throwaway upgrades and no extra deprecation noise. |
| lmc-user, lmc-rbac-mvc, lmc-cors | The user, role and CORS stack had maintained successors, so authentication and permissions moved over without a rewrite. |
| Local modules for Goalio, ScnSocialAuth, WebinoImageThumb and Soflomo | Packages with no Laminas release could not be installed from Composer any more. Owning them removed the dependency on abandoned authors. |
Result
Breezio now runs on a current framework, a modern PHP, an up-to-date search engine and a maintained operating system, builds in a reproducible environment and loads a much smaller frontend, while its users see the same product (see also the Breezio case study). The upgrade also left the team with an automated test suite that did not exist before. The work took place in early 2026, and we chose PHP 8.2 because it was the newest version we could verify against the whole dependency tree, and because the new tests and tooling turn the next PHP upgrade into a routine step instead of another leap.
Sources: the official Laminas migration guide, the laminas-zendframework-bridge project, the PHP 8.2 release notes and the PHP supported versions page.
Frequently asked questions
How long does it take to upgrade PHP 5.6 to PHP 8.2?
It depends on codebase size and dependencies. On Breezio, a platform of about 1,200 PHP files, the core upgrade took four weeks, followed by a stabilisation period on real data. A dependency audit in the first days gives a firm estimate.
Can you upgrade Zend Framework 2 to Laminas without rewriting the application?
Yes. Laminas is the official continuation of Zend Framework. Namespaces are migrated mechanically, a compatibility bridge covers third-party code, and the application keeps its architecture and behaviour. Only changes that PHP 8.2 or Laminas force are made.
Why skip the intermediate Zend Framework and PHP versions?
Each intermediate version adds its own deprecations and its own testing, and is discarded by the next step. When the destination is a direct successor, going straight to it is faster and leaves less throwaway work.
What do you do with abandoned dependencies?
We sort them into four groups: replace with a maintained successor, bring in-house as a local module ported to the new PHP version, patch in the repository when a small fix is enough, or delete if the code never uses it.
How do you avoid breaking a live platform during an upgrade?
We build automated tests first, follow a minimal-change rule, run old and new infrastructure such as Elasticsearch side by side, and verify the result on realistic data before it replaces what users rely on.
Planning a similar upgrade?
If you run a legacy PHP application on Zend Framework, an old Laminas or ZF-era packages, we start by looking at your actual stack, not by selling a rewrite. A first conversation covers:
- your current PHP and framework versions and which dependencies are unmaintained;
- whether automated tests exist and how much downtime the business can accept;
- the main migration risks and the most practical path forward, in stages if that is safer.
Write to info@theprojectdrivers.com with your PHP version and a short description of the system, and we will tell you plainly what an upgrade would involve. See also Pricing and System integration & DevOps.