Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

REST API

An API is a set of site addresses that return data rather than pages, usually as JSON. Mobile apps and JavaScript use them. REST is a convention for such addresses: one address stands for a resource (posts), and the HTTP method for the action on it. Laminas has a ready-made base controller for this.

Preparation

In the response lesson the ViewJsonStrategy strategy was already enabled in config/autoload/global.php — without it a JsonModel does not turn into a JSON response.

The API controller

Create a file in the module's controller folder:

// module/Blog/src/Controller/PostApiController.php
namespace Blog\Controller;
 
use Blog\Model\PostTable;
use Laminas\Mvc\Controller\AbstractRestfulController;
use Laminas\View\Model\JsonModel;
 
class PostApiController extends AbstractRestfulController
{
    public function __construct(private PostTable $posts) {}
 
    public function getList()
    {
        return new JsonModel(['data' => $this->posts->fetchLatest(20)]);
    }
}

The class extends AbstractRestfulController. Unlike an ordinary controller it has no actions ending in Action: the method is chosen by the HTTP method.

The route

Add the controller to controllers.factories with ReflectionBasedAbstractFactory and describe a route in module/Blog/config/module.config.php:

// module/Blog/config/module.config.php, inside 'routes'
'api-posts' => [
    'type' => Segment::class,
    'options' => [
        'route' => '/api/posts[/:id]',
        'constraints' => ['id' => '[0-9]+'],
        'defaults' => [
            'controller' => Controller\PostApiController::class,
        ],
    ],
],

The route has no action key — the controller picks the method itself. Open /api/posts: the browser shows JSON with a data key and the list of posts.

Which method is called

RequestController method
GET /api/postsgetList()
GET /api/posts/5get($id)
POST /api/postscreate($data)
PUT /api/posts/5update($id, $data)
DELETE /api/posts/5delete($id)

If a method is not defined in your class, the controller answers with the 405 "method not allowed" code.

A single record

public function get($id)
{
    $post = $this->posts->find((int) $id);
 
    if ($post === null) {
        $this->getResponse()->setStatusCode(404);
 
        return new JsonModel(['error' => 'Post not found']);
    }
 
    return new JsonModel(['data' => $post]);
}

For a post that does not exist the client gets the 404 code and an error message in JSON.

Creating and deleting

public function create($data)
{
    $post = new Post();
    $post->exchangeArray($data);
    $this->posts->save($post);
 
    $this->getResponse()->setStatusCode(201);
 
    return new JsonModel(['data' => $post]);
}
 
public function delete($id)
{
    $this->posts->delete((int) $id);
 
    $this->getResponse()->setStatusCode(204);
 
    return new JsonModel();
}

$data holds an array of fields. If the client sent a JSON body with the Content-Type: application/json header, the controller parses it itself. Code 201 means "created", 204 means "done, no response body". Before saving, the data is checked with an InputFilter rule set from the validation lesson.

Checking with curl

curl http://localhost:8080/api/posts/1
 
curl -X POST http://localhost:8080/api/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "A post from the API", "body": "The post text."}'

The first command prints post number 1, the second creates a new one and returns it together with the assigned id.

Common mistake

The API is not protected by the listener from the access control lesson unless its route is in the protected list: DELETE /api/posts/5 deletes a post at any visitor's request. Add the api-posts route to the check, and for requests that change data answer with the 401 code instead of redirecting to the login page.

Thanks for reading!

Donate to the site — we work hard for you.