The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

Laminas Migration Guide: Moving from Zend Framework


In the 1990s, many organizations built critical infrastructure on DEC VAX systems running VMS. By the early 2000s, those systems were becoming obsolete — the original manufacturer had shifted focus, spare parts were scarce, and skilled administrators were retiring. Organizations faced a choice: keep running those aging systems until failure, or undertake the difficult migration to modern platforms.

This same fundamental dilemma confronts teams maintaining applications built on Zend Framework today. What was once a vibrant, commercially-backed framework now runs on momentum alone. The framework’s transfer to the Linux Foundation as Laminas represents a passing of the torch—from corporate stewardship to community maintenance.

You might wonder: is this comparison valid? After all, software doesn’t wear out like hardware does. The parallel, though, lies not in physical decay but in ecosystem viability. When the original maintainers step back, three practical realities emerge—security updates dwindle, PHP compatibility lags, and community momentum shifts. Those are the “spare parts” that become scarce for aging frameworks.

This guide walks through the migration to Laminas, the community-driven continuation of Zend Framework. We’ll examine the practical realities that make migration necessary, walk through the step-by-step process, explore common challenges, and help you make an informed decision about your project’s future.

Understanding the Ecosystem: Where Are We Today?

Before we discuss how to migrate, let’s establish context. In 2019, Zend Framework was transferred to the Linux Foundation and renamed Laminas. This wasn’t merely a rebranding—it represented a fundamental shift in governance from corporate stewardship to community-led development. The framework components continue under the Laminas name, with three primary branches:

  • Laminas: The MVC framework and component library (direct successor to Zend Framework)
  • Mezzio: A middleware runtime that succeeded Expressive, implementing PSR-15 standards
  • Laminas API Tools: API building capabilities, formerly Apigility

For organizations maintaining Zend Framework applications, this governance shift has created three practical realities that affect day-to-day operations:

Security and maintenance: Zend Framework receives no further official updates. Security vulnerabilities discovered after the transition will not be patched by the original maintainers—one must either backport fixes from Laminas or accept the risk. Laminas, by contrast, has an active contributor base addressing issues and releasing updates. The distinction matters because running unsupported software in production environments exposes organizations to unmitigated security risks. Let’s put this in perspective: if a critical vulnerability like Log4Shell affected a Zend Framework component, your team would need to fix it themselves or remain vulnerable.

PHP compatibility: Laminas tracks current PHP versions, supporting language features introduced after Zend Framework’s final release. This isn’t an academic concern—PHP 8.3 introduced readonly classes and several performance improvements; PHP 8.4 (expected 2026) will add new features that may break older frameworks. Applications on PHP 8.3 or later may find Zend Framework components incompatible, while Laminas accommodates modern PHP releases. Of course, you might think you can stay on an older PHP version, but doing so means missing security updates and performance improvements—and eventually, hosting providers may drop support for outdated PHP versions altogether.

Community momentum: The ecosystem around Laminas—documentation, tutorials, third-party packages—continues to grow. Third-party libraries increasingly target Laminas compatibility rather than Zend Framework. This creates a quiet but accumulating problem: when you need to add new functionality, you’ll find fewer packages that work with Zend Framework. The workaround—forking and updating dependencies yourself—adds maintenance burden. Over time, this ecosystem momentum becomes a compelling reason to migrate independent of security concerns.

Standards evolution: Laminas maintains adherence to PHP-FIG (Framework Interop Group) standards, promoting interoperability with the broader PHP ecosystem. This matters because modern libraries increasingly assume PSR-compliant autoloading and interfaces. A package built for Laminas will integrate smoothly with other modern tools; a Zend Framework package may require adapters or may not work at all. Standards compliance isn’t just a technical nicety—it affects your ability to use contemporary libraries without friction.

You might wonder: can’t we remain on Zend Framework indefinitely? The answer depends on your specific context. If your application is stable, runs on an older but supported PHP version, and doesn’t require new features, staying put is a valid choice—one that some organizations make deliberately.

However, let’s examine what “staying put” really entails. Two scenarios typically emerge:

First, you might run indefinitely on an older PHP version, say PHP 7.4 or 8.0. The problem is that PHP’s own lifecycle means those versions eventually reach end-of-life. When PHP 8.0 reached end-of-life in November 2023, it no longer received security patches. Running an unsupported PHP version compounds your risk. You could, in theory, backport PHP security fixes yourself, but that’s a substantial engineering effort comparable to migration.

Second, you might attempt to upgrade PHP while keeping Zend Framework. Here, compatibility issues often surface. Zend Framework’s final release targeted PHP 7.2-7.4; running on PHP 8.1+ can trigger deprecation warnings and fatal errors due to removed extensions and changed function signatures. You’d need to patch Zend Framework components yourself—essentially creating a private fork and maintaining it perpetually. This isn’t migration, but it’s not staying put either; it’s adopting a maintenance burden comparable to using Laminas.

This brings us to the fundamental trade-off: migration requires effort now versus accumulating technical debt and security exposure over time. For many organizations, the migration cost is an investment—a way to preserve their application’s viability. One must consider not just the upfront time but the ongoing maintenance savings: staying current with security patches, using modern PHP features, and accessing updated libraries. What looks like saving money today often becomes more expensive tomorrow.

Of course, if your application is truly static—no security-sensitive operations, running in an isolated environment, with no need for new features—staying on Zend Framework might be acceptable. Be honest with yourself about whether that describes your situation.

Migration Approaches: Your Options

Given the practical realities we’ve just examined, what migration paths are available? There are four major approaches worth considering. Let’s enumerate them, comparing their trade-offs.

Option 1: Automated Migration Tool

Laminas provides a dedicated migration tool, laminas-migration, designed to automate the bulk of the namespace conversion. The tool performs three primary functions:

  • Replaces Zend\ namespace declarations with Laminas\
  • Updates use statements accordingly
  • Modifies your composer.json to replace zendframework/* packages with laminas/* equivalents

For most projects following conventional Zend Framework 3.x patterns, this tool handles 95%+ of the necessary changes. The key advantage is speed—what might take days manually can be done in minutes. The limitation is that it doesn’t handle edge cases: highly customized code, unusual patterns, or third-party packages still referencing Zend Framework classes.

Option 2: Manual Migration

For applications where the automated tool falls short, manual migration may be necessary. This involves:

  • Searching for all Zend\ references throughout your codebase
  • Updating namespace declarations individually
  • Verifying compatibility of each component manually
  • Testing thoroughly after each change

Manual migration provides complete control and can handle edge cases that automation misses. The cost is substantial: a small to medium application might require 2-5 days of focused work; larger applications could take weeks. This approach makes sense when you need to refactor during migration anyway—it’s not adding much overhead.

Option 3: Rewrite or Replace

Some teams choose to rewrite applications entirely using a different framework—Laravel, Symfony, or a custom solution—rather than migrate. This offers a clean slate but carries far higher risk and cost. A full rewrite typically takes longer and introduces more potential for bugs than incremental migration because you’re re-solving solved problems and must re-implement business logic from scratch.

Rewriting is only justified if you’re already planning major architectural changes that exceed what migration accommodates—for instance, switching from MVC to a microservices architecture. For simply addressing the Zend Framework migration need, rewriting is usually overkill.

Option 4: Stay on Zend Framework

Don’t overlook this option. If your application is stable, minimal-maintenance, and runs in an isolated environment with appropriate security controls, staying on Zend Framework may be a valid choice. We examined the risks earlier—no security updates, PHP compatibility issues, ecosystem decline—but those risks may be acceptable depending on your context.

The question is not whether Zend Framework is obsolete—it is—but whether that obsolescence actually affects your specific application. A small internal tool used by five people on an air-gapped network faces different constraints than a public-facing e-commerce platform processing credit cards.

Comparing the Approaches

Let’s step back and compare these options systematically:

ApproachEffortRiskWhen It Makes Sense
Automated toolLowLowStandard ZF3 applications, want fastest path
Manual migrationMedium-HighMediumHighly customized code, edge cases, or refactoring anyway
RewriteVery HighVery HighAlready planning major architecture changes, or Zend Framework deeply problematic
Stay putNoneAccumulatingTruly static applications in controlled environments

In my experience working with teams on these migrations, the automated migration tool approach works best for most scenarios—it’s faster than manual migration, less risky than rewriting, and addresses the core problem of running unsupported software. Let’s examine how it works in practice.

Migration Process: A Step-by-Step Walkthrough

The automated migration process involves several distinct phases. We’ll walk through each one with concrete examples.

Prerequisites and Preparation

Before running the migration tool, verify two prerequisites. Both are straightforward but skipping them can cause problems later.

First, confirm your application uses Zend Framework 3.x. The migration tool targets ZF3 specifically. If you’re on ZF1 or ZF2, you’ll need to upgrade to ZF3 first—a separate process we won’t cover here but that’s documented in the Laminas project’s migration guide. How do you check your Zend Framework version? Look at your composer.json for dependencies like "zendframework/zend-mvc" and run:

grep "zendframework" composer.json

The output will show which version constraints you’re using. If you see "zendframework/zend-mvc": "^3.0" or similar, you’re on ZF3. If you see ^2.0 or ^1.0, you’ll need to upgrade first.

Second, ensure your project is under version control. Migration isn’t dangerous—the tool makes systematic changes that are well-understood—but having a rollback point is prudent. Plus, you’ll want to review the changes before committing them. Run:

git status

You should see a clean working tree. If you have uncommitted changes, commit them or stash them:

git add .
git commit -m "Pre-migration backup"

The git status command shows modified, deleted, and untracked files; a clean working tree means no uncommitted changes that would complicate review. By committing before migration, you create a clear checkpoint—if anything goes wrong, you can always git reset --hard HEAD to return to this state.

Using the Migration Tool

Now we’re ready to install and run the migration tool. Let’s proceed step by step. Before we begin, a quick reminder: since we confirmed earlier that we have a clean git working tree, we’re in a good position to review changes after the migration.

Step 1: Install the tool as a development dependency.

composer require --dev laminas/laminas-migration

This command does two things: it updates your composer.json by adding laminas/laminas-migration to the require-dev section, and it installs the package by running composer install. Notice we use --dev—the migration tool is only needed during the migration process; it doesn’t belong in production. The distinction matters because development dependencies aren’t installed when you run composer install --no-dev on production servers.

After running this command, you should see output similar to:

Using version ^1.0 for laminas/laminas-migration
...
- Installing laminas/laminas-migration (1.0.3): ...

The exact version number will vary based on the latest release; at the time of writing, 1.0.x is the stable branch. If you see a different version like 1.1.x or 2.0.x, that’s fine—the tool’s basic functionality remains consistent.

Step 2: Run the migration analysis (optional but recommended).

Before making changes, you can perform a dry-run to see what will be modified:

vendor/bin/laminas-migration migrate --dry-run

The --dry-run flag tells the tool to analyze without modifying files. This is valuable for understanding the scope of changes and verifying that the tool will handle your codebase correctly. The output includes a list of file paths that would be modified and a summary showing how many use statements would change, how many namespaces would be replaced, and what composer.json updates would occur.

Of course, the dry-run might miss issues that only appear during actual modification—file permissions problems, read-only files, etc.—but it gives you a reasonable preview.

One may wonder: why not just run the migration directly? The dry-run lets you assess the changes and decide whether to proceed or address any surprises first. It’s a safety net, though not a complete guarantee.

Step 3: Execute the migration.

vendor/bin/laminas-migration migrate

The migrate subcommand is the main action. Without --dry-run, the tool actually modifies files. Before doing so, it prompts for confirmation:

> This will modify 47 files. Continue? [y/N]:

The number shown reflects the files the tool determined need changes. Type y to proceed; any other input aborts. The confirmation prevents accidental migrations if you run the command in the wrong context.

The tool then performs these actions:

  • Scans your PHP files for Zend\ namespace declarations
  • Replaces them with Laminas\ equivalents
  • Updates use statements to reflect the new namespaces
  • Modifies your composer.json to replace zendframework/* package references with laminas/*
  • Reports a summary of all changes

Upon completion, you’ll see something like:

Migration complete!
- 47 files modified
- composer.json updated
- 12 packages replaced (zendframework/* → laminas/*)

At this point, your codebase has been transformed from Zend Framework to Laminas namespaces in all PHP files. But we’re not finished yet—the migration tool doesn’t handle third-party packages or configuration files with hardcoded class names. Let’s continue with the post-migration steps.

Post-Migration Steps

Step 4: Install the updated dependencies.

composer install

This removes the old zendframework/* packages and installs their laminas/* replacements. If you see dependency conflicts, don’t panic—we’ll address those shortly.

Step 5: Verify the migration succeeded.

Check a few representative files to confirm namespaces changed correctly. For example, if you had a controller:

<?php
// Before migration:
// namespace Application\Controller;
// use Zend\Mvc\Controller\AbstractActionController;

// After migration:
namespace Application\Controller;
use Laminas\Mvc\Controller\AbstractActionController;

Search for any remaining Zend\ references:

grep -r "Zend\\\\" .

Be aware: this search will also hit legitimate occurrences in comments or documentation. You’re looking for actual code references.

Step 6: Run your test suite.

If your project has automated tests, run them now:

./vendor/bin/phpunit

Or if you use a different test runner:

composer test

Address any failures. Common issues include:

  • Missing class names (if the migration tool missed a reference)
  • Third-party packages that still depend on Zend Framework
  • Configuration files with hardcoded class names

Step 7: Handle dependencies.

Third-party packages are the most common post-migration complication. Some packages haven’t released Laminas-compatible versions. We’ll discuss strategies for this in the next section.

Let’s pause here. You’ve run the migration tool, installed updated dependencies, and verified your codebase. Your application should now be running on Laminas rather than Zend Framework. But what about those dependency issues?

Dependency Challenges and Solutions

The migration tool handles your codebase and composer.json dependencies, but it cannot update third-party packages that themselves depend on Zend Framework components. These present a challenge.

You might encounter errors like:

 laminas/laminas-mvc depends on zendframework/zend-loader ^2.5 -> zendframework/zend-loader[2.5.0] cannot be installed because it conflicts with laminas/laminas-loader.

Or runtime errors:

Fatal error: Class 'Zend\Loader\StandardAutoloader' not found

These indicate that a package you’re using hasn’t migrated to Laminas yet. Here’s how to address them.

Strategy 1: Check for updated package versions.

Many popular Zend Framework packages have Laminas equivalents available. First, check whether the maintainer has released a Laminas-compatible version. Search Packagist or run:

composer depends zendframework/zend-loader

Then check if those packages have newer versions with Laminas dependencies. Often, updating to the latest version resolves the issue:

composer update vendor/package-name

Strategy 2: Find Laminas-native alternatives.

Some packages have been replaced entirely by Laminas components. For example, zendframework/zend-http maps to laminas/laminas-http. If a dependency relies on the old package, you might:

  • Remove the dependency if it’s not essential
  • Replace it with a Laminas-native package
  • Find an alternative library with similar functionality

Strategy 3: Fork and update yourself.

If a dependency is critical but hasn’t been updated, you can fork it, modify its composer.json to use Laminas packages, and point your project to your fork:

composer config repositories.forked-package vcs https://github.com/yourusername/package-name
composer require vendor/package-name:dev-main

Then in the forked repository, run the migration tool on its codebase and update its dependencies. This approach requires ongoing maintenance but can be viable for critical dependencies.

Strategy 4: Use laminas/laminas-zendframework-bridge.

For packages that still reference Zend Framework classes but can work with Laminas, the laminas/laminas-zendframework-bridge provides polyfills that map old class names to new ones. Install it:

composer require laminas/laminas-zendframework-bridge

This package creates class aliases so that references to Zend\ classes resolve to their Laminas\ counterparts. It’s not a complete solution for all cases, but it helps bridge compatibility gaps.

We’ll examine this bridge package more in the troubleshooting section.

Common Pitfalls and Troubleshooting

Even with the migration tool, certain areas often require manual attention. Let’s anticipate those.

Configuration Files

The migration tool focuses on PHP namespace declarations. It doesn’t process configuration files, which may contain class name strings. Check:

  • config/autoload/*.php or config/autoload/*.globals.php
  • module/Application/config/module.config.php
  • Any service manager or invokable class configurations

Look for strings like 'Zend\Controller\AbstractActionController' and replace them:

// Before:
'service_manager' => [
    'invokables' => [
        'Zend\Authentication\AuthenticationService' => 'Zend\Authentication\AuthenticationService',
    ],
],

// After:
'service_manager' => [
    'invokables' => [
        'Laminas\Authentication\AuthenticationService' => 'Laminas\Authentication\AuthenticationService',
    ],
],

Annotations and DocBlocks

While the migration tool may update use statements in DocBlocks, it generally doesn’t change the content. Review annotations like:

/**
 * @Route("/route-name", name="route-name")
 * @Method({"GET", "POST"})
 */

If these contain Zend\ class references, update them manually.

Custom Extensions and Plugins

If you extended Zend Framework classes directly, verify that your extensions still resolve correctly. For example:

// Before:
class MyController extends \Zend\Mvc\Controller\AbstractActionController

// After migration should be:
class MyController extends \Laminas\Mvc\Controller\AbstractActionController

The migration tool should handle these, but it’s worth checking especially if you used fully-qualified class names (starting with \Zend\).

Service Manager Configuration

Laminas maintains compatibility with Zend Framework’s service manager, but component namespaces changed. Ensure your service_manager, controllers, and view_manager configurations reference Laminas classes.

Event Listeners

If you attached event listeners using string identifiers like 'Zend\ModuleManager\ModuleEvent::EVENT_LOAD_MODULE', update these to Laminas equivalents:

// Before:
$eventManager->attach('Zend\ModuleManager\ModuleEvent::EVENT_LOAD_MODULE', $listener);

// After:
$eventManager->attach('Laminas\ModuleManager\ModuleEvent::EVENT_LOAD_MODULE', $listener);

Testing for Missed References

Even after the automated migration, you may have missed references. Here’s a comprehensive search approach:

# Search for Zend namespace in PHP files (excluding vendor)
grep -r --include="*.php" "Zend\\\\" . | grep -v vendor/

# Search for Zend in config files
grep -r --include="*.php" --include="*.yml" --include="*.json" "zendframework" . | grep -v vendor/

Review each result and determine whether it needs updating.

Trade-offs and Long-Term Considerations

Let’s step back and consider the broader picture. Migration isn’t only a technical upgrade—it has implications for your team, your architecture, and your future maintenance burden.

The effort you’re investing now. Migration takes time. A small to medium application might require 2-5 days of work including testing. Larger applications with many custom components could take weeks. That’s time your team isn’t spending on new features.

The risk profile. Migration introduces risk: things can break, edge cases may emerge in production, and you might discover incompatible dependencies late in the process. A phased approach—migrating module by module—can mitigate this, but requires more planning.

The ongoing maintenance picture. Once migrated, your application runs on a framework with active security updates and compatibility with current PHP versions. That’s a significant long-term benefit. You’ll also be able to use modern tooling and libraries that assume Laminas compatibility.

The alternative costs. Compare migration cost against the cost of remaining on Zend Framework. If you need to upgrade PHP and discover Zend Framework isn’t compatible, you’d need to either backport fixes yourself (costly) or undertake an emergency migration (risky). Proactive migration spreads effort over a controlled timeline.

Future flexibility. Being on Laminas opens options: you can adopt Mezzio for middleware applications, use Laminas API Tools for RESTful services, or gradually migrate components toother frameworks over time if desired. Staying on Zend Framework limits these possibilities.

In my experience, the decision often comes down to this: if your application is business-critical and you plan to maintain it for more than another year or two, migration is worth the upfront cost. The alternative is accumulating technical liability that eventually requires emergency attention.

Conclusion

Migrating from Zend Framework to Laminas is a practical response to a fundamental change in the PHP ecosystem. The automated migration tool handles the bulk of the work—namespace updates, Composer dependency changes—while your team focuses on verifying correctness, handling edge cases, and testing thoroughly.

The process we’ve walked through follows a consistent pattern:

  1. Prepare your project with backups and version control
  2. Run the migration tool to update namespaces and dependencies
  3. Install the new Laminas packages
  4. Verify changes and search for missed references
  5. Test thoroughly to catch functional regressions
  6. Address dependency issues with updates, alternatives, or bridges
  7. Deploy with confidence once validation is complete

Before beginning, consider whether migration aligns with your project’s priorities. The effort is significant but finite; the benefit is ongoing access to security updates and modern PHP compatibility.

For applications actively using Zend Framework components, migration to Laminas offers a clear path forward—one that preserves your codebase while positioning it for sustainable maintenance.

If you encounter specific issues during migration, the Laminas community maintains documentation and forums where you can seek help. And of course, this guide aims to equip you with the understanding needed to navigate the process successfully.

Sponsored by Durable Programming

Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.

Hire Durable Programming