Package Data | |
---|---|
Maintainer Username: | solvire |
Maintainer Contact: | info@scotttactical.com (Steven Scott) |
Package Create Date: | 2015-09-01 |
Package Last Update: | 2017-08-13 |
Home Page: | |
Language: | PHP |
License: | MIT |
Last Refreshed: | 2024-12-15 03:01:40 |
Package Statistics | |
---|---|
Total Downloads: | 61 |
Monthly Downloads: | 0 |
Daily Downloads: | 0 |
Total Stars: | 14 |
Total Watchers: | 3 |
Total Forks: | 5 |
Total Open Issues: | 0 |
Let's just say it. Your API is probably not RESTful. I don't even like using the term anymore.
Sadly I do not maintain this anymore. The PHP community did not seem to feel the need for such structure.
composer require solvire/php-hypermedia-api
Runs on:
This work originally started trying to bring the LeadFerret front-end user API into compliance. It was something I started a few years ago, but was never given the chance to finish. Late nights and coffee pushed it through.
Why?
"Knowing the questions to ask is sometimes more important than knowing the answer." - Smart A. Dude
When you land on a random webpage the authors care enough to provide you a way to navigate the server. Most APIs will only return exactly what you are asking for. You have to read the documentation to know where to go next and what that record actually means.
The / root of the application should provide enough information that a machine may transverse the server on it's own.
This library intends to wrap an agnostic framework and provide utilities to produce hypermedia context more easily. This context is meaning behind the returned data.
For REST to be valid it needs to provide context in the form of hypermedia. Again see Roy T. Fielding on the topic.
Taking a queue from google developers discoverability service, we opted for using the JSON Schema definition structure. Specifically JSON Schema draft 04.
There are obvious benefits with having a schema in place. Validation, client generation, etc.
In order for JSON Schema to work properly the types of responses are strongly defined. There is no guessing.
Some of the features were loosely modeled on the Django Rest Framework (DRF). Primarily how they treat their views which we call renderers.
When creating a web service the default schema return from the root level of the resources. This is part of a somewhat separate JSON-Schema controller. It shows some of the application level items.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"basePath": "/public/api",
"baseUrl": "https://api.yourdomain.com",
"description": "API Cool End-User API",
"documentationUrl": "http://docs.domain.apiary.io/#",
"id": "appname:v1",
"name": "appname",
"version": "v1.0"
}
This object will be the authority to respond back to the control with all data and context related to the resource. Attached to it are the renderers which can present and handle interactions with the data.
This is the gateway in and out of the database. It takes the data in and moves it through the serializer into the DB. Also in reverse. Some can also just parse an array for static content. They should be output agnostic. The serializers are attached.
Define your resource structure here. Each field has an object with very specific types.
There is an object for each one of your favorite data types. Below is the list.
For each data field there are a set of attributes which will affect the functionality of your serializer. Most of the attribute refer to transformations that might occur, while some are validators or filters.
readOnly
A field that is marked as 'readOnly'=>true
may not be written to. The serializer should ignore this field in determining data writes.
Examples of this are date fields or account status fields.
Default: false
writeOnly
A field marked as 'writeOnly'=>true
will not be displayed to the end user. They are written to and may have filters or validators applied to them.
Examples are passwords or point increment
Default: false
required
If a field is marked with 'required'=>true
causes the system to throw an error if the field is not present during the update. Setting this to false is unnecessary since that is the default setting.
Default: false
allowNull
If the field is marked with 'allowNull'=>true
the serializer will allow you to pass a null value with the field. The recording agent should also respect this request and set the data field to null as well.
Default: false
allowEmpty
If the field is marked with 'allowEmpty'=>true
the serializer will allow you to pass an empty string ''. The recording agent should also respect this request and set the data field to blank as well. Note that null and empty strings are not the same. They should be treated differently.
Default: false
defaultValue
Many times you may want to set a default value so that if the data isn't in the db or passed in that it is set already. 'defaultValue'=>'something'
Default: null
One of the biggest problems with an API response value is setting the keys and translating back and forth. Setting a column name will help readability and usability. 'columnName'=>'mapped_column'
. For instance your users doesn't need to know the primary column is CompanyNameID. Just 'id' will be fine.
Examples: Primary keys, sensitive names, ugly names
Default: null - uses your DB column name
This is not implemented yet, but you can assume that we will be allowing the ability to pass in chains of validators. We may standardize but that hasn't happened yet. Suggestions are welcome.
This is not implemented yet but you may pass back output related styling.
For form fields and UI components it may be helpful to deploy information about it for the human interface. Not implemented yet.
Here is an example of a serializer for a company record. It extends the LaravelModelSerializer
which can interface with Laravel models explicitly. At some point we will integrate with symfony but not at this time.
use Solvire\API\Serializers\DataFields\CharField;
use Solvire\API\Serializers\DataFields\BooleanField;
use Solvire\API\Serializers\DataFields\IPAddressField;
use Solvire\API\Serializers\DataFields\DateTimeField;
use Solvire\API\Serializers\DataFields\EmailField;
use Solvire\API\Serializers\DataFields\IntegerField;
use Solvire\API\Serializers\LaravelModelSerializer;
use Solvire\API\Serializers\DataFields\SplitPointField;
/**
*
* @author solvire <info@scotttactical.com>
* @package Serializers
* @namespace App\Http\Controllers\API\Serializers
*/
class CompanySerializer extends LaravelModelSerializer
{
public function initDataFields()
{
$this->addField('id', new IntegerField(['readOnly'=>true, 'columnName'=>'ComapnyID']))
->addField('name', new CharField(['columnName'=>'Company_name']))
->addField('address', new CharField(['columnName'=>'Address']))
->addField('city', new CharField(['columnName'=>'City']))
->addField('state', new CharField(['columnName'=>'State']))
->addField('zip', new CharField(['columnName'=>'Zip']))
->addField('county', new CharField())
->addField('area_code', new IntegerField(['columnName'=>'AreaCode']))
->addField('phone', new CharField(['columnName'=>'Comp_phone']))
->addField('domain', new CharField())
->addField('sic', new CharField(['columnName'=>'SIC']))
->addField('revenue', new IntegerField(['columnName'=>'Rev']))
->addField('employees', new IntegerField(['columnName'=>'Emps']))
->addField('exp_id', new IntegerField(['writeOnly'=>true,'columnName'=>'ExpID']))
->addField('naics', new IntegerField(['columnName'=>'NAICS']))
->addField('year_founded', new IntegerField())
->addField('f1000', new IntegerField())
->addField('alexa', new IntegerField())
->addField('fbpage', new CharField())
->addField('opt_out', new BooleanField(['writeOnly'=>true]))
->addField('location', new SplitPointField(['latitudeColumn' => 'latitude', 'longitudeColumn' => 'longitude', 'allowNull' => true]));
}
}
Here we have an example of a health check serializer. Since this is just a heartbeat response we don't really need to interface with the database. It doesn't need data binding. For this case we just extended the ArraySerializer
. Basically only output although we might be able to find some input use cases for it.
use Solvire\API\Serializers\ArraySerializer;
use Solvire\API\Serializers\DataFields\CharField;
use Solvire\API\Serializers\DataFields\BooleanField;
use Solvire\API\Serializers\DataFields\IPAddressField;
use Solvire\API\Serializers\DataFields\DateTimeField;
/**
*
* @author solvire <info@scotttactical.com>
* @package Serializers
* @namespace LeadFerret\Http\Controllers\API\Serializers
*/
class HealthSerializer extends ArraySerializer
{
public function initDataFields()
{
$this->addField('status', new CharField());
$this->addField('server_data', new CharField());
$this->addField('ip', new IPAddressField());
$this->addField('you', new CharField());
$this->addField('stage', new CharField());
$this->addField('alive', new BooleanField());
$this->addField('timestamp', new DateTimeField());
}
}
The server response will look like this. It should format the fields into their proper data types. More flexibility will be added for the various fields in the future. Things such as setting timezones and adding translations.
{
"alive": true,
"ip": "192.168.56.1",
"server_data": "LF API",
"stage": "development",
"status": "OK",
"timestamp": {
"date": "2015-09-25 22:23:51.000000",
"timezone": "UTC",
"timezone_type": 3
},
"you": "HTTPie/0.9.2"
}
I've included a class here with some examples of two data field types: TransformerField and SerializerField. As you expect these perform complex nested serializations. Below this class is an example of a sample output.
use Solvire\API\Serializers\DataFields\CharField;
use Solvire\API\Serializers\DataFields\BooleanField;
use Solvire\API\Serializers\DataFields\DateTimeField;
use Solvire\API\Serializers\DataFields\IntegerField;
use Solvire\API\Serializers\DataFields\PhoneField;
use Solvire\API\Serializers\LaravelModelSerializer;
use Solvire\API\Serializers\DataFields\SerializerField;
use Solvire\API\Serializers\DataFields\TransformerField;
use Solvire\API\Serializers\DataFields\SplitPointField;
use LeadFerret\Models\Transformers\API\DepartmentToCategoryTransformer;
use Solvire\API\Serializers\DataFields\GenderField;
/**
* Serialize a contact object
*
* @author solvire <info@scotttactical.com>
* @package Serializers
* @namespace LeadFerret\Http\Controllers\API\Serializers
*/
class ContactSerializer extends LaravelModelSerializer
{
/**
* (non-PHPdoc)
* @see \Solvire\API\Serializers\BaseSerializer::initDataFields()
*/
public function initDataFields()
{
// the serializer field takes a closure
$company = function ($model) {
return $model->company;
};
$getModel = function ($model){
return $model;
};
$this->addField('id', new IntegerField(['readOnly'=>true, 'columnName'=>'ContactID']))
->addField('first_name', new CharField(['columnName'=>'first_name']))
->addField('last_name', new CharField(['columnName'=>'last_name']))
->addField('title', new CharField(['columnName'=>'user_title']))
->addField('phone', new PhoneField(['columnName'=>'contact_phone']))
->addField('created', new DateTimeField(['columnName'=>'created_on','format'=>'Y-m-d H:i:s']))
->addField('score', new IntegerField())
->addField('crowd_score', new IntegerField())
->addField('title_level', new CharField(['columnName'=>'title_levels']))
->addField('gender', new GenderField(['maleValue'=>'Male','femaleValue'=>'Female'])) // normalized gender values
->addField('ads_status', new IntegerField(['writeOnly'=>true]))
->addField('useragent', new CharField())
->addField('device', new CharField(['writeOnly'=>true,'columnName'=>'uadevice']))
->addField('opt_out', new BooleanField(['writeOnly'=>true]))
->addField('company', new SerializerField( ['serializer'=>new CompanySerializer(), 'callback' => $company] ))
->addField('professional_categories', new TransformerField(['transformer'=> (new DepartmentToCategoryTransformer()), 'callback' => $getModel ]));
}
}
The serializer field is basically a tested data field for bringing in already defined serializers.
The transformer can transform your data in any format you need. Literally. I had a unique case I had to combine a lot of fields to create the data as it was so I passed the model in the closure.
And the output:
{
"currentItemCount": 1,
"items": [
{
"company": {
"address": "4209 Technology Drive",
"alexa": 0,
"area_code": 510,
"city": "Fremont",
"county": "Alameda",
"domain": "123.com",
"employees": 15,
"f1000": 0,
"fbpage": "",
"id": 38252,
"location": {
"latitude": 37.520000457764,
"longitude": -121.95999908447
},
"naics": 0,
"name": "3PARdata, Inc.",
"phone": "510-413-5999",
"revenue": 35,
"sic": "3572",
"state": "CA",
"year_founded": 1999,
"zip": "94538"
},
"created": {
"date": "2015-09-30 06:06:55.000000",
"timezone": "UTC",
"timezone_type": 3
},
"crowd_score": 0,
"first_name": "Jeff",
"gender": "male",
"id": 12756,
"last_name": "Song",
"phone": "510-413-1234",
"professional_categories": [
"engineering",
"photographer"
],
"score": 38,
"title": "Vice President of Engineering and Founder",
"title_level": "VP",
"useragent": "Chrome"
}
],
"itemsPerPage": "50",
"pageIndex": 1,
"totalItems": 1,
"totalPages": 1
}