Migrating from Zend Framework to Laminas is one of the most common upgrade paths for PHP teams maintaining applications built on the Zend ecosystem, and this guide provides a concrete checklist for working through it methodically. This page covers the practical search intent around what changed in the move to Laminas, what breaks, and how to fix it - drawn from performing this migration on multiple production codebases across ZF2, ZF3, and Expressive applications. The topics below walk through namespace changes, Composer package swaps, configuration updates, MVC migration specifics, service manager adjustments, view helper changes, testing modifications, common pitfalls, and a verification process to confirm everything works. Every item on this checklist comes from real migration work, not from reading the docs and guessing.
For background on the framework’s history and the original architecture, see the Survive the Deep End guides hub.
What Laminas Is and Why It Exists
When Zend Technologies was acquired by Rogue Wave Software, and subsequently when Perforce acquired Rogue Wave, the future of the open-source Zend Framework became uncertain. The Linux Foundation stepped in to provide a vendor-neutral home for the project under a new name: Laminas.
Laminas is not a new framework. It is Zend Framework with different namespaces and package names. The codebase, the architecture, the design patterns - they are all the same. The API surface is identical apart from the namespace prefix. If your application runs on ZF3 or Expressive 3, it can run on Laminas with mechanical changes. The framework did not undergo a redesign. This is a rename and re-home operation.
This matters because it sets your expectations correctly. You are not learning a new framework. You are doing a systematic find-and-replace with some edge cases that need manual attention.
Before You Start: Prerequisites
Run through this checklist before touching any code:
- PHP version: Laminas packages require PHP 7.3 at minimum. Most current releases require 7.4 or 8.0+. Confirm your production PHP version supports the Laminas packages you need.
- Composer 2.x: The migration tool and Laminas packages work best with Composer 2. Upgrade if you are still on Composer 1.
- Version control: Make sure your working tree is clean. Create a dedicated branch for the migration. You want to be able to diff the entire change set and revert if needed.
- Test suite: If you have tests, run them and confirm they pass before starting. You need a known-good baseline. If you have no tests, write a handful of smoke tests that hit your critical endpoints and verify they return expected status codes.
- Dependency audit: Run
composer outdatedand note which Zend packages you use. Some packages were abandoned and not migrated to Laminas (notablyzendframework/zend-json-serverand a few others). Check whether your dependencies have Laminas equivalents.
The Migration Tool
Laminas provides an official migration tool that automates the bulk of the work:
1 | composer require laminas/laminas-migration |
The tool performs three operations:
- Composer dependency replacement: renames
zendframework/*andzfcampus/*packages to theirlaminas/*andlaminas-api-tools/*equivalents incomposer.json. - Source code namespace replacement: scans PHP files and replaces
Zend\withLaminas\,ZendService\withLaminasService\,ZF\withLaminas\ApiTools\, and related patterns. - Configuration file updates: replaces namespace references in configuration arrays, module lists, and similar structures.
Run the tool, then immediately run composer update to pull in the renamed packages. Do not skip the update step - your lock file still references the old Zend packages until you update.
Namespace Changes in Detail
The migration tool handles most namespace changes, but understanding the mapping helps you catch what it misses:
| Old Namespace | New Namespace |
|---|---|
Zend\Mvc |
Laminas\Mvc |
Zend\ServiceManager |
Laminas\ServiceManager |
Zend\Db |
Laminas\Db |
Zend\Form |
Laminas\Form |
Zend\View |
Laminas\View |
Zend\Http |
Laminas\Http |
Zend\EventManager |
Laminas\EventManager |
Zend\ModuleManager |
Laminas\ModuleManager |
ZF\Apigility |
Laminas\ApiTools |
ZF\ContentNegotiation |
Laminas\ApiTools\ContentNegotiation |
The tool handles these and many more. What it sometimes misses:
- String references: class names stored as strings in database records, cache entries, or serialised data. If your application stores fully-qualified class names in the database (common with polymorphic serialisation), you need a data migration.
- Comments and docblocks: the tool may or may not update
@var Zend\Db\Adapter\Adapterin docblocks depending on the version. Check your IDE for unresolved type hints after migration. - External configuration: any configuration managed outside the repository (environment variables with class names, deployment scripts, infrastructure-as-code templates) needs manual review.
Configuration File Changes
Laminas uses the same configuration structure as ZF3. The primary changes are in module names and factory references:
1 | // Before |
Check these specific files:
config/application.config.php(orconfig/application.config.global.php)config/modules.config.phpif you use a separate module listconfig/autoload/*.global.phpand*.local.php- Each module’s
module.config.php
The migration tool should catch most of these, but I have seen it miss entries in autoload files that use unusual formatting or conditional logic.
Composer Package Name Changes
Every zendframework/* package becomes laminas/*. The mapping is straightforward but the volume can be surprising. A typical ZF3 MVC application might depend on 20-30 Zend packages. All of them change names.
1 | // Before |
Version constraints generally carry over. Laminas initial releases matched the version numbers of their Zend Framework counterparts. Subsequent releases have incremented independently, so if you want the latest Laminas versions, you may need to widen your constraints.
After updating composer.json, run:
1 | composer update --with-all-dependencies |
The --with-all-dependencies flag ensures transitive dependencies also update to their Laminas equivalents. Without it, Composer may keep old Zend packages that are pulled in by other dependencies.
MVC Migration Specifics
The MVC layer has the most surface area for breakage because it touches routing, controllers, view rendering, and the module system. Pay particular attention to:
Controller class names in routing config: if your routes reference controllers by their fully-qualified class name (the recommended approach in ZF3), the migration tool updates them. If they reference controllers by alias, check that the aliases map to updated class names.
Controller plugins: custom controller plugins need their namespaces updated. The plugin manager configuration in your module config must reference Laminas classes:
1 | 'controller_plugins' => [ |
As long as MyPlugin extends Laminas\Mvc\Controller\Plugin\AbstractPlugin (updated from Zend\Mvc\Controller\Plugin\AbstractPlugin), this works without further changes.
Dispatch listeners and event hooks: if you attach listeners to MVC events, the event class names change. Zend\Mvc\MvcEvent becomes Laminas\Mvc\MvcEvent. The migration tool should handle this in source files, but check any event identifiers stored as strings.
Service Manager Changes
The service manager API is identical between ZF3 and Laminas. The only changes are namespace-related: factory interfaces, initialiser interfaces, and delegator interfaces all move from Zend\ServiceManager\ to Laminas\ServiceManager\.
If you have factories that type-hint against the container interface:
1 | use Zend\ServiceManager\Factory\FactoryInterface; |
This becomes:
1 | use Laminas\ServiceManager\Factory\FactoryInterface; |
Note the double change: the factory interface namespace updates, and Interop\Container\ContainerInterface should become Psr\Container\ContainerInterface. The interop package is deprecated. Laminas service manager 3.x accepts both, but you should migrate to PSR-11 now to avoid future breakage.
View Helper Changes
View helpers follow the same namespace migration pattern. Custom view helpers extending Zend\View\Helper\AbstractHelper now extend Laminas\View\Helper\AbstractHelper. The view helper manager configuration updates the same way as controller plugins.
One area that trips people up: if you reference view helpers by their short name in templates ($this->url(), $this->partial()), those continue to work unchanged. The short names are registered through configuration, and the migration tool updates the configuration. But if you reference helpers by FQCN in configuration, those references must point to Laminas classes.
Template files (.phtml) generally need no changes unless they contain inline PHP that references Zend classes directly. The migration tool scans .phtml files, but verify with a grep:
1 | grep -r "Zend\\\\" resources/views/ |
Testing Adjustments
PHPUnit tests need the same namespace updates as your application code. The migration tool should handle test files, but verify:
usestatements at the top of test files- Mock expectations that reference class names as strings
- Test configuration files (
phpunit.xmlorphpunit.xml.dist) that reference bootstrap files - make sure the bootstrap still works after migration - Any test doubles or fake implementations of Zend interfaces
Run your full test suite after migration. Failures typically fall into two categories:
- Missed namespace changes: a test references
Zend\Somethingthat was not caught by the migration tool. Fix with a find-and-replace. - Behavioural changes in newer Laminas versions: if you updated to newer Laminas versions (not just the initial matching versions), some behaviour may have changed. Check the changelogs for the specific packages that fail.
Common Pitfalls
Serialised data: if your application serialises objects using serialize() and stores them (in sessions, cache, or database), the serialised string contains the full class name including namespace. After migration, deserialising old data produces either an error or an object of the wrong class. You need a migration strategy for stored serialised data, or you need to implement Serializable with custom logic, or you need to flush all caches and invalidate all sessions during deployment.
Hardcoded strings in third-party packages: if you use third-party packages that depend on Zend Framework, those packages may contain hardcoded references to Zend namespaces. Check whether your third-party dependencies have been updated for Laminas compatibility. Many popular packages released Laminas-compatible versions.
IDE configuration: PhpStorm and other IDEs may cache old class indexes. After migration, invalidate caches and regenerate indexes to get accurate autocompletion and error detection.
CI/CD pipelines: update any CI configuration that references Zend package names, runs Zend-specific commands, or uses Zend-specific Docker images.
Verification Steps
After completing the migration, run through this verification checklist:
composer validatepasses with no errorscomposer installcompletes without dependency conflicts- All automated tests pass
- Application boots without errors (check logs, not just the browser)
grep -ri "zendframework" composer.jsonreturns no resultsgrep -ri "Zend\\\\" src/ module/ config/returns no results (excluding comments you have chosen to leave)- Critical user journeys work end-to-end: login, form submission, data display, API calls
- Session handling works (especially if sessions contain serialised objects)
- Cache operations work (flush caches before testing)
- Email sending, file uploads, and other I/O operations function correctly
Deploy to a staging environment that mirrors production before going live. The migration is mechanical, but the surface area is large enough that something always slips through. Give it at least a few days of real usage on staging before promoting to production.
If you are still running ZF1 and need to modernise before you can even think about Laminas, the guide on modernising Zend Framework applications covers the path from ZF1 to a state where this migration becomes feasible.