How to Develop Zend Expressive Applications as Easily as Laravel - The Definitive Guide

Can you develop application in Zend Expressive as easily as you can with Laravel? Is it possible to make Zend Expressive fly, like Laravel does out-of-the-box? Recently I sought to find out. Here’s my step-by-step guide to doing it + some key recommendations.

How to Develop Zend Expressive Applications as Easily as Laravel

Can you develop application in Zend Expressive as easily as you can with Laravel? Is it possible to make Zend Expressive fly, like Laravel does out-of-the-box? Recently I sought to find out. Here’s my step-by-step guide to doing it + some key recommendations.

But First - A Little Background

Laravel is the PHP framework For web Artisans, able to create applications nary with the speed of thought. Zend Expressive, on the other hand, is the enterprise-ready framework. It’s designed for build the largest of applications, for companies in the Fortune 500.

I was asked, recently, if applications could be built as easily in Zend Expressive as in Laravel. Recently I sought to find out. Here’s what I found.

I’ll say right from the get go, that this won’t be a truly objective comparison. But, given that I’ve spent so much time around Zend Framework, whether developing or writing about it, and not near as much time around Laravel, that’s to be expected.

Perhaps this doesn’t need to be said. But I want to add this qualifier right from the start. That way, you’ll not think that this is a hack-job on Laravel.

This all started because I was asked to begin working with Laravel recently, as part of a write-up I’m doing for a client. After working my way through some of the latest documentation, I was able to couple together a reasonably simple URL shortener application.

The application is a basic CRUD application, which makes use of the zenapply/laravel-shortener package, to provide an interface to Google’s URL Shortener API.

The reason that I chose to write this application, was because I wanted something which would do the CRUD basics, have to interact with a data source and an external service. I wanted it to be more than “hello world”, but without being overly complicated at the same time.

After a while of getting to know how Laravel’s directory structure is laid out, I began adjusting to what I believe is the Laravel way of thinking.

I started to pick up speed in creating the application. By the time I’d finished the initial version — nothing too spectacular for what it’s worth — I was actually starting to enjoy the process, the simplicity.

Given that feeling of nerdy satisfaction, I tweeted about it:

I wasn’t really surprised when my tweet contained some reference to Laravel that I saw a flurry of replies and retweets. What did surprise me was this question:

Can This Really Be Done?

I’ll be honest with you, I didn’t know. I honestly wasn’t sure whether it would be as simple to create, near enough, the same application, in as few lines of code in Zend Expressive, as I had with Laravel.

After a little bit of thought, I was convinced that it was. And as it turns out, I pretty much was able to do it. So my, qualified, answer to @andylondon’s original question is: pretty much.

But now, let’s set how you create a Zend Expressive app as quickly as with Laravel. To do so, there’s eight areas to be aware of:

  1. Forms and Entities
  2. Form ViewHelpers
  3. Routes and Controllers
  4. View Helpers
  5. Database Access
  6. Data Models
  7. Database Migrations and Testing
  8. Testing

1. Forms and Entities

The core simplification I found, is perhaps a bit of a controversial one. I used annotations. You see, normally I’d have at least an entity class, a form class, an input filter for validation, and a hydrator, when working with data.

But then, to use all of those in Expressive, I’d need a number of accompanying factory classes, as well as the dependencies configuration. What’s more, as the application’s rather simple, I started asking myself, why do I need separate classes for everything?

Why can’t I combine as much as possible, perhaps in to one class? This tied in well with my recent suggestion of “do you need a class for that”. So I had a hunt through the manual, and found Zend Form Annotations, which allowed me to auto-generate a form from annotations in an entity class.

Here’s the core of the class definition, where you can see the annotation at play:


namespace App\Entity;

use Zend\Form\Annotation;

 * @Annotation\Name("url")
class Url
     * @Annotation\Type("Zend\Form\Element\Text")
     * @Annotation\Options({"label":"The original url:", "class": "form-control"})
     * @var string
    private $original_url;

    /** @var string */
    private $shortened_url;

I’m only interested in including $original_url in the form, so that’s the only one that has an annotation. The annotation says that the form will include a text field, called original_url, and defines some properties for the label element.

Then, to generate a form from the entity’s annotations, required one line in the page (controller) action, which you can see below:

$this->form = (new AnnotationBuilder())->createForm(Url::class);

AnnotationBuilder’s createForm() method reads the annotations from the class name string provided, and generates a matching form object. This was then assigned to a member variable, $form, and that provided as a template variable, also called form.

2. Form ViewHelpers

Taking this approach helped me solve another little, unexpected gotcha: rendering the form in the view templates. In the Laravel equivalent, I used the laravelcollective/html package, which provides the equivalent of Form ViewHelpers, with one exception, they don’t need a form object.

You can call them to render a form, with or without a form object. In Zend Expressive, the Form ViewHelpers aren’t as accommodating. Here’s an summary from the Laravel Blade template which I created:

{!! Form::open() !!}
<div class="form-group">
{!! Form::label('url', 'URL to shorten') !!}
{!! Form::text('url', null, ['class' => 'form-control']) !!}
{!! Form::token() !!}
{!! Form::submit('Save', ['class' => 'btn btn-primary']) !!}

{!! Form::close() !!}

Now here’s the equivalent snippet from the Expressive phtml file:

    $form = $this->form;
    $form->setAttribute('class', 'form-horizontal');
    echo $this->form()->openTag($form);

<div class="form-group">
    $element = $form->get('original_url');
    echo $this->formLabel()->openTag(['class' => 'col-sm-2 control-label']);
    echo $this->formLabel($element);
    echo $this->formLabel()->closeTag();
    <div class="col-sm-10">
        echo $this->formText($element);
        echo $this->formElementErrors($element);

<input type="submit" value="submit" class="btn btn-primary" />

<?php echo $this->form()->closeTag(); ?>

Now I could have compressed the Expressive version down, and I’ll likely do that shortly. But for the moment, it’s the more verbose of the two.

However, up till this point, the applications are only marginally different, both in complexity and verbosity. The amount of code is largely equivalent, and the readability is also on par. Given that I’m giving one point to both.

3. Routes and Controllers

Here’s where things started to diverge. From what I understand, in Laravel, there’s a lot of bootstrapping and service configuration which is automatically (perhaps auto-magically) done for you. My source of truth on all things Laravel, Joe P. Ferguson, said this:

You just have to assume Laravel auto wires up all the things you’d normally have to do yourself in ZF.

Controllers, unlike in Zend Expressive, don’t require explicit dependency configuration. All I needed to do in Laravel, to have a route be handled by a Controller action, was to specify the controller and action in the route configuration, such as the one below, for the /view-urls route.

Route::get('/view-urls', 'UrlController@viewUrls')->name('view-urls');

Using the Laravel Facade, it was a one-liner to complete. I could even name the route as well. The equivalent in Zend Expressive was a bit longer; it required the following:

  • A page action
  • A page action factory
  • A route
  • A dependency configuration addition

You can see the combined route and dependency configuration below. It shows that ViewUrlsPageFactory will handle the request to retrieve ViewUrlsPageAction, and that the home route will result in a request for said class.

I should say, that I probably could have avoided using the factory class, by instead using an invokable. This would have achieved the same affect, yet with fewer classes and less configuration required.


return [
    'dependencies' => [
        'factories' => [
            App\Action\ViewUrlsPageAction::class => App\Action\ViewUrlsPageFactory::class,

    'routes' => [
            'name' => 'home',
            'path' => '/',
            'middleware' => App\Action\ViewUrlsPageAction::class,
            'allowed_methods' => ['GET'],

So for sheer simplicity, it’s a win to Laravel. However, for complete transparency, I’m giving the nod to Zend Expressive.

4. View Helpers

The next component is the Form ViewHelpers. I’m going to use that term, as this is a Zend-based blog. So I’m sticking with the terminology that most readers would be familiar with.

Both for Zend Expressive, and for Laravel, form view helpers were not part of the core. They were removed from Laravel in version 5.0, and they’ve never been a part of Zend Expressive.

To make them available in Laravel, I added laravelcollective/html to composer.json and ran composer update. Following that I added Collective\Html\HtmlServiceProvider::class, to the providers list and 'Html' => Collective\Html\HtmlFacade::class to the aliases list in app/config/app.php. After that was done, I was able to use them in the Blade template files.

In Zend Expressive, I had to do about the same level of work, to make them available. I first added support for them, by running composer require zendframework/zend-form.

After that, I added a new file under config/autoload/ called In that file, on suggestion from project team lead Matthew Weier O’Phinney, I added the following code:


use Zend\Form\ConfigProvider;

return (new ConfigProvider())->__invoke();

There’s a new trend in Zend Expressive, which is to make use of ConfigProvider files. These handle most of the configuration-related setup for a particular package. One of these is already available for Zend\Form.

So, all I had to do was to invoke it, and the ViewHelpers were available for use in the template files. So, for this component, I’m awarding a point to Zend Expressive.

5. Database Access

Here’s where Laravel started to edge ahead — just. Out of the box, Laravel provides a host of pre-generated configuration files, which include database configuration.

Specifically, it provides configuration for access to an SQLite, MySQL, and PostgreSQL database, with MySQL as the default.

As I was using a SQLite database, all I needed to do was to change the default connection to sqlite in app/config/database.php, as well as update .env to match. With that done, database access was ready to go.

Zend Expressive wasn’t quite so simple. Zend Expressive, like Laravel, provides the necessary plumbing, requiring you only to ensure that your dependency configuration has at least the following:

return [
    'dependencies' => [
        'factories' => [
            Zend\Db\Adapter\Adapter::class => Zend\Db\Adapter\AdapterServiceFactory::class,

However, you also need to provide the connection configuration. None are pre-generated, because Zend Framework takes a hands-off approach.

The reason why, is that the development team believes that no two applications will be alike. Given that, they cannot know what your needs will be.

So, they provide the plumbing, but you have to take it from there. As a result, I had to add the following to config/autoload/database.local.php:

return [
    'db' => [
        'driver' => 'Pdo_Sqlite',
        'database' => __DIR__ . '/../../data/database/database.sqlite',

With that done, the applications were ready for database access. So for simplicity, it’s a win to Laravel. On the point of explicitness, I’d argue that it’s pretty much of a muchness.

6. Data Models

When it comes to data models, depending on how you approach it, at least for simplicity, it’s a hands-down win to Laravel.

All I needed to do was to create a new class, called Url, under /app, and have it extend Illuminate\Database\Eloquent\Model.

Then, I needed only set a protected member variable, $table, so that it would know which table in the schema to refer to.

From there, in UrlController::manageUrl(), I only needed to instantiate a new Url object, set the required properties, and then call its save() method.

In true ActiveRecord (or in this case, RowGateway) style, the object could persist itself to the database. You can see manageUrl below:

public function manageUrl(Request $request)
    if ($request->isMethod('post')) {
        $url = new Url();
        $url->original_url = $request->input('url');
        $url->shortened_url = Shortener::shorten($request->input('url'));

        return redirect('/view-urls');

    return view('url.manage', []);

Retrieving all records was equally as easy. To do that, I only needed to call the Url object’s all() method, which would return all records from the model’s underlying table.

The retrieved data could then be set as a template variable, and iterated over in the template. Here’s the function definition:

public function viewUrls()
    return view('url.view', ['urls' => Url::all()]);

In Zend Expressive, it wasn’t so easy. But as you’ll see, I’m OK with that. I could have almost mimicked the Laravel approach, by making use of a RowGateway object.

Doing so would have resulted in about the same amount of code. Instead, I decided to go with one of my fall-backs, the TableGateway.

Like the page action, to make use of it, I needed a factory class, which would be registered as a service in the ServiceManager. It’s a little verbose, but it does the job.

public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    /** @var Adapter $dbAdapter */
    $dbAdapter = $container->get(Adapter::class);
    $tableGateway = null;

    switch ($requestedName) {
        case ('UrlsTableGateway'):
            $tableGateway = new TableGateway(
                    new RowGatewayFeature('id')

    return $tableGateway;

Here, when "UrlsTableGateway" is requested from the container, it will instantiate a new TableGateway object, which wraps around the urls table, and in turn uses a RowGatewayFeature.

The reason for this is that, normally, the resultset from a TableGateway is an array of associative arrays, where the key is the column name, and the value of the column’s value. By using a RowGatewayFeature, the resultset will instead contain an array of RowGateway objects.

Given that, the records retrieved can, like Laravel Models, have their properties set, and then be persisted back to the database.

Now to why I prefer this approach - it’s clearer and more transparent.

Referring back to my source of all things Laravel, Joe Ferguson, when we had a conversation after PHP World, in Washington D.C., last November; he told me that he never uses Laravel Facades. This is primarily because they hide so much away, along with the difficulty in testing, and all the other issues that they can create.

I am aware that, while the documentation’s rife with references to them, you don’t need to use them. However, I’m still rather new to Laravel, so I’m not yet familiar with alternate approaches.

But regardless, I’m voting for Zend Expressive, because transparency, testability, and clarity are essential for me. Furthermore, this abstract factory could be easily extended to handle instantiating any number of TableGateway objects.

Now to use it, I needed to add a further dependency configuration to config/autoload/, which you can see below:

'abstract_factories' => [

Then there’s the final piece to the puzzle: the page action’s constructor and instantiation. As I’m not making use of any magic, deferring to explicitness, AddUrlPageAction’s constructor takes a TableGateway object as a dependency.

This is then used to initialise a private member variable, called $table, which is then used for all database interaction. Have a look at /src/App/Action/AddUrlPageAction.php and /src/App/Action/AddUrlPageFactory.php to see how I approached it.

7. Database Migrations

Now that’s the core of the application discussion out of the way. But it wouldn’t be complete without including coverage on what I consider two essential elements, those being database migrations and testing. Let’s start with database migrations.

Here’s where I have come to appreciate how Laravel’s put together. It includes components for both migrations and testing as part of its core. Zend Expressive, on the other hand, only provides a testing component, and if I’m correct, one that’s not quite as extensive as that provided by Laravel.

### Database Migrations

Starting with database migrations, it was relatively trivial, after the database configuration had been established, to create migration files. I created the following one, with a minimum of effort:

class CreateUrlsTable extends Migration
    public function up()
        Schema::create('urls', function (Blueprint $table) {
            $table->string('shortened_url')->comment('Stores shortened url');
            $table->string('original_url')->comment('Stores shortened url');

    public function down()

To run them, I only needed to call php artisan migrate. I’ve not used Artisan a lot. But I found it pretty handy to have a command line tool that manage so many aspects of setup and administration.

Given that no database migrations come with Zend Expressive, out of the box, I needed to make use of an external package to fill the gap. Gladly, Doctrine Migrations readily fit the bill.

I won’t go in to an exhaustive discussion about how to set it up. I’ll save that for another post. Alternatively, you can read this concise introduction by Rob Allen.

Suffice to say that once setup, the migration file, which you can see below, was equally as simple, and the command to run it, vendor/bin/doctrine-migrations migrations:migrate, was as well.

class Version20160718060344 extends AbstractMigration
    public function up(Schema $schema)
        $table = $schema->createTable("urls");
        $table->addColumn("id", "integer");
        $table->addColumn("shortened_url", "text", ["notnull" => true]);
        $table->addColumn("original_url", "text", ["notnull" => true]);
        $table->addColumn("created", "date", ["notnull" => false]);
        $table->addColumn("updated", "date", ["notnull" => false]);


    public function down(Schema $schema)

So on this point, I’m awarding the point to Laravel.

8. Testing

Now let’s conclude with testing. The stock Laravel application, which I extended upon, already had some basic tests which, with some searching of the code and documentation, I was able to expand upon.

They show how to perform both basic unit and acceptance testing. You can see in the example below, that I’ve loaded some sample data in to the application’s database, prior to executing the assertions.

Then I’ve run a set of assertions, made quite simple by way of the fluent interface, which test the response code, and find a variety of information within the rendered page.

public function testViewUrlsPage()
    // Load some sample data
    $url = factory(App\Url::class)->create([
        'shortened_url' => 'http://tSQ1r84',
        'original_url' => '',

        ->see('Manage URLs')
        ->see("This form let's you shorten a new url, or update an existing one.")

        ->click('View URLs')

However, when I needed to start going beyond the information in the documentation, progress was anything but rapid.

I found that I could do a host of basics, but more advanced tests, such as DOM inspections using XPath selectors, either weren’t possible, or the code comments, and documentation weren’t as transparent as I’d have expected.

To get the equivalent level of testing support available in Zend Expressive, was able to be done through making use of Codeception.

This is an excellent package, one I’ve come to really enjoy using, over the last couple of months.

Once it had been added to the project, by running composer require codeception/codeception, it only required a bit of setup from the command-line to get everything running.

Specifically, the core configuration files needed to be created, by running codecept bootstrap. After that, some further customisation of the configuration files was required, such as enabling specific modules and adding details for the database module.

But after that was completed, tests could be created through the well thought out command-line interface.

For example, to create an acceptance test, the following command could be used

codecept generate:cept acceptance Welcome

Then, I could have rather powerful acceptance, functional, and unit tests, up and running rather quickly. Once tests were created, they could be run, again from said interface, rather like Artisan provides for Laravel, via codecept run.

On this score, it’s clear that more work’s required for Zend Expressive, to get the same level of test support as Laravel provides, out of the box.

However, based on my respective knowledge of both, Codeception provides more functionality, once it’s ready. So I’ll give the point to Laravel, but with a half point to Codeception.

Key Takeaways

Going through this process taught me two key things:

  1. You Can’t Make An Easy Comparison
  2. Don’t Force A Square Peg In To A Round Hole

You Can’t Make An Easy Comparison

The reason being, is that you can’t give a straight yes or no answer. It’s like asking: is desktop Linux as easy as Windows? The presumption there is that you want to do exactly the same thing in Linux as you can with Windows.

Well, if you wanted the exact same experience on Linux, as you get with Windows, then use Windows!

As you’re working with two different systems, two different approaches to solving the same challenge, then the end result may be the same — but how they work will naturally be different.

So it is with Laravel and Zend Expressive. They’re two exceptional PHP frameworks which can be used to create similar applications.

Yet they were designed with different preconceptions about how an application’s should be put together. They were designed for different developer mindsets. And the list goes on.

So, whilst I was able to create the same application, roughly about the same size, the way they were developed was different.

Don’t Force A Square Peg In To A Round Hole

There’s one other thing worth mentioning; in effect, what I was trying to do was build a Zend Expressive application, as though I was using Laravel.

That made for, at times, an awkward and slower development process.

Why? Well I’m used to building applications in Zend Expressive in a certain way; that being:

  • Heavily relying on constructor injection and the ServiceManager
  • Working with TableGateway and RowGateway classes
  • Using lots of hydrators

And this is to name but a few concepts.

What I’ve seen, so far, in Laravel, at least using the documentation as a source of truth, is that there’s a lot of emphasis placed on using the ActiveRecord pattern, and Laravel Facades.

I tried to keep that in mind, as I was building the Zend Expressive version avoiding, as much as possible, using too many classes.

But it was hard to attempt to overlay a different paradigm on top the one I was used to. However, this turned out to be a bit of a blessing in disguise.

Through the vein of ultra-simplification, I started forcing myself to look further and deeper in to the available Zend Framework libraries for ways in which I could build the application extremely modestly. Turns out, it was a worthwhile pursuit.

In Conclusion

Whilst this has been a lengthy comparison of creating an elementary application in both Laravel and Zend Expressive, it’s not been an exhaustive one.

However, the process of building both applications, and writing about them has been a very enlightening experience.

I’ve come to appreciate some of the simplicity and auto-wiring, to borrow Joe’s terminology, which Laravel provides, which Zend Expressive doesn’t.

And I’ve learned how to build far simpler, and less involved applications in Zend Expressive. I’ve also gained a greater appreciation of the transparency that Expressive provides, through how it views the world.

This isn’t a dig at Laravel, or any of the fine people who are involved in the community, contribute to it, or use it on regular basis.

It’s simply to say that for me Zend Expressive is the framework I prefer. From what I’ve seen of Laravel, it’s nicely done.

However, the auto-wiring is something that I’m not altogether comfortable with. Perhaps, in time, as I learn more, that will pass.

But, to be fair, it does make building an initial application rather simple and efficient.

I did my best to make this a fair comparison, avoiding any temptation to have it be a hack job. You may not agree with me. But, then you don’t have to.

I’d love to get your opinion. Have you used both frameworks? What’s been your experience? What things have I overlooked, over-complicated, or perhaps even oversimplified?

How would you have approached either one? Share your thoughts in the comments.

You might also be interested in these tutorials too...

Mon, Aug 22, 2016

How To Use Laravel’s Eloquent ORM with Zend Expressive

Laravel’s Eloquent ORM isn’t likely the first one you think of when using Zend Expressive. You likely think of Zend\Db or Doctrine. But, with a little bit of work, it’s possible to use Eloquent with Expressive. Today’s tutorial shows you how - step-by-step.

Wed, May 31, 2017

Create Modules and Middleware with Command-Line Tooling Support

For the longest time, Zend Framework hasn’t had the strongest support for command-line tooling and scaffolding. However, in recent times, that’s all changed. Come find out how to use Expressive’s new tooling support to create modules and middleware rapidly.

Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus