.. _basics.example:

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.

.. code-block:: php
    :linenos:

    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.

.. code-block:: php
    :linenos:

    namespace Paste;

    use ZF\Rest\Exception\CreationException;
    use ZF\Rest\Exception\DomainException;
    use ZF\Rest\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:

.. code-block:: php
    :linenos:

    'router' => array('routes' => array(
        'paste' => array(
            'type' => 'Literal',
            'options' => array(
                'route' => '/paste',
                'controller' => 'Paste\PasteController', // for the web UI
            ),
            'may_terminate' => true,
            'child_routes' => array(
                'api' => array(
                    'type' => 'Segment',
                    'options' => array(
                        'route'      => '/api/pastes[/:id]',
                        '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 ``zf-rest``
key and its ``resources`` subkey.

.. code-block:: php
    :linenos:

    'zf-rest' => 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.

.. code-block:: php
    :linenos:

    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.

.. index:: paste, example, resource listener, resource controller
