├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── composer.json
├── lib
├── AbstractLink.php
├── Curie.php
├── Deserialization
│ ├── Builder.php
│ ├── Construction
│ │ └── ProxyObjectConstruction.php
│ ├── Handler
│ │ ├── ArrayCollectionHandler.php
│ │ └── DateHandler.php
│ └── ResourceDeserializationVisitor.php
├── EntryPoint.php
├── Exception
│ └── RequestException.php
├── HttpClient
│ ├── FileGetContentsHttpClient.php
│ ├── HttpClientInterface.php
│ └── HttpResponse.php
├── Link.php
├── Proxy
│ ├── HalResourceEntity.php
│ └── HalResourceEntityInterface.php
├── Resource.php
└── ResourceCollection.php
├── phpunit.xml.dist
└── tests
├── HalClient
├── Deserialization
│ ├── Construction
│ │ └── ProxyObjectConstructionTest.php
│ └── DeserializationTest.php
├── EntryPointTest.php
├── HttpClient
│ └── FileGetContentsHttpClientTest.php
├── LinkTest.php
└── ResourceTest.php
├── autoload.php.dist
├── bootstrap.php
└── fixtures
├── documents.json
├── entry_point.json
└── entry_point_invalid_self.json
/.gitignore:
--------------------------------------------------------------------------------
1 | phpunit.xml
2 | /nbproject/
3 | /coverage/
4 | /vendor/
5 | /docs/_build
6 | composer.lock
7 | composer.phar
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - hhvm
7 |
8 | before_script:
9 | - composer install --dev --prefer-source
10 |
11 | script: make test
12 |
13 | matrix:
14 | allow_failures:
15 | - php: hhvm
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Ekino
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test
2 |
3 | test:
4 | phpunit
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | HalClient
2 | =========
3 |
4 | [](https://secure.travis-ci.org/#!/ekino/php-hal-client)
5 |
6 | HalClient is a lightweight library to consume HAL resources.
7 |
8 | ### Installation using Composer
9 |
10 | Add the dependency:
11 |
12 | ```bash
13 | php composer.phar require ekino/hal-client
14 | ```
15 |
16 | If asked for a version, type in 'dev-master' (unless you want another version):
17 |
18 | ```bash
19 | Please provide a version constraint for the ekino/hal-client requirement: dev-master
20 | ```
21 |
22 | ### Limitations
23 |
24 | There is no support for POST/PUT/PATCH/DELETE methods.
25 |
26 | ### Usage
27 |
28 | ```php
29 |
30 | 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
35 | ));
36 |
37 | // create an entry point to retrieve the data
38 | $entryPoint = new EntryPoint('/', $client);
39 | $resource = $entryPoint->get(); // return the main resource
40 |
41 | // retrieve a Resource object, which acts as a Pager
42 | $pager = $resource->get('p:documents');
43 |
44 | $pager->get('page');
45 |
46 | $collection = $pager->get('documents'); // return a ResourceCollection
47 |
48 | // a ResourceCollection implements the \Iterator and \Countable interface
49 | foreach ($collection as $document) {
50 | // the document is a resource object
51 | $document->get('title');
52 | }
53 |
54 | ```
55 |
56 |
57 | ### Integrate JMS/Deserializer
58 |
59 | The library support deserialization of Resource object into native PHP object.
60 |
61 | ```php
62 |
63 | $serializer = Ekino\HalClient\Deserialization\Builder::build();
64 |
65 | $object = $serializer->deserialize($resource, 'Acme\Article', 'hal');
66 |
67 | ```
68 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ekino/hal-client",
3 | "description": "HalClient",
4 | "keywords": ["hal", "Hypermedia"],
5 | "homepage": "https://github.com/ekino/php-hal-client",
6 | "type": "library",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Thomas Rabaix",
11 | "email": "thomas.rabaix@gmail.com"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=5.4.0",
16 | "guzzle/parser": "~3.9",
17 | "doctrine/collections": "~1.2"
18 | },
19 | "require-dev": {
20 | "jms/serializer": "~0.16",
21 | "doctrine/collections": "~1.2"
22 | },
23 | "suggest": {
24 | "jms/serializer": "Enable support of Resource Deserialization"
25 | },
26 | "autoload": {
27 | "psr-4": { "Ekino\\HalClient\\": "lib" }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/AbstractLink.php:
--------------------------------------------------------------------------------
1 | name = isset($data['name']) ? $data['name'] : null;
43 | $this->href = isset($data['href']) ? $data['href'] : null;
44 | $this->templated = isset($data['templated']) ? (boolean) $data['templated'] : false;
45 | }
46 |
47 | /**
48 | * @return null|string
49 | */
50 | public function getName()
51 | {
52 | return $this->name;
53 | }
54 |
55 | /**
56 | * Returns the href.
57 | *
58 | * @param array $variables Required if the link is templated
59 | *
60 | * @return null|string
61 | *
62 | * @throws \RuntimeException When call with property "href" empty and sets variables
63 | */
64 | public function getHref(array $variables = array())
65 | {
66 | if (!empty($variables)) {
67 | return $this->prepareUrl($variables);
68 | }
69 |
70 | return $this->href;
71 | }
72 |
73 | /**
74 | * @return bool
75 | */
76 | public function isTemplated()
77 | {
78 | return $this->templated;
79 | }
80 |
81 | /**
82 | * Prepare the url with variables.
83 | *
84 | * @param array $variables Required if the link is templated
85 | *
86 | * @return string
87 | *
88 | * @throws \RuntimeException When call with property "href" empty
89 | */
90 | private function prepareUrl(array $variables = array())
91 | {
92 | if (null === $this->href) {
93 | throw new \RuntimeException('Href must to be sets.');
94 | }
95 |
96 | if (!$this->templated) {
97 | return $this->href;
98 | }
99 |
100 | $template = new UriTemplate();
101 |
102 | return $template->expand($this->href, $variables);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/lib/Curie.php:
--------------------------------------------------------------------------------
1 | setDeserializationVisitor('hal', new ResourceDeserializationVisitor(new CamelCaseNamingStrategy(), $autoload));
35 | $serializerBuilder->configureHandlers(function($handlerRegistry) {
36 | $handlerRegistry->registerSubscribingHandler(new DateHandler());
37 | $handlerRegistry->registerSubscribingHandler(new ArrayCollectionHandler());
38 | });
39 |
40 | if ($cacheDir) {
41 | $serializerBuilder->setCacheDir($cacheDir);
42 | }
43 |
44 | return $serializerBuilder;
45 | }
46 |
47 | /**
48 | * @param bool $autoload
49 | * @param bool $cacheDir
50 | *
51 | * @return Serializer
52 | */
53 | static function build($autoload = true, $cacheDir = false)
54 | {
55 | return self::get($autoload, $cacheDir)->build();
56 | }
57 | }
--------------------------------------------------------------------------------
/lib/Deserialization/Construction/ProxyObjectConstruction.php:
--------------------------------------------------------------------------------
1 | patterns = $patterns;
38 | }
39 |
40 | /**
41 | * @param Serializer $serializer
42 | */
43 | public function setSerializer(Serializer $serializer)
44 | {
45 | $this->serializer = $serializer;
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function construct(VisitorInterface $visitor, ClassMetadata $metadata, $data, array $type, DeserializationContext $context)
52 | {
53 | $pos = strrpos($metadata->name, '\\');
54 |
55 | $instance = false;
56 | foreach ($this->patterns as $pattern) {
57 | $name = str_replace(['{ns}', '{ln}'], [substr($metadata->name, 0, $pos), substr($metadata->name, $pos + 1)], $pattern);
58 |
59 | if (class_exists($name, true)) {
60 | $instance = unserialize(sprintf('O:%d:"%s":0:{}', strlen($name), $name));
61 |
62 | break;
63 | }
64 | }
65 |
66 | if (!$instance) {
67 | $instance = unserialize(sprintf('O:%d:"%s":0:{}', strlen($metadata->name), $metadata->name));
68 | }
69 |
70 | if ($instance instanceof HalResourceEntityInterface && $data instanceof Resource) {
71 | $instance->setHalResource($data);
72 | if ($this->serializer) {
73 | $instance->setHalSerializer($this->serializer);
74 | }
75 | }
76 |
77 | return $instance;
78 | }
79 | }
--------------------------------------------------------------------------------
/lib/Deserialization/Handler/ArrayCollectionHandler.php:
--------------------------------------------------------------------------------
1 | GraphNavigator::DIRECTION_DESERIALIZATION,
35 | 'type' => $type,
36 | 'format' => $format,
37 | 'method' => 'deserializeCollection',
38 | );
39 | }
40 | }
41 |
42 | return $methods;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/Deserialization/Handler/DateHandler.php:
--------------------------------------------------------------------------------
1 | 'DateTime',
33 | 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
34 | 'format' => $format,
35 | );
36 | }
37 |
38 | return $methods;
39 | }
40 |
41 | public function __construct($defaultFormat = \DateTime::ISO8601, $defaultTimezone = 'UTC')
42 | {
43 | $this->defaultFormat = $defaultFormat;
44 | $this->defaultTimezone = new \DateTimeZone($defaultTimezone);
45 | }
46 |
47 | private function parseDateTime($data, array $type)
48 | {
49 | $timezone = isset($type['params'][1]) ? new \DateTimeZone($type['params'][1]) : $this->defaultTimezone;
50 | $format = $this->getFormat($type);
51 | $datetime = \DateTime::createFromFormat($format, (string) $data, $timezone);
52 | if (false === $datetime) {
53 | throw new RuntimeException(sprintf('Invalid datetime "%s", expected format %s.', $data, $format));
54 | }
55 |
56 | return $datetime;
57 | }
58 |
59 | /**
60 | * @return string
61 | * @param array $type
62 | */
63 | private function getFormat(array $type)
64 | {
65 | return isset($type['params'][0]) ? $type['params'][0] : $this->defaultFormat;
66 | }
67 |
68 | public function deserializeDateTimeFromhal(ResourceDeserializationVisitor $visitor, $data, array $type)
69 | {
70 | if (null === $data) {
71 | return null;
72 | }
73 |
74 | return $this->parseDateTime($data, $type);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/lib/Deserialization/ResourceDeserializationVisitor.php:
--------------------------------------------------------------------------------
1 | namingStrategy = $namingStrategy;
42 | $this->autoload = $autoload;
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | protected function decode($str)
49 | {
50 | if (!$str instanceof Resource) {
51 | throw new \RuntimeException('Invalid argument, Ekino\HalClient\Resource required');
52 | }
53 |
54 | return $str;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function visitProperty(PropertyMetadata $metadata, $data, Context $context)
61 | {
62 | $name = $this->namingStrategy->translateName($metadata);
63 |
64 | if ($data instanceof Resource && !$data->hasEmbedded($name) && $data->hasLink($name) && !$data->hasProperty($name) && !$this->autoload) {
65 | return;
66 | }
67 |
68 | if (isset($data[$name]) === false) {
69 | return;
70 | }
71 |
72 | if (!$metadata->type) {
73 | throw new RuntimeException(sprintf('You must define a type for %s::$%s.', $metadata->reflection->class, $metadata->name));
74 | }
75 |
76 | $v = $data[$name] !== null ? $this->getNavigator()->accept($data[$name], $metadata->type, $context) : null;
77 |
78 | if (null === $metadata->setter) {
79 | $metadata->reflection->setValue($this->getCurrentObject(), $v);
80 |
81 | return;
82 | }
83 |
84 | $this->getCurrentObject()->{$metadata->setter}($v);
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | public function setNavigator(GraphNavigator $navigator)
91 | {
92 | $this->navigator = $navigator;
93 | $this->result = null;
94 | $this->objectStack = new \SplStack;
95 | }
96 |
97 | /**
98 | * {@inheritdoc}
99 | */
100 | public function getNavigator()
101 | {
102 | return $this->navigator;
103 | }
104 |
105 | /**
106 | * {@inheritdoc}
107 | */
108 | public function prepare($data)
109 | {
110 | return $this->decode($data);
111 | }
112 |
113 | /**
114 | * {@inheritdoc}
115 | */
116 | public function visitNull($data, array $type, Context $context)
117 | {
118 | return null;
119 | }
120 |
121 | /**
122 | * {@inheritdoc}
123 | */
124 | public function visitString($data, array $type, Context $context)
125 | {
126 | $data = (string) $data;
127 |
128 | if (null === $this->result) {
129 | $this->result = $data;
130 | }
131 |
132 | return $data;
133 | }
134 |
135 | /**
136 | * {@inheritdoc}
137 | */
138 | public function visitBoolean($data, array $type, Context $context)
139 | {
140 | $data = (Boolean) $data;
141 |
142 | if (null === $this->result) {
143 | $this->result = $data;
144 | }
145 |
146 | return $data;
147 | }
148 |
149 | /**
150 | * {@inheritdoc}
151 | */
152 | public function visitInteger($data, array $type, Context $context)
153 | {
154 | $data = (integer) $data;
155 |
156 | if (null === $this->result) {
157 | $this->result = $data;
158 | }
159 |
160 | return $data;
161 | }
162 |
163 | /**
164 | * {@inheritdoc}
165 | */
166 | public function visitDouble($data, array $type, Context $context)
167 | {
168 | $data = (double) $data;
169 |
170 | if (null === $this->result) {
171 | $this->result = $data;
172 | }
173 |
174 | return $data;
175 | }
176 |
177 | /**
178 | * {@inheritdoc}
179 | */
180 | public function visitArray($data, array $type, Context $context)
181 | {
182 | if ( ! $data instanceof ResourceCollection && !is_array($data) ) {
183 | throw new RuntimeException(sprintf('Expected ResourceCollection or array, but got %s: %s', gettype($data), json_encode($data)));
184 | }
185 |
186 | // If no further parameters were given, keys/values are just passed as is.
187 | if ( ! $type['params']) {
188 | if (null === $this->result) {
189 | $this->result = $data;
190 | }
191 |
192 | return $data;
193 | }
194 |
195 | switch (count($type['params'])) {
196 | case 1: // Array is a list.
197 | $listType = $type['params'][0];
198 |
199 | $result = array();
200 | if (null === $this->result) {
201 | $this->result = &$result;
202 | }
203 |
204 | foreach ($data as $v) {
205 | $result[] = $this->navigator->accept($v, $listType, $context);
206 | }
207 |
208 | return $result;
209 |
210 | case 2: // Array is a map.
211 | list($keyType, $entryType) = $type['params'];
212 |
213 | $result = array();
214 | if (null === $this->result) {
215 | $this->result = &$result;
216 | }
217 |
218 | foreach ($data as $k => $v) {
219 | $result[$this->navigator->accept($k, $keyType, $context)] = $this->navigator->accept($v, $entryType, $context);
220 | }
221 |
222 | return $result;
223 |
224 | default:
225 | throw new RuntimeException(sprintf('Array type cannot have more than 2 parameters, but got %s.', json_encode($type['params'])));
226 | }
227 | }
228 |
229 | /**
230 | * {@inheritdoc}
231 | */
232 | public function startVisitingObject(ClassMetadata $metadata, $object, array $type, Context $context)
233 | {
234 | $this->setCurrentObject($object);
235 |
236 | if (null === $this->result) {
237 | $this->result = $this->currentObject;
238 | }
239 | }
240 |
241 | /**
242 | * {@inheritdoc}
243 | */
244 | public function endVisitingObject(ClassMetadata $metadata, $data, array $type, Context $context)
245 | {
246 | $obj = $this->currentObject;
247 | $this->revertCurrentObject();
248 |
249 | return $obj;
250 | }
251 |
252 | /**
253 | * {@inheritdoc}
254 | */
255 | public function getResult()
256 | {
257 | return $this->result;
258 | }
259 |
260 | public function setCurrentObject($object)
261 | {
262 | $this->objectStack->push($this->currentObject);
263 | $this->currentObject = $object;
264 | }
265 |
266 | public function getCurrentObject()
267 | {
268 | return $this->currentObject;
269 | }
270 |
271 | public function revertCurrentObject()
272 | {
273 | return $this->currentObject = $this->objectStack->pop();
274 | }
275 | }
--------------------------------------------------------------------------------
/lib/EntryPoint.php:
--------------------------------------------------------------------------------
1 | url = $url;
38 | $this->client = $client;
39 | $this->headers = $headers;
40 |
41 | $this->resource = false;
42 | }
43 |
44 | /**
45 | * @param HttpResponse $response
46 | * @param HttpClientInterface $client
47 | *
48 | * @return Resource
49 | *
50 | * @throws \RuntimeException
51 | */
52 | public static function parse(HttpResponse $response, HttpClientInterface $client)
53 | {
54 | if (substr($response->getHeader('Content-Type'), 0, 20) !== 'application/hal+json') {
55 | throw new \RuntimeException('Invalid content type');
56 | }
57 |
58 | $data = @json_decode($response->getBody(), true);
59 |
60 | if ($data === null) {
61 | throw new \RuntimeException('Invalid JSON format');
62 | }
63 |
64 | return Resource::create($client, $data);
65 | }
66 |
67 | /**
68 | * @param string $name
69 | *
70 | * @return Resource
71 | */
72 | public function get($name = null)
73 | {
74 | $this->initialize();
75 |
76 | if ($name) {
77 | return $this->resource->get($name);
78 | }
79 |
80 | return $this->resource;
81 | }
82 |
83 | /**
84 | * Initialize the resource.
85 | */
86 | protected function initialize()
87 | {
88 | if ($this->resource) {
89 | return;
90 | }
91 |
92 | $this->resource = static::parse($this->client->get($this->url), $this->client);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/Exception/RequestException.php:
--------------------------------------------------------------------------------
1 | defaultHeaders = $defaultHeaders;
44 | $this->timeout = $timeout;
45 | $this->proxy = $proxy;
46 |
47 | // normalize
48 | if (substr($baseUrl, -1) !== '/') {
49 | $baseUrl .= '/';
50 | }
51 |
52 | $this->baseUrl = $baseUrl;
53 | }
54 |
55 | /**
56 | * @param string $url
57 | * @param string $method
58 | * @param array $headers
59 | * @param array $data
60 | *
61 | * @return HttpResponse
62 | *
63 | * @throws RequestException
64 | */
65 | protected function doRequest($url, $method, array $headers, array $data)
66 | {
67 | $headers['Accept'] = 'application/hal+json';
68 |
69 | $opts = array(
70 | 'http' => array(
71 | 'method' => strtoupper($method),
72 | 'header' => $this->buildHeaders(array_merge($this->defaultHeaders, $headers)),
73 | 'timeout' => $this->timeout,
74 | 'ignore_errors' => true,
75 |
76 | // need to set configuration options
77 | 'user_agent' => 'Ekino HalClient v0.1',
78 | 'follow_location' => 0,
79 | 'max_redirects' => 20,
80 | )
81 | );
82 |
83 | if ($this->proxy) {
84 | $opts['http']['proxy'] = $this->proxy;
85 | $opts['http']['request_fulluri'] = true;
86 | }
87 |
88 | // if is relative url
89 | if ('http' !== substr($url, 0, 4)) {
90 | // clean
91 | if ($url[0] === '/') {
92 | $url = substr($url, 1);
93 | }
94 |
95 | $url = $this->baseUrl . $url;
96 | }
97 |
98 | // http://php.net/manual/en/reserved.variables.httpresponseheader.php
99 | $content = @file_get_contents($url, false, stream_context_create($opts));
100 |
101 | if (empty($http_response_header) && $content === false) {
102 | throw new RequestException('Empty response, no headers or impossible to reach the remote server');
103 | }
104 |
105 | $data = explode(" ", $http_response_header[0]);
106 |
107 | return new HttpResponse($data[1], $this->parseHeaders($http_response_header), $content);
108 | }
109 |
110 | /**
111 | * @param array $headers
112 | *
113 | * @return string
114 | */
115 | protected function buildHeaders(array $headers)
116 | {
117 | $data = "";
118 | foreach ($headers as $name => $value) {
119 | $data .= sprintf("%s: %s\r\n", $name, $value);
120 | }
121 |
122 | return $data;
123 | }
124 |
125 | /**
126 | * @param $lines
127 | *
128 | * @return array
129 | */
130 | protected function parseHeaders($lines)
131 | {
132 | $headers = array();
133 | foreach($lines as $line) {
134 | $data = explode(":", $line, 2);
135 |
136 | if (count($data) !== 2) {
137 | continue;
138 | }
139 |
140 | $headers[$data[0]] = trim($data[1]);
141 | }
142 |
143 | return $headers;
144 | }
145 |
146 | /**
147 | * {@inheritdoc}
148 | */
149 | public function get($url, $headers = array())
150 | {
151 | return $this->doRequest($url, 'GET', $headers, array());
152 | }
153 |
154 | /**
155 | * {@inheritdoc}
156 | */
157 | public function post($url, $data = array(), $headers = array())
158 | {
159 | throw new \RuntimeException('Feature not implemented');
160 | }
161 |
162 | /**
163 | * {@inheritdoc}
164 | */
165 | public function put($url, $data = array(), $headers = array())
166 | {
167 | throw new \RuntimeException('Feature not implemented');
168 | }
169 |
170 | /**
171 | * {@inheritdoc}
172 | */
173 | public function delete($url, $headers = array())
174 | {
175 | throw new \RuntimeException('Feature not implemented');
176 | }
177 |
178 | /**
179 | * {@inheritdoc}
180 | */
181 | public function patch($url, $data = array(), $headers = array())
182 | {
183 | throw new \RuntimeException('Feature not implemented');
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/lib/HttpClient/HttpClientInterface.php:
--------------------------------------------------------------------------------
1 | status = (int) $status;
30 | $this->headers = $headers;
31 | $this->body = $body;
32 | }
33 |
34 | /**
35 | * @return int
36 | */
37 | public function getStatus()
38 | {
39 | return $this->status;
40 | }
41 |
42 | /**
43 | * @return mixed
44 | */
45 | public function getBody()
46 | {
47 | return $this->body;
48 | }
49 |
50 | /**
51 | * @return mixed
52 | */
53 | public function getHeaders()
54 | {
55 | return $this->headers;
56 | }
57 |
58 | /**
59 | * @param string $name
60 | * @param mixed $default
61 | *
62 | * @return mixed
63 | */
64 | public function getHeader($name, $default = null)
65 | {
66 | if (!array_key_exists($name, $this->headers)) {
67 | return $default;
68 | }
69 |
70 | return $this->headers[$name];
71 | }
72 | }
--------------------------------------------------------------------------------
/lib/Link.php:
--------------------------------------------------------------------------------
1 | title = isset($data['title']) ? $data['title'] : null;
46 |
47 | if (null !== $this->name && false !== strpos($this->name, ':')) {
48 | list($this->ncName, $this->reference) = explode(':', $this->name, 2);
49 | }
50 | }
51 |
52 | /**
53 | * @return null|string
54 | */
55 | public function getNCName()
56 | {
57 | return $this->ncName;
58 | }
59 |
60 | /**
61 | * @return null|string
62 | */
63 | public function getReference()
64 | {
65 | return $this->reference;
66 | }
67 |
68 | /**
69 | * @return null|string
70 | */
71 | public function getTitle()
72 | {
73 | return $this->title;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/lib/Proxy/HalResourceEntity.php:
--------------------------------------------------------------------------------
1 | __halResourceLoaded = $halResourceLoaded;
40 | }
41 |
42 | /**
43 | * @return array
44 | */
45 | public function getHalResourceLoaded()
46 | {
47 | return $this->__halResourceLoaded;
48 | }
49 |
50 | /**
51 | * @param mixed $halSerializer
52 | */
53 | public function setHalSerializer(Serializer $halSerializer)
54 | {
55 | $this->__halSerializer = $halSerializer;
56 | }
57 |
58 | /**
59 | * @return mixed
60 | */
61 | public function getHalSerializer()
62 | {
63 | return $this->__halSerializer;
64 | }
65 |
66 | /**
67 | * @param Resource $halResource
68 | */
69 | public function setHalResource(Resource $halResource)
70 | {
71 | $this->__halResource = $halResource;
72 | }
73 |
74 | /**
75 | * @return Resource
76 | */
77 | public function getHalResource()
78 | {
79 | return $this->__halResource;
80 | }
81 |
82 | /**
83 | * @param $name
84 | */
85 | public function halLoaded($name)
86 | {
87 | $this->__halResourceLoaded[$name] = true;
88 | }
89 |
90 | /**
91 | * @param $name
92 | *
93 | * @return bool
94 | */
95 | public function halIsLoaded($name)
96 | {
97 | return isset($this->__halResourceLoaded[$name]);
98 | }
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/lib/Proxy/HalResourceEntityInterface.php:
--------------------------------------------------------------------------------
1 | client = $client;
38 | $this->properties = $properties;
39 | $this->links = $links;
40 | $this->embedded = $embedded;
41 |
42 | $this->parseCuries();
43 | }
44 |
45 | /**
46 | * @return array
47 | */
48 | public function getEmbedded()
49 | {
50 | return $this->embedded;
51 | }
52 |
53 | /**
54 | * @return array
55 | */
56 | public function getLinks()
57 | {
58 | return $this->links;
59 | }
60 |
61 | /**
62 | * Reloads the Resource by using the self reference
63 | *
64 | * @throws \RuntimeException
65 | */
66 | public function refresh()
67 | {
68 | $link = $this->getLink('self');
69 |
70 | if (!$link) {
71 | throw new \RuntimeException('Invalid resource, not `self` reference available');
72 | }
73 |
74 | $r = $this->getResource($link);
75 |
76 | $this->properties = $r->getProperties();
77 | $this->links = $r->getLinks();
78 | $this->embedded = $r->getEmbedded();
79 |
80 | $this->parseCuries();
81 | }
82 |
83 | /**
84 | * @param $name
85 | *
86 | * @return Link
87 | */
88 | public function getLink($name)
89 | {
90 | if (!array_key_exists($name, $this->links)) {
91 | return null;
92 | }
93 |
94 | if (!$this->links[$name] instanceof Link) {
95 | $this->links[$name] = new Link(array_merge(array('name' => $name), $this->links[$name]));
96 | }
97 |
98 | return $this->links[$name];
99 | }
100 |
101 | /**
102 | * @param $name
103 | *
104 | * @return Curie
105 | */
106 | public function getCurie($name)
107 | {
108 | if (!array_key_exists($name, $this->curies)) {
109 | return null;
110 | }
111 |
112 | return $this->curies[$name];
113 | }
114 |
115 | /**
116 | * @return array
117 | */
118 | public function getProperties()
119 | {
120 | return $this->properties;
121 | }
122 |
123 | protected function parseCuries()
124 | {
125 | $this->curies = array();
126 |
127 | if (!array_key_exists('curies', $this->links)) {
128 | return;
129 | }
130 |
131 | foreach ($this->links['curies'] as $curie) {
132 | $this->curies[$curie['name']] = new Curie($curie);
133 | }
134 | }
135 |
136 | /**
137 | * @param $name
138 | *
139 | * @return Resource|ResourceCollection|null
140 | */
141 | public function get($name)
142 | {
143 | if (array_key_exists($name, $this->properties)) {
144 | return $this->properties[$name];
145 | }
146 |
147 | if (!array_key_exists($name, $this->embedded)) {
148 | if (!$this->buildResourceValue($name)) {
149 | return null;
150 | }
151 | }
152 |
153 | return $this->getEmbeddedValue($name);
154 | }
155 |
156 | /**
157 | * @param $name
158 | *
159 | * @return bool
160 | */
161 | public function has($name)
162 | {
163 | return $this->hasProperty($name) || $this->hasLink($name) || $this->hasEmbedded($name);
164 | }
165 |
166 | /**
167 | * @param $name
168 | *
169 | * @return bool
170 | */
171 | public function hasLink($name)
172 | {
173 | return isset($this->links[$name]);
174 | }
175 |
176 | /**
177 | * @param $name
178 | *
179 | * @return bool
180 | */
181 | public function hasProperty($name)
182 | {
183 | return isset($this->properties[$name]);
184 | }
185 |
186 | /**
187 | * @param $name
188 | *
189 | * @return bool
190 | */
191 | public function hasEmbedded($name)
192 | {
193 | return isset($this->embedded[$name]);
194 | }
195 |
196 | /**
197 | * @param $name
198 | *
199 | * @return boolean
200 | */
201 | protected function buildResourceValue($name)
202 | {
203 | $link = $this->getLink($name);
204 |
205 | if (!$link) {
206 | return false;
207 | }
208 |
209 | $this->embedded[$name] = $this->getResource($link);
210 |
211 | return true;
212 | }
213 |
214 | /**
215 | * @param $name
216 | *
217 | * @return Resource|ResourceCollection
218 | */
219 | protected function getEmbeddedValue($name)
220 | {
221 | if ( !is_object($this->embedded[$name])) {
222 | if (is_integer(key($this->embedded[$name])) || empty($this->embedded[$name])) {
223 | $this->embedded[$name] = new ResourceCollection($this->client, $this->embedded[$name]);
224 | } else {
225 | $this->embedded[$name] = self::create($this->client, $this->embedded[$name]);
226 | }
227 | }
228 |
229 | return $this->embedded[$name];
230 | }
231 |
232 | /**
233 | * @param HttpClientInterface $client
234 | * @param array $data
235 | *
236 | * @return Resource
237 | */
238 | public static function create(HttpClientInterface $client, array $data)
239 | {
240 | $links = isset($data['_links']) ? $data['_links'] : array();
241 | $embedded = isset($data['_embedded']) ? $data['_embedded'] : array();
242 |
243 | unset(
244 | $data['_links'],
245 | $data['_embedded']
246 | );
247 |
248 | return new self($client, $data, $links, $embedded);
249 | }
250 |
251 | /**
252 | * Create a resource from link href.
253 | *
254 | * @param Link $link
255 | * @param array $variables Required if the link is templated
256 | *
257 | * @return Resource
258 | *
259 | * @throws \RuntimeException When call with property "href" of Link is empty and sets variables
260 | * Or response server is invalid
261 | */
262 | public function getResource(Link $link, array $variables = array())
263 | {
264 | $response = $this->client->get($link->getHref($variables));
265 |
266 | if (!$response instanceof HttpResponse) {
267 | throw new \RuntimeException(sprintf('HttpClient does not return a valid HttpResponse object, given: %s', $response));
268 | }
269 |
270 | if ($response->getStatus() !== 200) {
271 | throw new \RuntimeException(sprintf('HttpClient does not return a status code, given: %s', $response->getStatus()));
272 | }
273 |
274 | return EntryPoint::parse($response, $this->client);
275 | }
276 |
277 | /**
278 | * Returns the href of curie assoc given by link.
279 | *
280 | * @param Link $link
281 | *
282 | * @return string
283 | */
284 | public function getCurieHref(Link $link)
285 | {
286 | if (null === $link->getNCName() || null === $this->getCurie($link->getNCName())) {
287 | return null;
288 | }
289 |
290 | return $this->getCurie($link->getNCName())->getHref(array('rel' => $link->getReference()));
291 | }
292 |
293 | /**
294 | * {@inheritdoc}
295 | */
296 | public function offsetExists($offset)
297 | {
298 | return $this->has($offset);
299 | }
300 |
301 | /**
302 | * {@inheritdoc}
303 | */
304 | public function offsetGet($offset)
305 | {
306 | return $this->get($offset);
307 | }
308 |
309 | /**
310 | * {@inheritdoc}
311 | */
312 | public function offsetSet($offset, $value)
313 | {
314 | throw new \RuntimeException('Operation not available');
315 | }
316 |
317 | /**
318 | * {@inheritdoc}
319 | */
320 | public function offsetUnset($offset)
321 | {
322 | throw new \RuntimeException('Operation not available');
323 | }
324 | }
--------------------------------------------------------------------------------
/lib/ResourceCollection.php:
--------------------------------------------------------------------------------
1 | client = $client;
31 | $this->iterator = new \ArrayIterator($collection);
32 | }
33 |
34 | /**
35 | * @param HttpClientInterface $client
36 | * @param \Iterator $collection
37 | * @param bool $updateIterator if the Iterator should be updated to wrap the data inside Resource instances
38 | *
39 | * @return ResourceCollection
40 | */
41 | public static function createFromIterator(HttpClientInterface $client, \Iterator $collection, $updateIterator = false)
42 | {
43 | $col = new self($client);
44 | $col->iterator = $collection;
45 | $col->updateIterator = $updateIterator;
46 |
47 | return $col;
48 | }
49 |
50 | /**
51 | * @param null|array $data
52 | *
53 | * @return Resource
54 | */
55 | protected function createResource($data)
56 | {
57 | if (null === $data) {
58 | return null;
59 | }
60 |
61 | return Resource::create($this->client, $data);
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | public function current()
68 | {
69 | $resource = $this->iterator->current();
70 | if (null === $resource) {
71 | return null;
72 | }
73 |
74 | if ($this->updateIterator && !$resource instanceof Resource) {
75 | $resource = $this->createResource($resource);
76 | $this->iterator->offsetSet($this->iterator->key(), $resource);
77 | }
78 |
79 | return $resource;
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | public function next()
86 | {
87 | $this->iterator->next();
88 | }
89 |
90 | /**
91 | * {@inheritdoc}
92 | */
93 | public function key()
94 | {
95 | return $this->iterator->key();
96 | }
97 |
98 | /**
99 | * {@inheritdoc}
100 | */
101 | public function valid()
102 | {
103 | return $this->iterator->valid();
104 | }
105 |
106 | /**
107 | * {@inheritdoc}
108 | */
109 | public function rewind()
110 | {
111 | $this->iterator->rewind();
112 | }
113 |
114 | /**
115 | * {@inheritdoc}
116 | */
117 | public function count()
118 | {
119 | if (!$this->iterator instanceof \Countable) {
120 | throw new \RuntimeException('Operation not allowed');
121 | }
122 |
123 | return count($this->iterator);
124 | }
125 |
126 | /**
127 | * {@inheritdoc}
128 | */
129 | public function offsetExists($offset)
130 | {
131 | if (!$this->iterator instanceof \ArrayAccess) {
132 | throw new \RuntimeException('Operation not allowed');
133 | }
134 |
135 | return isset($this->iterator[$offset]);
136 | }
137 |
138 | /**
139 | * {@inheritdoc}
140 | */
141 | public function offsetGet($offset)
142 | {
143 | if (!$this->iterator instanceof \ArrayAccess) {
144 | throw new \RuntimeException('Operation not allowed');
145 | }
146 |
147 | $resource = $this->iterator->offsetGet($offset);
148 | if (null === $resource) {
149 | return null;
150 | }
151 |
152 | if ($this->updateIterator && !$resource instanceof Resource) {
153 | $resource = $this->createResource($resource);
154 | $this->iterator->offsetSet($offset, $resource);
155 | }
156 |
157 | return $resource;
158 | }
159 |
160 | /**
161 | * {@inheritdoc}
162 | */
163 | public function offsetSet($offset, $value)
164 | {
165 | throw new \RuntimeException('Operation not allowed');
166 | }
167 |
168 | /**
169 | * {@inheritdoc}
170 | */
171 | public function offsetUnset($offset)
172 | {
173 | throw new \RuntimeException('Operation not allowed');
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ./tests/HalClient/
7 |
8 |
9 |
10 |
11 |
12 | ./
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/HalClient/Deserialization/Construction/ProxyObjectConstructionTest.php:
--------------------------------------------------------------------------------
1 | getMock('JMS\Serializer\VisitorInterface');
24 | $client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
25 |
26 | $context = new DeserializationContext();
27 | $resource = new Resource($client);
28 |
29 | $constructor = new ProxyObjectConstruction();
30 | $object = $constructor->construct($visitor, new ClassMetadata('Acme\Post'), $resource, array(), $context);
31 |
32 | $this->assertInstanceOf('Ekino\HalClient\Proxy\HalResourceEntityInterface', $object);
33 | $this->assertInstanceOf('Ekino\HalClient\Resource', $object->getHalResource());
34 | $this->assertInstanceOf('Acme\Post', $object);
35 | }
36 |
37 | public function testConstructorWithoutProxy()
38 | {
39 | $visitor = $this->getMock('JMS\Serializer\VisitorInterface');
40 | $client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
41 | $context = new DeserializationContext();
42 | $resource = new Resource($client);
43 |
44 | $constructor = new ProxyObjectConstruction();
45 | $object = $constructor->construct($visitor, new ClassMetadata('Acme\NoProxy'), $resource, array(), $context);
46 |
47 | $this->assertNotInstanceOf('Ekino\HalClient\Proxy\HalResourceEntityInterface', $object);
48 | $this->assertInstanceOf('Acme\NoProxy', $object);
49 | }
50 |
51 | public function testWithMultiplePatterns()
52 | {
53 | $visitor = $this->getMock('JMS\Serializer\VisitorInterface');
54 | $client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
55 | $context = new DeserializationContext();
56 | $resource = new Resource($client);
57 |
58 | $constructor = new ProxyObjectConstruction(["{ns}\\Proxy\\{ln}", "Proxy\\{ns}\\{ln}"]);
59 | $object = $constructor->construct($visitor, new ClassMetadata('Acme\Post'), $resource, array(), $context);
60 |
61 | $this->assertInstanceOf('Ekino\HalClient\Proxy\HalResourceEntityInterface', $object);
62 | $this->assertInstanceOf('Ekino\HalClient\Resource', $object->getHalResource());
63 | $this->assertInstanceOf('Proxy\Acme\Post', $object);
64 | }
65 | }
66 | }
67 |
68 | namespace Acme
69 | {
70 | class Post {}
71 |
72 | class NoProxy {}
73 | }
74 |
75 | namespace Proxy\Acme
76 | {
77 | use Ekino\HalClient\Proxy\HalResourceEntity;
78 | use Ekino\HalClient\Proxy\HalResourceEntityInterface;
79 |
80 | class Post extends \Acme\Post implements HalResourceEntityInterface
81 | {
82 | use HalResourceEntity;
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/tests/HalClient/Deserialization/DeserializationTest.php:
--------------------------------------------------------------------------------
1 | getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
32 | $client->expects($this->exactly(1))->method('get')->will($this->returnCallback(function($url) {
33 | if ($url == '/users/1') {
34 | return new HttpResponse(200, array(
35 | 'Content-Type' => 'application/hal+json'
36 | ), json_encode(array(
37 | 'name' => 'Thomas Rabaix',
38 | 'email' => 'thomas.rabaix@ekino.com'
39 | )));
40 | }
41 | }));
42 |
43 | $resource = new Resource($client, array(
44 | 'name' => 'Salut',
45 | ), array(
46 | 'fragments' => array('href' => '/document/1/fragments'),
47 | 'author' => array('href' => '/users/1')
48 | ), array(
49 | 'fragments' => array(
50 | array(
51 | 'type' => 'test',
52 | 'settings' => array(
53 | 'color' => 'red'
54 | )
55 | ),
56 | array(
57 | 'type' => 'image',
58 | 'settings' => array(
59 | 'url' => 'http://dummyimage.com/600x400/000/fff'
60 | )
61 | )
62 | )
63 | ));
64 |
65 | return $resource;
66 | }
67 |
68 | public function testMapping()
69 | {
70 | $resource = $this->getResource();
71 |
72 | $object = Builder::build()->deserialize($resource, 'Ekino\HalClient\Deserialization\Article', 'hal');
73 |
74 | $this->assertEquals('Salut', $object->getName());
75 |
76 | $fragments = $object->getFragments();
77 | $this->assertCount(2, $fragments);
78 | $this->assertEquals($fragments[0]->getType(), 'test');
79 | $this->assertEquals($fragments[1]->getType(), 'image');
80 | $this->assertNotNull($object->getAuthor());
81 | $this->assertEquals($object->getAuthor()->getEmail(), 'thomas.rabaix@ekino.com');
82 | }
83 |
84 | public function testWithValidProxy()
85 | {
86 | $serializerBuilder = Builder::get(false)
87 | ->setObjectConstructor($constructor = new ProxyObjectConstruction());
88 |
89 | // todo, inject proxy handler
90 | $serializerBuilder->setObjectConstructor($constructor);
91 |
92 | $resource = $this->getResource();
93 | $serializer = $serializerBuilder->build();
94 |
95 | $constructor->setSerializer($serializer);
96 |
97 | $object = $serializer->deserialize($resource, 'Ekino\HalClient\Deserialization\Article', 'hal');
98 |
99 | $this->assertInstanceOf('Proxy\Ekino\HalClient\Deserialization\Article', $object);
100 | $this->assertInstanceOf('Ekino\HalClient\Deserialization\Article', $object);
101 | $this->assertEquals('Salut', $object->getName());
102 |
103 | $fragments = $object->getFragments();
104 |
105 | $this->assertCount(2, $fragments);
106 | $this->assertEquals($fragments[0]->getType(), 'test');
107 | $this->assertEquals($fragments[1]->getType(), 'image');
108 |
109 | $this->assertInstanceOf('Proxy\Ekino\HalClient\Deserialization\Author', $object->getAuthor());
110 | $this->assertInstanceOf('Ekino\HalClient\Deserialization\Author', $object->getAuthor());
111 |
112 | $this->assertEquals($object->getAuthor()->getEmail(), 'thomas.rabaix@ekino.com');
113 | }
114 | }
115 | }
116 |
117 | namespace Proxy\Ekino\HalClient\Deserialization {
118 |
119 | use Ekino\HalClient\Proxy\HalResourceEntity;
120 | use Ekino\HalClient\Proxy\HalResourceEntityInterface;
121 | use Ekino\HalClient\Resource;
122 |
123 | class Article extends \Ekino\HalClient\Deserialization\Article implements HalResourceEntityInterface
124 | {
125 | use HalResourceEntity;
126 |
127 | /**
128 | * @return Author
129 | */
130 | public function getAuthor()
131 | {
132 | if (!$this->author && !$this->halIsLoaded('author')) {
133 | $this->halLoaded('author');
134 |
135 | $resource = $this->getHalResource()->get('author');
136 |
137 | if ($resource instanceof Resource) {
138 | $this->author = $this->getHalSerializer()->deserialize($resource, 'Ekino\HalClient\Deserialization\Author', 'hal');
139 | }
140 | }
141 |
142 | return $this->author;
143 | }
144 | }
145 |
146 | class Author extends \Ekino\HalClient\Deserialization\Author implements HalResourceEntityInterface
147 | {
148 | use HalResourceEntity;
149 | }
150 | }
151 |
152 | namespace Ekino\HalClient\Deserialization {
153 | use JMS\Serializer\Annotation as Serializer;
154 |
155 | class Article
156 | {
157 | /**
158 | * @Serializer\Type("string")
159 | *
160 | * @var string
161 | */
162 | protected $name;
163 |
164 | /**
165 | * @Serializer\Type("Doctrine\Common\Collections\ArrayCollection")
166 | *
167 | * @var array
168 | */
169 | protected $fragments;
170 |
171 | /**
172 | * @param array $author
173 | */
174 | public function setAuthor(Author $author)
175 | {
176 | $this->author = $author;
177 | }
178 |
179 | /**
180 | * @return array
181 | */
182 | public function getAuthor()
183 | {
184 | return $this->author;
185 | }
186 |
187 | /**
188 | * @Serializer\Type("Ekino\HalClient\Deserialization\Author")
189 | *
190 | * @var array
191 | */
192 | protected $author;
193 |
194 | /**
195 | * @param array $fragments
196 | */
197 | public function setFragments(ArrayCollection $fragments)
198 | {
199 | $this->fragments = $fragments;
200 | }
201 |
202 | /**
203 | * @return array
204 | */
205 | public function getFragments()
206 | {
207 | return $this->fragments;
208 | }
209 |
210 | /**
211 | * @param string $name
212 | */
213 | public function setName($name)
214 | {
215 | $this->name = $name;
216 | }
217 |
218 | /**
219 | * @return string
220 | */
221 | public function getName()
222 | {
223 | return $this->name;
224 | }
225 | }
226 |
227 | class Fragment
228 | {
229 | /**
230 | * @Serializer\Type("string")
231 | *
232 | * @var string
233 | */
234 | protected $type;
235 |
236 | /**
237 | * @Serializer\Type("array")
238 | *
239 | * @var array
240 | */
241 | protected $settings;
242 |
243 | /**
244 | * @param array $settings
245 | */
246 | public function setSettings($settings)
247 | {
248 | $this->settings = $settings;
249 | }
250 |
251 | /**
252 | * @return array
253 | */
254 | public function getSettings()
255 | {
256 | return $this->settings;
257 | }
258 |
259 | /**
260 | * @param string $type
261 | */
262 | public function setType($type)
263 | {
264 | $this->type = $type;
265 | }
266 |
267 | /**
268 | * @return string
269 | */
270 | public function getType()
271 | {
272 | return $this->type;
273 | }
274 | }
275 |
276 | class Author
277 | {
278 | /**
279 | * @Serializer\Type("string")
280 | *
281 | * @var string
282 | */
283 | protected $name;
284 |
285 | /**
286 | * @Serializer\Type("string")
287 | *
288 | * @var string
289 | */
290 | protected $email;
291 |
292 | /**
293 | * @param string $email
294 | */
295 | public function setEmail($email)
296 | {
297 | $this->email = $email;
298 | }
299 |
300 | /**
301 | * @return string
302 | */
303 | public function getEmail()
304 | {
305 | return $this->email;
306 | }
307 |
308 | /**
309 | * @param string $name
310 | */
311 | public function setName($name)
312 | {
313 | $this->name = $name;
314 | }
315 |
316 | /**
317 | * @return string
318 | */
319 | public function getName()
320 | {
321 | return $this->name;
322 | }
323 | }
324 | }
--------------------------------------------------------------------------------
/tests/HalClient/EntryPointTest.php:
--------------------------------------------------------------------------------
1 | getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
26 | $client->expects($this->once())->method('get')->will($this->returnValue(new HttpResponse(200, array(
27 | 'Content-Type' => 'application/json'
28 | )), '{}'));
29 |
30 | $entryPoint = new EntryPoint('/', $client);
31 |
32 | $entryPoint->get();
33 | }
34 |
35 | public function testVersionHeader()
36 | {
37 | $client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
38 | $client->expects($this->once())->method('get')->will($this->returnCallback(function($url) {
39 | return new HttpResponse(200, array(
40 | 'Content-Type' => 'application/hal+json;version=42'
41 | ), json_encode([]));
42 | }));
43 |
44 | (new EntryPoint('/', $client))->get();
45 | }
46 |
47 | public function testGetResource()
48 | {
49 | $client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
50 | $client->expects($this->any())->method('get')->will($this->returnCallback(function($url) {
51 |
52 | if ($url === '/') {
53 | return new HttpResponse(200, array(
54 | 'Content-Type' => 'application/hal+json'
55 | ), file_get_contents(__DIR__.'/../fixtures/entry_point.json'));
56 | }
57 |
58 | if ($url === 'http://propilex.herokuapp.com/documents') {
59 | return new HttpResponse(200, array(
60 | 'Content-Type' => 'application/hal+json'
61 | ), file_get_contents(__DIR__.'/../fixtures/documents.json'));
62 | }
63 | }));
64 |
65 | $entryPoint = new EntryPoint('/', $client);
66 |
67 | $resource = $entryPoint->get();
68 |
69 | $this->assertInstanceOf('Ekino\HalClient\Resource', $resource);
70 | $this->assertCount(1, $resource->getProperties());
71 | $this->assertEmpty($resource->getEmbedded());
72 |
73 | $link = $resource->getLink('p:documents');
74 |
75 | $this->assertInstanceOf('Ekino\HalClient\Link', $link);
76 |
77 | $this->assertEquals($link->getHref(), 'http://propilex.herokuapp.com/documents');
78 |
79 | $this->assertNull($resource->get('fake'));
80 |
81 | $resource = $resource->get('p:documents');
82 |
83 | $this->assertInstanceOf('Ekino\HalClient\Resource', $resource);
84 |
85 | $expected = array(
86 | "page" => 1,
87 | "limit" => 10,
88 | "pages" => 1,
89 | );
90 |
91 | $this->assertEquals($expected, $resource->getProperties());
92 | $this->assertEquals(1, $resource->get('page'));
93 | $this->assertEquals(10, $resource->get('limit'));
94 | $this->assertEquals(1, $resource->get('pages'));
95 |
96 | $collection = $resource->get('documents');
97 |
98 | $this->assertInstanceOf('Ekino\HalClient\ResourceCollection', $collection);
99 |
100 | $this->assertEquals(4, $collection->count());
101 |
102 | foreach ($collection as $child) {
103 | $this->assertInstanceOf('Ekino\HalClient\Resource', $child);
104 | $this->assertNotNull($child->get('title'));
105 | $this->assertNotNull($child->get('body'));
106 | $this->assertNotNull($child->get('id'));
107 | $this->assertNull($child->get('fake'));
108 | }
109 |
110 |
111 | $this->assertEquals('teste', $collection[1]->get('title'));
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/HalClient/HttpClient/FileGetContentsHttpClientTest.php:
--------------------------------------------------------------------------------
1 | get('/');
23 |
24 | $this->assertInstanceOf('Ekino\HalClient\HttpClient\HttpResponse', $response);
25 |
26 | $this->assertEquals(200, $response->getStatus());
27 | $this->assertNotNull($response->getHeader('Content-Type'));
28 |
29 | $this->assertContains('Example Domain', $response->getBody());
30 | }
31 |
32 | public function testGet404()
33 | {
34 | $client = new FileGetContentsHttpClient('http://www.google.com/this-is-an-invalid-url', array(), 5.0);
35 |
36 | $response = $client->get('/');
37 |
38 | $this->assertEquals($response->getStatus(), 404);
39 | }
40 |
41 | /**
42 | * @expectedException \Ekino\HalClient\Exception\RequestException
43 | * @expectedExceptionMessage Empty response, no headers or impossible to reach the remote server
44 | */
45 | public function testGetUnreachable()
46 | {
47 | $client = new FileGetContentsHttpClient('https://8.8.8.8/', array(), 2.0); // Google DNS ;)
48 |
49 | $response = $client->get('/');
50 |
51 | $this->assertEquals($response->getStatus(), 404);
52 | }
53 | }
--------------------------------------------------------------------------------
/tests/HalClient/LinkTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf('Ekino\\HalClient\\Link', $this->resource->getLink('test:media'));
29 |
30 | $this->assertEquals('test', $this->resource->getLink('test:media')->getNCName());
31 | $this->assertEquals('media', $this->resource->getLink('test:media')->getReference());
32 | $this->assertEquals('test:media', $this->resource->getLink('test:media')->getName());
33 |
34 | $this->assertInstanceOf('Ekino\\HalClient\\Link', $this->resource->getLink('article'));
35 |
36 | $this->assertNull($this->resource->getLink('article')->getNCName());
37 | $this->assertNull($this->resource->getLink('article')->getReference());
38 | $this->assertEquals('article', $this->resource->getLink('article')->getName());
39 |
40 | $this->assertInstanceOf('Ekino\\HalClient\\Link', $this->resource->getLink('bar:tag'));
41 |
42 | $this->assertEquals('bar', $this->resource->getLink('bar:tag')->getNCName());
43 | $this->assertEquals('tag', $this->resource->getLink('bar:tag')->getReference());
44 | $this->assertEquals('bar:tag', $this->resource->getLink('bar:tag')->getName());
45 | }
46 |
47 | public function testGetCurie()
48 | {
49 | $this->assertInstanceOf('Ekino\\HalClient\\Curie', $this->resource->getCurie('test'));
50 | $this->assertNull($this->resource->getCurie('bar'));
51 |
52 | $this->assertEquals('test', $this->resource->getCurie('test')->getName());
53 | $this->assertEquals('http://localhost/path/to/docs/{rel}', $this->resource->getCurie('test')->getHref());
54 | $this->assertTrue($this->resource->getCurie('test')->isTemplated());
55 | }
56 |
57 | public function testGetDocs()
58 | {
59 | $this->assertEquals('http://localhost/path/to/docs/media', $this->resource->getCurieHref($this->resource->getLink('test:media')));
60 |
61 | $this->assertNull($this->resource->getCurieHref($this->resource->getLink('article')));
62 |
63 | $this->assertNull($this->resource->getCurieHref($this->resource->getLink('bar:tag')));
64 | }
65 |
66 | /**
67 | * @expectedException \RuntimeException
68 | */
69 | public function testLinkGetFail()
70 | {
71 | $this->resource->getResource($this->resource->getLink('article'));
72 | }
73 |
74 | public function testLinkHref()
75 | {
76 | $this->assertEquals('http://localhost/article/42', $this->resource->getLink('article')->getHref(array('id' => 42)));
77 |
78 | $this->assertEquals('http://localhost/tag/{id}', $this->resource->getLink('bar:tag')->getHref(array('id' => 42)));
79 |
80 | $this->assertEquals('http://localhost/media', $this->resource->getLink('test:media')->getHref());
81 | }
82 |
83 | protected function setUp()
84 | {
85 | $this->client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
86 |
87 | $this->resource = new Resource($this->client, array(), array(
88 | 'curies' => array(
89 | array(
90 | 'name' => 'test',
91 | 'href' => 'http://localhost/path/to/docs/{rel}',
92 | 'templated' => true
93 | )
94 | ),
95 | 'test:media' => array(
96 | 'href' => 'http://localhost/media'
97 | ),
98 | 'article' => array(
99 | 'href' => 'http://localhost/article/{id}',
100 | 'templated' => true
101 | ),
102 | 'bar:tag' => array(
103 | 'href' => 'http://localhost/tag/{id}',
104 | 'templated' => false
105 | )
106 | ));
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/tests/HalClient/ResourceTest.php:
--------------------------------------------------------------------------------
1 | getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
23 |
24 | $resource = new Resource($client);
25 | }
26 |
27 | /**
28 | * @expectedException \RuntimeException
29 | * @expectedExceptionMessage HttpClient does not return a status code, given: 500
30 | */
31 | public function testInvalidStatus()
32 | {
33 | $client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
34 | $client->expects($this->once())->method('get')->will($this->returnValue(new HttpResponse(500)));
35 |
36 | $resource = new Resource($client, array(), array(
37 | 'foo' => array(
38 | 'href' => 'http://fake.com/foo',
39 | 'title' => 'foo'
40 | )
41 | ));
42 |
43 | $resource->get('foo');
44 | }
45 |
46 | /**
47 | * @expectedException \RuntimeException
48 | * @expectedExceptionMessage Invalid resource, not `self` reference available
49 | */
50 | public function testRefreshWithInvalidSelfReference()
51 | {
52 | $client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
53 | $client->expects($this->never())->method('get');
54 |
55 | $resource = new Resource($client);
56 |
57 | $resource->refresh();
58 | }
59 |
60 | public function testRefresh()
61 | {
62 | $client = $this->getMock('Ekino\HalClient\HttpClient\HttpClientInterface');
63 | $client->expects($this->exactly(1))->method('get')->will($this->returnCallback(function($url) {
64 | if ($url == 'http://propilex.herokuapp.com') {
65 | return new HttpResponse(200, array(
66 | 'Content-Type' => 'application/hal+json'
67 | ), file_get_contents(__DIR__.'/../fixtures/entry_point.json'));
68 | }
69 | }));
70 |
71 | $resource = new Resource($client, array(), array(
72 | 'self' => array('href'=>'http://propilex.herokuapp.com')
73 | ));
74 |
75 | $this->assertNull($resource->get('field'));
76 | $resource->refresh();
77 |
78 | $this->assertEquals($resource->get('field'), 'value');
79 | }
80 | }
--------------------------------------------------------------------------------
/tests/autoload.php.dist:
--------------------------------------------------------------------------------
1 | getFilename(), 0, -34)
32 | );
33 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |