Creating a REST Service

In this chapter, we'll create a simple REST service.

Assumptions

This chapter assumes you have read and followed both the installation guide and the getting started chapter. If you have not, please do before continuing.

You will need to install and configure the laminas-api-tools/statuslib-example module in order to perform this tutorial. Follow these steps:

Step 1

Within your application root, execute the following:

$ composer require laminas-api-tools/statuslib-example

You will be prompted as to whether or not you wish to inject the module into configuration; you do, and you should inject it into config/modules.config.php.

Manual registration

If you do not get prompted to inject the module, or accept the default, which does not inject, you will need to add the entry 'StatusLib' to config/modules.config.php.

Step 2

Create a PHP file data/statuslib.php that returns an array:

<?php
return [];

Make sure the file is writable by the web server user.

Step 3

Edit the file config/autoload/local.php to add the following configuration:

return [
    /* ... */
    'statuslib' => [
        'array_mapper_path' => 'data/statuslib.php',
    ],
];

Step 4

Finally, you will need a valid HTTP basic credentials file, usually titled htpasswd. You can generate one using the standard htpasswd tool provided by Apache, or use an online htpasswd generator. Store the htpasswd file as data/htpasswd in your application. Make a note of the credentials you use so that you can use them later.

Once those steps are complete, continue with the tutorial.

Terminology

Within the Laminas API Tools documentation, and, in particular, this chapter, uses the following terminology:

Entity
An addressable item being returned. Entities are distinguished by a unique identifier present in the URI.
Collection
A addressable set of entities. Typically, all entities contained in the collection are of the same type, and share the same base URI as the collection.
Resource
An object that receives the incoming request data, determines whether a collection or entity was identified in the URI, and determines what operation to perform.
Relational Links
A URI to a resource that has the described relation. Relational links allow you to describe relations between different entities and collections, as well as directly link to them so that the web service client can perform operations on those relations. These are also sometimes called hypermedia links.

REST services return entities and collections, and provide hypermedia links between related entities and collections. Resource objects coordinate operations, and return entities and collections.

Create a REST Service

In this chapter, we're going to build a sample REST service.

In the sidebar, click the "New Service" button. The REST tab is selected by default; provide the value "Status" for the "Service name" field and press the "Create service" button.

REST Services Screen

REST vs DB-Connected services

When you create a REST service, API Tools creates a stub "Resource" class that defines all the various operations available in the service. These operations return 405 Method Not Allowed responses until you fill them in with your own code. That means you need to supply the code that performs the actual work of your API; API Tools provides the wiring for exposing that code as an API.

DB-Connected services are also REST services. They allow you to specify a database adapter, and then to choose one or more tables to expose as services. API Tools then creates "virtual" Resources which delegate operations to underlying Laminas\Db\TableGateway\TableGateway instances. In other words, DB-Connected is more of a rapid application development (RAD) or prototyping tool.

Once the service has been successfully created, the "Status" page will show up.

REST Services Screen

API Tools provides a number of sane defaults:

  • Collections only allow GET (fetch a list) and POST (create a new entity) operations.
  • Entities allow GET (fetch an entity), PUT (replace the entity), PATCH (perform a partial update), and DELETE (remove the entity) operations.
  • If your collection supports pagination, API Tools will limit to 25 items per "page" of results.
  • API Tools creates a routing URI based on the service name (e.g., /status[/:status_id]).

URI Routing

API Tools runs on top of a Laminas MVC stack, and thus uses its routing engine.

The routes generated by API Tools are all what are known as "Segment" routes. Segment routes allow you to:

  • Specify optional portions of the URI, using [ ] syntax.
  • Specify named parameters to match using :varName or :var_name syntax.
  • Specify literal matches; anything not a named parameter, or within braces ({ }) is considered a literal.

For REST services, the URI generated has a literal, mandatory match that, when specified by itself, resolves to a collection; in the example above, this would be the path /status. It also has an optional segment with a named parameter, what we call the "entity identifer": [/:status_id]. This will match URIs such as /status/foo, /status/2, /status/96fa5ac9-3ae2-45b2-84d5-c346936be292.

One note: the / between the collection URI and the entity URI can only be specified when specifying an entity; you cannot request the collection with a trailing slash.

Why? Because one tenet of REST is one URI, one resource. If we allowed a trailing slash, we'd be allowing multiple URIs to resolve to the same resource.

In the REST service page, you'll see a field named "Hydrator Service Name" with a value of Laminas\Hydrator\ArraySerializableHydrator. We're going to change this to work with our StatusLib example library. For the "Hydrator Service Name", select the value Laminas\Hydrator\ObjectPropertyHydrator.

Edit REST Parameter Settings

Hydrators

Hydrators are objects that allow for casting an associative array to a specific object type and vice versa. Each hydrator employs a different strategy for how this is done. The default hydrator type that API Tools uses is the ArraySerializableHydrator type, which expects an object to implement two methods:

  • getArrayCopy() for extracting an array representation
  • exchangeArray($array) for casting an array to the object

(These are the same methods used in PHP's ArrayObject!)

The ObjectPropertyHydrator hydrator will extract any public properties of an object when creating an array representation, and populate public properties of the object from an arraywhen casting to an object.

For our example, the StatusLib library provides its own Entity and Collection classes. Edit the "Entity Class" field to read StatusLib\Entity and the "Collection Class" field to read StatusLib\Collection. Finally click the "Save" button at the bottom of the screen.

Edit Service Classes

Service Classes

When you create a Code-Connected service, API Tools generates four PHP class files for you:

  • An Entity class
  • A Collection class which extends Laminas\Paginator\Paginator, which will allow you to provide paginated result sets.
  • A Resource class for performing operations.
  • A Factory class for the Resource created.

Your own code may already define entity and collection classes that you want to use, so you are free to ignore the stub classes API Tools creates. One note, however: if you end up versioning your API, you may find that having version-specific entity and collection classes can be useful, as they can allow you to model only the properties you wish to expose for each specific version.

Next, let's define some fields, and document our API.

Define fields for our service

The fields we'll define are:

  • message - a status message. It must be non-empty, and no more than 140 characters.
  • user - the user providing the status message. It must be non-empty, and fulfill a regular expression.
  • timestamp - an integer timestamp. It does not need to be submitted, but if it is, must consist of only digits. It will always be returned in representations.

We'll also have an id field, but this will only be for purposes of display.

Navigate to the "Fields" tab, and click the "New field" button. In the text input titled "Name", type the word "message"; for the "Description", enter "A status message of no more than 140 characters", and for the "Validation Failure Message," enter "A status message must contain between 1 and 140 characters". Finally press the "Save" button to create the new field.

Message Field

Repeat the procedure to create the fields "user" and "timestamp", without descriptions or failure messages.

Following the best practices of "Filter Input, Escape Output", we want to create a filter for the "message" field. Click the + (plus) icon in the Filter column for the "message" field.

Message Field - Filter

Select Laminas\Filter\StringTrim in the "Filter" drop-down list, and click the "Save" button.

We want also to add a validator for the "message" field. Click the "+" (plus) icon in the "Validator" colum. In the modal window, select Laminas\Validator\StringLength as "Validator", and select the max option for the "Option". Insert the value 140 for the max option and click on the "Add option" button; you will see the option appear in the table below.

Message Field - StringLength Validator

Now you can click on the "Save" button to save the configuration; you should see the following on your screen:

Message Field - Completed

At this point, perform the following:

  • Edit the "user" field:
    • Add a description of "The user submitting the status message."
    • Add a validation failure message of "You must provide a valid user."
    • Add a Laminas\Filter\StringTrim filter.
    • Add a Laminas\Validator\Regex validator; give it a pattern option, with the value /^(mwop|andi|zeev)$/ (feel free to substitute or add other names or nicknames as desired).
  • Update the "timestamp" field:
    • Add a description of "The timestamp when the status message was last modified."
    • Add a validation failure message of "You must provide a timestamp."
    • Toggle the "Required" flag to read "No."
    • Add a Laminas\Validator\Digits validator.

Below is a screenshot detailing what the "Fields" tab will look like on completion.

Fields - Completed

Let's move on to documentation.

Documentation

REST services allow you to document not only by HTTP method, but by HTTP method for each of Collections and Entities.

The procedure for documenting a REST service is just like we learned in the Getting Started chapter, with only one difference:

  • You will need to document HTTP methods for both collections and entities.

Your exercise now is to document both collection and entity operations:

  • Give the service a description of "Create, manipulate, and retrieve status messages."
  • Give collections a description of "Manipulate lists of status messages."
    • For the GET method, describe it as "Retrieve a paginated list of status messages."
    • For the POST method, describe it as "Create a new status messages."
  • Give entities a description of "Manipulate and retrieve individual status messages."
    • For the GET method, describe it as "Retrieve a status message."
    • For the PATCH method, describe it as "Update a status message."
    • For the PUT method, describe it as "Replace a status message."
    • For the DELETE method, describe it as "Delete a status message."

We'll examine the documentation later. For now, let's move on to authentication and authorization.