Key Takeaways
- Setting up a module in Zend Expressive involves running a command that generates base code for the module, which is then automatically registered with the application and the Composer autoloader.
- The module setup process also involves creating a Blog entity and database tables, and letting the application know that the module provides Doctrine entities. This can be done by modifying the ConfigProvider.php file.
- Routing in Expressive modules does not register their own routes, but they can be made to do so using a specific trick involving the creation of a RoutesDelegator.php file and modification of the getDependencies() method in the ConfigProvider.php file.
- The implementation of a read-only blog module with Zend Expressive is a simple process that can be completed in a few minutes. This involves the creation of actions to respond to each route and the setting up of templates for the blog list and view pages.
I have learned a few tricks when writing Zend Expressive modules that I want to share with you.
Please follow the previous post first to set up a proper working environment. I explained how to install and configure Zend Expressive with Doctrine, Gulp, and an abstract reflection factory – it’ll take a total of 10 minutes.
In this tutorial, we’ll build a simple read-only blog module (a page listing blog posts from a database) in minutes, demonstrating the kind of rapid development one is capable of with Zend Expressive.
Module Setup
Run this command from your expressive app to get started:
./vendor/bin/expressive module:create Blog
This will generate some base code for a Blog module and will register your module automatically with your application. It will also register your module with the Composer autoloader.
Doctrine Entity and Database Table
Let’s make our Blog entity and database tables. First, we need to let our application know that this module provides Doctrine entities.
Open src/Blog/src/ConfigProvider.php
and add the following:
public function __invoke()
{
return [
'dependencies' => $this->getDependencies(),
'doctrine' => $this->getDoctrine(),
'templates' => $this->getTemplates(),
];
}
/**
* @return array
*/
public function getDoctrine(): array
{
return [
'driver' => [
'orm_default' => [
'drivers' => [
'Blog\Entity' => 'blog_entity',
],
],
'blog_entity' => [
'class' => \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver::class,
'cache' => 'array',
'paths' => [
dirname(__DIR__) . '/config/doctrine' => 'Blog\Entity',
],
],
],
];
}
Create a blog post entity config at src/Blog/config/doctrine/BlogPost.orm.yml
:
---
Blog\Entity\BlogPost:
type: entity
table: blog_post
id:
id:
type: integer
generator:
strategy: AUTO
fields:
title:
type: string
length: 255
content:
type: string
length: 16777215
Then, run ./vendor/bin/doctrine orm:generate-entities src
.
Sadly, Doctrine doesn’t and probably won’t support PSR-4 because the standard doesn’t force a directory structure.
To get around this, we need to move src/Blog/Entity
to src/Blog/src/Entity
.
Then, run this command to create your database table:
./vendor/bin/doctrine orm:schema-tool:create
Now, you can populate the database table by running the following SQL:
INSERT INTO expressive.blog_post VALUES
(null, 'Post 1', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'),
(null, 'Post 2', 'Mauris in libero laoreet, euismod lorem eget, tincidunt justo.'),
(null, 'Post 3', 'Donec sed diam congue, ultrices tellus at, venenatis felis.');
Routing
Modules in Expressive do not register their own routes. We can make them do so, however, with this handy trick. You don’t have to understand it. Just put the files in place and know that it works.
Create a src/Blog/src/Factory/RoutesDelegator.php
with the following contents:
<?php
namespace Blog\Factory;
use Blog\Action;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
class RoutesDelegator
{
/**
* @param ContainerInterface $container
* @param string $serviceName Name of the service being created.
* @param callable $callback Creates and returns the service.
* @return Application
*/
public function __invoke(ContainerInterface $container, $serviceName, callable $callback)
{
/** @var $app Application */
$app = $callback();
include __DIR__ . '/../../config/routes.php';
return $app;
}
}
In src/Blog/src/ConfigProvider.php
, add this as a top level array key to the getDependencies()
method:
'delegators' => [
\Zend\Expressive\Application::class => [
Factory\RoutesDelegator::class,
],
],
Now you can create a src/Blog/config/routes.php
file and start adding blog routes.
<?php
/**
* Setup routes with a single request method:
* @var \Zend\Expressive\Application $app
*
* $app->post('/album', App\Action\AlbumCreateAction::class, 'album.create');
* $app->put('/album/:id', App\Action\AlbumUpdateAction::class, 'album.put');
* $app->patch('/album/:id', App\Action\AlbumUpdateAction::class, 'album.patch');
* $app->delete('/album/:id', App\Action\AlbumDeleteAction::class, 'album.delete');
*
* Or with multiple request methods:
*
* $app->route('/contact', App\Action\ContactAction::class, ['GET', 'POST', ...], 'contact');
*
* Or handling all request methods:
*
* $app->route('/contact', App\Action\ContactAction::class)->setName('contact');
*
* or:
*
* $app->route(
* '/contact',
* App\Action\ContactAction::class,
* Zend\Expressive\Router\Route::HTTP_METHOD_ANY,
* 'contact'
* );
*/
use Blog\Action;
// Setup routes:
$app->get('/blog', Action\BlogPostListAction::class, 'blog_post_list');
$app->get('/blog/view/:blog_post_id', Action\BlogPostViewAction::class, 'blog_post_view');
Actions
Then, we need to create an action to respond to each route.
Create src/Blog/src/Action/BlogPostListAction.php
:
<?php
namespace Blog\Action;
use Blog\Entity\BlogPost;
use Doctrine\ORM\EntityManager;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Expressive\Router;
use Zend\Expressive\Template;
class BlogPostListAction implements ServerMiddlewareInterface
{
/**
* @var Template\TemplateRendererInterface
*/
private $templateRenderer;
/**
* @var EntityManager
*/
private $entityManager;
public function __construct(
EntityManager $entityManager,
Template\TemplateRendererInterface $templateRenderer = null
) {
$this->templateRenderer = $templateRenderer;
$this->entityManager = $entityManager;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$posts = $this->entityManager->getRepository(BlogPost::class)
->findAll();
$data = [
'posts' => $posts,
];
return new HtmlResponse($this->templateRenderer->render('blog::list', $data));
}
}
Create src/Blog/src/Action/BlogPostViewAction.php
:
<?php
namespace Blog\Action;
use Blog\Entity\BlogPost;
use Doctrine\ORM\EntityManager;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Expressive\Router;
use Zend\Expressive\Router\RouteResult;
use Zend\Expressive\Template;
class BlogPostViewAction implements ServerMiddlewareInterface
{
/**
* @var Router\RouterInterface
*/
private $router;
/**
* @var Template\TemplateRendererInterface
*/
private $templateRenderer;
/**
* @var EntityManager
*/
private $entityManager;
public function __construct(
EntityManager $entityManager,
Router\RouterInterface $router,
Template\TemplateRendererInterface $templateRenderer = null
) {
$this->router = $router;
$this->templateRenderer = $templateRenderer;
$this->entityManager = $entityManager;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
/** @var RouteResult $routeResult */
$routeResult = $request->getAttribute(RouteResult::class);
$routeMatchedParams = $routeResult->getMatchedParams();
if (empty($routeMatchedParams['blog_post_id'])) {
throw new \RuntimeException('Invalid route: "blog_post_id" not set in matched route params.');
}
$blogId = $routeMatchedParams['blog_post_id'];
/** @var BlogPost $blogPost */
$blogPost = $this->entityManager->find(BlogPost::class, $blogId);
if (!$blogPost) {
return new HtmlResponse($this->templateRenderer->render('error::404'), 404);
}
$data = [
'post' => $blogPost,
];
return new HtmlResponse($this->templateRenderer->render('blog::view', $data));
}
}
Templates
Open src/Blog/src/ConfigProvider.php
. and update the getTemplates()
method to this:
public function getTemplates()
{
return [
'paths' => [
'blog' => [__DIR__ . '/../templates/blog'],
],
];
}
Now we can make some quick templates:
Create src/Blog/templates/blog/list.html.twig
:
{% extends '@layout/default.html.twig' %}
{% block title %}Blog{% endblock %}
{% block content %}
<div class="row">
{% for post in posts %}
<div class="col-md-4">
<h2>
<a href="/blog/view/{{ post.id }}">
<i class="fa fa-refresh"></i> {{ post.title }}
</a>
</h2>
<p>
{{ post.content }}
</p>
</div>
{% endfor %}
</div>
{% endblock %}
Create src/Blog/templates/blog/view.html.twig
:
{% extends '@layout/default.html.twig' %}
{% block title %}{{ post.title }} | Blog {% endblock %}
{% block content %}
<div class="row">
<div class="col-xs-12">
<h1>{{ post.title }}</h1>
<p>
{{ post.content }}
</p>
</div>
</div>
{% endblock %}
If you open the /blog
URL, you’ll have a functional, database driven blog list and will be able to view pages.
We’ll leave the implementation of create, edit, and delete functionality up to you as homework.
Conclusion
In this short tutorial, we saw how simple it was to implement a read-only blog module with Zend Expressive. In no more than a handful of files and 10 minutes of work, the list page could display our posts from the database and was ready for additional routes, like /edit
, and /delete
.
Do you use Zend Expressive in your projects? What do you like/dislike about it? Let us know how you get on!
Frequently Asked Questions (FAQs) about Rapid Development with Zend Expressive Modules
What is Zend Expressive and how does it work?
Zend Expressive is a middleware microframework for PHP. It’s built on top of Zend Stratigility, which allows for PSR-7 middleware. Middleware is a software design pattern that provides a way to add additional behavior to HTTP requests and responses. In Zend Expressive, each middleware is a PHP callable that accepts a PSR-7 ServerRequestInterface instance, a PSR-7 ResponseInterface instance, and a callable that can produce the next middleware in the queue.
How can I install Zend Expressive?
You can install Zend Expressive using Composer, a tool for dependency management in PHP. You can download Composer and then run the following command in your terminal: composer require zendframework/zend-expressive
. This will install Zend Expressive and its dependencies in your project.
What are the benefits of using Zend Expressive?
Zend Expressive allows for rapid application development with a focus on simplicity and flexibility. It supports any kind of application – from microservices to monolithic applications. It also supports multiple routing and templating systems, allowing developers to choose the tools that best suit their needs.
How can I create a module in Zend Expressive?
To create a module in Zend Expressive, you need to create a new directory in the src
directory of your application. This directory should contain a ConfigProvider
class that returns an array of configuration for the module. The configuration array should include keys for dependencies
, routes
, and templates
.
How can I add a route in Zend Expressive?
To add a route in Zend Expressive, you need to add a new entry to the routes
key in the configuration array returned by the ConfigProvider
class of your module. Each entry should be an array with keys for name
, path
, middleware
, and allowed_methods
.
How can I use templates in Zend Expressive?
Zend Expressive supports multiple templating systems, including Twig, Plates, and Zend View. To use templates, you need to add a new entry to the templates
key in the configuration array returned by the ConfigProvider
class of your module. Each entry should be an array with keys for layout
and map
.
How can I handle errors in Zend Expressive?
Zend Expressive includes a default error handler middleware that catches any exceptions thrown during the execution of your application. You can customize the error handler by creating a new middleware that implements the Zend\Stratigility\Middleware\ErrorHandlerInterface
.
How can I test my Zend Expressive application?
You can test your Zend Expressive application using PHPUnit, a popular testing framework for PHP. Zend Expressive includes a phpunit.xml.dist
file that you can use as a starting point for your test configuration.
How can I deploy my Zend Expressive application?
You can deploy your Zend Expressive application like any other PHP application. You can use a web server like Apache or Nginx, or you can use a PHP built-in server for development purposes.
Where can I find more resources about Zend Expressive?
You can find more resources about Zend Expressive on the official Zend Framework website, the Zend Expressive documentation, and the Zend Framework community forums. You can also find many tutorials and articles online.
I am the Director of Technology at Robofirm with experience in Magento, Zend Framework, Symfony, and DevOps technologies. I work to ensure Robofirm continues to grow in its technical strengths and maintains a leading edge over the competition in order to allow delivery of higher quality work to our clients with less time and budget. I am a father of two girls and happily married. I code in my free time because it's fun to build new things. I also play guitar and listen to lots of heavy metal.