ExampleΒΆ

The following is an example detailing a service that allows creating new resources and fetching existing resources only. It could be expanded to allow updating, patching, and deletion, but the basic premise stays the same.

First, I’ll define an interface for persistence. I’m doing this in order to focus on the pieces related to the API; how you actually persist your data is completely up to you.

1
2
3
4
5
6
7
8
namespace Paste;

interface PersistenceInterface
{
    public function save(array $data);
    public function fetch($id);
    public function fetchAll();
}

Next, I’ll create a resource listener. This example assumes you are using Zend Framework 2.2.0 or above, which includes the AbstractListenerAggregate; if you are using a previous version, you will need to manually implement the ListenerAggregateInterface and its detach() method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
namespace Paste;

use PhlyRestfully\Exception\CreationException;
use PhlyRestfully\Exception\DomainException;
use PhlyRestfully\ResourceEvent;
use Zend\EventManager\AbstractListenerAggregate;
use Zend\EventManager\EventManagerInterface;

class PasteResourceListener extends AbstractListenerAggregate
{
    protected $persistence;

    public function __construct(PersistenceInterface $persistence)
    {
        $this->persistence = $persistence;
    }

    public function attach(EventManagerInterface $events)
    {
        $this->listeners[] = $events->attach('create', array($this, 'onCreate'));
        $this->listeners[] = $events->attach('fetch', array($this, 'onFetch'));
        $this->listeners[] = $events->attach('fetchAll', array($this, 'onFetchAll'));
    }

    public function onCreate(ResourceEvent $e)
    {
        $data  = $e->getParam('data');
        $paste = $this->persistence->save($data);
        if (!$paste) {
            throw new CreationException();
        }
        return $paste;
    }

    public function onFetch(ResourceEvent $e)
    {
        $id = $e->getParam('id');
        $paste = $this->persistence->fetch($id);
        if (!$paste) {
            throw new DomainException('Paste not found', 404);
        }
        return $paste;
    }

    public function onFetchAll(ResourceEvent $e)
    {
        return $this->persistence->fetchAll();
    }
}

The job of the listeners is to pull arguments from the passed event instance, and then work with the persistence storage. Based on what is returned we either throw an exception with appropriate messages and/or codes, or we return a result.

Now that we have a resource listener, we can begin integrating it into our application.

For the purposes of our example, we’ll assume:

  • The persistence engine is returning arrays (or arrays of arrays, when it comes to fetchAll().
  • The identifier field in each array is simply “id”.

First, let’s create a route. In our module’s configuration file, usually config/module.config.php, we’d add the following routing definitions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'router' => array('routes' => array(
    'paste' => array(
        'type' => 'Literal',
        'options' => array(
            'route' => '/paste',
            'defaults' => array(
                'controller' => 'Paste\PasteController', // for the web UI
            ),
        ),
        'may_terminate' => true,
        'child_routes' => array(
            'api' => array(
                'type' => 'Segment',
                'options' => array(
                    'route'      => '/api/pastes[/:id]',
                    'defaults' => array(
                        'controller' => 'Paste\ApiController',
                    ),
                ),
            ),
        ),
    ),
)),

I defined a top-level route for the namespace, which will likely be accessible via a web UI, and will have a different controller. For the purposes of this example, we’ll ignore that for now. The import route is paste/api, which is our RESTful endpoint.

Next, let’s define the controller configuration. Again, inside our module configuration, we’ll add configuration, this time under the phlyrestfully key and its resources subkey.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
'phlyrestfully' => array(
    'resources' => array(
        'Paste\ApiController' => array(
            'identifier'              => 'Pastes',
            'listener'                => 'Paste\PasteResourceListener',
            'resource_identifiers'    => array('PasteResource'),
            'collection_http_options' => array('get', 'post'),
            'collection_name'         => 'pastes',
            'page_size'               => 10,
            'resource_http_options'   => array('get'),
            'route_name'              => 'paste/api',
        ),
    ),
),

Notie that the configuration is a subset of all configuration at this point; we’re only defining the options needed for our particular resource.

Now, how can we get our PasteResourceListener instance? Remember, it requires a PersistenceInterface instance to the constructor. Let’s add a factory inside our Module class. The full module class is presented here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace Paste;

class Module
{
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }

    public function getServiceConfig()
    {
        return array('factories' => array(
            'Paste\PasteResourceListener' => function ($services) {
                $persistence = $services->get('Paste\PersistenceInterface');
                return new PasteResourceListener($persistence);
            },
        ));
    }
}

Note

I lied: I’m not giving the full configuration. The reason is that I’m not defining the actual persistence implementation in the example. If you continue with the example, you would need to define it, and assign a factory to the service name Paste\PersistenceInterface.

At this point, we’re done! Register your module with the application configuration (usually config/application.config.php), and you should immediately be able to access the API.

Note

When hitting the API, make sure you send an Accept header with either the content type application/json, application/hal+json, or text/json; otherwise, it will try to deliver HTML to you, and, unless you have defined view scripts accordingly, you will see errors.