From a8c7d36c02e45fe490b16bf2f49de11262589c70 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 3 Mar 2014 14:58:37 -0500 Subject: [PATCH] Finished HATEOAS support. --- docs/guide/rest.md | 2 +- framework/base/Model.php | 7 ++++ framework/data/Pagination.php | 7 ++-- framework/web/Link.php | 83 +++++++++++++++++++++++++++++++++++++++++++ framework/web/Linkable.php | 42 ++++++++++++++++++++++ 5 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 framework/web/Link.php create mode 100644 framework/web/Linkable.php diff --git a/docs/guide/rest.md b/docs/guide/rest.md index 192ad05..bf36b78 100644 --- a/docs/guide/rest.md +++ b/docs/guide/rest.md @@ -12,8 +12,8 @@ In particular, Yii provides support for the following aspects regarding RESTful * Support `OPTIONS` and `HEAD` verbs; * Authentication; * Authorization; +* Support for HATEOAS; * Caching via `yii\web\HttpCache`; -* Support for HATEOAS: TBD * Rate limiting: TBD * Searching and filtering: TBD * Testing: TBD diff --git a/framework/base/Model.php b/framework/base/Model.php index c16852c..548c0e9 100644 --- a/framework/base/Model.php +++ b/framework/base/Model.php @@ -17,6 +17,8 @@ use yii\helpers\ArrayHelper; use yii\helpers\Inflector; use yii\validators\RequiredValidator; use yii\validators\Validator; +use yii\web\Link; +use yii\web\Linkable; /** * Model is the base class for data models. @@ -876,6 +878,11 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab foreach ($this->resolveFields($fields, $expand) as $field => $definition) { $data[$field] = is_string($definition) ? $this->$definition : call_user_func($definition, $field, $this); } + + if ($this instanceof Linkable) { + $data['_links'] = Link::serialize($this->getLinks()); + } + return $recursive ? ArrayHelper::toArray($data) : $data; } diff --git a/framework/data/Pagination.php b/framework/data/Pagination.php index c2c06d1..cb3f263 100644 --- a/framework/data/Pagination.php +++ b/framework/data/Pagination.php @@ -9,6 +9,8 @@ namespace yii\data; use Yii; use yii\base\Object; +use yii\web\Link; +use yii\web\Linkable; use yii\web\Request; /** @@ -65,9 +67,8 @@ use yii\web\Request; * @author Qiang Xue * @since 2.0 */ -class Pagination extends Object +class Pagination extends Object implements Linkable { - const LINK_SELF = 'self'; const LINK_NEXT = 'next'; const LINK_PREV = 'prev'; const LINK_FIRST = 'first'; @@ -301,7 +302,7 @@ class Pagination extends Object $currentPage = $this->getPage(); $pageCount = $this->getPageCount(); $links = [ - self::LINK_SELF => $this->createUrl($currentPage, $absolute), + Link::REL_SELF => $this->createUrl($currentPage, $absolute), ]; if ($currentPage > 0) { $links[self::LINK_FIRST] = $this->createUrl(0, $absolute); diff --git a/framework/web/Link.php b/framework/web/Link.php new file mode 100644 index 0000000..9e10e9b --- /dev/null +++ b/framework/web/Link.php @@ -0,0 +1,83 @@ + + * @since 2.0 + */ +class Link extends Object implements Arrayable +{ + /** + * The self link. + */ + const REL_SELF = 'self'; + + /** + * @var string a URI [RFC3986](https://tools.ietf.org/html/rfc3986) or + * URI template [RFC6570](https://tools.ietf.org/html/rfc6570). This property is required. + */ + public $href; + /** + * @var string a secondary key for selecting Link Objects which share the same relation type + */ + public $name; + /** + * @var string a hint to indicate the media type expected when dereferencing the target resource + */ + public $type; + /** + * @var boolean a value indicating whether [[href]] refers to a URI or URI template. + */ + public $templated = false; + /** + * @var string a URI that hints about the profile of the target resource. + */ + public $profile; + /** + * @var string a label describing the link + */ + public $title; + /** + * @var string the language of the target resource + */ + public $hreflang; + + /** + * @inheritdoc + */ + public function toArray() + { + return array_filter((array)$this); + } + + /** + * Serializes a list of links into proper array format. + * @param array $links the links to be serialized + * @return array the proper array representation of the links. + */ + public static function serialize(array $links) + { + foreach ($links as $rel => $link) { + if (is_array($link)) { + foreach ($link as $i => $l) { + $link[$i] = $l instanceof self ? $l->toArray() : ['href' => $l]; + } + $links[$rel] = $link; + } elseif (!$link instanceof self) { + $links[$rel] = ['href' => $link]; + } + } + return $links; + } +} diff --git a/framework/web/Linkable.php b/framework/web/Linkable.php new file mode 100644 index 0000000..8d1558b --- /dev/null +++ b/framework/web/Linkable.php @@ -0,0 +1,42 @@ + + * @since 2.0 + */ +interface Linkable +{ + /** + * Returns a list of links. + * + * Each link is either a URI or a [[Link]] object. The return value of this method should + * be an array whose keys are the relation names and values the corresponding links. + * + * If a relation name corresponds to multiple links, use an array to represent them. + * + * For example, + * + * ```php + * [ + * 'self' => 'http://example.com/users/1', + * 'friends' => [ + * 'http://example.com/users/2', + * 'http://example.com/users/3', + * ], + * 'manager' => $managerLink, // $managerLink is a Link object + * ] + * ``` + * + * @return array the links + */ + public function getLinks(); +}