Adding Apigility to an Existing Project
Because Apigility's functionality is provided by a number of Zend Framework 2 modules, you can add Apigility to an existing ZF2 application by adding its modules to the application.
You may skip this step, but for the purposes of the examples in this tutorial, we'll be using a ZF2 application based on StatusLib (which was used in the REST Service tutorial. To get a working ZF2 application like it, please follow the directions in the StatusLib README.
Preparing a ZF2-based application
Now that you have an existing ZF2 application you wish to add Apigility to, it is time to add the dependencies.
$ composer require "zfcampus/zf-apigility:~1.0"
$ composer require --dev "zfcampus/zf-apigility-admin:~1.0"
$ composer require --dev "zfcampus/zf-development-mode:~2.0"
Now, to ensure that the development-time tools are accessible and cannot be accidentially deployed
in the production website, we need to make some modifications to the public/index.php
file.
Replace:
// Run the application!
Zend\Mvc\Application::init(require 'config/application.config.php')->run();
with:
if (!defined('APPLICATION_PATH')) {
define('APPLICATION_PATH', realpath(__DIR__ . '/../'));
}
$appConfig = include APPLICATION_PATH . '/config/application.config.php';
if (file_exists(APPLICATION_PATH . '/config/development.config.php')) {
$appConfig = Zend\Stdlib\ArrayUtils::merge($appConfig, include APPLICATION_PATH . '/config/development.config.php');
}
// Run the application!
Zend\Mvc\Application::init($appConfig)->run();
Now, enable the necessary production modules by editing your config/application.config.php
/* ... */
'modules' => [
'Application',
'ZF\Apigility',
'ZF\Apigility\Provider',
'AssetManager',
'ZF\ApiProblem',
'ZF\MvcAuth',
'ZF\OAuth2',
'ZF\Hal',
'ZF\ContentNegotiation',
'ZF\ContentValidation',
'ZF\Rest',
'ZF\Rpc',
'ZF\Versioning',
'ZF\DevelopmentMode',
// any other modules you have...
],
/* ... */
You'll notice the ZF\DevelopmentMode
module is included in config/application.config.php
, which
we would intend is available when this application is deployed to production. This is fine since
this particular module is responsible for only adding commands to the application to provide the
ability to switch development mode off and on on your development machine.
Next, we want to create a file called config/development.config.php.dist
, with the following
content:
<?php
/**
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
* @copyright Copyright (c) 2014 Zend Technologies USA Inc. (http://www.zend.com)
*/
return [
// Development time modules
'modules' => [
'ZF\Apigility\Admin',
'ZF\Configuration',
],
// development time configuration globbing
'module_listener_options' => [
'config_glob_paths' => ['config/autoload/{,*.}{global,local}-development.php'],
],
];
The above file is a template file used by ZF\DevelopmentMode
; when you call
php public/index.php development enable
from the command line, the module copies this file to
config/development.config.php
, and your public/index.php
now sees the file and merges it with
what config/application.config.php
returns -- giving you your "development mode" settings.
config/development.config.php
should never be checked into your version control system. By
omitting it, you can ensure that the application is production ready whenever a fresh checkout is
created. As such, add the line config/development.config.php
to your .gitignore
file;
afterwards, it should read something like the following:
vendor/
public/vendor/
config/development.config.php
config/autoload/local.php
config/autoload/*.local.php
!public/vendor/README.md
data/cache/*
!data/cache/.gitkeep
At this point, all the various peices that you would expect to find in the Apigility skeleton application have been ported into your existing ZF2 application. Finally, issue the following command, just like you would in Apigility:
$ php public/index.php development enable
Once complete, this particular ZF2 project can be accessed like any other Apigility project.
Building Apigility API modules
At this point there are effectively two ways of building out Apigility modules:
- New API modules that consume existing module's models.
- Creating services inside an existing module.
There are a couple of important notes to remember:
- Apigility does not modify code inside the
vendor
directory. This means your modules need to exist in the ZF2module
directory. - Apigility will create a specific directory structure inside the module's source code:
- When services are created, they will be created as PSR-0 compatible classes in the specified module source directory.
- The naming and namespace pattern for these classes will be
{Namespace}\V{Version Number}\Rest|Rpc\{Service Name}
Choosing to go the route of having separate API modules will ensure a higher level of separation of concerns between modules. The unfortunate downside to this is that there will be more modules, and thus a higher chance of naming collisions.
Apigility-enabling existing modules
In order to enable an existing module as an Apigility module, ensure the module is in the module
directory; then perform one of the following.
Manually enabling a module
Edit the module class by hand to implement the ApigilityProviderInterface
(which is a marker
interface).
Using StatusLib
as an example, we would edit module/StatusLib/Module.php
:
/* ... */
use ZF\Apigility\Provider\ApigilityProviderInterface;
class Module implements ApigilityProviderInterface
{
/* ... */
Using the Apigility Admin API
You can also use the Apigility Admin API to Apigility-enable the module.
To do this, you will need a web server running your application; this can be the built-in PHP web server, as detailed in the installation guide:
php -S 0.0.0.0:8888 -ddisplay_errors=0 -t public public/index.php
Once running, initiate a PUT
request to the /apigility/api/module.enable
path, providing the
module name as the module
variable of the payload:
PUT /apigility/api/module.enable HTTP/1.1
Accept: application/json
Content-Type: application/json
{"module":"StatusLib"}
Consuming existing services
In new API services you create, you can consume any other services you've already created in your
application. As an example, we could consume the StatusLib
mapper inside a newly minted REST
service resource with the name Status
.
To do this, we'd edit the factory for the StatusResource
to pass the mapper as a constructor
argument:
// In module/StatusLib/src/StatusLib/V1/Rest/Status/StatusResourceFactory.php :
namespace Status\V1\Rest\Status;
class StatusResourceFactory
{
public function __invoke($services)
{
return new StatusResource($services->get('StatusLib\Mapper'));
}
}
Next, we'd edit our StatusResource
to accept the argument and assign it to a property:
// In module/StatusLib/src/StatusLib/V1/Rest/Status/StatusResource.php :
/* ... */
use StatusLib\MapperInterface;
class StatusResource extends AbstractResourceListener
{
protected $mapper;
public function __construct(MapperInterface $statusMapper)
{
$this->mapper = $statusMapper;
}
/* ... */
}
Now we can consume the mapper within a method; below shows how we'd do so from fetchAll()
.
/* ... */
class StatusResource extends AbstractResourceListener
{
/* ... */
public function fetchAll($params = [])
{
return $this->statusMapper->fetchAll();
}
/* ... */
}
This technique can be performed for any API service, and using any service exposed in your application.