Migration to Laminas FAQ


Removing the PHP Memory Limit When Migrating

In some cases when running the migration tooling, you may run into a fatal error indicating the PHP memory_limit has been reached.

To resolve this, you will need to run the laminas-migration tool through the PHP interpreter.

First, determine where the script lives:

$ which laminas-migration

Next, when invoking the command, invoke it as follows:

$ php -d memory_limit=-1 path/to/bin/laminas-migration # and any arguments you need

This will disable the memory limit, which should allow your script to finish successfully.

Module and Config Post Processor injection

If you are migrating an MVC, Apigility, or Expressive application to Laminas, the migration tooling attempts to inject some code in your application. This can fail if you have non-standard configuration.

Migrating MVC and Apigility applications

When migrating MVC and Apigility applications to Laminas MVC and Laminas API Tools, the migration tooling attempts to add Laminas\ZendFrameworkBridge as a module to the top of the config/modules.config.php file. If injection fails, add the module in a way appropriate to your application.


When migrating Expressive applications to Mezzio, the migration tooling attempts to add Laminas\ZendFrameworkBridge\ConfigPostProcessor as a post processor class to the ConfigAggregator constructor. The ConfigAggregator constructor has the following signature:

public function __construct(
    array $providers = [],
    ?string $cachedConfigFile = null,
    array $postProcessors = []

Typically, the structure of the config/config.php file in Expressive and Mezzio applications looks like the following:

$cacheConfig = [
    'config_cache_path' => 'data/cache/app_config.php',

$aggregator = new ConfigAggregator([
    // config providers from 3rd party code
    // ...

    // App-specific modules
    // ...

    // Include cache configuration
    new ArrayProvider($cacheConfig),

    // Load application config in a pre-defined order in such a way that local settings
    // overwrite global settings. (Loaded as first to last):
    //   - `global.php`
    //   - `*.global.php`
    //   - `local.php`
    //   - `*.local.php`
    new PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),

    // Load development config if it exists
    new PhpFileProvider('config/development.config.php'),
], $cacheConfig['config_cache_path']);

return $aggregator->getMergedConfig();

As such, the migration tooling rewrites the second to last line to read:

], $cacheConfig['config_cache_path'], [\Laminas\ZendFrameworkBridge\ConfigPostProcessor::class]);

In most cases, failure to inject means that the individual arguments have been pushed to their own line. In such cases, add the third argument as detailed above.

In other cases, applications may already be using post processors. If so, add \Laminas\ZendFrameworkBridge\ConfigPostProcessor::class to the list of post processors.

Alias Rewrites and 3rd Party Packages

While we have made huge efforts to prevent the migration tooling from clobbering your own code, there is two scenarios where we often cannot avoid it: when importing a namespace, and then referencing a class via that namespace; and when using 3rd party packages that reference legacy names.

As an example of the first scenario, consider the following usage of a class from the zendframework/zend-expressive-authorization-acl package:

use Zend\Expressive\Authorization\Acl;

// And later:
$acl = new Acl\ZendAcl();

The code above imports the Zend\Expressive\Authorization\Acl namespace, and then later instantiates a class relative to that namespace. Migration will rewrite this to:

use Mezzio\Authorization\Acl;

// And later:
$acl = new Acl\ZendAcl();

The latter line is incorrect at this point; it should read new Acl\LaminasAcl();. You will need to check for such scenarios either via your test suite, or manually. The grep tool or your IDE's "find" functionality can help you identify these; search for "Zend" in your codebase after a migration).

For the second scenario, sometimes 3rd party packages use the term "Zend", "ZF", "Apigility", or "Expressive" in their namespaces or class names:

use Some\Vendor\ZendAcl;

// And later:
$acl = new ZendAcl();

In the above example, a class name from a 3rd party package contains the word "Zend" in it. After migration, this becomes:

use Some\Vendor\ZendAcl;

// And later:
$acl = new LaminasAcl();

The above is now incorrect, as it's referring to a class that is not imported and thus does not exist. These sorts of changes are harder to locate. Make sure you review your diffs closely, and that you have an adequate test suite, to ensure that the code is exercised; this is the best way to ensure you can catch these changes.

Clear your caches

If your application is not running in development mode, you will need to clear any configuration caches you have before testing. If you are using zf-development-mode (which becomes laminas-development-mode!), try enabling development mode:

$ composer development-enable

Expressive/Mezzio users can use the clear-config-cache command:

$ composer clear-config-cache

Migrating With A zendframework/zendframework Package

If you have chosen to install the package zendframework/zendframework in your application, or if you started your project from the skeleton application before the 3.0 release, you will have some extra steps post-migration.

First, please make sure you use version 1.2.0 or later of laminas-migration. That version provides some important updates that ensure that your zendframework/zendframework package is migrated to the correct Laminas packages.

Afterwards, we recommend that you remove any packages you are not explicitly using from your project, per the following steps.

Identify Packages You Use

The first step is to identify which individual packages you actually consume.

From a *nix or Mac command-line, you can run code similar to the following to get a list of uniquely imported classes:

$ grep -rP 'use Laminas' {source code directories} | uniq | sort

Where {source code directories} can be one or more of src/, modules/, config/, bin/, public/ etc., but NOT vendor/. You will want to ensure every such directory is listed. If you are using an IDE, you may be able to do a project-wide search.

The above will provide a list of namespaces and classes that you are importing in your project. From there, you can match those namespaces to the appropriate package. As examples:

  • Laminas\Mvc\Controller\AbstractController would translate to the laminas/laminas-mvc package.
  • Laminas\Form\Form would translate to the laminas/laminas-form package.
  • Laminas\Navigation\Page\Mvc would translate to the laminas/laminas-navigation package.
  • Laminas\ApiTools\ContentNegotiation\JsonModel would translate to the laminas-api-tools/api-tools-content-negotiation package.
  • And so on.

Remove Old Dependencies

Once you have your list of packages that you use, do the following: your application:

  • Open your composer.json file, and look for any entries related to Laminas packages, and make a list of any Laminas packages that are not in the list you compiled in the previous step.
  • Run composer remove {package} for each of these packages.

Once done, commit your composer.json and composer.lock file.

Zend Framework v1 Considerations

Zend Framework version 1 is past end of life, so it does not receive any rewrites during migration. However, custom code in the project being migrated does receive rewrites. This has the potential to cause errors.


The Zend_Mail component uses a "ZendMail_" prefix for filenames generated by the Zend_Mail_Transport_File transport. After migration, this transport adapter will continue to generate filenames using the "ZendMail_" prefix. Any custom code that also references the "ZendMail_" prefix will be rewritten to instead use a prefix of "LaminasMail_".

Since the v1 Zend_Mail component and later versions both use the same "ZendMail_" prefix, the migration cannot discriminate when rewriting custom project code. Consequently, you will need to reconcile any errors in your code arising from this prefix rewrite.