Rationale
During ZendCon 2013, I was excited to hear about a new toolset called Apigility that the folks of Zend Framework have just released. It is an API toolset built on top of ZF2. The exciting thing is that this Apigility takes care of all the API dirty work for us. HAL content, error handling, JSON structure, and so much more!
What I find funny about this toolset, from an implementation standpoint, is that it is not that much different from what we have tooled together ourselves on top of ZF2. Whats better is, we had yet to implement HAL and our error handling logic was still sub-par. So testing this toolset is exciting, in that its another portion of our code that I do not need to implement (well... finish implementing) and maintain.
My focus on this Apigility tutorial will be primarily from the perspective of a Code-Connected REST API's. While the Database-Connected REST API's are fantastic, our internal approach to development has shifted to an API First development strategy. This design decision is implemented by first creating an internal class based API, then leverage Apigility to greatly simplify the creation of a public facing API.
Getting Started - Installation
So, lets dive right in! Before you can begin playing with Apigility, you need to "install" it. The makers of this toolset were kind enough to implement the entire structure and logic flow in just code. This means, no databases to setup, no proxies to configure, no complex routing to setup. Simply bring in the code that is needed and you are ready to go!
There are a number of methods you could use to bring down the code. For me, the simplest was running a Composer script. This requires you have composer installed, but since I did... Move to the directory you wish to place your new API and execute these commands from your favorite terminal:
url -s https://getcomposer.org/installer | php --
php composer.phar create-project -sdev --repository-url="https://packages.zendframework.com/" zfcampus/zf-apigility-skeleton /var/www/apigility
Next, you will want to place your local instance of Apigility into development mode. This will give you access to all of the nifty tools that streamline the scaffolding of your API. A couple things to keep in mind... You will NOT want to leave your Apigility instance in development mode for your production uses as it exposes many of the configuration options at the hart of your new API. Also, once you have a solid understanding of the structure and makeup of Apigility, you honestly will not need to use the admin interface all that often... of course it does handle a lot of the back end wiring that is needed, so it may be handy to have available, perhaps when you want to iterate the version of your API. To enable development mode, simply type this into your terminal:
php public/index.php development enable
Now we just need to initialize our new application. This can be done by modifying your virtual hosts file or simply spinning up a server using the new to PHP 5.4 built-in php web server. Simply execute this command in your terminal:
php -S 0:8080 -t public/ public/index.phpYou can now access Apigility by pointing your web browser at http://localhost:8080!
Installation Summary
Below is the exact code I am using to install Apigility on my local linux box. This is a summary of the above commands, plus a few supplemental commands needed for my specific use case. My webroot is located at /var/www. You may need to make adjustments to this for it to work for you as well.cd /var/www
mkdir apigility
curl -s https://getcomposer.org/installer | php --
php composer.phar create-project -sdev --repository-url="https://packages.zendframework.com/" zfcampus/zf-apigility-skeleton /var/www/apigility
rm composer.phar
cd apigility
php public/index.php development enable
php -S 0:8080 -t public/ public/index.php
Creating Your First API
Once you point your web browser at http://localhost:8080, you will be presented with a welcome screen. Simply hit the Get Started! button:Next, you'll hit the Create New API button:
Type "Compnay" in the text box then click the Create API button:
Then click the REST Services link:
Followed by the Create New REST Service button.
Here we will name our first REST service. Lets make one that will return a list of employees, along with some basic employee information. Select the Code-Connected tab, enter Employees as the REST Service Name, and click the Create REST Service button.
Your new API structure is now built and ready to be populated with code.
If you were to make a request from the API now (http://localhost:8080/employees), it would return an error about the GET method not being defined.
So lets define the methods for our new API. We'll start by creating the item entity. We'll use the fields employee_id, name, title, & base_salary. Set the class vars as private, add a getter method for each, and the constructor will set each based on an optionally passed in array. We will do all of this in the module/Company/src/Company/V1/Rest/Employees/EmployeesEntity.php file.
<?phpNext, we need to edit the module/Company/src/Company/V1/Rest/Employees/EmployeesResource.php file. Normally, you would interface directly with your internal API classes. For the sake of this simple implementation, we will add a getData() method that will return an array of employee information:
namespace Company\V1\Rest\Employees;
class EmployeesEntity{
private $employee_id;
private $name;
private $title;
private $base_salary;
public function __construct(array $entity = null){
if($entity !== null && is_array($entity)){
foreach(array_keys(get_class_vars(__CLASS__)) as $key){
if(isset($entity[$key])){
$this->$key = $entity[$key];
}
}
}
}
public function getEmployeeId(){
return $this->employee_id;
}
public function getName(){
return $this->name;
}
public function getTitle(){
return $this->title;
}
public function getBaseSalary(){
return $this->base_salary;
}
}
private function getData(){Next, we need to modify the fetch method to retrieve the correct employee entity based on the passed employee_id. In this case, we simply:
return array(
1 => array(
'employees_id' => 1,
'name' => 'John Doe',
'title' => 'Cat Behavior Consultant',
'base_salary' => 42000
),
2 => array(
'employees_id' => 2,
'name' => 'Jane Poe',
'title' => 'Cheif Bottlewasher',
'base_salary' => 37000
),
3 => array(
'employees_id' => 3,
'name' => 'Robert Roe',
'title' => 'Town Crier',
'base_salary' => 14000
),
4 => array(
'employees_id' => 4,
'name' => 'Mark Moe',
'title' => 'Creative director, Unicorn Division',
'base_salary' => 73000
),
5 => array(
'employees_id' => 5,
'name' => 'Brett Boe',
'title' => 'Executive Moonshiner',
'base_salary' => 58000
)
);
}
public function fetch($id){Then the fetchAll method. We will get the full dataset and pass it into an ArrayAdapter. We will then pass the adapter into the EmployeesCollection. While it's true we have not implemented any code in the EmployeesCollection, the fact that it extends Zend Paginator gives us the functionality we need to afford API pagination.
if(is_int($id)){
return new EmployeesEntity($this->getData()[$id]);
}else{
return new ApiProblem(405, 'Invalid id passed');
}
}
public function fetchAll($params = array()){Since we are using the ArrayAdapter in the fetchAll method, we'll need to include the ArrayAdapter at the top of the file:
$adapter = new ArrayAdapter($this->getData());
$collection = new EmployeesCollection($adapter);
return $collection;
}
use Zend\Paginator\Adapter\ArrayAdapter;
Finally, we need to define a strategy for hydrating the EmployeesEntity. In this case, we simply need to use the ClassMethods hydrator. In the module/Company/config/module.config.php file, locate the zf-hal -> metadata_map -> Company\\V1\\Rest\\Employees\\EmployeesEntity section and add 'hydrator' => 'ClassMethods', directly after the element rout_name like this:
'zf-hal' =>
array (
'metadata_map' =>
array (
'Company\\V1\\Rest\\Employees\\EmployeesEntity' =>
array (
'identifier_name' => 'employees_id',
'route_name' => 'company.rest.employees',
'hydrator' => 'ClassMethods',
),
That should be it! You now have a working Apigility Api. In my next post, I'll dig a bit more into tying this directly into your internal Api class structure, as well as some tips on easing the job of interacting with your new API using a great Chrome extension Advanced REST Client.
No comments:
Post a Comment