Skip to content
Permalink
Browse files

First two posts in the Example Application with Mezzio series

  • Loading branch information
marcguyer committed Dec 4, 2019
1 parent 4c2d016 commit 51ae669adeb9f141ab9d9d0b5f2df75d9ac72543
@@ -25,6 +25,7 @@ disqusShortname = "marcguyer"
[taxonomies]
tag = "tags"
# Categories are disabled by default.
series = "series"

[params]
dateform = "2006-01-02"
@@ -30,9 +30,9 @@ The Resite platform was written initially in PHP3 and as of 2006 was serving the

In 2009, as a cofounder of [SproutBox](http://sproutbox.com), I founded [GetCheddar, Inc](https://www.getcheddar.com).

Since founding GetCheddar (formerly CheddarGetter), I have remained focused as CTO and Full Stack Architect. Cheddar was the first of it's kind to offer a true software-as-a-service recurring billing and payments platform with fully automated merchant self-signup and self-service. Cheddar specializes in providing usage tracking and billing for web application subscriptions.
Since founding GetCheddar (formerly CheddarGetter), I have remained focused as CTO and Full Stack Architect. Cheddar is the first of it's kind to offer a true software-as-a-service recurring billing and payments platform with fully automated merchant self-signup and self-service. Cheddar specializes in providing usage tracking and billing for web application subscriptions.

The Cheddar platform was initially written in PHP5 and Zend Framework version 1. Initially hosted on the Rackspace Cloud, the entire environment was soon migrated to a proprietary PCI-compliant environment. The hosting environment migration resulted in zerod downtime. Later the codebase was migrated to PHP7 and Zend Framework 2. Most recently, it was rewritten as a pure REST API on Zend Expressive with OAuth2. I wrote 99% of the code.
The Cheddar platform was initially written in PHP5 and Zend Framework version 1. Initially hosted on the Rackspace Cloud, the entire environment was soon migrated to a proprietary PCI-compliant environment. The hosting environment migration resulted in zero downtime. Later the codebase was migrated to PHP7 and Zend Framework 2. Most recently, it was rewritten as a pure REST API on Zend Expressive with OAuth2. I wrote 99% of the code.

### The Future

@@ -0,0 +1,43 @@
---
title: "Example Application with Mezzio: Introduction"
date: 2019-11-04T15:08:36-04:00
draft: true
toc: false
images:
tags:
- php
- mezzio
- laminas
- prophecy
- phpunit
series:
- "Example Application with Mezzio"
---

## What is this?

Let's create a REST API with [Laminas](https://getlaminas.org/) [Mezzio](https://getexpressive.org/). In this series, you'll learn how to create a REST API from the ground up. We'll use Mezzio as the base framework, Doctrine for the data layer, OAuth2 for authentication and authorization, and Phpunit for automated testing.

### Mezzio

[Mezzio](https://getexpressive.org/) is a micro framework using the middleware pattern. Middleware is a bit of code that sits between a HTTP request and response. I find it quite refreshing after working with the complexities of MVC for many years. Since [the PHP community has standardized](https://www.php-fig.org/psr/) [HTTP requests & responses via PSR-7](https://www.php-fig.org/psr/psr-7/), [dependency injection containers via PSR-11](https://www.php-fig.org/psr/psr-11/), and the [HTTP request handler and middleware interfaces via PSR-15](https://www.php-fig.org/psr/psr-15/) (among others), PHP has seen growth in modernizing legacy applications using the new tools. Mezzio has taken full advantage of the new standards and serves as the basis for much of that new growth.

I won't go into any more detail. Since you're here, you've probably already chosen Mezzio. If you have any questions at all, please don't hesitate to leave a comment in Disqus below.

### Doctrine

[Doctrine](https://www.doctrine-project.org/) is a collection of several related packages concerned with the data layer of a PHP application. Doctrine makes it possible to create applications that can run on any supported database engine via the [Database Abstraction Layer (DBAL)](https://www.doctrine-project.org/projects/dbal.html) and makes working with data objects refreshingly easy with the [Object Relational Mapper (ORM)](https://www.doctrine-project.org/projects/orm.html).

### OAuth2

[OAuth2](https://oauth.net/2/) is the current standard framework for building authorization into an application. The Mezzio framework comes with an implementation of OAuth2 using the wonderful [OAuth2 Server implementation from the League of Extraordinary Packages](https://oauth2.thephpleague.com/). That implementation comes out of the box with a basic [PDO](https://www.php.net/pdo) setup for the data layer. In this series, I'll show how we can use Doctrine instead.

### PHPUnit

[PHPUnit](https://phpunit.de/) is the de facto standard framework for automated testing in PHP applications. The [Mezzio Skeleton Application](https://github.com/zendframework/zend-expressive-skeleton) comes with a basic PHPUnit setup.

## Source Code

I've created a new bare-bones application using the [Mezzio Skeleton Application](https://github.com/zendframework/zend-expressive-skeleton) where all the code shown in this series lives. You can install it in your local environment and hack on it all you want. You're also welcome to contribute to the example application via PR. Check it out here:

[Example Application with Mezzio, Doctrine, and OAuth2](https://github.com/marcguyer/mezzio-doctrine-oauth2-example)
@@ -0,0 +1,225 @@
---
title: "Example Application with Mezzio: Testing"
date: 2019-12-01T14:28:49-05:00
draft: true
toc: false
images:
tags:
- php
- mezzio
- laminas
- prophecy
- phpunit
series:
- "Example Application with Mezzio"
---

## Where do we start?

I think it's a good exercise to start any application with a solid structure for testing the application with automated testing tools. That's why the first post in this series is about testing.

In this post, I'll show a basic setup for testing Mezzio applications. We'll get to some more advanced testing topics in later posts.

## Legacy ZF Testing Tools

Those experienced with [Zend Framework MVC](https://docs.zendframework.com/zend-mvc/) applications are likely familiar with the [zend-test](https://docs.zendframework.com/zend-test/) library for ZF3, or [Zend\\Test](https://framework.zend.com/manual/2.4/en/modules/zend.test.introduction.html) for ZF2 and maybe even [Zend_Test](https://framework.zend.com/manual/1.12/en/zend.test.introduction.html), the old ZF1 testing framework.

The documentation for these components allude to _unit testing_ your application using these tools. That, unfortunately is a misnomer. All of the testing libraries provided by Zend Framework support _integration testing_ or _functional testing_ your code, but certainly not _unit testing_. The difference between unit tests and integration tests is dramatic. Those differences have been blogged about at length. Check the goog for more information about the different types of tests.

## A Basic Example

Let's start with an example functional test of your new complete Mezzio application middleware stack. This example should be somewhat familiar if you're used to how [zend-test](https://docs.zendframework.com/zend-test/) works with old MVC apps. We'll test the ping route that comes with the [Mezzio Skeleton Application](https://github.com/zendframework/zend-expressive-skeleton).

You may have noticed that skeleton comes with some basic tests of the simple handlers that come with the skeleton. We'll show those in a later post. For now, we're going to start with a functional test of the `GET /api/ping` REST endpoint from end to end. This test will exercise the entire stack so we can prove that the endpoint "works".

I've created a couple of simple abstract classes that all of my functional tests can extend, making writing new tests fast and easy. These tests use real instances of your application components -- the dependency injection container and the application itself including the middleware pipeline and the routes. In fact the base class might remind you of an application bootstrap like that found in `public/index.php` in a Mezzio skeleton app.

Here's the base class for all functional tests. You'll note that this does nothing except initialize the application to be tested. Some assumptions must hold true for this to work out of the box. In short, if you start with the Mezzio skeleton, you should be all set. If the paths to your container/pipeline/routes config is non-standard, you might have to do some extra work -- perhaps a symlink or two.

```php
<?php
declare(strict_types=1);
namespace FunctionalTest;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\MiddlewareFactory;
use Helmich\Psr7Assert\Psr7Assertions;
/**
* {@inheritdoc}
*/
abstract class AbstractFunctionalTest extends TestCase
{
use Psr7Assertions;
/** @var ContainerInterface */
protected static $container;
/** @var Application */
protected static $app;
public static function setUpBeforeClass(): void
{
static::initContainer();
static::initApp();
static::initPipeline();
static::initRoutes();
}
public static function tearDownAfterClass(): void
{
static::$container = null;
static::$app = null;
}
/**
* Initialize new container instance.
*/
protected static function initContainer(): void
{
static::$container = require 'config/container.php';
}
/**
* Initialize app.
*/
protected static function initApp(): void
{
static::$app = static::$container->get(Application::class);
}
/**
* Initialize pipeline.
*/
protected static function initPipeline(): void
{
(require 'config/pipeline.php')(
static::$app,
static::$container->get(MiddlewareFactory::class),
static::$container
);
}
/**
* Initialize routes.
*/
protected static function initRoutes(): void
{
(require 'config/routes.php')(
static::$app,
static::$container->get(MiddlewareFactory::class),
static::$container
);
}
}
```

Any simple tests that require only a minimally bootstrapped application can extend the `AbstractFunctionalTest` class and test away.

Now it gets a little bit more interesting. Here's an abstract extension that adds some framework for defining REST endpoints to be tested and assertions for testing those endpoints:

```php
<?php
declare(strict_types=1);
namespace FunctionalTest;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Stream;
use Zend\Diactoros\Request;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Uri;
use PHPUnit\Framework\Constraint;
/**
* Abstract to set up functional testing via endpoint provider config.
*/
abstract class AbstractFunctionalEndpointTest extends AbstractFunctionalTest
{
/**
* Provider for testEndpoint() method.
*
* @see self::testEndpoint() for provider signature
*
* @return array
*/
abstract public function endpointProvider(): array;
/**
* @param string $method
* @param string $uri
* @param array $requestHeaders
* @param array $body
* @param array $queryParams
*
* @return ServerRequestInterface
*/
protected function getRequest(
string $method,
string $uri,
array $requestHeaders = [],
array $body = [],
array $queryParams = []
): ServerRequestInterface {
$uri = new Uri($uri);
if (null !== $body) {
$bodyStream = fopen('php://memory', 'r+');
fwrite($bodyStream, json_encode($body));
$body = new Stream($bodyStream);
}
if (!empty($queryParams)) {
$uri = $uri->withQuery(http_build_query($queryParams));
}
return new ServerRequest(
[],
[],
$uri,
$method,
$body ?? 'php://input',
$requestHeaders ?? []
);
}
/**
* @dataProvider endpointProvider
*
* @param ServerRequestInterface $request
* @param Constraint[] $responseConstraints
*/
public function testEndpoint(
ServerRequestInterface $request,
array $responseConstraints = []
): void {
$response = static::$app->handle($request);
$this->assertInstanceOf(ResponseInterface::class, $response);
$this->assertResponseConstraints($responseConstraints, $response);
}
/**
* @param Constraint[] $responseConstraints
* @param ResponseInterface $response
*/
protected function assertResponseConstraints(
array $responseConstraints,
ResponseInterface $response
): void {
foreach ($responseConstraints as $msg => $constraint) {
$this->assertThat(
$response,
$constraint,
is_string($msg) ? $msg : ''
);
}
}
}
```

0 comments on commit 51ae669

Please sign in to comment.
You can’t perform that action at this time.