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.